diff -Nru nginx-1.9.15/CHANGES nginx-1.10.0/CHANGES --- nginx-1.9.15/CHANGES 2016-04-19 16:02:43.000000000 +0000 +++ nginx-1.10.0/CHANGES 2016-04-26 13:31:24.000000000 +0000 @@ -1,4 +1,9 @@ +Changes with nginx 1.10.0 26 Apr 2016 + + *) 1.10.x stable branch. + + Changes with nginx 1.9.15 19 Apr 2016 *) Bugfix: "recv() failed" errors might occur when using HHVM as a diff -Nru nginx-1.9.15/CHANGES.ru nginx-1.10.0/CHANGES.ru --- nginx-1.9.15/CHANGES.ru 2016-04-19 16:02:42.000000000 +0000 +++ nginx-1.10.0/CHANGES.ru 2016-04-26 13:31:22.000000000 +0000 @@ -1,4 +1,9 @@ +Изменения в nginx 1.10.0 26.04.2016 + + *) Стабильная ветка 1.10.x. + + Изменения в nginx 1.9.15 19.04.2016 *) Исправление: при использовании HHVM в качестве FastCGI-сервера могли diff -Nru nginx-1.9.15/debian/changelog nginx-1.10.0/debian/changelog --- nginx-1.9.15/debian/changelog 2016-04-19 16:44:35.000000000 +0000 +++ nginx-1.10.0/debian/changelog 2016-05-31 10:03:55.000000000 +0000 @@ -1,3 +1,20 @@ +nginx (1.10.0-1+xenial1) xenial; urgency=medium + + * Added nginx-rtmp-module + + -- Tao Jun Tue, 31 May 2016 18:03:55 +0800 + +nginx (1.10.0-0ubuntu0.16.04.1) xenial-proposed; urgency=medium + + * Stable Release Update (LP: #1575212) + * New upstream release (1.10.0) - full changelog available at upstream + website - http://nginx.org/en/CHANGES-1.10 + * All Ubuntu specific changes from 1.9.15-0ubuntu1 remain included. + * Additional changes: + * debian/patches/ubuntu-branding.patch: Refreshed Ubuntu Branding patch. + + -- Thomas Ward Tue, 26 Apr 2016 10:21:29 -0400 + nginx (1.9.15-0ubuntu1) xenial-proposed; urgency=medium * New upstream release (1.9.15) - full changelog available at upstream diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/AUTHORS nginx-1.10.0/debian/modules/nginx-rtmp-module/AUTHORS --- nginx-1.9.15/debian/modules/nginx-rtmp-module/AUTHORS 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/AUTHORS 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,8 @@ +Project author: + + Roman Arutyunyan + Moscow, Russia + + Contacts: + arut@qip.ru + arutyunyan.roman@gmail.com diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/config nginx-1.10.0/debian/modules/nginx-rtmp-module/config --- nginx-1.9.15/debian/modules/nginx-rtmp-module/config 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/config 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,132 @@ +ngx_addon_name="ngx_rtmp_module" + +RTMP_CORE_MODULES=" \ + ngx_rtmp_module \ + ngx_rtmp_core_module \ + ngx_rtmp_cmd_module \ + ngx_rtmp_codec_module \ + ngx_rtmp_access_module \ + ngx_rtmp_record_module \ + ngx_rtmp_live_module \ + ngx_rtmp_play_module \ + ngx_rtmp_flv_module \ + ngx_rtmp_mp4_module \ + ngx_rtmp_netcall_module \ + ngx_rtmp_relay_module \ + ngx_rtmp_exec_module \ + ngx_rtmp_auto_push_module \ + ngx_rtmp_notify_module \ + ngx_rtmp_log_module \ + ngx_rtmp_limit_module \ + ngx_rtmp_hls_module \ + ngx_rtmp_dash_module \ + " + + +RTMP_HTTP_MODULES=" \ + ngx_rtmp_stat_module \ + ngx_rtmp_control_module \ + " + + +RTMP_DEPS=" \ + $ngx_addon_dir/ngx_rtmp_amf.h \ + $ngx_addon_dir/ngx_rtmp_bandwidth.h \ + $ngx_addon_dir/ngx_rtmp_cmd_module.h \ + $ngx_addon_dir/ngx_rtmp_codec_module.h \ + $ngx_addon_dir/ngx_rtmp_eval.h \ + $ngx_addon_dir/ngx_rtmp.h \ + $ngx_addon_dir/ngx_rtmp_version.h \ + $ngx_addon_dir/ngx_rtmp_live_module.h \ + $ngx_addon_dir/ngx_rtmp_netcall_module.h \ + $ngx_addon_dir/ngx_rtmp_play_module.h \ + $ngx_addon_dir/ngx_rtmp_record_module.h \ + $ngx_addon_dir/ngx_rtmp_relay_module.h \ + $ngx_addon_dir/ngx_rtmp_streams.h \ + $ngx_addon_dir/ngx_rtmp_bitop.h \ + $ngx_addon_dir/ngx_rtmp_proxy_protocol.h \ + $ngx_addon_dir/hls/ngx_rtmp_mpegts.h \ + $ngx_addon_dir/dash/ngx_rtmp_mp4.h \ + " + + +RTMP_CORE_SRCS=" \ + $ngx_addon_dir/ngx_rtmp.c \ + $ngx_addon_dir/ngx_rtmp_init.c \ + $ngx_addon_dir/ngx_rtmp_handshake.c \ + $ngx_addon_dir/ngx_rtmp_handler.c \ + $ngx_addon_dir/ngx_rtmp_amf.c \ + $ngx_addon_dir/ngx_rtmp_send.c \ + $ngx_addon_dir/ngx_rtmp_shared.c \ + $ngx_addon_dir/ngx_rtmp_eval.c \ + $ngx_addon_dir/ngx_rtmp_receive.c \ + $ngx_addon_dir/ngx_rtmp_core_module.c \ + $ngx_addon_dir/ngx_rtmp_cmd_module.c \ + $ngx_addon_dir/ngx_rtmp_codec_module.c \ + $ngx_addon_dir/ngx_rtmp_access_module.c \ + $ngx_addon_dir/ngx_rtmp_record_module.c \ + $ngx_addon_dir/ngx_rtmp_live_module.c \ + $ngx_addon_dir/ngx_rtmp_play_module.c \ + $ngx_addon_dir/ngx_rtmp_flv_module.c \ + $ngx_addon_dir/ngx_rtmp_mp4_module.c \ + $ngx_addon_dir/ngx_rtmp_netcall_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 \ + $ngx_addon_dir/ngx_rtmp_auto_push_module.c \ + $ngx_addon_dir/ngx_rtmp_notify_module.c \ + $ngx_addon_dir/ngx_rtmp_log_module.c \ + $ngx_addon_dir/ngx_rtmp_limit_module.c \ + $ngx_addon_dir/ngx_rtmp_bitop.c \ + $ngx_addon_dir/ngx_rtmp_proxy_protocol.c \ + $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/dash/ngx_rtmp_mp4.c \ + " + + +RTMP_HTTP_SRCS=" \ + $ngx_addon_dir/ngx_rtmp_stat_module.c \ + $ngx_addon_dir/ngx_rtmp_control_module.c \ + " + +if [ -f auto/module ] ; then + ngx_module_incs=$ngx_addon_dir + ngx_module_deps=$RTMP_DEPS + + 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 + + else + ngx_module_type=CORE + 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_incs= + ngx_module_deps= + ngx_module_srcs=$RTMP_HTTP_SRCS + + . auto/module + fi + +else + CORE_MODULES="$CORE_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" + + CFLAGS="$CFLAGS -I$ngx_addon_dir" +fi + +USE_OPENSSL=YES + diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/dash/ngx_rtmp_dash_module.c nginx-1.10.0/debian/modules/nginx-rtmp-module/dash/ngx_rtmp_dash_module.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/dash/ngx_rtmp_dash_module.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/dash/ngx_rtmp_dash_module.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,1528 @@ + + +#include +#include +#include +#include +#include "ngx_rtmp_live_module.h" +#include "ngx_rtmp_mp4.h" + + +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_int_t ngx_rtmp_dash_postconfiguration(ngx_conf_t *cf); +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); + + +#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_DIR_ACCESS 0744 + + +typedef struct { + uint32_t timestamp; + uint32_t duration; +} ngx_rtmp_dash_frag_t; + + +typedef struct { + ngx_uint_t id; + ngx_uint_t opened; + ngx_uint_t mdat_size; + ngx_uint_t sample_count; + ngx_uint_t sample_mask; + ngx_fd_t fd; + char type; + uint32_t earliest_pres_time; + uint32_t latest_pres_time; + ngx_rtmp_mp4_sample_t samples[NGX_RTMP_DASH_MAX_SAMPLES]; +} ngx_rtmp_dash_track_t; + + +typedef struct { + ngx_str_t playlist; + ngx_str_t playlist_bak; + ngx_str_t name; + ngx_str_t stream; + ngx_time_t start_time; + + ngx_uint_t nfrags; + ngx_uint_t frag; + ngx_rtmp_dash_frag_t *frags; /* circular 2 * winfrags + 1 */ + + unsigned opened:1; + unsigned has_video:1; + unsigned has_audio:1; + + ngx_file_t video_file; + ngx_file_t audio_file; + + ngx_uint_t id; + + ngx_rtmp_dash_track_t audio; + ngx_rtmp_dash_track_t video; +} ngx_rtmp_dash_ctx_t; + + +typedef struct { + ngx_str_t path; + ngx_msec_t playlen; +} ngx_rtmp_dash_cleanup_t; + + +typedef struct { + ngx_flag_t dash; + ngx_msec_t fraglen; + ngx_msec_t playlen; + ngx_flag_t nested; + ngx_str_t path; + ngx_uint_t winfrags; + ngx_flag_t cleanup; + ngx_path_t *slot; +} ngx_rtmp_dash_app_conf_t; + + +static ngx_command_t ngx_rtmp_dash_commands[] = { + + { ngx_string("dash"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_dash_app_conf_t, dash), + NULL }, + + { ngx_string("dash_fragment"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_dash_app_conf_t, fraglen), + NULL }, + + { ngx_string("dash_path"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_dash_app_conf_t, path), + NULL }, + + { ngx_string("dash_playlist_length"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_dash_app_conf_t, playlen), + NULL }, + + { ngx_string("dash_cleanup"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_dash_app_conf_t, cleanup), + NULL }, + + { ngx_string("dash_nested"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_dash_app_conf_t, nested), + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_rtmp_dash_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_dash_postconfiguration, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + ngx_rtmp_dash_create_app_conf, /* create location configuration */ + ngx_rtmp_dash_merge_app_conf, /* merge location configuration */ +}; + + +ngx_module_t ngx_rtmp_dash_module = { + NGX_MODULE_V1, + &ngx_rtmp_dash_module_ctx, /* module context */ + ngx_rtmp_dash_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_rtmp_dash_frag_t * +ngx_rtmp_dash_get_frag(ngx_rtmp_session_t *s, ngx_int_t n) +{ + ngx_rtmp_dash_ctx_t *ctx; + ngx_rtmp_dash_app_conf_t *dacf; + + dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); + + return &ctx->frags[(ctx->frag + n) % (dacf->winfrags * 2 + 1)]; +} + + +static void +ngx_rtmp_dash_next_frag(ngx_rtmp_session_t *s) +{ + ngx_rtmp_dash_ctx_t *ctx; + ngx_rtmp_dash_app_conf_t *dacf; + + dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); + + if (ctx->nfrags == dacf->winfrags) { + ctx->frag++; + } else { + ctx->nfrags++; + } +} + + +static ngx_int_t +ngx_rtmp_dash_rename_file(u_char *src, u_char *dst) +{ + /* rename file with overwrite */ + +#if (NGX_WIN32) + return MoveFileEx((LPCTSTR) src, (LPCTSTR) dst, MOVEFILE_REPLACE_EXISTING); +#else + return ngx_rename_file(src, dst); +#endif +} + + +static ngx_int_t +ngx_rtmp_dash_write_playlist(ngx_rtmp_session_t *s) +{ + char *sep; + u_char *p, *last; + ssize_t n; + ngx_fd_t fd; + struct tm tm; + ngx_str_t noname, *name; + ngx_uint_t i; + 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; + + 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")]; + + dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + if (dacf == NULL || ctx == NULL || codec_ctx == NULL) { + return NGX_ERROR; + } + + if (ctx->id == 0) { + ngx_rtmp_dash_write_init_segments(s); + } + + fd = ngx_open_file(ctx->playlist_bak.data, NGX_FILE_WRONLY, + NGX_FILE_TRUNCATE, NGX_FILE_DEFAULT_ACCESS); + + if (fd == NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "dash: open failed: '%V'", &ctx->playlist_bak); + return NGX_ERROR; + } + + +#define NGX_RTMP_DASH_MANIFEST_HEADER \ + "\n" \ + "\n" \ + " \n" + + +#define NGX_RTMP_DASH_MANIFEST_VIDEO \ + " \n" \ + " \n" \ + " \n" \ + " \n" + + +#define NGX_RTMP_DASH_MANIFEST_VIDEO_FOOTER \ + " \n" \ + " \n" \ + " \n" \ + " \n" + + +#define NGX_RTMP_DASH_MANIFEST_TIME \ + " \n" + + +#define NGX_RTMP_DASH_MANIFEST_AUDIO \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + + +#define NGX_RTMP_DASH_MANIFEST_AUDIO_FOOTER \ + " \n" \ + " \n" \ + " \n" \ + " \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, + 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; + + last = buffer + sizeof(buffer); + + 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)); + + n = ngx_write_fd(fd, buffer, p - buffer); + + ngx_str_null(&noname); + + name = (dacf->nested ? &noname : &ctx->name); + sep = (dacf->nested ? "" : "-"); + + if (ctx->has_video) { + p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_MANIFEST_VIDEO, + codec_ctx->width, + codec_ctx->height, + codec_ctx->frame_rate, + &ctx->name, + codec_ctx->avc_profile, + codec_ctx->avc_compat, + codec_ctx->avc_level, + codec_ctx->width, + codec_ctx->height, + codec_ctx->frame_rate, + (ngx_uint_t) (codec_ctx->video_data_rate * 1000), + name, sep, + name, sep); + + for (i = 0; i < ctx->nfrags; i++) { + f = ngx_rtmp_dash_get_frag(s, i); + p = ngx_slprintf(p, last, NGX_RTMP_DASH_MANIFEST_TIME, + f->timestamp, f->duration); + } + + p = ngx_slprintf(p, last, NGX_RTMP_DASH_MANIFEST_VIDEO_FOOTER); + + n = ngx_write_fd(fd, buffer, p - buffer); + } + + if (ctx->has_audio) { + p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_MANIFEST_AUDIO, + &ctx->name, + codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC ? + (codec_ctx->aac_sbr ? "40.5" : "40.2") : "6b", + codec_ctx->sample_rate, + (ngx_uint_t) (codec_ctx->audio_data_rate * 1000), + name, sep, + name, sep); + + for (i = 0; i < ctx->nfrags; i++) { + f = ngx_rtmp_dash_get_frag(s, i); + p = ngx_slprintf(p, last, NGX_RTMP_DASH_MANIFEST_TIME, + f->timestamp, f->duration); + } + + p = ngx_slprintf(p, last, NGX_RTMP_DASH_MANIFEST_AUDIO_FOOTER); + + n = ngx_write_fd(fd, buffer, p - buffer); + } + + p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_MANIFEST_FOOTER); + n = ngx_write_fd(fd, buffer, p - buffer); + + if (n < 0) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "dash: write failed: '%V'", &ctx->playlist_bak); + ngx_close_file(fd); + return NGX_ERROR; + } + + ngx_close_file(fd); + + if (ngx_rtmp_dash_rename_file(ctx->playlist_bak.data, ctx->playlist.data) + == NGX_FILE_ERROR) + { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "dash: rename failed: '%V'->'%V'", + &ctx->playlist_bak, &ctx->playlist); + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_dash_write_init_segments(ngx_rtmp_session_t *s) +{ + ngx_fd_t fd; + ngx_int_t rc; + ngx_buf_t b; + ngx_rtmp_dash_ctx_t *ctx; + ngx_rtmp_codec_ctx_t *codec_ctx; + + static u_char buffer[NGX_RTMP_DASH_BUFSIZE]; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + if (ctx == NULL || codec_ctx == NULL) { + return NGX_ERROR; + } + + /* init video */ + + *ngx_sprintf(ctx->stream.data + ctx->stream.len, "init.m4v") = 0; + + fd = ngx_open_file(ctx->stream.data, NGX_FILE_RDWR, NGX_FILE_TRUNCATE, + NGX_FILE_DEFAULT_ACCESS); + + if (fd == NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "dash: error creating video init file"); + return NGX_ERROR; + } + + b.start = buffer; + b.end = b.start + sizeof(buffer); + b.pos = b.last = b.start; + + ngx_rtmp_mp4_write_ftyp(&b); + ngx_rtmp_mp4_write_moov(s, &b, NGX_RTMP_MP4_VIDEO_TRACK); + + rc = ngx_write_fd(fd, b.start, (size_t) (b.last - b.start)); + if (rc == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "dash: writing video init failed"); + } + + ngx_close_file(fd); + + /* init audio */ + + *ngx_sprintf(ctx->stream.data + ctx->stream.len, "init.m4a") = 0; + + fd = ngx_open_file(ctx->stream.data, NGX_FILE_RDWR, NGX_FILE_TRUNCATE, + NGX_FILE_DEFAULT_ACCESS); + + if (fd == NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "dash: error creating dash audio init file"); + return NGX_ERROR; + } + + b.pos = b.last = b.start; + + ngx_rtmp_mp4_write_ftyp(&b); + ngx_rtmp_mp4_write_moov(s, &b, NGX_RTMP_MP4_AUDIO_TRACK); + + rc = ngx_write_fd(fd, b.start, (size_t) (b.last - b.start)); + if (rc == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "dash: writing audio init failed"); + } + + ngx_close_file(fd); + + return NGX_OK; +} + + +static void +ngx_rtmp_dash_close_fragment(ngx_rtmp_session_t *s, ngx_rtmp_dash_track_t *t) +{ + u_char *pos, *pos1; + size_t left; + ssize_t n; + ngx_fd_t fd; + ngx_buf_t b; + ngx_rtmp_dash_ctx_t *ctx; + ngx_rtmp_dash_frag_t *f; + + static u_char buffer[NGX_RTMP_DASH_BUFSIZE]; + + if (!t->opened) { + return; + } + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "dash: close fragment id=%ui, type=%c, pts=%uD", + t->id, t->type, t->earliest_pres_time); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); + + b.start = buffer; + b.end = buffer + sizeof(buffer); + b.pos = b.last = b.start; + + ngx_rtmp_mp4_write_styp(&b); + + pos = b.last; + b.last += 44; /* leave room for sidx */ + + ngx_rtmp_mp4_write_moof(&b, t->earliest_pres_time, t->sample_count, + t->samples, t->sample_mask, t->id); + pos1 = b.last; + b.last = pos; + + ngx_rtmp_mp4_write_sidx(&b, t->mdat_size + 8 + (pos1 - (pos + 44)), + t->earliest_pres_time, t->latest_pres_time); + b.last = pos1; + ngx_rtmp_mp4_write_mdat(&b, t->mdat_size + 8); + + /* move the data down to make room for the headers */ + + f = ngx_rtmp_dash_get_frag(s, ctx->nfrags); + + *ngx_sprintf(ctx->stream.data + ctx->stream.len, "%uD.m4%c", + f->timestamp, t->type) = 0; + + fd = ngx_open_file(ctx->stream.data, NGX_FILE_RDWR, + NGX_FILE_TRUNCATE, NGX_FILE_DEFAULT_ACCESS); + + if (fd == NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "dash: error creating dash temp video file"); + goto done; + } + + if (ngx_write_fd(fd, b.pos, (size_t) (b.last - b.pos)) == NGX_ERROR) { + goto done; + } + + left = (size_t) t->mdat_size; + +#if (NGX_WIN32) + if (SetFilePointer(t->fd, 0, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "dash: SetFilePointer error"); + goto done; + } +#else + if (lseek(t->fd, 0, SEEK_SET) == -1) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "dash: lseek error"); + goto done; + } +#endif + + while (left > 0) { + + n = ngx_read_fd(t->fd, buffer, ngx_min(sizeof(buffer), left)); + if (n == NGX_ERROR) { + break; + } + + n = ngx_write_fd(fd, buffer, (size_t) n); + if (n == NGX_ERROR) { + break; + } + + left -= n; + } + +done: + + if (fd != NGX_INVALID_FILE) { + ngx_close_file(fd); + } + + ngx_close_file(t->fd); + + t->fd = NGX_INVALID_FILE; + t->opened = 0; +} + + +static ngx_int_t +ngx_rtmp_dash_close_fragments(ngx_rtmp_session_t *s) +{ + ngx_rtmp_dash_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); + if (ctx == NULL || !ctx->opened) { + return NGX_OK; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "dash: close fragments"); + + ngx_rtmp_dash_close_fragment(s, &ctx->video); + ngx_rtmp_dash_close_fragment(s, &ctx->audio); + + ngx_rtmp_dash_next_frag(s); + + ngx_rtmp_dash_write_playlist(s); + + ctx->id++; + ctx->opened = 0; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_dash_open_fragment(ngx_rtmp_session_t *s, ngx_rtmp_dash_track_t *t, + ngx_uint_t id, char type) +{ + ngx_rtmp_dash_ctx_t *ctx; + + if (t->opened) { + return NGX_OK; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "dash: open fragment id=%ui, type='%c'", id, type); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); + + *ngx_sprintf(ctx->stream.data + ctx->stream.len, "raw.m4%c", type) = 0; + + t->fd = ngx_open_file(ctx->stream.data, NGX_FILE_RDWR, + NGX_FILE_TRUNCATE, NGX_FILE_DEFAULT_ACCESS); + + if (t->fd == NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "dash: error creating fragment file"); + return NGX_ERROR; + } + + t->id = id; + t->type = type; + t->sample_count = 0; + t->earliest_pres_time = 0; + t->latest_pres_time = 0; + t->mdat_size = 0; + t->opened = 1; + + if (type == 'v') { + t->sample_mask = NGX_RTMP_MP4_SAMPLE_SIZE| + NGX_RTMP_MP4_SAMPLE_DURATION| + NGX_RTMP_MP4_SAMPLE_DELAY| + NGX_RTMP_MP4_SAMPLE_KEY; + } else { + t->sample_mask = NGX_RTMP_MP4_SAMPLE_SIZE| + NGX_RTMP_MP4_SAMPLE_DURATION; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_dash_open_fragments(ngx_rtmp_session_t *s) +{ + ngx_rtmp_dash_ctx_t *ctx; + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "dash: open fragments"); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); + + if (ctx->opened) { + return NGX_OK; + } + + ngx_rtmp_dash_open_fragment(s, &ctx->video, ctx->id, 'v'); + + ngx_rtmp_dash_open_fragment(s, &ctx->audio, ctx->id, 'a'); + + ctx->opened = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_dash_ensure_directory(ngx_rtmp_session_t *s) +{ + size_t len; + ngx_file_info_t fi; + ngx_rtmp_dash_ctx_t *ctx; + ngx_rtmp_dash_app_conf_t *dacf; + + static u_char path[NGX_MAX_PATH + 1]; + + dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module); + + *ngx_snprintf(path, sizeof(path) - 1, "%V", &dacf->path) = 0; + + if (ngx_file_info(path, &fi) == NGX_FILE_ERROR) { + + if (ngx_errno != NGX_ENOENT) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "dash: " ngx_file_info_n " failed on '%V'", + &dacf->path); + return NGX_ERROR; + } + + /* ENOENT */ + + if (ngx_create_dir(path, NGX_RTMP_DASH_DIR_ACCESS) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "dash: " ngx_create_dir_n " failed on '%V'", + &dacf->path); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "dash: directory '%V' created", &dacf->path); + + } else { + + if (!ngx_is_dir(&fi)) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "dash: '%V' exists and is not a directory", + &dacf->path); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "dash: directory '%V' exists", &dacf->path); + } + + if (!dacf->nested) { + return NGX_OK; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); + + len = dacf->path.len; + if (dacf->path.data[len - 1] == '/') { + len--; + } + + *ngx_snprintf(path, sizeof(path) - 1, "%*s/%V", len, dacf->path.data, + &ctx->name) = 0; + + if (ngx_file_info(path, &fi) != NGX_FILE_ERROR) { + + if (ngx_is_dir(&fi)) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "dash: directory '%s' exists", path); + return NGX_OK; + } + + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "dash: '%s' exists and is not a directory", path); + + return NGX_ERROR; + } + + if (ngx_errno != NGX_ENOENT) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "dash: " ngx_file_info_n " failed on '%s'", path); + return NGX_ERROR; + } + + /* NGX_ENOENT */ + + if (ngx_create_dir(path, NGX_RTMP_DASH_DIR_ACCESS) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "dash: " ngx_create_dir_n " failed on '%s'", path); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "dash: directory '%s' created", path); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_dash_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) +{ + u_char *p; + size_t len; + ngx_rtmp_dash_ctx_t *ctx; + ngx_rtmp_dash_frag_t *f; + ngx_rtmp_dash_app_conf_t *dacf; + + dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module); + if (dacf == NULL || !dacf->dash || dacf->path.len == 0) { + goto next; + } + + if (s->auto_pushed) { + goto next; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "dash: publish: name='%s' type='%s'", v->name, v->type); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); + + if (ctx == NULL) { + ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_rtmp_dash_ctx_t)); + if (ctx == NULL) { + goto next; + } + ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_dash_module); + + } else { + if (ctx->opened) { + goto next; + } + + f = ctx->frags; + ngx_memzero(ctx, sizeof(ngx_rtmp_dash_ctx_t)); + ctx->frags = f; + } + + if (ctx->frags == NULL) { + ctx->frags = ngx_pcalloc(s->connection->pool, + sizeof(ngx_rtmp_dash_frag_t) * + (dacf->winfrags * 2 + 1)); + if (ctx->frags == NULL) { + return NGX_ERROR; + } + } + + ctx->id = 0; + + if (ngx_strstr(v->name, "..")) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "dash: bad stream name: '%s'", v->name); + return NGX_ERROR; + } + + ctx->name.len = ngx_strlen(v->name); + ctx->name.data = ngx_palloc(s->connection->pool, ctx->name.len + 1); + + if (ctx->name.data == NULL) { + return NGX_ERROR; + } + + *ngx_cpymem(ctx->name.data, v->name, ctx->name.len) = 0; + + len = dacf->path.len + 1 + ctx->name.len + sizeof(".mpd"); + if (dacf->nested) { + len += sizeof("/index") - 1; + } + + ctx->playlist.data = ngx_palloc(s->connection->pool, len); + p = ngx_cpymem(ctx->playlist.data, dacf->path.data, dacf->path.len); + + if (p[-1] != '/') { + *p++ = '/'; + } + + p = ngx_cpymem(p, ctx->name.data, ctx->name.len); + + /* + * ctx->stream holds initial part of stream file path + * however the space for the whole stream path + * is allocated + */ + + ctx->stream.len = p - ctx->playlist.data + 1; + ctx->stream.data = ngx_palloc(s->connection->pool, + ctx->stream.len + NGX_INT32_LEN + + sizeof(".m4x")); + + ngx_memcpy(ctx->stream.data, ctx->playlist.data, ctx->stream.len - 1); + ctx->stream.data[ctx->stream.len - 1] = (dacf->nested ? '/' : '-'); + + if (dacf->nested) { + p = ngx_cpymem(p, "/index.mpd", sizeof("/index.mpd") - 1); + } else { + p = ngx_cpymem(p, ".mpd", sizeof(".mpd") - 1); + } + + ctx->playlist.len = p - ctx->playlist.data; + + *p = 0; + + /* playlist bak (new playlist) path */ + + ctx->playlist_bak.data = ngx_palloc(s->connection->pool, + ctx->playlist.len + sizeof(".bak")); + p = ngx_cpymem(ctx->playlist_bak.data, ctx->playlist.data, + ctx->playlist.len); + p = ngx_cpymem(p, ".bak", sizeof(".bak") - 1); + + ctx->playlist_bak.len = p - ctx->playlist_bak.data; + + *p = 0; + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "dash: playlist='%V' playlist_bak='%V' stream_pattern='%V'", + &ctx->playlist, &ctx->playlist_bak, &ctx->stream); + + ctx->start_time = *ngx_cached_time; + + if (ngx_rtmp_dash_ensure_directory(s) != NGX_OK) { + return NGX_ERROR; + } + +next: + return next_publish(s, v); +} + + +static ngx_int_t +ngx_rtmp_dash_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v) +{ + ngx_rtmp_dash_ctx_t *ctx; + ngx_rtmp_dash_app_conf_t *dacf; + + dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); + + if (dacf == NULL || !dacf->dash || ctx == NULL) { + goto next; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "dash: delete stream"); + + ngx_rtmp_dash_close_fragments(s); + +next: + return next_close_stream(s, v); +} + + +static void +ngx_rtmp_dash_update_fragments(ngx_rtmp_session_t *s, ngx_int_t boundary, + uint32_t timestamp) +{ + int32_t d; + ngx_int_t hit; + ngx_rtmp_dash_ctx_t *ctx; + ngx_rtmp_dash_frag_t *f; + ngx_rtmp_dash_app_conf_t *dacf; + + dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); + f = ngx_rtmp_dash_get_frag(s, ctx->nfrags); + + d = (int32_t) (timestamp - f->timestamp); + + if (d >= 0) { + + f->duration = timestamp - f->timestamp; + hit = (f->duration >= dacf->fraglen); + + } else { + + /* sometimes clients generate slightly unordered frames */ + + hit = (-d > 1000); + } + + if (ctx->has_video && !hit) { + boundary = 0; + } + + if (!ctx->has_video && ctx->has_audio) { + boundary = hit; + } + + if (ctx->audio.mdat_size >= NGX_RTMP_DASH_MAX_MDAT) { + boundary = 1; + } + + if (ctx->video.mdat_size >= NGX_RTMP_DASH_MAX_MDAT) { + boundary = 1; + } + + if (!ctx->opened) { + boundary = 1; + } + + if (boundary) { + ngx_rtmp_dash_close_fragments(s); + ngx_rtmp_dash_open_fragments(s); + + f = ngx_rtmp_dash_get_frag(s, ctx->nfrags); + f->timestamp = timestamp; + } +} + + +static ngx_int_t +ngx_rtmp_dash_append(ngx_rtmp_session_t *s, ngx_chain_t *in, + ngx_rtmp_dash_track_t *t, ngx_int_t key, uint32_t timestamp, uint32_t delay) +{ + u_char *p; + size_t size, bsize; + ngx_rtmp_mp4_sample_t *smpl; + + static u_char buffer[NGX_RTMP_DASH_BUFSIZE]; + + p = buffer; + size = 0; + + for (; in && size < sizeof(buffer); in = in->next) { + + bsize = (size_t) (in->buf->last - in->buf->pos); + if (size + bsize > sizeof(buffer)) { + bsize = (size_t) (sizeof(buffer) - size); + } + + p = ngx_cpymem(p, in->buf->pos, bsize); + size += bsize; + } + + ngx_rtmp_dash_update_fragments(s, key, timestamp); + + if (t->sample_count == 0) { + t->earliest_pres_time = timestamp; + } + + t->latest_pres_time = timestamp; + + if (t->sample_count < NGX_RTMP_DASH_MAX_SAMPLES) { + + if (ngx_write_fd(t->fd, buffer, size) == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "dash: " ngx_write_fd_n " failed"); + return NGX_ERROR; + } + + smpl = &t->samples[t->sample_count]; + + smpl->delay = delay; + smpl->size = (uint32_t) size; + smpl->duration = 0; + smpl->timestamp = timestamp; + smpl->key = (key ? 1 : 0); + + if (t->sample_count > 0) { + smpl = &t->samples[t->sample_count - 1]; + smpl->duration = timestamp - smpl->timestamp; + } + + t->sample_count++; + t->mdat_size += (ngx_uint_t) size; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_dash_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + u_char htype; + ngx_rtmp_dash_ctx_t *ctx; + ngx_rtmp_codec_ctx_t *codec_ctx; + ngx_rtmp_dash_app_conf_t *dacf; + + dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + if (dacf == NULL || !dacf->dash || ctx == NULL || + codec_ctx == NULL || h->mlen < 2) + { + return NGX_OK; + } + + /* Only AAC is supported */ + + if (codec_ctx->audio_codec_id != NGX_RTMP_AUDIO_AAC || + codec_ctx->aac_header == NULL) + { + return NGX_OK; + } + + if (in->buf->last - in->buf->pos < 2) { + return NGX_ERROR; + } + + /* skip AAC config */ + + htype = in->buf->pos[1]; + if (htype != 1) { + return NGX_OK; + } + + ctx->has_audio = 1; + + /* skip RTMP & AAC headers */ + + in->buf->pos += 2; + + return ngx_rtmp_dash_append(s, in, &ctx->audio, 0, h->timestamp, 0); +} + + +static ngx_int_t +ngx_rtmp_dash_video(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + u_char *p; + uint8_t ftype, htype; + uint32_t delay; + ngx_rtmp_dash_ctx_t *ctx; + ngx_rtmp_codec_ctx_t *codec_ctx; + ngx_rtmp_dash_app_conf_t *dacf; + + dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module); + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + if (dacf == NULL || !dacf->dash || ctx == NULL || codec_ctx == NULL || + codec_ctx->avc_header == NULL || h->mlen < 5) + { + return NGX_OK; + } + + /* Only H264 is supported */ + + if (codec_ctx->video_codec_id != NGX_RTMP_VIDEO_H264) { + return NGX_OK; + } + + if (in->buf->last - in->buf->pos < 5) { + return NGX_ERROR; + } + + ftype = (in->buf->pos[0] & 0xf0) >> 4; + + /* skip AVC config */ + + htype = in->buf->pos[1]; + if (htype != 1) { + return NGX_OK; + } + + p = (u_char *) &delay; + + p[0] = in->buf->pos[4]; + p[1] = in->buf->pos[3]; + p[2] = in->buf->pos[2]; + p[3] = 0; + + ctx->has_video = 1; + + /* skip RTMP & H264 headers */ + + in->buf->pos += 5; + + return ngx_rtmp_dash_append(s, in, &ctx->video, ftype == 1, h->timestamp, + delay); +} + + +static ngx_int_t +ngx_rtmp_dash_stream_begin(ngx_rtmp_session_t *s, ngx_rtmp_stream_begin_t *v) +{ + return next_stream_begin(s, v); +} + + +static ngx_int_t +ngx_rtmp_dash_stream_eof(ngx_rtmp_session_t *s, ngx_rtmp_stream_eof_t *v) +{ + ngx_rtmp_dash_close_fragments(s); + + return next_stream_eof(s, v); +} + + +static ngx_int_t +ngx_rtmp_dash_cleanup_dir(ngx_str_t *ppath, ngx_msec_t playlen) +{ + time_t mtime, max_age; + u_char *p; + u_char path[NGX_MAX_PATH + 1], mpd_path[NGX_MAX_PATH + 1]; + ngx_dir_t dir; + ngx_err_t err; + ngx_str_t name, spath, mpd; + ngx_int_t nentries, nerased; + ngx_file_info_t fi; + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0, + "dash: cleanup path='%V' playlen=%M", ppath, playlen); + + if (ngx_open_dir(ppath, &dir) != NGX_OK) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, ngx_errno, + "dash: cleanup open dir failed '%V'", ppath); + return NGX_ERROR; + } + + nentries = 0; + nerased = 0; + + for ( ;; ) { + ngx_set_errno(0); + + if (ngx_read_dir(&dir) == NGX_ERROR) { + err = ngx_errno; + + if (ngx_close_dir(&dir) == NGX_ERROR) { + ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, ngx_errno, + "dash: cleanup " ngx_close_dir_n " \"%V\" failed", + ppath); + } + + if (err == NGX_ENOMOREFILES) { + return nentries - nerased; + } + + ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, err, + "dash: cleanup " ngx_read_dir_n + " '%V' failed", ppath); + return NGX_ERROR; + } + + name.data = ngx_de_name(&dir); + if (name.data[0] == '.') { + continue; + } + + name.len = ngx_de_namelen(&dir); + + p = ngx_snprintf(path, sizeof(path) - 1, "%V/%V", ppath, &name); + *p = 0; + + spath.data = path; + spath.len = p - path; + + nentries++; + + if (!dir.valid_info && ngx_de_info(path, &dir) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, ngx_errno, + "dash: cleanup " ngx_de_info_n " \"%V\" failed", + &spath); + + continue; + } + + if (ngx_de_is_dir(&dir)) { + + if (ngx_rtmp_dash_cleanup_dir(&spath, playlen) == 0) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0, + "dash: cleanup dir '%V'", &name); + + /* + * null-termination gets spoiled in win32 + * version of ngx_open_dir + */ + + *p = 0; + + if (ngx_delete_dir(path) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno, + "dash: cleanup " ngx_delete_dir_n + " failed on '%V'", &spath); + } else { + nerased++; + } + } + + continue; + } + + if (!ngx_de_is_file(&dir)) { + continue; + } + + if (name.len >= 8 && name.data[name.len - 8] == 'i' && + name.data[name.len - 7] == 'n' && + name.data[name.len - 6] == 'i' && + name.data[name.len - 5] == 't' && + name.data[name.len - 4] == '.' && + name.data[name.len - 3] == 'm' && + name.data[name.len - 2] == '4') + { + if (name.len == 8) { + ngx_str_set(&mpd, "index"); + } else { + mpd.data = name.data; + mpd.len = name.len - 9; + } + + p = ngx_snprintf(mpd_path, sizeof(mpd_path) - 1, "%V/%V.mpd", + ppath, &mpd); + *p = 0; + + if (ngx_file_info(mpd_path, &fi) != NGX_FILE_ERROR) { + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0, + "dash: cleanup '%V' delayed, mpd exists '%s'", + &name, mpd_path); + continue; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0, + "dash: cleanup '%V' allowed, mpd missing '%s'", + &name, mpd_path); + + max_age = 0; + + } else if (name.len >= 4 && name.data[name.len - 4] == '.' && + name.data[name.len - 3] == 'm' && + name.data[name.len - 2] == '4' && + name.data[name.len - 1] == 'v') + { + max_age = playlen / 500; + + } else if (name.len >= 4 && name.data[name.len - 4] == '.' && + name.data[name.len - 3] == 'm' && + name.data[name.len - 2] == '4' && + name.data[name.len - 1] == 'a') + { + max_age = playlen / 500; + + } else if (name.len >= 4 && name.data[name.len - 4] == '.' && + name.data[name.len - 3] == 'm' && + name.data[name.len - 2] == 'p' && + name.data[name.len - 1] == 'd') + { + max_age = playlen / 500; + + } else if (name.len >= 4 && name.data[name.len - 4] == '.' && + name.data[name.len - 3] == 'r' && + name.data[name.len - 2] == 'a' && + name.data[name.len - 1] == 'w') + { + max_age = playlen / 1000; + + } else { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0, + "dash: cleanup skip unknown file type '%V'", &name); + continue; + } + + mtime = ngx_de_mtime(&dir); + if (mtime + max_age > ngx_cached_time->sec) { + continue; + } + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0, + "dash: cleanup '%V' mtime=%T age=%T", + &name, mtime, ngx_cached_time->sec - mtime); + + if (ngx_delete_file(path) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno, + "dash: cleanup " ngx_delete_file_n " failed on '%V'", + &spath); + continue; + } + + nerased++; + } +} + + +static time_t +ngx_rtmp_dash_cleanup(void *data) +{ + ngx_rtmp_dash_cleanup_t *cleanup = data; + + ngx_rtmp_dash_cleanup_dir(&cleanup->path, cleanup->playlen); + + return cleanup->playlen / 500; +} + + +static void * +ngx_rtmp_dash_create_app_conf(ngx_conf_t *cf) +{ + ngx_rtmp_dash_app_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_dash_app_conf_t)); + if (conf == NULL) { + return NULL; + } + + conf->dash = NGX_CONF_UNSET; + conf->fraglen = NGX_CONF_UNSET_MSEC; + conf->playlen = NGX_CONF_UNSET_MSEC; + conf->cleanup = NGX_CONF_UNSET; + conf->nested = NGX_CONF_UNSET; + + return conf; +} + + +static char * +ngx_rtmp_dash_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_dash_app_conf_t *prev = parent; + ngx_rtmp_dash_app_conf_t *conf = child; + ngx_rtmp_dash_cleanup_t *cleanup; + + ngx_conf_merge_value(conf->dash, prev->dash, 0); + ngx_conf_merge_msec_value(conf->fraglen, prev->fraglen, 5000); + 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); + + if (conf->fraglen) { + conf->winfrags = conf->playlen / conf->fraglen; + } + + /* schedule cleanup */ + + if (conf->dash && conf->path.len && conf->cleanup) { + if (conf->path.data[conf->path.len - 1] == '/') { + conf->path.len--; + } + + cleanup = ngx_pcalloc(cf->pool, sizeof(*cleanup)); + if (cleanup == NULL) { + return NGX_CONF_ERROR; + } + + cleanup->path = conf->path; + cleanup->playlen = conf->playlen; + + conf->slot = ngx_pcalloc(cf->pool, sizeof(*conf->slot)); + if (conf->slot == NULL) { + return NGX_CONF_ERROR; + } + + conf->slot->manager = ngx_rtmp_dash_cleanup; + conf->slot->name = conf->path; + conf->slot->data = cleanup; + conf->slot->conf_file = cf->conf_file->file.name.data; + conf->slot->line = cf->conf_file->line; + + if (ngx_add_path(cf, &conf->slot) != NGX_OK) { + return NGX_CONF_ERROR; + } + } + + ngx_conf_merge_str_value(conf->path, prev->path, ""); + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_rtmp_dash_postconfiguration(ngx_conf_t *cf) +{ + ngx_rtmp_handler_pt *h; + ngx_rtmp_core_main_conf_t *cmcf; + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + + h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]); + *h = ngx_rtmp_dash_video; + + h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]); + *h = ngx_rtmp_dash_audio; + + next_publish = ngx_rtmp_publish; + ngx_rtmp_publish = ngx_rtmp_dash_publish; + + next_close_stream = ngx_rtmp_close_stream; + ngx_rtmp_close_stream = ngx_rtmp_dash_close_stream; + + next_stream_begin = ngx_rtmp_stream_begin; + ngx_rtmp_stream_begin = ngx_rtmp_dash_stream_begin; + + next_stream_eof = ngx_rtmp_stream_eof; + ngx_rtmp_stream_eof = ngx_rtmp_dash_stream_eof; + + return NGX_OK; +} diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/dash/ngx_rtmp_mp4.c nginx-1.10.0/debian/modules/nginx-rtmp-module/dash/ngx_rtmp_mp4.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/dash/ngx_rtmp_mp4.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/dash/ngx_rtmp_mp4.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,1167 @@ + + +#include +#include +#include "ngx_rtmp_mp4.h" +#include + + +static ngx_int_t +ngx_rtmp_mp4_field_32(ngx_buf_t *b, uint32_t n) +{ + u_char bytes[4]; + + bytes[0] = ((uint32_t) n >> 24) & 0xFF; + bytes[1] = ((uint32_t) n >> 16) & 0xFF; + bytes[2] = ((uint32_t) n >> 8) & 0xFF; + bytes[3] = (uint32_t) n & 0xFF; + + if (b->last + sizeof(bytes) > b->end) { + return NGX_ERROR; + } + + b->last = ngx_cpymem(b->last, bytes, sizeof(bytes)); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_field_24(ngx_buf_t *b, uint32_t n) +{ + u_char bytes[3]; + + bytes[0] = ((uint32_t) n >> 16) & 0xFF; + bytes[1] = ((uint32_t) n >> 8) & 0xFF; + bytes[2] = (uint32_t) n & 0xFF; + + if (b->last + sizeof(bytes) > b->end) { + return NGX_ERROR; + } + + b->last = ngx_cpymem(b->last, bytes, sizeof(bytes)); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_field_16(ngx_buf_t *b, uint16_t n) +{ + u_char bytes[2]; + + bytes[0] = ((uint32_t) n >> 8) & 0xFF; + bytes[1] = (uint32_t) n & 0xFF; + + if (b->last + sizeof(bytes) > b->end) { + return NGX_ERROR; + } + + b->last = ngx_cpymem(b->last, bytes, sizeof(bytes)); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_field_8(ngx_buf_t *b, uint8_t n) +{ + u_char bytes[1]; + + bytes[0] = n & 0xFF; + + if (b->last + sizeof(bytes) > b->end) { + return NGX_ERROR; + } + + b->last = ngx_cpymem(b->last, bytes, sizeof(bytes)); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_put_descr(ngx_buf_t *b, int tag, size_t size) +{ + ngx_rtmp_mp4_field_8(b, (uint8_t) tag); + ngx_rtmp_mp4_field_8(b, size & 0x7F); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_data(ngx_buf_t *b, void *data, size_t n) +{ + if (b->last + n > b->end) { + return NGX_ERROR; + } + + b->last = ngx_cpymem(b->last, (u_char *) data, n); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_box(ngx_buf_t *b, const char box[4]) +{ + if (b->last + 4 > b->end) { + return NGX_ERROR; + } + + b->last = ngx_cpymem(b->last, (u_char *) box, 4); + + return NGX_OK; +} + + +static u_char * +ngx_rtmp_mp4_start_box(ngx_buf_t *b, const char box[4]) +{ + u_char *p; + + p = b->last; + + if (ngx_rtmp_mp4_field_32(b, 0) != NGX_OK) { + return NULL; + } + + if (ngx_rtmp_mp4_box(b, box) != NGX_OK) { + return NULL; + } + + return p; +} + + +static ngx_int_t +ngx_rtmp_mp4_update_box_size(ngx_buf_t *b, u_char *p) +{ + u_char *curpos; + + if (p == NULL) { + return NGX_ERROR; + } + + curpos = b->last; + + b->last = p; + + ngx_rtmp_mp4_field_32(b, (uint32_t) (curpos - p)); + + b->last = curpos; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_matrix(ngx_buf_t *buf, uint32_t a, uint32_t b, uint32_t c, + uint32_t d, uint32_t tx, uint32_t ty) +{ + +/* + * transformation matrix + * |a b u| + * |c d v| + * |tx ty w| + */ + + ngx_rtmp_mp4_field_32(buf, a << 16); /* 16.16 format */ + ngx_rtmp_mp4_field_32(buf, b << 16); /* 16.16 format */ + ngx_rtmp_mp4_field_32(buf, 0); /* u in 2.30 format */ + ngx_rtmp_mp4_field_32(buf, c << 16); /* 16.16 format */ + ngx_rtmp_mp4_field_32(buf, d << 16); /* 16.16 format */ + ngx_rtmp_mp4_field_32(buf, 0); /* v in 2.30 format */ + ngx_rtmp_mp4_field_32(buf, tx << 16); /* 16.16 format */ + ngx_rtmp_mp4_field_32(buf, ty << 16); /* 16.16 format */ + ngx_rtmp_mp4_field_32(buf, 1 << 30); /* w in 2.30 format */ + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_mp4_write_ftyp(ngx_buf_t *b) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "ftyp"); + + /* major brand */ + ngx_rtmp_mp4_box(b, "iso6"); + + /* minor version */ + ngx_rtmp_mp4_field_32(b, 1); + + /* compatible brands */ + ngx_rtmp_mp4_box(b, "isom"); + ngx_rtmp_mp4_box(b, "iso6"); + ngx_rtmp_mp4_box(b, "dash"); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_mp4_write_styp(ngx_buf_t *b) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "styp"); + + /* major brand */ + ngx_rtmp_mp4_box(b, "iso6"); + + /* minor version */ + ngx_rtmp_mp4_field_32(b, 1); + + /* compatible brands */ + ngx_rtmp_mp4_box(b, "isom"); + ngx_rtmp_mp4_box(b, "iso6"); + ngx_rtmp_mp4_box(b, "dash"); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_mvhd(ngx_buf_t *b) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "mvhd"); + + /* version */ + ngx_rtmp_mp4_field_32(b, 0); + + /* creation time */ + ngx_rtmp_mp4_field_32(b, 0); + + /* modification time */ + ngx_rtmp_mp4_field_32(b, 0); + + /* timescale */ + ngx_rtmp_mp4_field_32(b, 1000); + + /* duration */ + ngx_rtmp_mp4_field_32(b, 0); + + /* reserved */ + ngx_rtmp_mp4_field_32(b, 0x00010000); + ngx_rtmp_mp4_field_16(b, 0x0100); + ngx_rtmp_mp4_field_16(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + + ngx_rtmp_mp4_write_matrix(b, 1, 0, 0, 1, 0, 0); + + /* reserved */ + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + + /* next track id */ + ngx_rtmp_mp4_field_32(b, 1); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_tkhd(ngx_rtmp_session_t *s, ngx_buf_t *b, + ngx_rtmp_mp4_track_type_t ttype) +{ + u_char *pos; + ngx_rtmp_codec_ctx_t *codec_ctx; + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + pos = ngx_rtmp_mp4_start_box(b, "tkhd"); + + /* version */ + ngx_rtmp_mp4_field_8(b, 0); + + /* flags: TrackEnabled */ + ngx_rtmp_mp4_field_24(b, 0x0000000f); + + /* creation time */ + ngx_rtmp_mp4_field_32(b, 0); + + /* modification time */ + ngx_rtmp_mp4_field_32(b, 0); + + /* track id */ + ngx_rtmp_mp4_field_32(b, 1); + + /* reserved */ + ngx_rtmp_mp4_field_32(b, 0); + + /* duration */ + ngx_rtmp_mp4_field_32(b, 0); + + /* reserved */ + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + + /* reserved */ + ngx_rtmp_mp4_field_16(b, ttype == NGX_RTMP_MP4_VIDEO_TRACK ? 0 : 0x0100); + + /* reserved */ + ngx_rtmp_mp4_field_16(b, 0); + + ngx_rtmp_mp4_write_matrix(b, 1, 0, 0, 1, 0, 0); + + if (ttype == NGX_RTMP_MP4_VIDEO_TRACK) { + ngx_rtmp_mp4_field_32(b, (uint32_t) codec_ctx->width << 16); + ngx_rtmp_mp4_field_32(b, (uint32_t) codec_ctx->height << 16); + } else { + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + } + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_mdhd(ngx_buf_t *b) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "mdhd"); + + /* version */ + ngx_rtmp_mp4_field_32(b, 0); + + /* creation time */ + ngx_rtmp_mp4_field_32(b, 0); + + /* modification time */ + ngx_rtmp_mp4_field_32(b, 0); + + /* time scale*/ + ngx_rtmp_mp4_field_32(b, 1000); + + /* duration */ + ngx_rtmp_mp4_field_32(b, 0); + + /* lanuguage */ + ngx_rtmp_mp4_field_16(b, 0x15C7); + + /* reserved */ + ngx_rtmp_mp4_field_16(b, 0); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_hdlr(ngx_buf_t *b, ngx_rtmp_mp4_track_type_t ttype) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "hdlr"); + + /* version and flags */ + ngx_rtmp_mp4_field_32(b, 0); + + /* pre defined */ + ngx_rtmp_mp4_field_32(b, 0); + + if (ttype == NGX_RTMP_MP4_VIDEO_TRACK) { + ngx_rtmp_mp4_box(b, "vide"); + } else { + ngx_rtmp_mp4_box(b, "soun"); + } + + /* reserved */ + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + + if (ttype == NGX_RTMP_MP4_VIDEO_TRACK) { + /* video handler string, NULL-terminated */ + ngx_rtmp_mp4_data(b, "VideoHandler", sizeof("VideoHandler")); + } else { + /* sound handler string, NULL-terminated */ + ngx_rtmp_mp4_data(b, "SoundHandler", sizeof("SoundHandler")); + } + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_vmhd(ngx_buf_t *b) +{ + /* size is always 20, apparently */ + ngx_rtmp_mp4_field_32(b, 20); + + ngx_rtmp_mp4_box(b, "vmhd"); + + /* version and flags */ + ngx_rtmp_mp4_field_32(b, 0x01); + + /* reserved (graphics mode=copy) */ + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_smhd(ngx_buf_t *b) +{ + /* size is always 16, apparently */ + ngx_rtmp_mp4_field_32(b, 16); + + ngx_rtmp_mp4_box(b, "smhd"); + + /* version and flags */ + ngx_rtmp_mp4_field_32(b, 0); + + /* reserved (balance normally=0) */ + ngx_rtmp_mp4_field_16(b, 0); + ngx_rtmp_mp4_field_16(b, 0); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_dref(ngx_buf_t *b) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "dref"); + + /* version and flags */ + ngx_rtmp_mp4_field_32(b, 0); + + /* entry count */ + ngx_rtmp_mp4_field_32(b, 1); + + /* url size */ + ngx_rtmp_mp4_field_32(b, 0xc); + + ngx_rtmp_mp4_box(b, "url "); + + /* version and flags */ + ngx_rtmp_mp4_field_32(b, 0x00000001); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_dinf(ngx_buf_t *b) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "dinf"); + + ngx_rtmp_mp4_write_dref(b); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_avcc(ngx_rtmp_session_t *s, ngx_buf_t *b) +{ + u_char *pos, *p; + ngx_chain_t *in; + ngx_rtmp_codec_ctx_t *codec_ctx; + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + if (codec_ctx == NULL) { + return NGX_ERROR; + } + + in = codec_ctx->avc_header; + if (in == NULL) { + return NGX_ERROR; + } + + pos = ngx_rtmp_mp4_start_box(b, "avcC"); + + /* assume config fits one chunk (highly probable) */ + + /* + * Skip: + * - flv fmt + * - H264 CONF/PICT (0x00) + * - 0 + * - 0 + * - 0 + */ + + p = in->buf->pos + 5; + + if (p < in->buf->last) { + ngx_rtmp_mp4_data(b, p, (size_t) (in->buf->last - p)); + } else { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "dash: invalid avcc received"); + } + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_video(ngx_rtmp_session_t *s, ngx_buf_t *b) +{ + u_char *pos; + ngx_rtmp_codec_ctx_t *codec_ctx; + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + pos = ngx_rtmp_mp4_start_box(b, "avc1"); + + /* reserved */ + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_16(b, 0); + + /* data reference index */ + ngx_rtmp_mp4_field_16(b, 1); + + /* codec stream version & revision */ + ngx_rtmp_mp4_field_16(b, 0); + ngx_rtmp_mp4_field_16(b, 0); + + /* reserved */ + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + + /* width & height */ + ngx_rtmp_mp4_field_16(b, (uint16_t) codec_ctx->width); + ngx_rtmp_mp4_field_16(b, (uint16_t) codec_ctx->height); + + /* horizontal & vertical resolutions 72 dpi */ + ngx_rtmp_mp4_field_32(b, 0x00480000); + ngx_rtmp_mp4_field_32(b, 0x00480000); + + /* data size */ + ngx_rtmp_mp4_field_32(b, 0); + + /* frame count */ + ngx_rtmp_mp4_field_16(b, 1); + + /* compressor name */ + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + + /* reserved */ + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_16(b, 0x18); + ngx_rtmp_mp4_field_16(b, 0xffff); + + ngx_rtmp_mp4_write_avcc(s, b); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_esds(ngx_rtmp_session_t *s, ngx_buf_t *b) +{ + size_t dsi_len; + u_char *pos, *dsi; + ngx_buf_t *db; + ngx_rtmp_codec_ctx_t *codec_ctx; + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + if (codec_ctx == NULL || codec_ctx->aac_header == NULL) { + return NGX_ERROR; + } + + db = codec_ctx->aac_header->buf; + if (db == NULL) { + return NGX_ERROR; + } + + dsi = db->pos + 2; + if (dsi > db->last) { + return NGX_ERROR; + } + + dsi_len = db->last - dsi; + + pos = ngx_rtmp_mp4_start_box(b, "esds"); + + /* version */ + ngx_rtmp_mp4_field_32(b, 0); + + + /* ES Descriptor */ + + ngx_rtmp_mp4_put_descr(b, 0x03, 23 + dsi_len); + + /* ES_ID */ + ngx_rtmp_mp4_field_16(b, 1); + + /* flags */ + ngx_rtmp_mp4_field_8(b, 0); + + + /* DecoderConfig Descriptor */ + + ngx_rtmp_mp4_put_descr(b, 0x04, 15 + dsi_len); + + /* objectTypeIndication: Audio ISO/IEC 14496-3 (AAC) */ + ngx_rtmp_mp4_field_8(b, 0x40); + + /* streamType: AudioStream */ + ngx_rtmp_mp4_field_8(b, 0x15); + + /* bufferSizeDB */ + ngx_rtmp_mp4_field_24(b, 0); + + /* maxBitrate */ + ngx_rtmp_mp4_field_32(b, 0x0001F151); + + /* avgBitrate */ + ngx_rtmp_mp4_field_32(b, 0x0001F14D); + + + /* DecoderSpecificInfo Descriptor */ + + ngx_rtmp_mp4_put_descr(b, 0x05, dsi_len); + ngx_rtmp_mp4_data(b, dsi, dsi_len); + + + /* SL Descriptor */ + + ngx_rtmp_mp4_put_descr(b, 0x06, 1); + ngx_rtmp_mp4_field_8(b, 0x02); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_audio(ngx_rtmp_session_t *s, ngx_buf_t *b) +{ + u_char *pos; + ngx_rtmp_codec_ctx_t *codec_ctx; + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + pos = ngx_rtmp_mp4_start_box(b, "mp4a"); + + /* reserved */ + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_16(b, 0); + + /* data reference index */ + ngx_rtmp_mp4_field_16(b, 1); + + /* reserved */ + ngx_rtmp_mp4_field_32(b, 0); + ngx_rtmp_mp4_field_32(b, 0); + + /* channel count */ + ngx_rtmp_mp4_field_16(b, (uint16_t) codec_ctx->audio_channels); + + /* sample size */ + ngx_rtmp_mp4_field_16(b, (uint16_t) (codec_ctx->sample_size * 8)); + + /* reserved */ + ngx_rtmp_mp4_field_32(b, 0); + + /* time scale */ + ngx_rtmp_mp4_field_16(b, 1000); + + /* sample rate */ + ngx_rtmp_mp4_field_16(b, (uint16_t) codec_ctx->sample_rate); + + ngx_rtmp_mp4_write_esds(s, b); +#if 0 + /* tag size*/ + ngx_rtmp_mp4_field_32(b, 8); + + /* null tag */ + ngx_rtmp_mp4_field_32(b, 0); +#endif + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_stsd(ngx_rtmp_session_t *s, ngx_buf_t *b, + ngx_rtmp_mp4_track_type_t ttype) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "stsd"); + + /* version & flags */ + ngx_rtmp_mp4_field_32(b, 0); + + /* entry count */ + ngx_rtmp_mp4_field_32(b, 1); + + if (ttype == NGX_RTMP_MP4_VIDEO_TRACK) { + ngx_rtmp_mp4_write_video(s, b); + } else { + ngx_rtmp_mp4_write_audio(s, b); + } + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_stts(ngx_buf_t *b) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "stts"); + + ngx_rtmp_mp4_field_32(b, 0); /* version */ + ngx_rtmp_mp4_field_32(b, 0); /* entry count */ + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_stsc(ngx_buf_t *b) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "stsc"); + + ngx_rtmp_mp4_field_32(b, 0); /* version */ + ngx_rtmp_mp4_field_32(b, 0); /* entry count */ + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_stsz(ngx_buf_t *b) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "stsz"); + + ngx_rtmp_mp4_field_32(b, 0); /* version */ + ngx_rtmp_mp4_field_32(b, 0); /* entry count */ + ngx_rtmp_mp4_field_32(b, 0); /* moar zeros */ + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_stco(ngx_buf_t *b) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "stco"); + + ngx_rtmp_mp4_field_32(b, 0); /* version */ + ngx_rtmp_mp4_field_32(b, 0); /* entry count */ + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_stbl(ngx_rtmp_session_t *s, ngx_buf_t *b, + ngx_rtmp_mp4_track_type_t ttype) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "stbl"); + + ngx_rtmp_mp4_write_stsd(s, b, ttype); + ngx_rtmp_mp4_write_stts(b); + ngx_rtmp_mp4_write_stsc(b); + ngx_rtmp_mp4_write_stsz(b); + ngx_rtmp_mp4_write_stco(b); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_minf(ngx_rtmp_session_t *s, ngx_buf_t *b, + ngx_rtmp_mp4_track_type_t ttype) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "minf"); + + if (ttype == NGX_RTMP_MP4_VIDEO_TRACK) { + ngx_rtmp_mp4_write_vmhd(b); + } else { + ngx_rtmp_mp4_write_smhd(b); + } + + ngx_rtmp_mp4_write_dinf(b); + ngx_rtmp_mp4_write_stbl(s, b, ttype); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_mdia(ngx_rtmp_session_t *s, ngx_buf_t *b, + ngx_rtmp_mp4_track_type_t ttype) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "mdia"); + + ngx_rtmp_mp4_write_mdhd(b); + ngx_rtmp_mp4_write_hdlr(b, ttype); + ngx_rtmp_mp4_write_minf(s, b, ttype); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + +static ngx_int_t +ngx_rtmp_mp4_write_trak(ngx_rtmp_session_t *s, ngx_buf_t *b, + ngx_rtmp_mp4_track_type_t ttype) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "trak"); + + ngx_rtmp_mp4_write_tkhd(s, b, ttype); + ngx_rtmp_mp4_write_mdia(s, b, ttype); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_mvex(ngx_buf_t *b) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "mvex"); + + ngx_rtmp_mp4_field_32(b, 0x20); + + ngx_rtmp_mp4_box(b, "trex"); + + /* version & flags */ + ngx_rtmp_mp4_field_32(b, 0); + + /* track id */ + ngx_rtmp_mp4_field_32(b, 1); + + /* default sample description index */ + ngx_rtmp_mp4_field_32(b, 1); + + /* default sample duration */ + ngx_rtmp_mp4_field_32(b, 0); + + /* default sample size, 1024 for AAC */ + ngx_rtmp_mp4_field_32(b, 0); + + /* default sample flags, key on */ + ngx_rtmp_mp4_field_32(b, 0); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_mp4_write_moov(ngx_rtmp_session_t *s, ngx_buf_t *b, + ngx_rtmp_mp4_track_type_t ttype) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "moov"); + + ngx_rtmp_mp4_write_mvhd(b); + ngx_rtmp_mp4_write_mvex(b); + ngx_rtmp_mp4_write_trak(s, b, ttype); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_tfhd(ngx_buf_t *b) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "tfhd"); + + /* version & flags */ + ngx_rtmp_mp4_field_32(b, 0x00020000); + + /* track id */ + ngx_rtmp_mp4_field_32(b, 1); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_tfdt(ngx_buf_t *b, uint32_t earliest_pres_time) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "tfdt"); + + /* version == 1 aka 64 bit integer */ + ngx_rtmp_mp4_field_32(b, 0x00000000); + ngx_rtmp_mp4_field_32(b, earliest_pres_time); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_trun(ngx_buf_t *b, uint32_t sample_count, + ngx_rtmp_mp4_sample_t *samples, ngx_uint_t sample_mask, u_char *moof_pos) +{ + u_char *pos; + uint32_t i, offset, nitems, flags; + + pos = ngx_rtmp_mp4_start_box(b, "trun"); + + nitems = 0; + + /* data offset present */ + flags = 0x01; + + if (sample_mask & NGX_RTMP_MP4_SAMPLE_DURATION) { + nitems++; + flags |= 0x000100; + } + + if (sample_mask & NGX_RTMP_MP4_SAMPLE_SIZE) { + nitems++; + flags |= 0x000200; + } + + if (sample_mask & NGX_RTMP_MP4_SAMPLE_KEY) { + nitems++; + flags |= 0x000400; + } + + if (sample_mask & NGX_RTMP_MP4_SAMPLE_DELAY) { + nitems++; + flags |= 0x000800; + } + + offset = (pos - moof_pos) + 20 + (sample_count * nitems * 4) + 8; + + ngx_rtmp_mp4_field_32(b, flags); + ngx_rtmp_mp4_field_32(b, sample_count); + ngx_rtmp_mp4_field_32(b, offset); + + for (i = 0; i < sample_count; i++, samples++) { + + if (sample_mask & NGX_RTMP_MP4_SAMPLE_DURATION) { + ngx_rtmp_mp4_field_32(b, samples->duration); + } + + if (sample_mask & NGX_RTMP_MP4_SAMPLE_SIZE) { + ngx_rtmp_mp4_field_32(b, samples->size); + } + + if (sample_mask & NGX_RTMP_MP4_SAMPLE_KEY) { + ngx_rtmp_mp4_field_32(b, samples->key ? 0x00000000 : 0x00010000); + } + + if (sample_mask & NGX_RTMP_MP4_SAMPLE_DELAY) { + ngx_rtmp_mp4_field_32(b, samples->delay); + } + } + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_traf(ngx_buf_t *b, uint32_t earliest_pres_time, + uint32_t sample_count, ngx_rtmp_mp4_sample_t *samples, + ngx_uint_t sample_mask, u_char *moof_pos) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "traf"); + + ngx_rtmp_mp4_write_tfhd(b); + ngx_rtmp_mp4_write_tfdt(b, earliest_pres_time); + ngx_rtmp_mp4_write_trun(b, sample_count, samples, sample_mask, moof_pos); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_write_mfhd(ngx_buf_t *b, uint32_t index) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "mfhd"); + + /* don't know what this is */ + ngx_rtmp_mp4_field_32(b, 0); + + /* fragment index. */ + ngx_rtmp_mp4_field_32(b, index); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_mp4_write_sidx(ngx_buf_t *b, ngx_uint_t reference_size, + uint32_t earliest_pres_time, uint32_t latest_pres_time) +{ + u_char *pos; + uint32_t duration; + + duration = latest_pres_time - earliest_pres_time; + + pos = ngx_rtmp_mp4_start_box(b, "sidx"); + + /* version */ + ngx_rtmp_mp4_field_32(b, 0); + + /* reference id */ + ngx_rtmp_mp4_field_32(b, 1); + + /* timescale */ + ngx_rtmp_mp4_field_32(b, 1000); + + /* earliest presentation time */ + ngx_rtmp_mp4_field_32(b, earliest_pres_time); + + /* first offset */ + ngx_rtmp_mp4_field_32(b, duration); /*TODO*/ + + /* reserved */ + ngx_rtmp_mp4_field_16(b, 0); + + /* reference count = 1 */ + ngx_rtmp_mp4_field_16(b, 1); + + /* 1st bit is reference type, the rest is reference size */ + ngx_rtmp_mp4_field_32(b, reference_size); + + /* subsegment duration */ + ngx_rtmp_mp4_field_32(b, duration); + + /* first bit is startsWithSAP (=1), next 3 bits are SAP type (=001) */ + ngx_rtmp_mp4_field_8(b, 0x90); + + /* SAP delta time */ + ngx_rtmp_mp4_field_24(b, 0); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_mp4_write_moof(ngx_buf_t *b, uint32_t earliest_pres_time, + uint32_t sample_count, ngx_rtmp_mp4_sample_t *samples, + ngx_uint_t sample_mask, uint32_t index) +{ + u_char *pos; + + pos = ngx_rtmp_mp4_start_box(b, "moof"); + + ngx_rtmp_mp4_write_mfhd(b, index); + ngx_rtmp_mp4_write_traf(b, earliest_pres_time, sample_count, samples, + sample_mask, pos); + + ngx_rtmp_mp4_update_box_size(b, pos); + + return NGX_OK; +} + + +ngx_uint_t +ngx_rtmp_mp4_write_mdat(ngx_buf_t *b, ngx_uint_t size) +{ + ngx_rtmp_mp4_field_32(b, size); + + ngx_rtmp_mp4_box(b, "mdat"); + + return NGX_OK; +} diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/dash/ngx_rtmp_mp4.h nginx-1.10.0/debian/modules/nginx-rtmp-module/dash/ngx_rtmp_mp4.h --- nginx-1.9.15/debian/modules/nginx-rtmp-module/dash/ngx_rtmp_mp4.h 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/dash/ngx_rtmp_mp4.h 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,52 @@ + + +#ifndef _NGX_RTMP_MP4_H_INCLUDED_ +#define _NGX_RTMP_MP4_H_INCLUDED_ + + +#include +#include +#include + + +#define NGX_RTMP_MP4_SAMPLE_SIZE 0x01 +#define NGX_RTMP_MP4_SAMPLE_DURATION 0x02 +#define NGX_RTMP_MP4_SAMPLE_DELAY 0x04 +#define NGX_RTMP_MP4_SAMPLE_KEY 0x08 + + +typedef struct { + uint32_t size; + uint32_t duration; + uint32_t delay; + uint32_t timestamp; + unsigned key:1; +} ngx_rtmp_mp4_sample_t; + + +typedef enum { + NGX_RTMP_MP4_FILETYPE_INIT, + NGX_RTMP_MP4_FILETYPE_SEG +} ngx_rtmp_mp4_file_type_t; + + +typedef enum { + NGX_RTMP_MP4_VIDEO_TRACK, + NGX_RTMP_MP4_AUDIO_TRACK +} ngx_rtmp_mp4_track_type_t; + + +ngx_int_t ngx_rtmp_mp4_write_ftyp(ngx_buf_t *b); +ngx_int_t ngx_rtmp_mp4_write_styp(ngx_buf_t *b); +ngx_int_t ngx_rtmp_mp4_write_moov(ngx_rtmp_session_t *s, ngx_buf_t *b, + ngx_rtmp_mp4_track_type_t ttype); +ngx_int_t ngx_rtmp_mp4_write_moof(ngx_buf_t *b, uint32_t earliest_pres_time, + uint32_t sample_count, ngx_rtmp_mp4_sample_t *samples, + ngx_uint_t sample_mask, uint32_t index); +ngx_int_t ngx_rtmp_mp4_write_sidx(ngx_buf_t *b, + ngx_uint_t reference_size, uint32_t earliest_pres_time, + uint32_t latest_pres_time); +ngx_uint_t ngx_rtmp_mp4_write_mdat(ngx_buf_t *b, ngx_uint_t size); + + +#endif /* _NGX_RTMP_MP4_H_INCLUDED_ */ diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/doc/README.md nginx-1.10.0/debian/modules/nginx-rtmp-module/doc/README.md --- nginx-1.9.15/debian/modules/nginx-rtmp-module/doc/README.md 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/doc/README.md 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,2 @@ +Documentation is available here: +https://github.com/arut/nginx-rtmp-module/wiki diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/hls/ngx_rtmp_hls_module.c nginx-1.10.0/debian/modules/nginx-rtmp-module/hls/ngx_rtmp_hls_module.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/hls/ngx_rtmp_hls_module.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/hls/ngx_rtmp_hls_module.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,2450 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include +#include +#include +#include "ngx_rtmp_mpegts.h" + + +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 char * ngx_rtmp_hls_variant(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static ngx_int_t ngx_rtmp_hls_postconfiguration(ngx_conf_t *cf); +static void * ngx_rtmp_hls_create_app_conf(ngx_conf_t *cf); +static char * ngx_rtmp_hls_merge_app_conf(ngx_conf_t *cf, + void *parent, void *child); +static ngx_int_t ngx_rtmp_hls_flush_audio(ngx_rtmp_session_t *s); +static ngx_int_t ngx_rtmp_hls_ensure_directory(ngx_rtmp_session_t *s, + ngx_str_t *path); + + +#define NGX_RTMP_HLS_BUFSIZE (1024*1024) +#define NGX_RTMP_HLS_DIR_ACCESS 0744 + + +typedef struct { + uint64_t id; + uint64_t key_id; + double duration; + unsigned active:1; + unsigned discont:1; /* before */ +} ngx_rtmp_hls_frag_t; + + +typedef struct { + ngx_str_t suffix; + ngx_array_t args; +} ngx_rtmp_hls_variant_t; + + +typedef struct { + unsigned opened:1; + + ngx_rtmp_mpegts_file_t file; + + ngx_str_t playlist; + ngx_str_t playlist_bak; + ngx_str_t var_playlist; + ngx_str_t var_playlist_bak; + ngx_str_t stream; + ngx_str_t keyfile; + ngx_str_t name; + u_char key[16]; + + uint64_t frag; + uint64_t frag_ts; + uint64_t key_id; + ngx_uint_t nfrags; + ngx_rtmp_hls_frag_t *frags; /* circular 2 * winfrags + 1 */ + + ngx_uint_t audio_cc; + ngx_uint_t video_cc; + ngx_uint_t key_frags; + + uint64_t aframe_base; + uint64_t aframe_num; + + ngx_buf_t *aframe; + uint64_t aframe_pts; + + ngx_rtmp_hls_variant_t *var; +} ngx_rtmp_hls_ctx_t; + + +typedef struct { + ngx_str_t path; + ngx_msec_t playlen; + ngx_uint_t frags_per_key; +} ngx_rtmp_hls_cleanup_t; + + +typedef struct { + ngx_flag_t hls; + ngx_msec_t fraglen; + ngx_msec_t max_fraglen; + ngx_msec_t muxdelay; + ngx_msec_t sync; + ngx_msec_t playlen; + ngx_uint_t winfrags; + ngx_flag_t continuous; + ngx_flag_t nested; + ngx_str_t path; + ngx_uint_t naming; + 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_array_t *variant; + ngx_str_t base_url; + ngx_int_t granularity; + ngx_flag_t keys; + ngx_str_t key_path; + ngx_str_t key_url; + ngx_uint_t frags_per_key; +} ngx_rtmp_hls_app_conf_t; + + +#define NGX_RTMP_HLS_NAMING_SEQUENTIAL 1 +#define NGX_RTMP_HLS_NAMING_TIMESTAMP 2 +#define NGX_RTMP_HLS_NAMING_SYSTEM 3 + + +#define NGX_RTMP_HLS_SLICING_PLAIN 1 +#define NGX_RTMP_HLS_SLICING_ALIGNED 2 + + +#define NGX_RTMP_HLS_TYPE_LIVE 1 +#define NGX_RTMP_HLS_TYPE_EVENT 2 + + +static ngx_conf_enum_t ngx_rtmp_hls_naming_slots[] = { + { ngx_string("sequential"), NGX_RTMP_HLS_NAMING_SEQUENTIAL }, + { ngx_string("timestamp"), NGX_RTMP_HLS_NAMING_TIMESTAMP }, + { ngx_string("system"), NGX_RTMP_HLS_NAMING_SYSTEM }, + { 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 }, + { ngx_null_string, 0 } +}; + + +static ngx_conf_enum_t ngx_rtmp_hls_type_slots[] = { + { ngx_string("live"), NGX_RTMP_HLS_TYPE_LIVE }, + { ngx_string("event"), NGX_RTMP_HLS_TYPE_EVENT }, + { ngx_null_string, 0 } +}; + + +static ngx_command_t ngx_rtmp_hls_commands[] = { + + { ngx_string("hls"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, hls), + NULL }, + + { ngx_string("hls_fragment"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, fraglen), + NULL }, + + { ngx_string("hls_max_fragment"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, max_fraglen), + NULL }, + + { ngx_string("hls_path"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, path), + NULL }, + + { ngx_string("hls_playlist_length"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, playlen), + NULL }, + + { ngx_string("hls_muxdelay"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, muxdelay), + NULL }, + + { ngx_string("hls_sync"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, sync), + NULL }, + + { ngx_string("hls_continuous"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, continuous), + NULL }, + + { ngx_string("hls_nested"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, nested), + NULL }, + + { ngx_string("hls_fragment_naming"), + 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, naming), + &ngx_rtmp_hls_naming_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, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, slicing), + &ngx_rtmp_hls_slicing_slots }, + + { ngx_string("hls_type"), + 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, type), + &ngx_rtmp_hls_type_slots }, + + { ngx_string("hls_max_audio_delay"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, max_audio_delay), + NULL }, + + { ngx_string("hls_audio_buffer_size"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, audio_buffer_size), + NULL }, + + { ngx_string("hls_cleanup"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, cleanup), + NULL }, + + { ngx_string("hls_variant"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_hls_variant, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("hls_base_url"), + 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_hls_app_conf_t, base_url), + NULL }, + + { ngx_string("hls_fragment_naming_granularity"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, granularity), + NULL }, + + { ngx_string("hls_keys"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, keys), + NULL }, + + { ngx_string("hls_key_path"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, key_path), + NULL }, + + { ngx_string("hls_key_url"), + 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_hls_app_conf_t, key_url), + NULL }, + + { ngx_string("hls_fragments_per_key"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_hls_app_conf_t, frags_per_key), + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_rtmp_hls_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_hls_postconfiguration, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + ngx_rtmp_hls_create_app_conf, /* create location configuration */ + ngx_rtmp_hls_merge_app_conf, /* merge location configuration */ +}; + + +ngx_module_t ngx_rtmp_hls_module = { + NGX_MODULE_V1, + &ngx_rtmp_hls_module_ctx, /* module context */ + ngx_rtmp_hls_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_rtmp_hls_frag_t * +ngx_rtmp_hls_get_frag(ngx_rtmp_session_t *s, ngx_int_t n) +{ + ngx_rtmp_hls_ctx_t *ctx; + ngx_rtmp_hls_app_conf_t *hacf; + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + + return &ctx->frags[(ctx->frag + n) % (hacf->winfrags * 2 + 1)]; +} + + +static void +ngx_rtmp_hls_next_frag(ngx_rtmp_session_t *s) +{ + ngx_rtmp_hls_ctx_t *ctx; + ngx_rtmp_hls_app_conf_t *hacf; + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + + if (ctx->nfrags == hacf->winfrags) { + ctx->frag++; + } else { + ctx->nfrags++; + } +} + + +static ngx_int_t +ngx_rtmp_hls_rename_file(u_char *src, u_char *dst) +{ + /* rename file with overwrite */ + +#if (NGX_WIN32) + return MoveFileEx((LPCTSTR) src, (LPCTSTR) dst, MOVEFILE_REPLACE_EXISTING); +#else + return ngx_rename_file(src, dst); +#endif +} + + +static ngx_int_t +ngx_rtmp_hls_write_variant_playlist(ngx_rtmp_session_t *s) +{ + static u_char buffer[1024]; + + u_char *p, *last; + ssize_t rc; + ngx_fd_t fd; + ngx_str_t *arg; + ngx_uint_t n, k; + ngx_rtmp_hls_ctx_t *ctx; + ngx_rtmp_hls_variant_t *var; + ngx_rtmp_hls_app_conf_t *hacf; + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + + fd = ngx_open_file(ctx->var_playlist_bak.data, NGX_FILE_WRONLY, + NGX_FILE_TRUNCATE, NGX_FILE_DEFAULT_ACCESS); + + if (fd == NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "hls: " ngx_open_file_n " failed: '%V'", + &ctx->var_playlist_bak); + + return NGX_ERROR; + } + +#define NGX_RTMP_HLS_VAR_HEADER "#EXTM3U\n#EXT-X-VERSION:3\n" + + rc = ngx_write_fd(fd, NGX_RTMP_HLS_VAR_HEADER, + sizeof(NGX_RTMP_HLS_VAR_HEADER) - 1); + if (rc < 0) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "hls: " ngx_write_fd_n " failed: '%V'", + &ctx->var_playlist_bak); + ngx_close_file(fd); + return NGX_ERROR; + } + + var = hacf->variant->elts; + for (n = 0; n < hacf->variant->nelts; n++, var++) + { + p = buffer; + last = buffer + sizeof(buffer); + + p = ngx_slprintf(p, last, "#EXT-X-STREAM-INF:PROGRAM-ID=1"); + + arg = var->args.elts; + for (k = 0; k < var->args.nelts; k++, arg++) { + p = ngx_slprintf(p, last, ",%V", arg); + } + + if (p < last) { + *p++ = '\n'; + } + + p = ngx_slprintf(p, last, "%V%*s%V", + &hacf->base_url, + ctx->name.len - ctx->var->suffix.len, ctx->name.data, + &var->suffix); + if (hacf->nested) { + p = ngx_slprintf(p, last, "%s", "/index"); + } + + p = ngx_slprintf(p, last, "%s", ".m3u8\n"); + + rc = ngx_write_fd(fd, buffer, p - buffer); + if (rc < 0) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "hls: " ngx_write_fd_n " failed '%V'", + &ctx->var_playlist_bak); + ngx_close_file(fd); + return NGX_ERROR; + } + } + + ngx_close_file(fd); + + if (ngx_rtmp_hls_rename_file(ctx->var_playlist_bak.data, + ctx->var_playlist.data) + == NGX_FILE_ERROR) + { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "hls: rename failed: '%V'->'%V'", + &ctx->var_playlist_bak, &ctx->var_playlist); + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s) +{ + static u_char buffer[1024]; + ngx_fd_t fd; + u_char *p, *end; + ngx_rtmp_hls_ctx_t *ctx; + ssize_t n; + ngx_rtmp_hls_app_conf_t *hacf; + ngx_rtmp_hls_frag_t *f; + ngx_uint_t i, max_frag; + ngx_str_t name_part, key_name_part; + uint64_t prev_key_id; + const char *sep, *key_sep; + + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + + fd = ngx_open_file(ctx->playlist_bak.data, NGX_FILE_WRONLY, + NGX_FILE_TRUNCATE, NGX_FILE_DEFAULT_ACCESS); + + if (fd == NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "hls: " ngx_open_file_n " failed: '%V'", + &ctx->playlist_bak); + return NGX_ERROR; + } + + max_frag = hacf->fraglen / 1000; + + for (i = 0; i < ctx->nfrags; i++) { + f = ngx_rtmp_hls_get_frag(s, i); + if (f->duration > max_frag) { + max_frag = (ngx_uint_t) (f->duration + .5); + } + } + + p = buffer; + end = p + sizeof(buffer); + + p = ngx_slprintf(p, end, + "#EXTM3U\n" + "#EXT-X-VERSION:3\n" + "#EXT-X-MEDIA-SEQUENCE:%uL\n" + "#EXT-X-TARGETDURATION:%ui\n", + ctx->frag, max_frag); + + if (hacf->type == NGX_RTMP_HLS_TYPE_EVENT) { + p = ngx_slprintf(p, end, "#EXT-X-PLAYLIST-TYPE: EVENT\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; + } + + sep = hacf->nested ? (hacf->base_url.len ? "/" : "") : "-"; + key_sep = hacf->nested ? (hacf->key_url.len ? "/" : "") : "-"; + + name_part.len = 0; + if (!hacf->nested || hacf->base_url.len) { + name_part = ctx->name; + } + + key_name_part.len = 0; + if (!hacf->nested || hacf->key_url.len) { + key_name_part = ctx->name; + } + + prev_key_id = 0; + + for (i = 0; i < ctx->nfrags; i++) { + f = ngx_rtmp_hls_get_frag(s, i); + + p = buffer; + end = p + sizeof(buffer); + + if (f->discont) { + p = ngx_slprintf(p, end, "#EXT-X-DISCONTINUITY\n"); + } + + if (hacf->keys && (i == 0 || f->key_id != prev_key_id)) { + p = ngx_slprintf(p, end, "#EXT-X-KEY:METHOD=AES-128," + "URI=\"%V%V%s%uL.key\",IV=0x%032XL\n", + &hacf->key_url, &key_name_part, + key_sep, f->key_id, f->key_id); + } + + prev_key_id = f->key_id; + + p = ngx_slprintf(p, end, + "#EXTINF:%.3f,\n" + "%V%V%s%uL.ts\n", + f->duration, &hacf->base_url, &name_part, sep, f->id); + + ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "hls: fragment frag=%uL, n=%ui/%ui, duration=%.3f, " + "discont=%i", + ctx->frag, i + 1, ctx->nfrags, f->duration, f->discont); + + 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; + } + } + + ngx_close_file(fd); + + if (ngx_rtmp_hls_rename_file(ctx->playlist_bak.data, ctx->playlist.data) + == NGX_FILE_ERROR) + { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "hls: rename failed: '%V'->'%V'", + &ctx->playlist_bak, &ctx->playlist); + return NGX_ERROR; + } + + if (ctx->var) { + return ngx_rtmp_hls_write_variant_playlist(s); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_hls_copy(ngx_rtmp_session_t *s, void *dst, u_char **src, size_t n, + ngx_chain_t **in) +{ + u_char *last; + size_t pn; + + if (*in == NULL) { + return NGX_ERROR; + } + + for ( ;; ) { + last = (*in)->buf->last; + + if ((size_t)(last - *src) >= n) { + if (dst) { + ngx_memcpy(dst, *src, n); + } + + *src += n; + + while (*in && *src == (*in)->buf->last) { + *in = (*in)->next; + if (*in) { + *src = (*in)->buf->pos; + } + } + + return NGX_OK; + } + + pn = last - *src; + + if (dst) { + ngx_memcpy(dst, *src, pn); + dst = (u_char *)dst + pn; + } + + n -= pn; + *in = (*in)->next; + + if (*in == NULL) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "hls: failed to read %uz byte(s)", n); + return NGX_ERROR; + } + + *src = (*in)->buf->pos; + } +} + + +static ngx_int_t +ngx_rtmp_hls_append_aud(ngx_rtmp_session_t *s, ngx_buf_t *out) +{ + static u_char aud_nal[] = { 0x00, 0x00, 0x00, 0x01, 0x09, 0xf0 }; + + if (out->last + sizeof(aud_nal) > out->end) { + return NGX_ERROR; + } + + out->last = ngx_cpymem(out->last, aud_nal, sizeof(aud_nal)); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_hls_append_sps_pps(ngx_rtmp_session_t *s, ngx_buf_t *out) +{ + ngx_rtmp_codec_ctx_t *codec_ctx; + u_char *p; + ngx_chain_t *in; + ngx_rtmp_hls_ctx_t *ctx; + int8_t nnals; + uint16_t len, rlen; + ngx_int_t n; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + if (ctx == NULL || codec_ctx == NULL) { + return NGX_ERROR; + } + + in = codec_ctx->avc_header; + if (in == NULL) { + return NGX_ERROR; + } + + p = in->buf->pos; + + /* + * Skip bytes: + * - flv fmt + * - H264 CONF/PICT (0x00) + * - 0 + * - 0 + * - 0 + * - version + * - profile + * - compatibility + * - level + * - nal bytes + */ + + if (ngx_rtmp_hls_copy(s, NULL, &p, 10, &in) != NGX_OK) { + return NGX_ERROR; + } + + /* number of SPS NALs */ + if (ngx_rtmp_hls_copy(s, &nnals, &p, 1, &in) != NGX_OK) { + return NGX_ERROR; + } + + nnals &= 0x1f; /* 5lsb */ + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "hls: SPS number: %uz", nnals); + + /* SPS */ + for (n = 0; ; ++n) { + for (; nnals; --nnals) { + + /* NAL length */ + if (ngx_rtmp_hls_copy(s, &rlen, &p, 2, &in) != NGX_OK) { + return NGX_ERROR; + } + + ngx_rtmp_rmemcpy(&len, &rlen, 2); + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "hls: header NAL length: %uz", (size_t) len); + + /* AnnexB prefix */ + if (out->end - out->last < 4) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "hls: too small buffer for header NAL size"); + return NGX_ERROR; + } + + *out->last++ = 0; + *out->last++ = 0; + *out->last++ = 0; + *out->last++ = 1; + + /* NAL body */ + if (out->end - out->last < len) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "hls: too small buffer for header NAL"); + return NGX_ERROR; + } + + if (ngx_rtmp_hls_copy(s, out->last, &p, len, &in) != NGX_OK) { + return NGX_ERROR; + } + + out->last += len; + } + + if (n == 1) { + break; + } + + /* number of PPS NALs */ + if (ngx_rtmp_hls_copy(s, &nnals, &p, 1, &in) != NGX_OK) { + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "hls: PPS number: %uz", nnals); + } + + return NGX_OK; +} + + +static uint64_t +ngx_rtmp_hls_get_fragment_id(ngx_rtmp_session_t *s, uint64_t ts) +{ + ngx_rtmp_hls_ctx_t *ctx; + ngx_rtmp_hls_app_conf_t *hacf; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); + + switch (hacf->naming) { + + case NGX_RTMP_HLS_NAMING_TIMESTAMP: + return ts; + + case NGX_RTMP_HLS_NAMING_SYSTEM: + return (uint64_t) ngx_cached_time->sec * 1000 + ngx_cached_time->msec; + + default: /* NGX_RTMP_HLS_NAMING_SEQUENTIAL */ + return ctx->frag + ctx->nfrags; + } +} + + +static ngx_int_t +ngx_rtmp_hls_close_fragment(ngx_rtmp_session_t *s) +{ + ngx_rtmp_hls_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + if (ctx == NULL || !ctx->opened) { + return NGX_OK; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "hls: close fragment n=%uL", ctx->frag); + + ngx_rtmp_mpegts_close_file(&ctx->file); + + ctx->opened = 0; + + ngx_rtmp_hls_next_frag(s); + + ngx_rtmp_hls_write_playlist(s); + + return NGX_OK; +} + + +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; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + + if (ctx->opened) { + return NGX_OK; + } + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); + + if (ngx_rtmp_hls_ensure_directory(s, &hacf->path) != NGX_OK) { + return NGX_ERROR; + } + + if (hacf->keys && + ngx_rtmp_hls_ensure_directory(s, &hacf->key_path) != NGX_OK) + { + return NGX_ERROR; + } + + id = ngx_rtmp_hls_get_fragment_id(s, ts); + + if (hacf->granularity) { + g = (ngx_uint_t) hacf->granularity; + id = (uint64_t) (id / g) * g; + } + + ngx_sprintf(ctx->stream.data + ctx->stream.len, "%uL.ts%Z", id); + + if (hacf->keys) { + if (ctx->key_frags == 0) { + + ctx->key_frags = hacf->frags_per_key - 1; + ctx->key_id = id; + + if (RAND_bytes(ctx->key, 16) < 0) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "hls: failed to create key"); + return NGX_ERROR; + } + + ngx_sprintf(ctx->keyfile.data + ctx->keyfile.len, "%uL.key%Z", id); + + fd = ngx_open_file(ctx->keyfile.data, NGX_FILE_WRONLY, + NGX_FILE_TRUNCATE, NGX_FILE_DEFAULT_ACCESS); + + if (fd == NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "hls: failed to open key file '%s'", + ctx->keyfile.data); + return NGX_ERROR; + } + + if (ngx_write_fd(fd, ctx->key, 16) != 16) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "hls: failed to write key file '%s'", + ctx->keyfile.data); + ngx_close_file(fd); + return NGX_ERROR; + } + + ngx_close_file(fd); + + } else { + if (hacf->frags_per_key) { + ctx->key_frags--; + } + + if (ngx_set_file_time(ctx->keyfile.data, 0, ngx_cached_time->sec) + != NGX_OK) + { + ngx_log_error(NGX_LOG_ALERT, s->connection->log, ngx_errno, + ngx_set_file_time_n " '%s' failed", + ctx->keyfile.data); + } + } + } + + ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "hls: open fragment file='%s', keyfile='%s', " + "frag=%uL, n=%ui, time=%uL, discont=%i", + ctx->stream.data, + ctx->keyfile.data ? ctx->keyfile.data : (u_char *) "", + ctx->frag, ctx->nfrags, ts, discont); + + if (hacf->keys && + ngx_rtmp_mpegts_init_encryption(&ctx->file, ctx->key, 16, ctx->key_id) + != NGX_OK) + { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "hls: failed to initialize hls encryption"); + return NGX_ERROR; + } + + if (ngx_rtmp_mpegts_open_file(&ctx->file, ctx->stream.data, + s->connection->log) + != NGX_OK) + { + return NGX_ERROR; + } + + ctx->opened = 1; + + f = ngx_rtmp_hls_get_frag(s, ctx->nfrags); + + ngx_memzero(f, sizeof(*f)); + + f->active = 1; + f->discont = discont; + f->id = id; + f->key_id = ctx->key_id; + + ctx->frag_ts = ts; + + /* start fragment with audio to make iPhone happy */ + + ngx_rtmp_hls_flush_audio(s); + + return NGX_OK; +} + + +static void +ngx_rtmp_hls_restore_stream(ngx_rtmp_session_t *s) +{ + ngx_rtmp_hls_ctx_t *ctx; + ngx_file_t file; + ssize_t ret; + off_t offset; + u_char *p, *last, *end, *next, *pa, *pp, c; + ngx_rtmp_hls_frag_t *f; + double duration; + ngx_int_t discont; + uint64_t mag, key_id, base; + static u_char buffer[4096]; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + + ngx_memzero(&file, sizeof(file)); + + file.log = s->connection->log; + + ngx_str_set(&file.name, "m3u8"); + + file.fd = ngx_open_file(ctx->playlist.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, + 0); + if (file.fd == NGX_INVALID_FILE) { + return; + } + + offset = 0; + ctx->nfrags = 0; + f = NULL; + duration = 0; + discont = 0; + key_id = 0; + + for ( ;; ) { + + ret = ngx_read_file(&file, buffer, sizeof(buffer), offset); + if (ret <= 0) { + goto done; + } + + p = buffer; + end = buffer + ret; + + for ( ;; ) { + last = ngx_strlchr(p, end, '\n'); + + if (last == NULL) { + if (p == buffer) { + goto done; + } + break; + } + + next = last + 1; + offset += (next - p); + + if (p != last && last[-1] == '\r') { + last--; + } + + +#define NGX_RTMP_MSEQ "#EXT-X-MEDIA-SEQUENCE:" +#define NGX_RTMP_MSEQ_LEN (sizeof(NGX_RTMP_MSEQ) - 1) + + + if (ngx_memcmp(p, NGX_RTMP_MSEQ, NGX_RTMP_MSEQ_LEN) == 0) { + + ctx->frag = (uint64_t) strtod((const char *) + &p[NGX_RTMP_MSEQ_LEN], NULL); + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "hls: restore sequence frag=%uL", ctx->frag); + } + + +#define NGX_RTMP_XKEY "#EXT-X-KEY:" +#define NGX_RTMP_XKEY_LEN (sizeof(NGX_RTMP_XKEY) - 1) + + if (ngx_memcmp(p, NGX_RTMP_XKEY, NGX_RTMP_XKEY_LEN) == 0) { + + /* recover key id from initialization vector */ + + key_id = 0; + base = 1; + pp = last - 1; + + for ( ;; ) { + if (pp < p) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "hls: failed to read key id"); + break; + } + + c = *pp; + if (c == 'x') { + break; + } + + if (c >= '0' && c <= '9') { + c -= '0'; + goto next; + } + + c |= 0x20; + + if (c >= 'a' && c <= 'f') { + c -= 'a' - 10; + goto next; + } + + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "hls: bad character in key id"); + break; + + next: + + key_id += base * c; + base *= 0x10; + pp--; + } + } + + +#define NGX_RTMP_EXTINF "#EXTINF:" +#define NGX_RTMP_EXTINF_LEN (sizeof(NGX_RTMP_EXTINF) - 1) + + + if (ngx_memcmp(p, NGX_RTMP_EXTINF, NGX_RTMP_EXTINF_LEN) == 0) { + + duration = strtod((const char *) &p[NGX_RTMP_EXTINF_LEN], NULL); + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "hls: restore durarion=%.3f", duration); + } + + +#define NGX_RTMP_DISCONT "#EXT-X-DISCONTINUITY" +#define NGX_RTMP_DISCONT_LEN (sizeof(NGX_RTMP_DISCONT) - 1) + + + if (ngx_memcmp(p, NGX_RTMP_DISCONT, NGX_RTMP_DISCONT_LEN) == 0) { + + discont = 1; + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "hls: discontinuity"); + } + + /* find '.ts\r' */ + + if (p + 4 <= last && + last[-3] == '.' && last[-2] == 't' && last[-1] == 's') + { + f = ngx_rtmp_hls_get_frag(s, ctx->nfrags); + + ngx_memzero(f, sizeof(*f)); + + f->duration = duration; + f->discont = discont; + f->active = 1; + f->id = 0; + + discont = 0; + + mag = 1; + for (pa = last - 4; pa >= p; pa--) { + if (*pa < '0' || *pa > '9') { + break; + } + f->id += (*pa - '0') * mag; + mag *= 10; + } + + f->key_id = key_id; + + ngx_rtmp_hls_next_frag(s); + + ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "hls: restore fragment '%*s' id=%uL, " + "duration=%.3f, frag=%uL, nfrags=%ui", + (size_t) (last - p), p, f->id, f->duration, + ctx->frag, ctx->nfrags); + } + + p = next; + } + } + +done: + ngx_close_file(file.fd); +} + + +static ngx_int_t +ngx_rtmp_hls_ensure_directory(ngx_rtmp_session_t *s, ngx_str_t *path) +{ + size_t len; + ngx_file_info_t fi; + ngx_rtmp_hls_ctx_t *ctx; + ngx_rtmp_hls_app_conf_t *hacf; + + static u_char zpath[NGX_MAX_PATH + 1]; + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); + + if (path->len + 1 > sizeof(zpath)) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "hls: too long path"); + return NGX_ERROR; + } + + ngx_snprintf(zpath, sizeof(zpath), "%V%Z", path); + + if (ngx_file_info(zpath, &fi) == NGX_FILE_ERROR) { + + if (ngx_errno != NGX_ENOENT) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "hls: " ngx_file_info_n " failed on '%V'", path); + return NGX_ERROR; + } + + /* ENOENT */ + + if (ngx_create_dir(zpath, NGX_RTMP_HLS_DIR_ACCESS) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "hls: " ngx_create_dir_n " failed on '%V'", path); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "hls: directory '%V' created", path); + + } else { + + if (!ngx_is_dir(&fi)) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "hls: '%V' exists and is not a directory", path); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "hls: directory '%V' exists", path); + } + + if (!hacf->nested) { + return NGX_OK; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + + len = path->len; + if (path->data[len - 1] == '/') { + len--; + } + + if (len + 1 + ctx->name.len + 1 > sizeof(zpath)) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "hls: too long path"); + return NGX_ERROR; + } + + ngx_snprintf(zpath, sizeof(zpath) - 1, "%*s/%V%Z", len, path->data, + &ctx->name); + + if (ngx_file_info(zpath, &fi) != NGX_FILE_ERROR) { + + if (ngx_is_dir(&fi)) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "hls: directory '%s' exists", zpath); + return NGX_OK; + } + + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "hls: '%s' exists and is not a directory", zpath); + + return NGX_ERROR; + } + + if (ngx_errno != NGX_ENOENT) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "hls: " ngx_file_info_n " failed on '%s'", zpath); + return NGX_ERROR; + } + + /* NGX_ENOENT */ + + if (ngx_create_dir(zpath, NGX_RTMP_HLS_DIR_ACCESS) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "hls: " ngx_create_dir_n " failed on '%s'", zpath); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "hls: directory '%s' created", zpath); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_hls_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) +{ + ngx_rtmp_hls_app_conf_t *hacf; + ngx_rtmp_hls_ctx_t *ctx; + u_char *p, *pp; + ngx_rtmp_hls_frag_t *f; + ngx_buf_t *b; + size_t len; + ngx_rtmp_hls_variant_t *var; + ngx_uint_t n; + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); + if (hacf == NULL || !hacf->hls || hacf->path.len == 0) { + goto next; + } + + if (s->auto_pushed) { + goto next; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "hls: publish: name='%s' type='%s'", + v->name, v->type); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + + if (ctx == NULL) { + + ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_rtmp_hls_ctx_t)); + ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_hls_module); + + } else { + + f = ctx->frags; + b = ctx->aframe; + + ngx_memzero(ctx, sizeof(ngx_rtmp_hls_ctx_t)); + + ctx->frags = f; + ctx->aframe = b; + + if (b) { + b->pos = b->last = b->start; + } + } + + if (ctx->frags == NULL) { + ctx->frags = ngx_pcalloc(s->connection->pool, + sizeof(ngx_rtmp_hls_frag_t) * + (hacf->winfrags * 2 + 1)); + if (ctx->frags == NULL) { + return NGX_ERROR; + } + } + + if (ngx_strstr(v->name, "..")) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "hls: bad stream name: '%s'", v->name); + return NGX_ERROR; + } + + ctx->name.len = ngx_strlen(v->name); + ctx->name.data = ngx_palloc(s->connection->pool, ctx->name.len + 1); + + if (ctx->name.data == NULL) { + return NGX_ERROR; + } + + *ngx_cpymem(ctx->name.data, v->name, ctx->name.len) = 0; + + len = hacf->path.len + 1 + ctx->name.len + sizeof(".m3u8"); + if (hacf->nested) { + len += sizeof("/index") - 1; + } + + ctx->playlist.data = ngx_palloc(s->connection->pool, len); + p = ngx_cpymem(ctx->playlist.data, hacf->path.data, hacf->path.len); + + if (p[-1] != '/') { + *p++ = '/'; + } + + p = ngx_cpymem(p, ctx->name.data, ctx->name.len); + + /* + * ctx->stream holds initial part of stream file path + * however the space for the whole stream path + * is allocated + */ + + ctx->stream.len = p - ctx->playlist.data + 1; + ctx->stream.data = ngx_palloc(s->connection->pool, + ctx->stream.len + NGX_INT64_LEN + + sizeof(".ts")); + + ngx_memcpy(ctx->stream.data, ctx->playlist.data, ctx->stream.len - 1); + ctx->stream.data[ctx->stream.len - 1] = (hacf->nested ? '/' : '-'); + + /* varint playlist path */ + + if (hacf->variant) { + var = hacf->variant->elts; + for (n = 0; n < hacf->variant->nelts; n++, var++) { + if (ctx->name.len > var->suffix.len && + ngx_memcmp(var->suffix.data, + ctx->name.data + ctx->name.len - var->suffix.len, + var->suffix.len) + == 0) + { + ctx->var = var; + + len = (size_t) (p - ctx->playlist.data); + + ctx->var_playlist.len = len - var->suffix.len + sizeof(".m3u8") + - 1; + ctx->var_playlist.data = ngx_palloc(s->connection->pool, + ctx->var_playlist.len + 1); + + pp = ngx_cpymem(ctx->var_playlist.data, ctx->playlist.data, + len - var->suffix.len); + pp = ngx_cpymem(pp, ".m3u8", sizeof(".m3u8") - 1); + *pp = 0; + + ctx->var_playlist_bak.len = ctx->var_playlist.len + + sizeof(".bak") - 1; + ctx->var_playlist_bak.data = ngx_palloc(s->connection->pool, + ctx->var_playlist_bak.len + 1); + + pp = ngx_cpymem(ctx->var_playlist_bak.data, + ctx->var_playlist.data, + ctx->var_playlist.len); + pp = ngx_cpymem(pp, ".bak", sizeof(".bak") - 1); + *pp = 0; + + break; + } + } + } + + + /* playlist path */ + + if (hacf->nested) { + p = ngx_cpymem(p, "/index.m3u8", sizeof("/index.m3u8") - 1); + } else { + p = ngx_cpymem(p, ".m3u8", sizeof(".m3u8") - 1); + } + + ctx->playlist.len = p - ctx->playlist.data; + + *p = 0; + + /* playlist bak (new playlist) path */ + + ctx->playlist_bak.data = ngx_palloc(s->connection->pool, + ctx->playlist.len + sizeof(".bak")); + p = ngx_cpymem(ctx->playlist_bak.data, ctx->playlist.data, + ctx->playlist.len); + p = ngx_cpymem(p, ".bak", sizeof(".bak") - 1); + + ctx->playlist_bak.len = p - ctx->playlist_bak.data; + + *p = 0; + + /* key path */ + + if (hacf->keys) { + len = hacf->key_path.len + 1 + ctx->name.len + 1 + NGX_INT64_LEN + + sizeof(".key"); + + ctx->keyfile.data = ngx_palloc(s->connection->pool, len); + if (ctx->keyfile.data == NULL) { + return NGX_ERROR; + } + + p = ngx_cpymem(ctx->keyfile.data, hacf->key_path.data, + hacf->key_path.len); + + if (p[-1] != '/') { + *p++ = '/'; + } + + p = ngx_cpymem(p, ctx->name.data, ctx->name.len); + *p++ = (hacf->nested ? '/' : '-'); + + ctx->keyfile.len = p - ctx->keyfile.data; + } + + ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "hls: playlist='%V' playlist_bak='%V' " + "stream_pattern='%V' keyfile_pattern='%V'", + &ctx->playlist, &ctx->playlist_bak, + &ctx->stream, &ctx->keyfile); + + if (hacf->continuous) { + ngx_rtmp_hls_restore_stream(s); + } + +next: + return next_publish(s, v); +} + + +static ngx_int_t +ngx_rtmp_hls_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v) +{ + ngx_rtmp_hls_app_conf_t *hacf; + ngx_rtmp_hls_ctx_t *ctx; + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + + if (hacf == NULL || !hacf->hls || ctx == NULL) { + goto next; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "hls: close stream"); + + ngx_rtmp_hls_close_fragment(s); + +next: + return next_close_stream(s, v); +} + + +static ngx_int_t +ngx_rtmp_hls_parse_aac_header(ngx_rtmp_session_t *s, ngx_uint_t *objtype, + ngx_uint_t *srindex, ngx_uint_t *chconf) +{ + ngx_rtmp_codec_ctx_t *codec_ctx; + ngx_chain_t *cl; + u_char *p, b0, b1; + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + cl = codec_ctx->aac_header; + + p = cl->buf->pos; + + if (ngx_rtmp_hls_copy(s, NULL, &p, 2, &cl) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_rtmp_hls_copy(s, &b0, &p, 1, &cl) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_rtmp_hls_copy(s, &b1, &p, 1, &cl) != NGX_OK) { + return NGX_ERROR; + } + + *objtype = b0 >> 3; + if (*objtype == 0 || *objtype == 0x1f) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "hls: unsupported adts object type:%ui", *objtype); + return NGX_ERROR; + } + + if (*objtype > 4) { + + /* + * Mark all extended profiles as LC + * to make Android as happy as possible. + */ + + *objtype = 2; + } + + *srindex = ((b0 << 1) & 0x0f) | ((b1 & 0x80) >> 7); + if (*srindex == 0x0f) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "hls: unsupported adts sample rate:%ui", *srindex); + return NGX_ERROR; + } + + *chconf = (b1 >> 3) & 0x0f; + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "hls: aac object_type:%ui, sample_rate_index:%ui, " + "channel_config:%ui", *objtype, *srindex, *chconf); + + return NGX_OK; +} + + +static void +ngx_rtmp_hls_update_fragment(ngx_rtmp_session_t *s, uint64_t ts, + ngx_int_t boundary, ngx_uint_t flush_rate) +{ + ngx_rtmp_hls_ctx_t *ctx; + ngx_rtmp_hls_app_conf_t *hacf; + ngx_rtmp_hls_frag_t *f; + ngx_msec_t ts_frag_len; + ngx_int_t same_frag, force,discont; + ngx_buf_t *b; + int64_t d; + + 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; + force = 0; + discont = 1; + + if (ctx->opened) { + f = ngx_rtmp_hls_get_frag(s, ctx->nfrags); + d = (int64_t) (ts - ctx->frag_ts); + + if (d > (int64_t) hacf->max_fraglen * 90 || d < -90000) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "hls: force fragment split: %.3f sec, ", d / 90000.); + force = 1; + + } else { + f->duration = (ts - ctx->frag_ts) / 90000.; + discont = 0; + } + } + + switch (hacf->slicing) { + case NGX_RTMP_HLS_SLICING_PLAIN: + if (f && f->duration < hacf->fraglen / 1000.) { + boundary = 0; + } + break; + + case NGX_RTMP_HLS_SLICING_ALIGNED: + + ts_frag_len = hacf->fraglen * 90; + same_frag = ctx->frag_ts / ts_frag_len == ts / ts_frag_len; + + if (f && same_frag) { + boundary = 0; + } + + if (f == NULL && (ctx->frag_ts == 0 || same_frag)) { + ctx->frag_ts = ts; + boundary = 0; + } + + break; + } + + if (boundary || force) { + ngx_rtmp_hls_close_fragment(s); + ngx_rtmp_hls_open_fragment(s, ts, discont); + } + + b = ctx->aframe; + if (ctx->opened && b && b->last > b->pos && + ctx->aframe_pts + (uint64_t) hacf->max_audio_delay * 90 / flush_rate + < ts) + { + ngx_rtmp_hls_flush_audio(s); + } +} + + +static ngx_int_t +ngx_rtmp_hls_flush_audio(ngx_rtmp_session_t *s) +{ + ngx_rtmp_hls_ctx_t *ctx; + ngx_rtmp_mpegts_frame_t frame; + ngx_int_t rc; + ngx_buf_t *b; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + + if (ctx == NULL || !ctx->opened) { + return NGX_OK; + } + + b = ctx->aframe; + + if (b == NULL || b->pos == b->last) { + return NGX_OK; + } + + ngx_memzero(&frame, sizeof(frame)); + + frame.dts = ctx->aframe_pts; + frame.pts = frame.dts; + frame.cc = ctx->audio_cc; + frame.pid = 0x101; + frame.sid = 0xc0; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "hls: flush audio pts=%uL", frame.pts); + + rc = ngx_rtmp_mpegts_write_frame(&ctx->file, &frame, b); + + if (rc != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "hls: audio flush failed"); + } + + ctx->audio_cc = frame.cc; + b->pos = b->last = b->start; + + return rc; +} + + +static ngx_int_t +ngx_rtmp_hls_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_rtmp_hls_app_conf_t *hacf; + ngx_rtmp_hls_ctx_t *ctx; + ngx_rtmp_codec_ctx_t *codec_ctx; + uint64_t pts, est_pts; + int64_t dpts; + size_t bsize; + ngx_buf_t *b; + u_char *p; + ngx_uint_t objtype, srindex, chconf, size; + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + if (hacf == NULL || !hacf->hls || ctx == NULL || + codec_ctx == NULL || h->mlen < 2) + { + 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; + } + + b = ctx->aframe; + + if (b == NULL) { + + b = ngx_pcalloc(s->connection->pool, sizeof(ngx_buf_t)); + if (b == NULL) { + return NGX_ERROR; + } + + ctx->aframe = b; + + b->start = ngx_palloc(s->connection->pool, hacf->audio_buffer_size); + if (b->start == NULL) { + return NGX_ERROR; + } + + b->end = b->start + hacf->audio_buffer_size; + b->pos = b->last = b->start; + } + + size = h->mlen - 2 + 7; + pts = (uint64_t) h->timestamp * 90; + + if (b->start + size > b->end) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "hls: too big audio frame"); + return NGX_OK; + } + + /* + * start new fragment here if + * there's no video at all, otherwise + * do it in video handler + */ + + ngx_rtmp_hls_update_fragment(s, pts, codec_ctx->avc_header == NULL, 2); + + if (b->last + size > b->end) { + ngx_rtmp_hls_flush_audio(s); + } + + 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; + + /* copy payload */ + + for (; in && b->last < b->end; in = in->next) { + + bsize = in->buf->last - in->buf->pos; + if (b->last + bsize > b->end) { + bsize = b->end - b->last; + } + + b->last = ngx_cpymem(b->last, in->buf->pos, bsize); + } + + /* 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; + } + + /* 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; + + if (p != b->start) { + ctx->aframe_num++; + return NGX_OK; + } + + ctx->aframe_pts = pts; + + if (!hacf->sync || codec_ctx->sample_rate == 0) { + return NGX_OK; + } + + /* align audio frames */ + + /* 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 / + codec_ctx->sample_rate; + dpts = (int64_t) (est_pts - pts); + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "hls: audio sync dpts=%L (%.5fs)", + dpts, dpts / 90000.); + + if (dpts <= (int64_t) hacf->sync * 90 && + dpts >= (int64_t) hacf->sync * -90) + { + ctx->aframe_num++; + ctx->aframe_pts = est_pts; + return NGX_OK; + } + + ctx->aframe_base = pts; + ctx->aframe_num = 1; + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "hls: audio sync gap dpts=%L (%.5fs)", + dpts, dpts / 90000.); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_hls_video(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_rtmp_hls_app_conf_t *hacf; + ngx_rtmp_hls_ctx_t *ctx; + ngx_rtmp_codec_ctx_t *codec_ctx; + u_char *p; + uint8_t fmt, ftype, htype, nal_type, src_nal_type; + uint32_t len, rlen; + ngx_buf_t out, *b; + uint32_t cts; + ngx_rtmp_mpegts_frame_t frame; + ngx_uint_t nal_bytes; + ngx_int_t aud_sent, sps_pps_sent, boundary; + static u_char buffer[NGX_RTMP_HLS_BUFSIZE]; + + hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + if (hacf == NULL || !hacf->hls || ctx == NULL || codec_ctx == NULL || + codec_ctx->avc_header == NULL || h->mlen < 1) + { + return NGX_OK; + } + + /* Only H264 is supported */ + if (codec_ctx->video_codec_id != NGX_RTMP_VIDEO_H264) { + return NGX_OK; + } + + p = in->buf->pos; + if (ngx_rtmp_hls_copy(s, &fmt, &p, 1, &in) != NGX_OK) { + return NGX_ERROR; + } + + /* 1: keyframe (IDR) + * 2: inter frame + * 3: disposable inter frame */ + + ftype = (fmt & 0xf0) >> 4; + + /* H264 HDR/PICT */ + + if (ngx_rtmp_hls_copy(s, &htype, &p, 1, &in) != NGX_OK) { + return NGX_ERROR; + } + + /* proceed only with PICT */ + + if (htype != 1) { + return NGX_OK; + } + + /* 3 bytes: decoder delay */ + + if (ngx_rtmp_hls_copy(s, &cts, &p, 3, &in) != NGX_OK) { + return NGX_ERROR; + } + + cts = ((cts & 0x00FF0000) >> 16) | ((cts & 0x000000FF) << 16) | + (cts & 0x0000FF00); + + ngx_memzero(&out, sizeof(out)); + + out.start = buffer; + out.end = buffer + sizeof(buffer); + out.pos = out.start; + out.last = out.pos; + + nal_bytes = codec_ctx->avc_nal_bytes; + aud_sent = 0; + sps_pps_sent = 0; + + while (in) { + if (ngx_rtmp_hls_copy(s, &rlen, &p, nal_bytes, &in) != NGX_OK) { + return NGX_OK; + } + + len = 0; + ngx_rtmp_rmemcpy(&len, &rlen, nal_bytes); + + if (len == 0) { + continue; + } + + if (ngx_rtmp_hls_copy(s, &src_nal_type, &p, 1, &in) != NGX_OK) { + return NGX_OK; + } + + nal_type = src_nal_type & 0x1f; + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "hls: h264 NAL type=%ui, len=%uD", + (ngx_uint_t) nal_type, len); + + if (nal_type >= 7 && nal_type <= 9) { + if (ngx_rtmp_hls_copy(s, NULL, &p, len - 1, &in) != NGX_OK) { + return NGX_ERROR; + } + continue; + } + + if (!aud_sent) { + switch (nal_type) { + case 1: + case 5: + case 6: + if (ngx_rtmp_hls_append_aud(s, &out) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "hls: error appending AUD NAL"); + } + case 9: + aud_sent = 1; + break; + } + } + + switch (nal_type) { + case 1: + sps_pps_sent = 0; + break; + case 5: + if (sps_pps_sent) { + break; + } + if (ngx_rtmp_hls_append_sps_pps(s, &out) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "hls: error appenging SPS/PPS NALs"); + } + sps_pps_sent = 1; + break; + } + + /* AnnexB prefix */ + + if (out.end - out.last < 5) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "hls: not enough buffer for AnnexB prefix"); + return NGX_OK; + } + + /* first AnnexB prefix is long (4 bytes) */ + + if (out.last == out.pos) { + *out.last++ = 0; + } + + *out.last++ = 0; + *out.last++ = 0; + *out.last++ = 1; + *out.last++ = src_nal_type; + + /* NAL body */ + + if (out.end - out.last < (ngx_int_t) len) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "hls: not enough buffer for NAL"); + return NGX_OK; + } + + if (ngx_rtmp_hls_copy(s, out.last, &p, len - 1, &in) != NGX_OK) { + return NGX_ERROR; + } + + out.last += (len - 1); + } + + ngx_memzero(&frame, sizeof(frame)); + + frame.cc = ctx->video_cc; + frame.dts = (uint64_t) h->timestamp * 90; + frame.pts = frame.dts + cts * 90; + frame.pid = 0x100; + frame.sid = 0xe0; + frame.key = (ftype == 1); + + /* + * start new fragment if + * - we have video key frame AND + * - we have audio buffered or have no audio at all or stream is closed + */ + + b = ctx->aframe; + boundary = frame.key && (codec_ctx->aac_header == NULL || !ctx->opened || + (b && b->last > b->pos)); + + ngx_rtmp_hls_update_fragment(s, frame.dts, boundary, 1); + + if (!ctx->opened) { + return NGX_OK; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "hls: video pts=%uL, dts=%uL", frame.pts, frame.dts); + + if (ngx_rtmp_mpegts_write_frame(&ctx->file, &frame, &out) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "hls: video frame failed"); + } + + ctx->video_cc = frame.cc; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_hls_stream_begin(ngx_rtmp_session_t *s, ngx_rtmp_stream_begin_t *v) +{ + return next_stream_begin(s, v); +} + + +static ngx_int_t +ngx_rtmp_hls_stream_eof(ngx_rtmp_session_t *s, ngx_rtmp_stream_eof_t *v) +{ + ngx_rtmp_hls_flush_audio(s); + + ngx_rtmp_hls_close_fragment(s); + + return next_stream_eof(s, v); +} + + +static ngx_int_t +ngx_rtmp_hls_cleanup_dir(ngx_str_t *ppath, ngx_msec_t playlen) +{ + ngx_dir_t dir; + time_t mtime, max_age; + ngx_err_t err; + ngx_str_t name, spath; + u_char *p; + ngx_int_t nentries, nerased; + u_char path[NGX_MAX_PATH + 1]; + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0, + "hls: cleanup path='%V' playlen=%M", + ppath, playlen); + + if (ngx_open_dir(ppath, &dir) != NGX_OK) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, ngx_errno, + "hls: cleanup open dir failed '%V'", ppath); + return NGX_ERROR; + } + + nentries = 0; + nerased = 0; + + for ( ;; ) { + ngx_set_errno(0); + + if (ngx_read_dir(&dir) == NGX_ERROR) { + err = ngx_errno; + + if (ngx_close_dir(&dir) == NGX_ERROR) { + ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, ngx_errno, + "hls: cleanup " ngx_close_dir_n " \"%V\" failed", + ppath); + } + + if (err == NGX_ENOMOREFILES) { + return nentries - nerased; + } + + ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, err, + "hls: cleanup " ngx_read_dir_n + " '%V' failed", ppath); + return NGX_ERROR; + } + + name.data = ngx_de_name(&dir); + if (name.data[0] == '.') { + continue; + } + + name.len = ngx_de_namelen(&dir); + + p = ngx_snprintf(path, sizeof(path) - 1, "%V/%V", ppath, &name); + *p = 0; + + spath.data = path; + spath.len = p - path; + + nentries++; + + if (!dir.valid_info && ngx_de_info(path, &dir) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, ngx_errno, + "hls: cleanup " ngx_de_info_n " \"%V\" failed", + &spath); + + continue; + } + + if (ngx_de_is_dir(&dir)) { + + if (ngx_rtmp_hls_cleanup_dir(&spath, playlen) == 0) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0, + "hls: cleanup dir '%V'", &name); + + /* + * null-termination gets spoiled in win32 + * version of ngx_open_dir + */ + + *p = 0; + + if (ngx_delete_dir(path) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno, + "hls: cleanup " ngx_delete_dir_n + " failed on '%V'", &spath); + } else { + nerased++; + } + } + + continue; + } + + if (!ngx_de_is_file(&dir)) { + continue; + } + + if (name.len >= 3 && name.data[name.len - 3] == '.' && + name.data[name.len - 2] == 't' && + name.data[name.len - 1] == 's') + { + max_age = playlen / 500; + + } else if (name.len >= 5 && name.data[name.len - 5] == '.' && + name.data[name.len - 4] == 'm' && + name.data[name.len - 3] == '3' && + name.data[name.len - 2] == 'u' && + name.data[name.len - 1] == '8') + { + max_age = playlen / 1000; + + } else if (name.len >= 4 && name.data[name.len - 4] == '.' && + name.data[name.len - 3] == 'k' && + name.data[name.len - 2] == 'e' && + name.data[name.len - 1] == 'y') + { + max_age = playlen / 500; + + } else { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0, + "hls: cleanup skip unknown file type '%V'", &name); + continue; + } + + mtime = ngx_de_mtime(&dir); + if (mtime + max_age > ngx_cached_time->sec) { + continue; + } + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0, + "hls: cleanup '%V' mtime=%T age=%T", + &name, mtime, ngx_cached_time->sec - mtime); + + if (ngx_delete_file(path) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno, + "hls: cleanup " ngx_delete_file_n " failed on '%V'", + &spath); + continue; + } + + nerased++; + } +} + + +static time_t +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; +} + + +static char * +ngx_rtmp_hls_variant(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_rtmp_hls_app_conf_t *hacf = conf; + + ngx_str_t *value, *arg; + ngx_uint_t n; + ngx_rtmp_hls_variant_t *var; + + value = cf->args->elts; + + if (hacf->variant == NULL) { + hacf->variant = ngx_array_create(cf->pool, 1, + sizeof(ngx_rtmp_hls_variant_t)); + if (hacf->variant == NULL) { + return NGX_CONF_ERROR; + } + } + + var = ngx_array_push(hacf->variant); + if (var == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memzero(var, sizeof(ngx_rtmp_hls_variant_t)); + + var->suffix = value[1]; + + if (cf->args->nelts == 2) { + return NGX_CONF_OK; + } + + if (ngx_array_init(&var->args, cf->pool, cf->args->nelts - 2, + sizeof(ngx_str_t)) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + arg = ngx_array_push_n(&var->args, cf->args->nelts - 2); + if (arg == NULL) { + return NGX_CONF_ERROR; + } + + for (n = 2; n < cf->args->nelts; n++) { + *arg++ = value[n]; + } + + return NGX_CONF_OK; +} + + +static void * +ngx_rtmp_hls_create_app_conf(ngx_conf_t *cf) +{ + ngx_rtmp_hls_app_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_hls_app_conf_t)); + if (conf == NULL) { + return NULL; + } + + conf->hls = NGX_CONF_UNSET; + conf->fraglen = NGX_CONF_UNSET_MSEC; + conf->max_fraglen = NGX_CONF_UNSET_MSEC; + conf->muxdelay = NGX_CONF_UNSET_MSEC; + conf->sync = NGX_CONF_UNSET_MSEC; + conf->playlen = NGX_CONF_UNSET_MSEC; + conf->continuous = NGX_CONF_UNSET; + conf->nested = NGX_CONF_UNSET; + conf->naming = 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->granularity = NGX_CONF_UNSET; + conf->keys = NGX_CONF_UNSET; + conf->frags_per_key = NGX_CONF_UNSET_UINT; + + return conf; +} + + +static char * +ngx_rtmp_hls_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_hls_app_conf_t *prev = parent; + ngx_rtmp_hls_app_conf_t *conf = child; + ngx_rtmp_hls_cleanup_t *cleanup; + + 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); + 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); + ngx_conf_merge_value(conf->continuous, prev->continuous, 1); + 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->slicing, prev->slicing, + NGX_RTMP_HLS_SLICING_PLAIN); + ngx_conf_merge_uint_value(conf->type, prev->type, + NGX_RTMP_HLS_TYPE_LIVE); + ngx_conf_merge_msec_value(conf->max_audio_delay, prev->max_audio_delay, + 300); + ngx_conf_merge_size_value(conf->audio_buffer_size, prev->audio_buffer_size, + NGX_RTMP_HLS_BUFSIZE); + ngx_conf_merge_value(conf->cleanup, prev->cleanup, 1); + ngx_conf_merge_str_value(conf->base_url, prev->base_url, ""); + ngx_conf_merge_value(conf->granularity, prev->granularity, 0); + ngx_conf_merge_value(conf->keys, prev->keys, 0); + ngx_conf_merge_str_value(conf->key_path, prev->key_path, ""); + ngx_conf_merge_str_value(conf->key_url, prev->key_url, ""); + ngx_conf_merge_uint_value(conf->frags_per_key, prev->frags_per_key, 0); + + if (conf->fraglen) { + conf->winfrags = conf->playlen / conf->fraglen; + } + + /* schedule cleanup */ + + if (conf->hls && conf->path.len && conf->cleanup && + conf->type != NGX_RTMP_HLS_TYPE_EVENT) + { + if (conf->path.data[conf->path.len - 1] == '/') { + conf->path.len--; + } + + cleanup = ngx_pcalloc(cf->pool, sizeof(*cleanup)); + if (cleanup == NULL) { + return NGX_CONF_ERROR; + } + + cleanup->path = conf->path; + cleanup->playlen = conf->playlen; + + conf->slot = ngx_pcalloc(cf->pool, sizeof(*conf->slot)); + if (conf->slot == NULL) { + return NGX_CONF_ERROR; + } + + conf->slot->manager = ngx_rtmp_hls_cleanup; + conf->slot->name = conf->path; + conf->slot->data = cleanup; + conf->slot->conf_file = cf->conf_file->file.name.data; + conf->slot->line = cf->conf_file->line; + + if (ngx_add_path(cf, &conf->slot) != NGX_OK) { + return NGX_CONF_ERROR; + } + } + + ngx_conf_merge_str_value(conf->path, prev->path, ""); + + if (conf->keys && conf->cleanup && conf->key_path.len && + ngx_strcmp(conf->key_path.data, conf->path.data) != 0 && + conf->type != NGX_RTMP_HLS_TYPE_EVENT) + { + if (conf->key_path.data[conf->key_path.len - 1] == '/') { + conf->key_path.len--; + } + + cleanup = ngx_pcalloc(cf->pool, sizeof(*cleanup)); + if (cleanup == NULL) { + return NGX_CONF_ERROR; + } + + cleanup->path = conf->key_path; + cleanup->playlen = conf->playlen; + + conf->slot = ngx_pcalloc(cf->pool, sizeof(*conf->slot)); + if (conf->slot == NULL) { + return NGX_CONF_ERROR; + } + + conf->slot->manager = ngx_rtmp_hls_cleanup; + conf->slot->name = conf->key_path; + conf->slot->data = cleanup; + conf->slot->conf_file = cf->conf_file->file.name.data; + conf->slot->line = cf->conf_file->line; + + if (ngx_add_path(cf, &conf->slot) != NGX_OK) { + return NGX_CONF_ERROR; + } + } + + ngx_conf_merge_str_value(conf->key_path, prev->key_path, ""); + + if (conf->key_path.len == 0) { + conf->key_path = conf->path; + } + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_rtmp_hls_postconfiguration(ngx_conf_t *cf) +{ + ngx_rtmp_core_main_conf_t *cmcf; + ngx_rtmp_handler_pt *h; + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + + h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]); + *h = ngx_rtmp_hls_video; + + h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]); + *h = ngx_rtmp_hls_audio; + + next_publish = ngx_rtmp_publish; + ngx_rtmp_publish = ngx_rtmp_hls_publish; + + next_close_stream = ngx_rtmp_close_stream; + ngx_rtmp_close_stream = ngx_rtmp_hls_close_stream; + + next_stream_begin = ngx_rtmp_stream_begin; + ngx_rtmp_stream_begin = ngx_rtmp_hls_stream_begin; + + next_stream_eof = ngx_rtmp_stream_eof; + ngx_rtmp_stream_eof = ngx_rtmp_hls_stream_eof; + + return NGX_OK; +} diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/hls/ngx_rtmp_mpegts.c nginx-1.10.0/debian/modules/nginx-rtmp-module/hls/ngx_rtmp_mpegts.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/hls/ngx_rtmp_mpegts.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/hls/ngx_rtmp_mpegts.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,399 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp_mpegts.h" + + +static u_char ngx_rtmp_mpegts_header[] = { + + /* TS */ + 0x47, 0x40, 0x00, 0x10, 0x00, + /* PSI */ + 0x00, 0xb0, 0x0d, 0x00, 0x01, 0xc1, 0x00, 0x00, + /* PAT */ + 0x00, 0x01, 0xf0, 0x01, + /* CRC */ + 0x2e, 0x70, 0x19, 0x05, + /* stuffing 167 bytes */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 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, + /* 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 */ + /* stuffing 157 bytes */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + + +/* 700 ms PCR delay */ +#define NGX_RTMP_HLS_DELAY 63000 + + +static ngx_int_t +ngx_rtmp_mpegts_write_file(ngx_rtmp_mpegts_file_t *file, u_char *in, + size_t in_size) +{ + u_char *out; + size_t out_size, n; + ssize_t rc; + + static u_char buf[1024]; + + if (!file->encrypt) { + ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0, + "mpegts: write %uz bytes", in_size); + + rc = ngx_write_fd(file->fd, in, in_size); + if (rc < 0) { + return NGX_ERROR; + } + + return NGX_OK; + } + + /* encrypt */ + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0, + "mpegts: write %uz encrypted bytes", in_size); + + out = buf; + out_size = sizeof(buf); + + if (file->size > 0 && file->size + in_size >= 16) { + ngx_memcpy(file->buf + file->size, in, 16 - file->size); + + in += 16 - file->size; + in_size -= 16 - file->size; + + AES_cbc_encrypt(file->buf, out, 16, &file->key, file->iv, AES_ENCRYPT); + + out += 16; + out_size -= 16; + + file->size = 0; + } + + for ( ;; ) { + n = in_size & ~0x0f; + + if (n > 0) { + if (n > out_size) { + n = out_size; + } + + AES_cbc_encrypt(in, out, n, &file->key, file->iv, AES_ENCRYPT); + + in += n; + in_size -= n; + + } else if (out == buf) { + break; + } + + rc = ngx_write_fd(file->fd, buf, out - buf + n); + if (rc < 0) { + return NGX_ERROR; + } + + out = buf; + out_size = sizeof(buf); + } + + if (in_size) { + ngx_memcpy(file->buf + file->size, in, in_size); + file->size += in_size; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mpegts_write_header(ngx_rtmp_mpegts_file_t *file) +{ + return ngx_rtmp_mpegts_write_file(file, ngx_rtmp_mpegts_header, + sizeof(ngx_rtmp_mpegts_header)); +} + + +static u_char * +ngx_rtmp_mpegts_write_pcr(u_char *p, uint64_t pcr) +{ + *p++ = (u_char) (pcr >> 25); + *p++ = (u_char) (pcr >> 17); + *p++ = (u_char) (pcr >> 9); + *p++ = (u_char) (pcr >> 1); + *p++ = (u_char) (pcr << 7 | 0x7e); + *p++ = 0; + + return p; +} + + +static u_char * +ngx_rtmp_mpegts_write_pts(u_char *p, ngx_uint_t fb, uint64_t pts) +{ + ngx_uint_t val; + + val = fb << 4 | (((pts >> 30) & 0x07) << 1) | 1; + *p++ = (u_char) val; + + val = (((pts >> 15) & 0x7fff) << 1) | 1; + *p++ = (u_char) (val >> 8); + *p++ = (u_char) val; + + val = (((pts) & 0x7fff) << 1) | 1; + *p++ = (u_char) (val >> 8); + *p++ = (u_char) val; + + return p; +} + + +ngx_int_t +ngx_rtmp_mpegts_write_frame(ngx_rtmp_mpegts_file_t *file, + ngx_rtmp_mpegts_frame_t *f, ngx_buf_t *b) +{ + ngx_uint_t pes_size, header_size, body_size, in_size, stuff_size, flags; + u_char packet[188], *p, *base; + ngx_int_t first, rc; + + ngx_log_debug6(NGX_LOG_DEBUG_CORE, file->log, 0, + "mpegts: pid=%ui, sid=%ui, pts=%uL, " + "dts=%uL, key=%ui, size=%ui", + f->pid, f->sid, f->pts, f->dts, + (ngx_uint_t) f->key, (size_t) (b->last - b->pos)); + + first = 1; + + while (b->pos < b->last) { + p = packet; + + f->cc++; + + *p++ = 0x47; + *p++ = (u_char) (f->pid >> 8); + + if (first) { + p[-1] |= 0x40; + } + + *p++ = (u_char) f->pid; + *p++ = 0x10 | (f->cc & 0x0f); /* payload */ + + if (first) { + + if (f->key) { + packet[3] |= 0x20; /* adaptation */ + + *p++ = 7; /* size */ + *p++ = 0x50; /* random access + PCR */ + + p = ngx_rtmp_mpegts_write_pcr(p, f->dts - NGX_RTMP_HLS_DELAY); + } + + /* PES header */ + + *p++ = 0x00; + *p++ = 0x00; + *p++ = 0x01; + *p++ = (u_char) f->sid; + + header_size = 5; + flags = 0x80; /* PTS */ + + if (f->dts != f->pts) { + header_size += 5; + flags |= 0x40; /* DTS */ + } + + pes_size = (b->last - b->pos) + header_size + 3; + if (pes_size > 0xffff) { + pes_size = 0; + } + + *p++ = (u_char) (pes_size >> 8); + *p++ = (u_char) pes_size; + *p++ = 0x80; /* H222 */ + *p++ = (u_char) flags; + *p++ = (u_char) header_size; + + p = ngx_rtmp_mpegts_write_pts(p, flags >> 6, f->pts + + NGX_RTMP_HLS_DELAY); + + if (f->dts != f->pts) { + p = ngx_rtmp_mpegts_write_pts(p, 1, f->dts + + NGX_RTMP_HLS_DELAY); + } + + first = 0; + } + + body_size = (ngx_uint_t) (packet + sizeof(packet) - p); + in_size = (ngx_uint_t) (b->last - b->pos); + + if (body_size <= in_size) { + ngx_memcpy(p, b->pos, body_size); + b->pos += body_size; + + } else { + stuff_size = (body_size - in_size); + + if (packet[3] & 0x20) { + + /* has adaptation */ + + base = &packet[5] + packet[4]; + p = ngx_movemem(base + stuff_size, base, p - base); + ngx_memset(base, 0xff, stuff_size); + packet[4] += (u_char) stuff_size; + + } else { + + /* no adaptation */ + + packet[3] |= 0x20; + p = ngx_movemem(&packet[4] + stuff_size, &packet[4], + p - &packet[4]); + + packet[4] = (u_char) (stuff_size - 1); + if (stuff_size >= 2) { + packet[5] = 0; + ngx_memset(&packet[6], 0xff, stuff_size - 2); + } + } + + ngx_memcpy(p, b->pos, in_size); + b->pos = b->last; + } + + rc = ngx_rtmp_mpegts_write_file(file, packet, sizeof(packet)); + if (rc != NGX_OK) { + return rc; + } + } + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_mpegts_init_encryption(ngx_rtmp_mpegts_file_t *file, + u_char *key, size_t key_len, uint64_t iv) +{ + if (AES_set_encrypt_key(key, key_len * 8, &file->key)) { + return NGX_ERROR; + } + + ngx_memzero(file->iv, 8); + + file->iv[8] = (u_char) (iv >> 56); + file->iv[9] = (u_char) (iv >> 48); + file->iv[10] = (u_char) (iv >> 40); + file->iv[11] = (u_char) (iv >> 32); + file->iv[12] = (u_char) (iv >> 24); + file->iv[13] = (u_char) (iv >> 16); + file->iv[14] = (u_char) (iv >> 8); + file->iv[15] = (u_char) (iv); + + file->encrypt = 1; + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_mpegts_open_file(ngx_rtmp_mpegts_file_t *file, u_char *path, + ngx_log_t *log) +{ + file->log = log; + + file->fd = ngx_open_file(path, NGX_FILE_WRONLY, NGX_FILE_TRUNCATE, + NGX_FILE_DEFAULT_ACCESS); + + if (file->fd == NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_ERR, log, ngx_errno, + "hls: error creating fragment file"); + return NGX_ERROR; + } + + file->size = 0; + + if (ngx_rtmp_mpegts_write_header(file) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, log, ngx_errno, + "hls: error writing fragment header"); + ngx_close_file(file->fd); + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_mpegts_close_file(ngx_rtmp_mpegts_file_t *file) +{ + u_char buf[16]; + ssize_t rc; + + if (file->encrypt) { + ngx_memset(file->buf + file->size, 16 - file->size, 16 - file->size); + + AES_cbc_encrypt(file->buf, buf, 16, &file->key, file->iv, AES_ENCRYPT); + + rc = ngx_write_fd(file->fd, buf, 16); + if (rc < 0) { + return NGX_ERROR; + } + } + + ngx_close_file(file->fd); + + return NGX_OK; +} diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/hls/ngx_rtmp_mpegts.h nginx-1.10.0/debian/modules/nginx-rtmp-module/hls/ngx_rtmp_mpegts.h --- nginx-1.9.15/debian/modules/nginx-rtmp-module/hls/ngx_rtmp_mpegts.h 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/hls/ngx_rtmp_mpegts.h 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,46 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_MPEGTS_H_INCLUDED_ +#define _NGX_RTMP_MPEGTS_H_INCLUDED_ + + +#include +#include +#include + + +typedef struct { + ngx_fd_t fd; + ngx_log_t *log; + unsigned encrypt:1; + unsigned size:4; + u_char buf[16]; + u_char iv[16]; + AES_KEY key; +} ngx_rtmp_mpegts_file_t; + + +typedef struct { + uint64_t pts; + uint64_t dts; + ngx_uint_t pid; + ngx_uint_t sid; + ngx_uint_t cc; + unsigned key:1; +} ngx_rtmp_mpegts_frame_t; + + +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_log_t *log); +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); + + +#endif /* _NGX_RTMP_MPEGTS_H_INCLUDED_ */ diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/LICENSE nginx-1.10.0/debian/modules/nginx-rtmp-module/LICENSE --- nginx-1.9.15/debian/modules/nginx-rtmp-module/LICENSE 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/LICENSE 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,22 @@ +Copyright (c) 2012-2014, Roman Arutyunyan +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_access_module.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_access_module.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_access_module.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_access_module.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,471 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rtmp_cmd_module.h" + + +static ngx_rtmp_publish_pt next_publish; +static ngx_rtmp_play_pt next_play; + + +#define NGX_RTMP_ACCESS_PUBLISH 0x01 +#define NGX_RTMP_ACCESS_PLAY 0x02 + + +static char * ngx_rtmp_access_rule(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static ngx_int_t ngx_rtmp_access_postconfiguration(ngx_conf_t *cf); +static void * ngx_rtmp_access_create_app_conf(ngx_conf_t *cf); +static char * ngx_rtmp_access_merge_app_conf(ngx_conf_t *cf, + void *parent, void *child); + + +typedef struct { + in_addr_t mask; + in_addr_t addr; + ngx_uint_t deny; + ngx_uint_t flags; +} ngx_rtmp_access_rule_t; + + +#if (NGX_HAVE_INET6) + +typedef struct { + struct in6_addr addr; + struct in6_addr mask; + ngx_uint_t deny; + ngx_uint_t flags; +} ngx_rtmp_access_rule6_t; + +#endif + + +typedef struct { + ngx_array_t rules; /* array of ngx_rtmp_access_rule_t */ +#if (NGX_HAVE_INET6) + ngx_array_t rules6; /* array of ngx_rtmp_access_rule6_t */ +#endif +} ngx_rtmp_access_app_conf_t; + + +static ngx_command_t ngx_rtmp_access_commands[] = { + + { ngx_string("allow"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE12, + ngx_rtmp_access_rule, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("deny"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE12, + ngx_rtmp_access_rule, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_rtmp_access_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_access_postconfiguration, /* postconfiguration */ + NULL, /* create main configuration */ + NULL, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + ngx_rtmp_access_create_app_conf, /* create app configuration */ + ngx_rtmp_access_merge_app_conf, /* merge app configuration */ +}; + + +ngx_module_t ngx_rtmp_access_module = { + NGX_MODULE_V1, + &ngx_rtmp_access_module_ctx, /* module context */ + ngx_rtmp_access_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_rtmp_access_create_app_conf(ngx_conf_t *cf) +{ + ngx_rtmp_access_app_conf_t *aacf; + + aacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_access_app_conf_t)); + if (aacf == NULL) { + return NULL; + } + + if (ngx_array_init(&aacf->rules, cf->pool, 1, + sizeof(ngx_rtmp_access_rule_t)) + != NGX_OK) + { + return NULL; + } + +#if (NGX_HAVE_INET6) + if (ngx_array_init(&aacf->rules6, cf->pool, 1, + sizeof(ngx_rtmp_access_rule6_t)) + != NGX_OK) + { + return NULL; + } +#endif + + return aacf; +} + + +static ngx_int_t +ngx_rtmp_access_merge_rules(ngx_array_t *prev, ngx_array_t *rules) +{ + void *p; + + if (prev->nelts == 0) { + return NGX_OK; + } + + if (rules->nelts == 0) { + *rules = *prev; + return NGX_OK; + } + + p = ngx_array_push_n(rules, prev->nelts); + if (p == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(p, prev->elts, prev->size * prev->nelts); + + return NGX_OK; +} + + +static char * +ngx_rtmp_access_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_access_app_conf_t *prev = parent; + ngx_rtmp_access_app_conf_t *conf = child; + + if (ngx_rtmp_access_merge_rules(&prev->rules, &conf->rules) != NGX_OK) { + return NGX_CONF_ERROR; + } + +#if (NGX_HAVE_INET6) + if (ngx_rtmp_access_merge_rules(&prev->rules6, &conf->rules6) != NGX_OK) { + return NGX_CONF_ERROR; + } +#endif + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_rtmp_access_found(ngx_rtmp_session_t *s, ngx_uint_t deny) +{ + if (deny) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "access forbidden by rule"); + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_access_inet(ngx_rtmp_session_t *s, in_addr_t addr, ngx_uint_t flag) +{ + ngx_uint_t i; + ngx_rtmp_access_rule_t *rule; + ngx_rtmp_access_app_conf_t *ascf; + + ascf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_access_module); + + rule = ascf->rules.elts; + for (i = 0; i < ascf->rules.nelts; i++) { + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, s->connection->log, 0, + "access: %08XD %08XD %08XD", + addr, rule[i].mask, rule[i].addr); + + if ((addr & rule[i].mask) == rule[i].addr && (flag & rule[i].flags)) { + return ngx_rtmp_access_found(s, rule[i].deny); + } + } + + return NGX_OK; +} + + +#if (NGX_HAVE_INET6) + +static ngx_int_t +ngx_rtmp_access_inet6(ngx_rtmp_session_t *s, u_char *p, ngx_uint_t flag) +{ + ngx_uint_t n; + ngx_uint_t i; + ngx_rtmp_access_rule6_t *rule6; + ngx_rtmp_access_app_conf_t *ascf; + + ascf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_access_module); + + rule6 = ascf->rules6.elts; + for (i = 0; i < ascf->rules6.nelts; i++) { + +#if (NGX_DEBUG) + { + size_t cl, ml, al; + u_char ct[NGX_INET6_ADDRSTRLEN]; + u_char mt[NGX_INET6_ADDRSTRLEN]; + u_char at[NGX_INET6_ADDRSTRLEN]; + + cl = ngx_inet6_ntop(p, ct, NGX_INET6_ADDRSTRLEN); + ml = ngx_inet6_ntop(rule6[i].mask.s6_addr, mt, NGX_INET6_ADDRSTRLEN); + al = ngx_inet6_ntop(rule6[i].addr.s6_addr, at, NGX_INET6_ADDRSTRLEN); + + ngx_log_debug6(NGX_LOG_DEBUG_HTTP, s->connection->log, 0, + "access: %*s %*s %*s", cl, ct, ml, mt, al, at); + } +#endif + + for (n = 0; n < 16; n++) { + if ((p[n] & rule6[i].mask.s6_addr[n]) != rule6[i].addr.s6_addr[n]) { + goto next; + } + } + + if (flag & rule6[i].flags) { + return ngx_rtmp_access_found(s, rule6[i].deny); + } + + next: + continue; + } + + return NGX_OK; +} + +#endif + + +static ngx_int_t +ngx_rtmp_access(ngx_rtmp_session_t *s, ngx_uint_t flag) +{ + struct sockaddr_in *sin; + ngx_rtmp_access_app_conf_t *ascf; +#if (NGX_HAVE_INET6) + u_char *p; + in_addr_t addr; + struct sockaddr_in6 *sin6; +#endif + + ascf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_access_module); + if (ascf == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, s->connection->log, 0, + "access: NULL app conf"); + return NGX_ERROR; + } + + /* relay etc */ + if (s->connection->sockaddr == NULL) { + return NGX_OK; + } + + switch (s->connection->sockaddr->sa_family) { + + case AF_INET: + sin = (struct sockaddr_in *) s->connection->sockaddr; + return ngx_rtmp_access_inet(s, sin->sin_addr.s_addr, flag); + +#if (NGX_HAVE_INET6) + + case AF_INET6: + sin6 = (struct sockaddr_in6 *) s->connection->sockaddr; + p = sin6->sin6_addr.s6_addr; + + if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { + addr = p[12] << 24; + addr += p[13] << 16; + addr += p[14] << 8; + addr += p[15]; + return ngx_rtmp_access_inet(s, htonl(addr), flag); + } + + return ngx_rtmp_access_inet6(s, p, flag); + +#endif + } + + return NGX_OK; +} + + +static char * +ngx_rtmp_access_rule(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_rtmp_access_app_conf_t *ascf = conf; + + ngx_int_t rc; + ngx_uint_t all; + ngx_str_t *value; + ngx_cidr_t cidr; + ngx_rtmp_access_rule_t *rule; +#if (NGX_HAVE_INET6) + ngx_rtmp_access_rule6_t *rule6; +#endif + size_t n; + ngx_uint_t flags; + + ngx_memzero(&cidr, sizeof(ngx_cidr_t)); + + value = cf->args->elts; + + n = 1; + flags = 0; + + if (cf->args->nelts == 2) { + + flags = NGX_RTMP_ACCESS_PUBLISH | NGX_RTMP_ACCESS_PLAY; + + } else { + + for(; n < cf->args->nelts - 1; ++n) { + + if (value[n].len == sizeof("publish") - 1 && + ngx_strcmp(value[1].data, "publish") == 0) + { + flags |= NGX_RTMP_ACCESS_PUBLISH; + continue; + + } + + if (value[n].len == sizeof("play") - 1 && + ngx_strcmp(value[1].data, "play") == 0) + { + flags |= NGX_RTMP_ACCESS_PLAY; + continue; + + } + + ngx_log_error(NGX_LOG_ERR, cf->log, 0, + "unexpected access specified: '%V'", &value[n]); + return NGX_CONF_ERROR; + } + } + + all = (value[n].len == 3 && ngx_strcmp(value[n].data, "all") == 0); + + if (!all) { + + rc = ngx_ptocidr(&value[n], &cidr); + + if (rc == NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[1]); + return NGX_CONF_ERROR; + } + + if (rc == NGX_DONE) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "low address bits of %V are meaningless", + &value[1]); + } + } + + switch (cidr.family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + case 0: /* all */ + + rule6 = ngx_array_push(&ascf->rules6); + if (rule6 == NULL) { + return NGX_CONF_ERROR; + } + + rule6->mask = cidr.u.in6.mask; + rule6->addr = cidr.u.in6.addr; + rule6->deny = (value[0].data[0] == 'd') ? 1 : 0; + rule6->flags = flags; + + if (!all) { + break; + } + + /* "all" passes through */ +#endif + + default: /* AF_INET */ + + rule = ngx_array_push(&ascf->rules); + if (rule == NULL) { + return NGX_CONF_ERROR; + } + + rule->mask = cidr.u.in.mask; + rule->addr = cidr.u.in.addr; + rule->deny = (value[0].data[0] == 'd') ? 1 : 0; + rule->flags = flags; + } + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_rtmp_access_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) +{ + if (s->auto_pushed) { + goto next; + } + + if (ngx_rtmp_access(s, NGX_RTMP_ACCESS_PUBLISH) != NGX_OK) { + return NGX_ERROR; + } + +next: + return next_publish(s, v); +} + + +static ngx_int_t +ngx_rtmp_access_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) +{ + if (ngx_rtmp_access(s, NGX_RTMP_ACCESS_PLAY) != NGX_OK) { + return NGX_ERROR; + } + + return next_play(s, v); +} + + +static ngx_int_t +ngx_rtmp_access_postconfiguration(ngx_conf_t *cf) +{ + /* chain handlers */ + next_publish = ngx_rtmp_publish; + ngx_rtmp_publish = ngx_rtmp_access_publish; + + next_play = ngx_rtmp_play; + ngx_rtmp_play = ngx_rtmp_access_play; + + return NGX_OK; +} diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_amf.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_amf.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_amf.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_amf.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,645 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp_amf.h" +#include "ngx_rtmp.h" +#include + + +static ngx_inline void* +ngx_rtmp_amf_reverse_copy(void *dst, void* src, size_t len) +{ + size_t k; + + if (dst == NULL || src == NULL) { + return NULL; + } + + for(k = 0; k < len; ++k) { + ((u_char*)dst)[k] = ((u_char*)src)[len - 1 - k]; + } + + return dst; +} + +#define NGX_RTMP_AMF_DEBUG_SIZE 16 + +#ifdef NGX_DEBUG +static void +ngx_rtmp_amf_debug(const char* op, ngx_log_t *log, u_char *p, size_t n) +{ + u_char hstr[3 * NGX_RTMP_AMF_DEBUG_SIZE + 1]; + u_char str[NGX_RTMP_AMF_DEBUG_SIZE + 1]; + u_char *hp, *sp; + static u_char hex[] = "0123456789ABCDEF"; + size_t i; + + hp = hstr; + sp = str; + + for(i = 0; i < n && i < NGX_RTMP_AMF_DEBUG_SIZE; ++i) { + *hp++ = ' '; + if (p) { + *hp++ = hex[(*p & 0xf0) >> 4]; + *hp++ = hex[*p & 0x0f]; + *sp++ = (*p >= 0x20 && *p <= 0x7e) ? + *p : (u_char)'?'; + ++p; + } else { + *hp++ = 'X'; + *hp++ = 'X'; + *sp++ = '?'; + } + } + *hp = *sp = '\0'; + + ngx_log_debug4(NGX_LOG_DEBUG_RTMP, log, 0, + "AMF %s (%d)%s '%s'", op, n, hstr, str); +} +#endif + +static ngx_int_t +ngx_rtmp_amf_get(ngx_rtmp_amf_ctx_t *ctx, void *p, size_t n) +{ + size_t size; + ngx_chain_t *l; + size_t offset; + u_char *pos, *last; +#ifdef NGX_DEBUG + void *op = p; + size_t on = n; +#endif + + if (!n) + return NGX_OK; + + for(l = ctx->link, offset = ctx->offset; l; l = l->next, offset = 0) { + + pos = l->buf->pos + offset; + last = l->buf->last; + + if (last >= pos + n) { + if (p) { + p = ngx_cpymem(p, pos, n); + } + ctx->offset = offset + n; + ctx->link = l; + +#ifdef NGX_DEBUG + ngx_rtmp_amf_debug("read", ctx->log, (u_char*)op, on); +#endif + + return NGX_OK; + } + + size = last - pos; + + if (p) { + p = ngx_cpymem(p, pos, size); + } + + n -= size; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ctx->log, 0, + "AMF read eof (%d)", n); + + return NGX_DONE; +} + + +static ngx_int_t +ngx_rtmp_amf_put(ngx_rtmp_amf_ctx_t *ctx, void *p, size_t n) +{ + ngx_buf_t *b; + size_t size; + ngx_chain_t *l, *ln; + +#ifdef NGX_DEBUG + ngx_rtmp_amf_debug("write", ctx->log, (u_char*)p, n); +#endif + + l = ctx->link; + + if (ctx->link && ctx->first == NULL) { + ctx->first = ctx->link; + } + + while(n) { + b = l ? l->buf : NULL; + + if (b == NULL || b->last == b->end) { + + ln = ctx->alloc(ctx->arg); + if (ln == NULL) { + return NGX_ERROR; + } + + if (ctx->first == NULL) { + ctx->first = ln; + } + + if (l) { + l->next = ln; + } + + l = ln; + ctx->link = l; + b = l->buf; + } + + size = b->end - b->last; + + if (size >= n) { + b->last = ngx_cpymem(b->last, p, n); + return NGX_OK; + } + + b->last = ngx_cpymem(b->last, p, size); + p = (u_char*)p + size; + n -= size; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_amf_read_object(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_amf_elt_t *elts, + size_t nelts) +{ + uint8_t type; + uint16_t len; + size_t n, namelen, maxlen; + ngx_int_t rc; + u_char buf[2]; + + maxlen = 0; + for(n = 0; n < nelts; ++n) { + namelen = elts[n].name.len; + if (namelen > maxlen) + maxlen = namelen; + } + + for( ;; ) { + +#if !(NGX_WIN32) + char name[maxlen]; +#else + char name[1024]; + if (maxlen > sizeof(name)) { + return NGX_ERROR; + } +#endif + /* read key */ + switch (ngx_rtmp_amf_get(ctx, buf, 2)) { + case NGX_DONE: + /* Envivio sends unfinalized arrays */ + return NGX_OK; + case NGX_OK: + break; + default: + return NGX_ERROR; + } + + ngx_rtmp_amf_reverse_copy(&len, buf, 2); + + if (!len) + break; + + if (len <= maxlen) { + rc = ngx_rtmp_amf_get(ctx, name, len); + + } else { + rc = ngx_rtmp_amf_get(ctx, name, maxlen); + if (rc != NGX_OK) + return NGX_ERROR; + rc = ngx_rtmp_amf_get(ctx, 0, len - maxlen); + } + + if (rc != NGX_OK) + return NGX_ERROR; + + /* TODO: if we require array to be sorted on name + * then we could be able to use binary search */ + for(n = 0; n < nelts + && (len != elts[n].name.len + || ngx_strncmp(name, elts[n].name.data, len)); + ++n); + + if (ngx_rtmp_amf_read(ctx, n < nelts ? &elts[n] : NULL, 1) != NGX_OK) + return NGX_ERROR; + } + + if (ngx_rtmp_amf_get(ctx, &type, 1) != NGX_OK + || type != NGX_RTMP_AMF_END) + { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_amf_read_array(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_amf_elt_t *elts, + size_t nelts) +{ + uint32_t len; + size_t n; + u_char buf[4]; + + /* read length */ + if (ngx_rtmp_amf_get(ctx, buf, 4) != NGX_OK) + return NGX_ERROR; + + ngx_rtmp_amf_reverse_copy(&len, buf, 4); + + for (n = 0; n < len; ++n) { + if (ngx_rtmp_amf_read(ctx, n < nelts ? &elts[n] : NULL, 1) != NGX_OK) + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_amf_read_variant(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_amf_elt_t *elts, + size_t nelts) +{ + uint8_t type; + ngx_int_t rc; + size_t n; + ngx_rtmp_amf_elt_t elt; + + rc = ngx_rtmp_amf_get(ctx, &type, 1); + if (rc != NGX_OK) { + return rc; + } + + ngx_memzero(&elt, sizeof(elt)); + for (n = 0; n < nelts; ++n, ++elts) { + if (type == elts->type) { + elt.data = elts->data; + elt.len = elts->len; + } + } + + elt.type = type | NGX_RTMP_AMF_TYPELESS; + + return ngx_rtmp_amf_read(ctx, &elt, 1); +} + + +static ngx_int_t +ngx_rtmp_amf_is_compatible_type(uint8_t t1, uint8_t t2) +{ + return t1 == t2 + || (t1 == NGX_RTMP_AMF_OBJECT && t2 == NGX_RTMP_AMF_MIXED_ARRAY) + || (t2 == NGX_RTMP_AMF_OBJECT && t1 == NGX_RTMP_AMF_MIXED_ARRAY); +} + + +ngx_int_t +ngx_rtmp_amf_read(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_amf_elt_t *elts, + size_t nelts) +{ + void *data; + ngx_int_t type; + uint8_t type8; + size_t n; + uint16_t len; + ngx_int_t rc; + u_char buf[8]; + uint32_t max_index; + + for(n = 0; n < nelts; ++n) { + + if (elts && elts->type & NGX_RTMP_AMF_TYPELESS) { + type = elts->type & ~NGX_RTMP_AMF_TYPELESS; + data = elts->data; + + } else { + switch (ngx_rtmp_amf_get(ctx, &type8, 1)) { + case NGX_DONE: + if (elts->type & NGX_RTMP_AMF_OPTIONAL) { + return NGX_OK; + } + case NGX_ERROR: + return NGX_ERROR; + } + type = type8; + data = (elts && + ngx_rtmp_amf_is_compatible_type( + (uint8_t) (elts->type & 0xff), (uint8_t) type)) + ? elts->data + : NULL; + + if (elts && (elts->type & NGX_RTMP_AMF_CONTEXT)) { + if (data) { + *(ngx_rtmp_amf_ctx_t *) data = *ctx; + } + data = NULL; + } + } + + switch (type) { + case NGX_RTMP_AMF_NUMBER: + if (ngx_rtmp_amf_get(ctx, buf, 8) != NGX_OK) { + return NGX_ERROR; + } + ngx_rtmp_amf_reverse_copy(data, buf, 8); + break; + + case NGX_RTMP_AMF_BOOLEAN: + if (ngx_rtmp_amf_get(ctx, data, 1) != NGX_OK) { + return NGX_ERROR; + } + break; + + case NGX_RTMP_AMF_STRING: + if (ngx_rtmp_amf_get(ctx, buf, 2) != NGX_OK) { + return NGX_ERROR; + } + ngx_rtmp_amf_reverse_copy(&len, buf, 2); + + if (data == NULL) { + rc = ngx_rtmp_amf_get(ctx, data, len); + + } else if (elts->len <= len) { + rc = ngx_rtmp_amf_get(ctx, data, elts->len - 1); + if (rc != NGX_OK) + return NGX_ERROR; + ((char*)data)[elts->len - 1] = 0; + rc = ngx_rtmp_amf_get(ctx, NULL, len - elts->len + 1); + + } else { + rc = ngx_rtmp_amf_get(ctx, data, len); + ((char*)data)[len] = 0; + } + + if (rc != NGX_OK) { + return NGX_ERROR; + } + + break; + + case NGX_RTMP_AMF_NULL: + case NGX_RTMP_AMF_ARRAY_NULL: + break; + + case NGX_RTMP_AMF_MIXED_ARRAY: + if (ngx_rtmp_amf_get(ctx, &max_index, 4) != NGX_OK) { + return NGX_ERROR; + } + + case NGX_RTMP_AMF_OBJECT: + if (ngx_rtmp_amf_read_object(ctx, data, + data && elts ? elts->len / sizeof(ngx_rtmp_amf_elt_t) : 0 + ) != NGX_OK) + { + return NGX_ERROR; + } + break; + + case NGX_RTMP_AMF_ARRAY: + if (ngx_rtmp_amf_read_array(ctx, data, + data && elts ? elts->len / sizeof(ngx_rtmp_amf_elt_t) : 0 + ) != NGX_OK) + { + return NGX_ERROR; + } + break; + + case NGX_RTMP_AMF_VARIANT_: + if (ngx_rtmp_amf_read_variant(ctx, data, + data && elts ? elts->len / sizeof(ngx_rtmp_amf_elt_t) : 0 + ) != NGX_OK) + { + return NGX_ERROR; + } + break; + + case NGX_RTMP_AMF_INT8: + if (ngx_rtmp_amf_get(ctx, data, 1) != NGX_OK) { + return NGX_ERROR; + } + break; + + case NGX_RTMP_AMF_INT16: + if (ngx_rtmp_amf_get(ctx, buf, 2) != NGX_OK) { + return NGX_ERROR; + } + ngx_rtmp_amf_reverse_copy(data, buf, 2); + break; + + case NGX_RTMP_AMF_INT32: + if (ngx_rtmp_amf_get(ctx, buf, 4) != NGX_OK) { + return NGX_ERROR; + } + ngx_rtmp_amf_reverse_copy(data, buf, 4); + break; + + case NGX_RTMP_AMF_END: + return NGX_OK; + + default: + return NGX_ERROR; + } + + if (elts) { + ++elts; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_amf_write_object(ngx_rtmp_amf_ctx_t *ctx, + ngx_rtmp_amf_elt_t *elts, size_t nelts) +{ + uint16_t len; + size_t n; + u_char buf[2]; + + for(n = 0; n < nelts; ++n) { + + len = (uint16_t) elts[n].name.len; + + if (ngx_rtmp_amf_put(ctx, + ngx_rtmp_amf_reverse_copy(buf, + &len, 2), 2) != NGX_OK) + { + return NGX_ERROR; + } + + if (ngx_rtmp_amf_put(ctx, elts[n].name.data, len) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_rtmp_amf_write(ctx, &elts[n], 1) != NGX_OK) { + return NGX_ERROR; + } + } + + if (ngx_rtmp_amf_put(ctx, "\0\0", 2) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_amf_write_array(ngx_rtmp_amf_ctx_t *ctx, + ngx_rtmp_amf_elt_t *elts, size_t nelts) +{ + uint32_t len; + size_t n; + u_char buf[4]; + + len = nelts; + if (ngx_rtmp_amf_put(ctx, + ngx_rtmp_amf_reverse_copy(buf, + &len, 4), 4) != NGX_OK) + { + return NGX_ERROR; + } + + for(n = 0; n < nelts; ++n) { + if (ngx_rtmp_amf_write(ctx, &elts[n], 1) != NGX_OK) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_amf_write(ngx_rtmp_amf_ctx_t *ctx, + ngx_rtmp_amf_elt_t *elts, size_t nelts) +{ + size_t n; + ngx_int_t type; + uint8_t type8; + void *data; + uint16_t len; + uint32_t max_index; + u_char buf[8]; + + for(n = 0; n < nelts; ++n) { + + type = elts[n].type; + data = elts[n].data; + len = (uint16_t) elts[n].len; + + if (type & NGX_RTMP_AMF_TYPELESS) { + type &= ~NGX_RTMP_AMF_TYPELESS; + } else { + type8 = (uint8_t)type; + if (ngx_rtmp_amf_put(ctx, &type8, 1) != NGX_OK) + return NGX_ERROR; + } + + switch(type) { + case NGX_RTMP_AMF_NUMBER: + if (ngx_rtmp_amf_put(ctx, + ngx_rtmp_amf_reverse_copy(buf, + data, 8), 8) != NGX_OK) + { + return NGX_ERROR; + } + break; + + case NGX_RTMP_AMF_BOOLEAN: + if (ngx_rtmp_amf_put(ctx, data, 1) != NGX_OK) { + return NGX_ERROR; + } + break; + + case NGX_RTMP_AMF_STRING: + if (len == 0 && data) { + len = (uint16_t) ngx_strlen((u_char*) data); + } + + if (ngx_rtmp_amf_put(ctx, + ngx_rtmp_amf_reverse_copy(buf, + &len, 2), 2) != NGX_OK) + { + return NGX_ERROR; + } + + if (ngx_rtmp_amf_put(ctx, data, len) != NGX_OK) { + return NGX_ERROR; + } + break; + + case NGX_RTMP_AMF_NULL: + case NGX_RTMP_AMF_ARRAY_NULL: + break; + + case NGX_RTMP_AMF_MIXED_ARRAY: + max_index = 0; + if (ngx_rtmp_amf_put(ctx, &max_index, 4) != NGX_OK) { + return NGX_ERROR; + } + + case NGX_RTMP_AMF_OBJECT: + type8 = NGX_RTMP_AMF_END; + if (ngx_rtmp_amf_write_object(ctx, data, + elts[n].len / sizeof(ngx_rtmp_amf_elt_t)) != NGX_OK + || ngx_rtmp_amf_put(ctx, &type8, 1) != NGX_OK) + { + return NGX_ERROR; + } + break; + + case NGX_RTMP_AMF_ARRAY: + if (ngx_rtmp_amf_write_array(ctx, data, + elts[n].len / sizeof(ngx_rtmp_amf_elt_t)) != NGX_OK) + { + return NGX_ERROR; + } + break; + + case NGX_RTMP_AMF_INT8: + if (ngx_rtmp_amf_put(ctx, data, 1) != NGX_OK) { + return NGX_ERROR; + } + break; + + case NGX_RTMP_AMF_INT16: + if (ngx_rtmp_amf_put(ctx, + ngx_rtmp_amf_reverse_copy(buf, + data, 2), 2) != NGX_OK) + { + return NGX_ERROR; + } + break; + + case NGX_RTMP_AMF_INT32: + if (ngx_rtmp_amf_put(ctx, + ngx_rtmp_amf_reverse_copy(buf, + data, 4), 4) != NGX_OK) + { + return NGX_ERROR; + } + break; + + default: + return NGX_ERROR; + } + } + + return NGX_OK; +} + diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_amf.h nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_amf.h --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_amf.h 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_amf.h 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,71 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_AMF_H_INCLUDED_ +#define _NGX_RTMP_AMF_H_INCLUDED_ + + +#include +#include + + +/* basic types */ +#define NGX_RTMP_AMF_NUMBER 0x00 +#define NGX_RTMP_AMF_BOOLEAN 0x01 +#define NGX_RTMP_AMF_STRING 0x02 +#define NGX_RTMP_AMF_OBJECT 0x03 +#define NGX_RTMP_AMF_NULL 0x05 +#define NGX_RTMP_AMF_ARRAY_NULL 0x06 +#define NGX_RTMP_AMF_MIXED_ARRAY 0x08 +#define NGX_RTMP_AMF_END 0x09 +#define NGX_RTMP_AMF_ARRAY 0x0a + +/* extended types */ +#define NGX_RTMP_AMF_INT8 0x0100 +#define NGX_RTMP_AMF_INT16 0x0101 +#define NGX_RTMP_AMF_INT32 0x0102 +#define NGX_RTMP_AMF_VARIANT_ 0x0103 + +/* r/w flags */ +#define NGX_RTMP_AMF_OPTIONAL 0x1000 +#define NGX_RTMP_AMF_TYPELESS 0x2000 +#define NGX_RTMP_AMF_CONTEXT 0x4000 + +#define NGX_RTMP_AMF_VARIANT (NGX_RTMP_AMF_VARIANT_\ + |NGX_RTMP_AMF_TYPELESS) + + +typedef struct { + ngx_int_t type; + ngx_str_t name; + void *data; + size_t len; +} ngx_rtmp_amf_elt_t; + + +typedef ngx_chain_t * (*ngx_rtmp_amf_alloc_pt)(void *arg); + + +typedef struct { + ngx_chain_t *link, *first; + size_t offset; + ngx_rtmp_amf_alloc_pt alloc; + void *arg; + ngx_log_t *log; +} ngx_rtmp_amf_ctx_t; + + +/* reading AMF */ +ngx_int_t ngx_rtmp_amf_read(ngx_rtmp_amf_ctx_t *ctx, + ngx_rtmp_amf_elt_t *elts, size_t nelts); + +/* writing AMF */ +ngx_int_t ngx_rtmp_amf_write(ngx_rtmp_amf_ctx_t *ctx, + ngx_rtmp_amf_elt_t *elts, size_t nelts); + + +#endif /* _NGX_RTMP_AMF_H_INCLUDED_ */ + diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_auto_push_module.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_auto_push_module.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_auto_push_module.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_auto_push_module.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,550 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp_cmd_module.h" +#include "ngx_rtmp_relay_module.h" + + +static ngx_rtmp_publish_pt next_publish; +static ngx_rtmp_delete_stream_pt next_delete_stream; + + +static ngx_int_t ngx_rtmp_auto_push_init_process(ngx_cycle_t *cycle); +static void ngx_rtmp_auto_push_exit_process(ngx_cycle_t *cycle); +static void * ngx_rtmp_auto_push_create_conf(ngx_cycle_t *cf); +static char * ngx_rtmp_auto_push_init_conf(ngx_cycle_t *cycle, void *conf); +#if (NGX_HAVE_UNIX_DOMAIN) +static ngx_int_t ngx_rtmp_auto_push_publish(ngx_rtmp_session_t *s, + ngx_rtmp_publish_t *v); +static ngx_int_t ngx_rtmp_auto_push_delete_stream(ngx_rtmp_session_t *s, + ngx_rtmp_delete_stream_t *v); +#endif + + +typedef struct ngx_rtmp_auto_push_ctx_s ngx_rtmp_auto_push_ctx_t; + +struct ngx_rtmp_auto_push_ctx_s { + ngx_int_t *slots; /* NGX_MAX_PROCESSES */ + u_char name[NGX_RTMP_MAX_NAME]; + u_char args[NGX_RTMP_MAX_ARGS]; + ngx_event_t push_evt; +}; + + +typedef struct { + ngx_flag_t auto_push; + ngx_str_t socket_dir; + ngx_msec_t push_reconnect; +} ngx_rtmp_auto_push_conf_t; + + +static ngx_command_t ngx_rtmp_auto_push_commands[] = { + + { ngx_string("rtmp_auto_push"), + NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + 0, + offsetof(ngx_rtmp_auto_push_conf_t, auto_push), + NULL }, + + { ngx_string("rtmp_auto_push_reconnect"), + NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + 0, + offsetof(ngx_rtmp_auto_push_conf_t, push_reconnect), + NULL }, + + { ngx_string("rtmp_socket_dir"), + NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + 0, + offsetof(ngx_rtmp_auto_push_conf_t, socket_dir), + NULL }, + + ngx_null_command +}; + + +static ngx_core_module_t ngx_rtmp_auto_push_module_ctx = { + ngx_string("rtmp_auto_push"), + ngx_rtmp_auto_push_create_conf, /* create conf */ + ngx_rtmp_auto_push_init_conf /* init conf */ +}; + + +ngx_module_t ngx_rtmp_auto_push_module = { + NGX_MODULE_V1, + &ngx_rtmp_auto_push_module_ctx, /* module context */ + ngx_rtmp_auto_push_commands, /* module directives */ + NGX_CORE_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + ngx_rtmp_auto_push_init_process, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + ngx_rtmp_auto_push_exit_process, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +#define NGX_RTMP_AUTO_PUSH_SOCKNAME "nginx-rtmp" + + +static ngx_int_t +ngx_rtmp_auto_push_init_process(ngx_cycle_t *cycle) +{ +#if (NGX_HAVE_UNIX_DOMAIN) + ngx_rtmp_auto_push_conf_t *apcf; + ngx_listening_t *ls, *lss; + struct sockaddr_un *saun; + int reuseaddr; + ngx_socket_t s; + size_t n; + ngx_file_info_t fi; + + if (ngx_process != NGX_PROCESS_WORKER) { + return NGX_OK; + } + + apcf = (ngx_rtmp_auto_push_conf_t *) ngx_get_conf(cycle->conf_ctx, + ngx_rtmp_auto_push_module); + if (apcf->auto_push == 0) { + return NGX_OK; + } + + next_publish = ngx_rtmp_publish; + ngx_rtmp_publish = ngx_rtmp_auto_push_publish; + + next_delete_stream = ngx_rtmp_delete_stream; + ngx_rtmp_delete_stream = ngx_rtmp_auto_push_delete_stream; + + reuseaddr = 1; + s = (ngx_socket_t) -1; + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, cycle->log, 0, + "auto_push: creating sockets"); + + /*TODO: clone all RTMP listenings? */ + ls = cycle->listening.elts; + lss = NULL; + for (n = 0; n < cycle->listening.nelts; ++n, ++ls) { + if (ls->handler == ngx_rtmp_init_connection) { + lss = ls; + break; + } + } + + if (lss == NULL) { + return NGX_OK; + } + + ls = ngx_array_push(&cycle->listening); + if (ls == NULL) { + return NGX_ERROR; + } + + *ls = *lss; + + /* Disable unix socket client address extraction + * from accept call + * Nginx generates bad addr_text with this enabled */ + ls->addr_ntop = 0; + + ls->socklen = sizeof(struct sockaddr_un); + saun = ngx_pcalloc(cycle->pool, ls->socklen); + ls->sockaddr = (struct sockaddr *) saun; + if (ls->sockaddr == NULL) { + return NGX_ERROR; + } + saun->sun_family = AF_UNIX; + *ngx_snprintf((u_char *) saun->sun_path, sizeof(saun->sun_path), + "%V/" NGX_RTMP_AUTO_PUSH_SOCKNAME ".%i", + &apcf->socket_dir, ngx_process_slot) + = 0; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, cycle->log, 0, + "auto_push: create socket '%s'", + saun->sun_path); + + if (ngx_file_info(saun->sun_path, &fi) != ENOENT) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, cycle->log, 0, + "auto_push: delete existing socket '%s'", + saun->sun_path); + ngx_delete_file(saun->sun_path); + } + + ngx_str_set(&ls->addr_text, "worker_socket"); + + s = ngx_socket(AF_UNIX, SOCK_STREAM, 0); + if (s == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno, + ngx_socket_n " worker_socket failed"); + return NGX_ERROR; + } + + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, + (const void *) &reuseaddr, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno, + "setsockopt(SO_REUSEADDR) worker_socket failed"); + goto sock_error; + } + + if (!(ngx_event_flags & NGX_USE_AIO_EVENT)) { + if (ngx_nonblocking(s) == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno, + ngx_nonblocking_n " worker_socket failed"); + return NGX_ERROR; + } + } + + if (bind(s, (struct sockaddr *) saun, sizeof(*saun)) == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno, + ngx_nonblocking_n " worker_socket bind failed"); + goto sock_error; + } + + if (listen(s, NGX_LISTEN_BACKLOG) == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno, + "listen() to worker_socket, backlog %d failed", + NGX_LISTEN_BACKLOG); + goto sock_error; + } + + ls->fd = s; + ls->listen = 1; + + return NGX_OK; + +sock_error: + if (s != (ngx_socket_t) -1 && ngx_close_socket(s) == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno, + ngx_close_socket_n " worker_socket failed"); + } + ngx_delete_file(saun->sun_path); + + return NGX_ERROR; + +#else /* NGX_HAVE_UNIX_DOMAIN */ + + return NGX_OK; + +#endif /* NGX_HAVE_UNIX_DOMAIN */ +} + + +static void +ngx_rtmp_auto_push_exit_process(ngx_cycle_t *cycle) +{ +#if (NGX_HAVE_UNIX_DOMAIN) + ngx_rtmp_auto_push_conf_t *apcf; + u_char path[NGX_MAX_PATH]; + + apcf = (ngx_rtmp_auto_push_conf_t *) ngx_get_conf(cycle->conf_ctx, + ngx_rtmp_auto_push_module); + if (apcf->auto_push == 0) { + return; + } + *ngx_snprintf(path, sizeof(path), + "%V/" NGX_RTMP_AUTO_PUSH_SOCKNAME ".%i", + &apcf->socket_dir, ngx_process_slot) + = 0; + + ngx_delete_file(path); + +#endif +} + + +static void * +ngx_rtmp_auto_push_create_conf(ngx_cycle_t *cycle) +{ + ngx_rtmp_auto_push_conf_t *apcf; + + apcf = ngx_pcalloc(cycle->pool, sizeof(ngx_rtmp_auto_push_conf_t)); + if (apcf == NULL) { + return NULL; + } + + apcf->auto_push = NGX_CONF_UNSET; + apcf->push_reconnect = NGX_CONF_UNSET_MSEC; + + return apcf; +} + + +static char * +ngx_rtmp_auto_push_init_conf(ngx_cycle_t *cycle, void *conf) +{ + ngx_rtmp_auto_push_conf_t *apcf = conf; + + ngx_conf_init_value(apcf->auto_push, 0); + ngx_conf_init_msec_value(apcf->push_reconnect, 100); + + if (apcf->socket_dir.len == 0) { + ngx_str_set(&apcf->socket_dir, "/tmp"); + } + + return NGX_CONF_OK; +} + + +#if (NGX_HAVE_UNIX_DOMAIN) +static void +ngx_rtmp_auto_push_reconnect(ngx_event_t *ev) +{ + ngx_rtmp_session_t *s = ev->data; + + ngx_rtmp_auto_push_conf_t *apcf; + ngx_rtmp_auto_push_ctx_t *ctx; + ngx_int_t *slot; + ngx_int_t n; + ngx_rtmp_relay_target_t at; + u_char path[sizeof("unix:") + NGX_MAX_PATH]; + u_char flash_ver[sizeof("APSH ,") + + NGX_INT_T_LEN * 2]; + u_char play_path[NGX_RTMP_MAX_NAME]; + ngx_str_t name; + u_char *p; + ngx_str_t *u; + ngx_pid_t pid; + ngx_int_t npushed; + ngx_core_conf_t *ccf; + ngx_file_info_t fi; + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "auto_push: reconnect"); + + 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); + if (ctx == NULL) { + return; + } + + name.data = ctx->name; + name.len = ngx_strlen(name.data); + + ngx_memzero(&at, sizeof(at)); + ngx_str_set(&at.page_url, "nginx-auto-push"); + at.tag = &ngx_rtmp_auto_push_module; + + if (ctx->args[0]) { + at.play_path.data = play_path; + at.play_path.len = ngx_snprintf(play_path, sizeof(play_path), + "%s?%s", ctx->name, ctx->args) - + play_path; + } + + slot = ctx->slots; + npushed = 0; + + for (n = 0; n < NGX_MAX_PROCESSES; ++n, ++slot) { + if (n == ngx_process_slot) { + continue; + } + + pid = ngx_processes[n].pid; + if (pid == 0 || pid == NGX_INVALID_PID) { + continue; + } + + if (*slot) { + npushed++; + continue; + } + + at.data = &ngx_processes[n]; + + ngx_memzero(&at.url, sizeof(at.url)); + u = &at.url.url; + p = ngx_snprintf(path, sizeof(path) - 1, + "unix:%V/" NGX_RTMP_AUTO_PUSH_SOCKNAME ".%i", + &apcf->socket_dir, n); + *p = 0; + + if (ngx_file_info(path + sizeof("unix:") - 1, &fi) != NGX_OK) { + ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "auto_push: " ngx_file_info_n " failed: " + "slot=%i pid=%P socket='%s'" "url='%V' name='%s'", + n, pid, path, u, ctx->name); + continue; + } + + u->data = path; + u->len = p - path; + if (ngx_parse_url(s->connection->pool, &at.url) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "auto_push: auto-push parse_url failed " + "url='%V' name='%s'", + u, ctx->name); + continue; + } + + p = ngx_snprintf(flash_ver, sizeof(flash_ver) - 1, "APSH %i,%i", + (ngx_int_t) ngx_process_slot, (ngx_int_t) ngx_pid); + at.flash_ver.data = flash_ver; + at.flash_ver.len = p - flash_ver; + + ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "auto_push: connect slot=%i pid=%P socket='%s' name='%s'", + n, pid, path, ctx->name); + + if (ngx_rtmp_relay_push(s, &name, &at) == NGX_OK) { + *slot = 1; + npushed++; + continue; + } + + ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "auto_push: connect failed: slot=%i pid=%P socket='%s'" + "url='%V' name='%s'", + n, pid, path, u, ctx->name); + } + + ccf = (ngx_core_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_core_module); + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "auto_push: pushed=%i total=%i failed=%i", + npushed, ccf->worker_processes, + ccf->worker_processes - 1 - npushed); + + if (ccf->worker_processes == npushed + 1) { + return; + } + + /* several workers failed */ + + slot = ctx->slots; + + for (n = 0; n < NGX_MAX_PROCESSES; ++n, ++slot) { + pid = ngx_processes[n].pid; + + if (n == ngx_process_slot || *slot == 1 || + pid == 0 || pid == NGX_INVALID_PID) + { + continue; + } + + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "auto_push: connect failed: slot=%i pid=%P name='%s'", + n, pid, ctx->name); + } + + if (!ctx->push_evt.timer_set) { + ngx_add_timer(&ctx->push_evt, apcf->push_reconnect); + } +} + + +static ngx_int_t +ngx_rtmp_auto_push_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) +{ + ngx_rtmp_auto_push_conf_t *apcf; + ngx_rtmp_auto_push_ctx_t *ctx; + + if (s->auto_pushed || (s->relay && !s->static_relay)) { + goto next; + } + + apcf = (ngx_rtmp_auto_push_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_rtmp_auto_push_module); + if (apcf->auto_push == 0) { + goto next; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_auto_push_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_memzero(ctx, sizeof(*ctx)); + + ctx->push_evt.data = s; + ctx->push_evt.log = s->connection->log; + ctx->push_evt.handler = ngx_rtmp_auto_push_reconnect; + + ctx->slots = ngx_pcalloc(s->connection->pool, + sizeof(ngx_int_t) * NGX_MAX_PROCESSES); + if (ctx->slots == NULL) { + goto next; + } + + ngx_memcpy(ctx->name, v->name, sizeof(ctx->name)); + ngx_memcpy(ctx->args, v->args, sizeof(ctx->args)); + + ngx_rtmp_auto_push_reconnect(&ctx->push_evt); + +next: + return next_publish(s, v); +} + + +static ngx_int_t +ngx_rtmp_auto_push_delete_stream(ngx_rtmp_session_t *s, + ngx_rtmp_delete_stream_t *v) +{ + ngx_rtmp_auto_push_conf_t *apcf; + ngx_rtmp_auto_push_ctx_t *ctx, *pctx; + ngx_rtmp_relay_ctx_t *rctx; + ngx_int_t slot; + + apcf = (ngx_rtmp_auto_push_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx, + ngx_rtmp_auto_push_module); + if (apcf->auto_push == 0) { + goto next; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_auto_push_module); + if (ctx) { + if (ctx->push_evt.timer_set) { + ngx_del_timer(&ctx->push_evt); + } + goto next; + } + + /* skip non-relays & publishers */ + rctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module); + if (rctx == NULL || + rctx->tag != &ngx_rtmp_auto_push_module || + rctx->publish == NULL) + { + goto next; + } + + slot = (ngx_process_t *) rctx->data - &ngx_processes[0]; + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "auto_push: disconnect slot=%i app='%V' name='%V'", + slot, &rctx->app, &rctx->name); + + pctx = ngx_rtmp_get_module_ctx(rctx->publish->session, + ngx_rtmp_auto_push_module); + if (pctx == NULL) { + goto next; + } + + pctx->slots[slot] = 0; + + /* push reconnect */ + if (!pctx->push_evt.timer_set) { + ngx_add_timer(&pctx->push_evt, apcf->push_reconnect); + } + +next: + return next_delete_stream(s, v); +} +#endif /* NGX_HAVE_UNIX_DOMAIN */ diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_bandwidth.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_bandwidth.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_bandwidth.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_bandwidth.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,26 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp_bandwidth.h" + + +void +ngx_rtmp_update_bandwidth(ngx_rtmp_bandwidth_t *bw, uint32_t bytes) +{ + if (ngx_cached_time->sec > bw->intl_end) { + bw->bandwidth = ngx_cached_time->sec > + bw->intl_end + NGX_RTMP_BANDWIDTH_INTERVAL + ? 0 + : bw->intl_bytes / NGX_RTMP_BANDWIDTH_INTERVAL; + bw->intl_bytes = 0; + bw->intl_end = ngx_cached_time->sec + NGX_RTMP_BANDWIDTH_INTERVAL; + } + + bw->bytes += bytes; + bw->intl_bytes += bytes; +} diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_bandwidth.h nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_bandwidth.h --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_bandwidth.h 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_bandwidth.h 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,31 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_BANDWIDTH_H_INCLUDED_ +#define _NGX_RTMP_BANDWIDTH_H_INCLUDED_ + + +#include +#include + + +/* Bandwidth update interval in seconds */ +#define NGX_RTMP_BANDWIDTH_INTERVAL 10 + + +typedef struct { + uint64_t bytes; + uint64_t bandwidth; /* bytes/sec */ + + time_t intl_end; + uint64_t intl_bytes; +} ngx_rtmp_bandwidth_t; + + +void ngx_rtmp_update_bandwidth(ngx_rtmp_bandwidth_t *bw, uint32_t bytes); + + +#endif /* _NGX_RTMP_BANDWIDTH_H_INCLUDED_ */ diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_bitop.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_bitop.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_bitop.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_bitop.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,63 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp_bitop.h" + + +void +ngx_rtmp_bit_init_reader(ngx_rtmp_bit_reader_t *br, u_char *pos, u_char *last) +{ + ngx_memzero(br, sizeof(ngx_rtmp_bit_reader_t)); + + br->pos = pos; + br->last = last; +} + + +uint64_t +ngx_rtmp_bit_read(ngx_rtmp_bit_reader_t *br, ngx_uint_t n) +{ + uint64_t v; + ngx_uint_t d; + + v = 0; + + while (n) { + + if (br->pos >= br->last) { + br->err = 1; + return 0; + } + + d = (br->offs + n > 8 ? (ngx_uint_t) (8 - br->offs) : n); + + v <<= d; + v += (*br->pos >> (8 - br->offs - d)) & ((u_char) 0xff >> (8 - d)); + + br->offs += d; + n -= d; + + if (br->offs == 8) { + br->pos++; + br->offs = 0; + } + } + + return v; +} + + +uint64_t +ngx_rtmp_bit_read_golomb(ngx_rtmp_bit_reader_t *br) +{ + ngx_uint_t n; + + for (n = 0; ngx_rtmp_bit_read(br, 1) == 0 && !br->err; n++); + + return ((uint64_t) 1 << n) + ngx_rtmp_bit_read(br, n) - 1; +} diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_bitop.h nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_bitop.h --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_bitop.h 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_bitop.h 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,46 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_BITOP_H_INCLUDED_ +#define _NGX_RTMP_BITOP_H_INCLUDED_ + + +#include +#include + + +typedef struct { + u_char *pos; + u_char *last; + ngx_uint_t offs; + ngx_uint_t err; +} ngx_rtmp_bit_reader_t; + + +void ngx_rtmp_bit_init_reader(ngx_rtmp_bit_reader_t *br, u_char *pos, + u_char *last); +uint64_t ngx_rtmp_bit_read(ngx_rtmp_bit_reader_t *br, ngx_uint_t n); +uint64_t ngx_rtmp_bit_read_golomb(ngx_rtmp_bit_reader_t *br); + + +#define ngx_rtmp_bit_read_err(br) ((br)->err) + +#define ngx_rtmp_bit_read_eof(br) ((br)->pos == (br)->last) + +#define ngx_rtmp_bit_read_8(br) \ + ((uint8_t) ngx_rtmp_bit_read(br, 8)) + +#define ngx_rtmp_bit_read_16(br) \ + ((uint16_t) ngx_rtmp_bit_read(br, 16)) + +#define ngx_rtmp_bit_read_32(br) \ + ((uint32_t) ngx_rtmp_bit_read(br, 32)) + +#define ngx_rtmp_bit_read_64(br) \ + ((uint64_t) ngx_rtmp_read(br, 64)) + + +#endif /* _NGX_RTMP_BITOP_H_INCLUDED_ */ diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,846 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include +#include +#include "ngx_rtmp.h" + + +static char *ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static ngx_int_t ngx_rtmp_add_ports(ngx_conf_t *cf, ngx_array_t *ports, + ngx_rtmp_listen_t *listen); +static char *ngx_rtmp_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports); +static ngx_int_t ngx_rtmp_add_addrs(ngx_conf_t *cf, ngx_rtmp_port_t *mport, + ngx_rtmp_conf_addr_t *addr); +#if (NGX_HAVE_INET6) +static ngx_int_t ngx_rtmp_add_addrs6(ngx_conf_t *cf, ngx_rtmp_port_t *mport, + ngx_rtmp_conf_addr_t *addr); +#endif +static ngx_int_t ngx_rtmp_cmp_conf_addrs(const void *one, const void *two); +static ngx_int_t ngx_rtmp_init_events(ngx_conf_t *cf, + ngx_rtmp_core_main_conf_t *cmcf); +static ngx_int_t ngx_rtmp_init_event_handlers(ngx_conf_t *cf, + ngx_rtmp_core_main_conf_t *cmcf); +static char * ngx_rtmp_merge_applications(ngx_conf_t *cf, + ngx_array_t *applications, void **app_conf, ngx_rtmp_module_t *module, + ngx_uint_t ctx_index); +static ngx_int_t ngx_rtmp_init_process(ngx_cycle_t *cycle); + + +#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; +#endif + + +ngx_uint_t ngx_rtmp_max_module; + + +static ngx_command_t ngx_rtmp_commands[] = { + + { ngx_string("rtmp"), + NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, + ngx_rtmp_block, + 0, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_core_module_t ngx_rtmp_module_ctx = { + ngx_string("rtmp"), + NULL, + NULL +}; + + +ngx_module_t ngx_rtmp_module = { + NGX_MODULE_V1, + &ngx_rtmp_module_ctx, /* module context */ + ngx_rtmp_commands, /* module directives */ + NGX_CORE_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + ngx_rtmp_init_process, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static char * +ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + char *rv; + ngx_uint_t i, m, mi, s; + ngx_conf_t pcf; + ngx_array_t ports; + ngx_rtmp_listen_t *listen; + ngx_rtmp_module_t *module; + ngx_rtmp_conf_ctx_t *ctx; + ngx_rtmp_core_srv_conf_t *cscf, **cscfp; + ngx_rtmp_core_main_conf_t *cmcf; + + ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_conf_ctx_t)); + if (ctx == NULL) { + return NGX_CONF_ERROR; + } + + *(ngx_rtmp_conf_ctx_t **) conf = ctx; + + /* count the number of the rtmp modules and set up their indices */ + + ngx_rtmp_max_module = 0; + for (m = 0; ngx_modules[m]; m++) { + if (ngx_modules[m]->type != NGX_RTMP_MODULE) { + continue; + } + + ngx_modules[m]->ctx_index = ngx_rtmp_max_module++; + } + + + /* the rtmp main_conf context, it is the same in the all rtmp contexts */ + + ctx->main_conf = ngx_pcalloc(cf->pool, + sizeof(void *) * ngx_rtmp_max_module); + if (ctx->main_conf == NULL) { + return NGX_CONF_ERROR; + } + + + /* + * the rtmp null srv_conf context, it is used to merge + * the server{}s' srv_conf's + */ + + ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module); + if (ctx->srv_conf == NULL) { + return NGX_CONF_ERROR; + } + + + /* + * the rtmp null app_conf context, it is used to merge + * the server{}s' app_conf's + */ + + ctx->app_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module); + if (ctx->app_conf == NULL) { + return NGX_CONF_ERROR; + } + + + /* + * create the main_conf's, the null srv_conf's, and the null app_conf's + * of the all rtmp modules + */ + + for (m = 0; ngx_modules[m]; m++) { + if (ngx_modules[m]->type != NGX_RTMP_MODULE) { + continue; + } + + module = ngx_modules[m]->ctx; + mi = ngx_modules[m]->ctx_index; + + if (module->create_main_conf) { + ctx->main_conf[mi] = module->create_main_conf(cf); + if (ctx->main_conf[mi] == NULL) { + return NGX_CONF_ERROR; + } + } + + if (module->create_srv_conf) { + ctx->srv_conf[mi] = module->create_srv_conf(cf); + if (ctx->srv_conf[mi] == NULL) { + return NGX_CONF_ERROR; + } + } + + if (module->create_app_conf) { + ctx->app_conf[mi] = module->create_app_conf(cf); + if (ctx->app_conf[mi] == NULL) { + return NGX_CONF_ERROR; + } + } + } + + pcf = *cf; + cf->ctx = ctx; + + for (m = 0; ngx_modules[m]; m++) { + if (ngx_modules[m]->type != NGX_RTMP_MODULE) { + continue; + } + + module = ngx_modules[m]->ctx; + + if (module->preconfiguration) { + if (module->preconfiguration(cf) != NGX_OK) { + return NGX_CONF_ERROR; + } + } + } + + /* parse inside the rtmp{} block */ + + cf->module_type = NGX_RTMP_MODULE; + cf->cmd_type = NGX_RTMP_MAIN_CONF; + rv = ngx_conf_parse(cf, NULL); + + if (rv != NGX_CONF_OK) { + *cf = pcf; + return rv; + } + + + /* init rtmp{} main_conf's, merge the server{}s' srv_conf's */ + + 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) { + continue; + } + + module = ngx_modules[m]->ctx; + mi = ngx_modules[m]->ctx_index; + + /* init rtmp{} main_conf's */ + + cf->ctx = ctx; + + if (module->init_main_conf) { + rv = module->init_main_conf(cf, ctx->main_conf[mi]); + if (rv != NGX_CONF_OK) { + *cf = pcf; + return rv; + } + } + + for (s = 0; s < cmcf->servers.nelts; s++) { + + /* merge the server{}s' srv_conf's */ + + cf->ctx = cscfp[s]->ctx; + + if (module->merge_srv_conf) { + rv = module->merge_srv_conf(cf, + ctx->srv_conf[mi], + cscfp[s]->ctx->srv_conf[mi]); + if (rv != NGX_CONF_OK) { + *cf = pcf; + return rv; + } + } + + if (module->merge_app_conf) { + + /* merge the server{}'s app_conf */ + + /*ctx->app_conf = cscfp[s]->ctx->loc_conf;*/ + + rv = module->merge_app_conf(cf, + ctx->app_conf[mi], + cscfp[s]->ctx->app_conf[mi]); + if (rv != NGX_CONF_OK) { + *cf = pcf; + return rv; + } + + /* merge the applications{}' app_conf's */ + + cscf = cscfp[s]->ctx->srv_conf[ngx_rtmp_core_module.ctx_index]; + + rv = ngx_rtmp_merge_applications(cf, &cscf->applications, + cscfp[s]->ctx->app_conf, + module, mi); + if (rv != NGX_CONF_OK) { + *cf = pcf; + return rv; + } + } + + } + } + + + if (ngx_rtmp_init_events(cf, cmcf) != NGX_OK) { + return NGX_CONF_ERROR; + } + + for (m = 0; ngx_modules[m]; m++) { + if (ngx_modules[m]->type != NGX_RTMP_MODULE) { + continue; + } + + module = ngx_modules[m]->ctx; + + if (module->postconfiguration) { + if (module->postconfiguration(cf) != NGX_OK) { + return NGX_CONF_ERROR; + } + } + } + + *cf = pcf; + + if (ngx_rtmp_init_event_handlers(cf, cmcf) != NGX_OK) { + return NGX_CONF_ERROR; + } + + if (ngx_array_init(&ports, cf->temp_pool, 4, sizeof(ngx_rtmp_conf_port_t)) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + listen = cmcf->listen.elts; + + for (i = 0; i < cmcf->listen.nelts; i++) { + if (ngx_rtmp_add_ports(cf, &ports, &listen[i]) != NGX_OK) { + return NGX_CONF_ERROR; + } + } + + return ngx_rtmp_optimize_servers(cf, &ports); +} + + +static char * +ngx_rtmp_merge_applications(ngx_conf_t *cf, ngx_array_t *applications, + void **app_conf, ngx_rtmp_module_t *module, ngx_uint_t ctx_index) +{ + char *rv; + ngx_rtmp_conf_ctx_t *ctx, saved; + ngx_rtmp_core_app_conf_t **cacfp; + ngx_uint_t n; + ngx_rtmp_core_app_conf_t *cacf; + + if (applications == NULL) { + return NGX_CONF_OK; + } + + ctx = (ngx_rtmp_conf_ctx_t *) cf->ctx; + saved = *ctx; + + cacfp = applications->elts; + for (n = 0; n < applications->nelts; ++n, ++cacfp) { + + ctx->app_conf = (*cacfp)->app_conf; + + rv = module->merge_app_conf(cf, app_conf[ctx_index], + (*cacfp)->app_conf[ctx_index]); + if (rv != NGX_CONF_OK) { + return rv; + } + + cacf = (*cacfp)->app_conf[ngx_rtmp_core_module.ctx_index]; + rv = ngx_rtmp_merge_applications(cf, &cacf->applications, + (*cacfp)->app_conf, + module, ctx_index); + if (rv != NGX_CONF_OK) { + return rv; + } + } + + *ctx = saved; + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_rtmp_init_events(ngx_conf_t *cf, ngx_rtmp_core_main_conf_t *cmcf) +{ + size_t n; + + for(n = 0; n < NGX_RTMP_MAX_EVENT; ++n) { + if (ngx_array_init(&cmcf->events[n], cf->pool, 1, + sizeof(ngx_rtmp_handler_pt)) != NGX_OK) + { + return NGX_ERROR; + } + } + + if (ngx_array_init(&cmcf->amf, cf->pool, 1, + sizeof(ngx_rtmp_amf_handler_t)) != NGX_OK) + { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_init_event_handlers(ngx_conf_t *cf, ngx_rtmp_core_main_conf_t *cmcf) +{ + ngx_hash_init_t calls_hash; + ngx_rtmp_handler_pt *eh; + ngx_rtmp_amf_handler_t *h; + ngx_hash_key_t *ha; + size_t n, m; + + static size_t pm_events[] = { + NGX_RTMP_MSG_CHUNK_SIZE, + NGX_RTMP_MSG_ABORT, + NGX_RTMP_MSG_ACK, + NGX_RTMP_MSG_ACK_SIZE, + NGX_RTMP_MSG_BANDWIDTH + }; + + static size_t amf_events[] = { + NGX_RTMP_MSG_AMF_CMD, + NGX_RTMP_MSG_AMF_META, + NGX_RTMP_MSG_AMF_SHARED, + NGX_RTMP_MSG_AMF3_CMD, + NGX_RTMP_MSG_AMF3_META, + NGX_RTMP_MSG_AMF3_SHARED + }; + + /* init standard protocol events */ + for(n = 0; n < sizeof(pm_events) / sizeof(pm_events[0]); ++n) { + eh = ngx_array_push(&cmcf->events[pm_events[n]]); + *eh = ngx_rtmp_protocol_message_handler; + } + + /* init amf events */ + for(n = 0; n < sizeof(amf_events) / sizeof(amf_events[0]); ++n) { + eh = ngx_array_push(&cmcf->events[amf_events[n]]); + *eh = ngx_rtmp_amf_message_handler; + } + + /* init user protocol events */ + eh = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_USER]); + *eh = ngx_rtmp_user_message_handler; + + /* aggregate to audio/video map */ + eh = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AGGREGATE]); + *eh = ngx_rtmp_aggregate_message_handler; + + /* init amf callbacks */ + ngx_array_init(&cmcf->amf_arrays, cf->pool, 1, sizeof(ngx_hash_key_t)); + + h = cmcf->amf.elts; + for(n = 0; n < cmcf->amf.nelts; ++n, ++h) { + ha = cmcf->amf_arrays.elts; + for(m = 0; m < cmcf->amf_arrays.nelts; ++m, ++ha) { + if (h->name.len == ha->key.len + && !ngx_strncmp(h->name.data, ha->key.data, ha->key.len)) + { + break; + } + } + if (m == cmcf->amf_arrays.nelts) { + ha = ngx_array_push(&cmcf->amf_arrays); + ha->key = h->name; + ha->key_hash = ngx_hash_key_lc(ha->key.data, ha->key.len); + ha->value = ngx_array_create(cf->pool, 1, + sizeof(ngx_rtmp_handler_pt)); + if (ha->value == NULL) { + return NGX_ERROR; + } + } + + eh = ngx_array_push((ngx_array_t*)ha->value); + *eh = h->handler; + } + + calls_hash.hash = &cmcf->amf_hash; + calls_hash.key = ngx_hash_key_lc; + calls_hash.max_size = 512; + calls_hash.bucket_size = ngx_cacheline_size; + calls_hash.name = "amf_hash"; + calls_hash.pool = cf->pool; + calls_hash.temp_pool = NULL; + + if (ngx_hash_init(&calls_hash, cmcf->amf_arrays.elts, cmcf->amf_arrays.nelts) + != NGX_OK) + { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_add_ports(ngx_conf_t *cf, ngx_array_t *ports, + ngx_rtmp_listen_t *listen) +{ + in_port_t p; + ngx_uint_t i; + struct sockaddr *sa; + struct sockaddr_in *sin; + ngx_rtmp_conf_port_t *port; + ngx_rtmp_conf_addr_t *addr; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; +#endif + + sa = (struct sockaddr *) &listen->sockaddr; + + switch (sa->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) sa; + p = sin6->sin6_port; + break; +#endif + + default: /* AF_INET */ + sin = (struct sockaddr_in *) sa; + p = sin->sin_port; + break; + } + + port = ports->elts; + for (i = 0; i < ports->nelts; i++) { + if (p == port[i].port && sa->sa_family == port[i].family) { + + /* a port is already in the port list */ + + port = &port[i]; + goto found; + } + } + + /* add a port to the port list */ + + port = ngx_array_push(ports); + if (port == NULL) { + return NGX_ERROR; + } + + port->family = sa->sa_family; + port->port = p; + + if (ngx_array_init(&port->addrs, cf->temp_pool, 2, + sizeof(ngx_rtmp_conf_addr_t)) + != NGX_OK) + { + return NGX_ERROR; + } + +found: + + addr = ngx_array_push(&port->addrs); + if (addr == NULL) { + return NGX_ERROR; + } + + addr->sockaddr = (struct sockaddr *) &listen->sockaddr; + addr->socklen = listen->socklen; + addr->ctx = listen->ctx; + addr->bind = listen->bind; + addr->wildcard = listen->wildcard; + addr->so_keepalive = listen->so_keepalive; + addr->proxy_protocol = listen->proxy_protocol; +#if (NGX_HAVE_KEEPALIVE_TUNABLE) + addr->tcp_keepidle = listen->tcp_keepidle; + addr->tcp_keepintvl = listen->tcp_keepintvl; + addr->tcp_keepcnt = listen->tcp_keepcnt; +#endif +#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY) + addr->ipv6only = listen->ipv6only; +#endif + + return NGX_OK; +} + + +static char * +ngx_rtmp_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports) +{ + ngx_uint_t i, p, last, bind_wildcard; + ngx_listening_t *ls; + ngx_rtmp_port_t *mport; + ngx_rtmp_conf_port_t *port; + ngx_rtmp_conf_addr_t *addr; + + port = ports->elts; + for (p = 0; p < ports->nelts; p++) { + + ngx_sort(port[p].addrs.elts, (size_t) port[p].addrs.nelts, + sizeof(ngx_rtmp_conf_addr_t), ngx_rtmp_cmp_conf_addrs); + + addr = port[p].addrs.elts; + last = port[p].addrs.nelts; + + /* + * if there is the binding to the "*:port" then we need to bind() + * to the "*:port" only and ignore the other bindings + */ + + if (addr[last - 1].wildcard) { + addr[last - 1].bind = 1; + bind_wildcard = 1; + + } else { + bind_wildcard = 0; + } + + i = 0; + + while (i < last) { + + if (bind_wildcard && !addr[i].bind) { + i++; + continue; + } + + ls = ngx_create_listening(cf, addr[i].sockaddr, addr[i].socklen); + if (ls == NULL) { + return NGX_CONF_ERROR; + } + + ls->addr_ntop = 1; + ls->handler = ngx_rtmp_init_connection; + ls->pool_size = 4096; + + /* TODO: error_log directive */ + ls->logp = &cf->cycle->new_log; + ls->log.data = &ls->addr_text; + ls->log.handler = ngx_accept_log_error; + + ls->keepalive = addr[i].so_keepalive; +#if (NGX_HAVE_KEEPALIVE_TUNABLE) + ls->keepidle = addr[i].tcp_keepidle; + ls->keepintvl = addr[i].tcp_keepintvl; + ls->keepcnt = addr[i].tcp_keepcnt; +#endif + +#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY) + ls->ipv6only = addr[i].ipv6only; +#endif + + mport = ngx_palloc(cf->pool, sizeof(ngx_rtmp_port_t)); + if (mport == NULL) { + return NGX_CONF_ERROR; + } + + ls->servers = mport; + + if (i == last - 1) { + mport->naddrs = last; + + } else { + mport->naddrs = 1; + i = 0; + } + + switch (ls->sockaddr->sa_family) { +#if (NGX_HAVE_INET6) + case AF_INET6: + if (ngx_rtmp_add_addrs6(cf, mport, addr) != NGX_OK) { + return NGX_CONF_ERROR; + } + break; +#endif + default: /* AF_INET */ + if (ngx_rtmp_add_addrs(cf, mport, addr) != NGX_OK) { + return NGX_CONF_ERROR; + } + break; + } + + addr++; + last--; + } + } + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_rtmp_add_addrs(ngx_conf_t *cf, ngx_rtmp_port_t *mport, + ngx_rtmp_conf_addr_t *addr) +{ + u_char *p; + size_t len; + ngx_uint_t i; + ngx_rtmp_in_addr_t *addrs; + struct sockaddr_in *sin; + u_char buf[NGX_SOCKADDR_STRLEN]; + + mport->addrs = ngx_pcalloc(cf->pool, + mport->naddrs * sizeof(ngx_rtmp_in_addr_t)); + if (mport->addrs == NULL) { + return NGX_ERROR; + } + + addrs = mport->addrs; + + for (i = 0; i < mport->naddrs; i++) { + + sin = (struct sockaddr_in *) addr[i].sockaddr; + addrs[i].addr = sin->sin_addr.s_addr; + + addrs[i].conf.ctx = addr[i].ctx; + + len = ngx_sock_ntop(addr[i].sockaddr, +#if (nginx_version >= 1005003) + addr[i].socklen, +#endif + buf, NGX_SOCKADDR_STRLEN, 1); + + p = ngx_pnalloc(cf->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(p, buf, len); + + addrs[i].conf.addr_text.len = len; + addrs[i].conf.addr_text.data = p; + addrs[i].conf.proxy_protocol = addr->proxy_protocol; + } + + return NGX_OK; +} + + +#if (NGX_HAVE_INET6) + +static ngx_int_t +ngx_rtmp_add_addrs6(ngx_conf_t *cf, ngx_rtmp_port_t *mport, + ngx_rtmp_conf_addr_t *addr) +{ + u_char *p; + size_t len; + ngx_uint_t i; + ngx_rtmp_in6_addr_t *addrs6; + struct sockaddr_in6 *sin6; + u_char buf[NGX_SOCKADDR_STRLEN]; + + mport->addrs = ngx_pcalloc(cf->pool, + mport->naddrs * sizeof(ngx_rtmp_in6_addr_t)); + if (mport->addrs == NULL) { + return NGX_ERROR; + } + + addrs6 = mport->addrs; + + for (i = 0; i < mport->naddrs; i++) { + + sin6 = (struct sockaddr_in6 *) addr[i].sockaddr; + addrs6[i].addr6 = sin6->sin6_addr; + + addrs6[i].conf.ctx = addr[i].ctx; + + len = ngx_sock_ntop(addr[i].sockaddr, +#if (nginx_version >= 1005003) + addr[i].socklen, +#endif + buf, NGX_SOCKADDR_STRLEN, 1); + + p = ngx_pnalloc(cf->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(p, buf, len); + + addrs6[i].conf.addr_text.len = len; + addrs6[i].conf.addr_text.data = p; + addrs6[i].conf.proxy_protocol = addr->proxy_protocol; + } + + return NGX_OK; +} + +#endif + + +static ngx_int_t +ngx_rtmp_cmp_conf_addrs(const void *one, const void *two) +{ + ngx_rtmp_conf_addr_t *first, *second; + + first = (ngx_rtmp_conf_addr_t *) one; + second = (ngx_rtmp_conf_addr_t *) two; + + if (first->wildcard) { + /* a wildcard must be the last resort, shift it to the end */ + return 1; + } + + if (first->bind && !second->bind) { + /* shift explicit bind()ed addresses to the start */ + return -1; + } + + if (!first->bind && second->bind) { + /* shift explicit bind()ed addresses to the start */ + return 1; + } + + /* do not sort by default */ + + return 0; +} + + +ngx_int_t +ngx_rtmp_fire_event(ngx_rtmp_session_t *s, ngx_uint_t evt, + ngx_rtmp_header_t *h, ngx_chain_t *in) +{ + ngx_rtmp_core_main_conf_t *cmcf; + ngx_array_t *ch; + ngx_rtmp_handler_pt *hh; + size_t n; + + cmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_core_module); + + ch = &cmcf->events[evt]; + hh = ch->elts; + for(n = 0; n < ch->nelts; ++n, ++hh) { + if (*hh && (*hh)(s, h, in) != NGX_OK) { + return NGX_ERROR; + } + } + return NGX_OK; +} + + +void * +ngx_rtmp_rmemcpy(void *dst, const void* src, size_t n) +{ + u_char *d, *s; + + d = dst; + s = (u_char*)src + n - 1; + + while(s >= (u_char*)src) { + *d++ = *s--; + } + + return dst; +} + + +static ngx_int_t +ngx_rtmp_init_process(ngx_cycle_t *cycle) +{ +#if (nginx_version >= 1007005) + ngx_queue_init(&ngx_rtmp_init_queue); +#endif + return NGX_OK; +} diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_cmd_module.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_cmd_module.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_cmd_module.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_cmd_module.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,856 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp_cmd_module.h" +#include "ngx_rtmp_streams.h" + + +#define NGX_RTMP_FMS_VERSION "FMS/3,0,1,123" +#define NGX_RTMP_CAPABILITIES 31 + + +static ngx_int_t ngx_rtmp_cmd_connect(ngx_rtmp_session_t *s, + ngx_rtmp_connect_t *v); +static ngx_int_t ngx_rtmp_cmd_disconnect(ngx_rtmp_session_t *s); +static ngx_int_t ngx_rtmp_cmd_create_stream(ngx_rtmp_session_t *s, + ngx_rtmp_create_stream_t *v); +static ngx_int_t ngx_rtmp_cmd_close_stream(ngx_rtmp_session_t *s, + ngx_rtmp_close_stream_t *v); +static ngx_int_t ngx_rtmp_cmd_delete_stream(ngx_rtmp_session_t *s, + ngx_rtmp_delete_stream_t *v); +static ngx_int_t ngx_rtmp_cmd_publish(ngx_rtmp_session_t *s, + ngx_rtmp_publish_t *v); +static ngx_int_t ngx_rtmp_cmd_play(ngx_rtmp_session_t *s, + ngx_rtmp_play_t *v); +static ngx_int_t ngx_rtmp_cmd_seek(ngx_rtmp_session_t *s, + ngx_rtmp_seek_t *v); +static ngx_int_t ngx_rtmp_cmd_pause(ngx_rtmp_session_t *s, + ngx_rtmp_pause_t *v); + + +static ngx_int_t ngx_rtmp_cmd_stream_begin(ngx_rtmp_session_t *s, + ngx_rtmp_stream_begin_t *v); +static ngx_int_t ngx_rtmp_cmd_stream_eof(ngx_rtmp_session_t *s, + ngx_rtmp_stream_eof_t *v); +static ngx_int_t ngx_rtmp_cmd_stream_dry(ngx_rtmp_session_t *s, + ngx_rtmp_stream_dry_t *v); +static ngx_int_t ngx_rtmp_cmd_recorded(ngx_rtmp_session_t *s, + ngx_rtmp_recorded_t *v); +static ngx_int_t ngx_rtmp_cmd_set_buflen(ngx_rtmp_session_t *s, + ngx_rtmp_set_buflen_t *v); + + +ngx_rtmp_connect_pt ngx_rtmp_connect; +ngx_rtmp_disconnect_pt ngx_rtmp_disconnect; +ngx_rtmp_create_stream_pt ngx_rtmp_create_stream; +ngx_rtmp_close_stream_pt ngx_rtmp_close_stream; +ngx_rtmp_delete_stream_pt ngx_rtmp_delete_stream; +ngx_rtmp_publish_pt ngx_rtmp_publish; +ngx_rtmp_play_pt ngx_rtmp_play; +ngx_rtmp_seek_pt ngx_rtmp_seek; +ngx_rtmp_pause_pt ngx_rtmp_pause; + + +ngx_rtmp_stream_begin_pt ngx_rtmp_stream_begin; +ngx_rtmp_stream_eof_pt ngx_rtmp_stream_eof; +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; + + +static ngx_int_t ngx_rtmp_cmd_postconfiguration(ngx_conf_t *cf); + + +static ngx_rtmp_module_t ngx_rtmp_cmd_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_cmd_postconfiguration, /* postconfiguration */ + NULL, /* create main configuration */ + NULL, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + NULL, /* create app configuration */ + NULL /* merge app configuration */ +}; + + +ngx_module_t ngx_rtmp_cmd_module = { + NGX_MODULE_V1, + &ngx_rtmp_cmd_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 +}; + + +void +ngx_rtmp_cmd_fill_args(u_char name[NGX_RTMP_MAX_NAME], + u_char args[NGX_RTMP_MAX_ARGS]) +{ + u_char *p; + + p = (u_char *)ngx_strchr(name, '?'); + if (p == NULL) { + return; + } + + *p++ = 0; + ngx_cpystrn(args, p, NGX_RTMP_MAX_ARGS); +} + + +static ngx_int_t +ngx_rtmp_cmd_connect_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + size_t len; + + static ngx_rtmp_connect_t v; + + static ngx_rtmp_amf_elt_t in_cmd[] = { + + { NGX_RTMP_AMF_STRING, + ngx_string("app"), + v.app, sizeof(v.app) }, + + { NGX_RTMP_AMF_STRING, + ngx_string("flashVer"), + v.flashver, sizeof(v.flashver) }, + + { NGX_RTMP_AMF_STRING, + ngx_string("swfUrl"), + v.swf_url, sizeof(v.swf_url) }, + + { NGX_RTMP_AMF_STRING, + ngx_string("tcUrl"), + v.tc_url, sizeof(v.tc_url) }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("audioCodecs"), + &v.acodecs, sizeof(v.acodecs) }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("videoCodecs"), + &v.vcodecs, sizeof(v.vcodecs) }, + + { NGX_RTMP_AMF_STRING, + ngx_string("pageUrl"), + v.page_url, sizeof(v.page_url) }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("objectEncoding"), + &v.object_encoding, 0}, + }; + + static ngx_rtmp_amf_elt_t in_elts[] = { + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &v.trans, 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + in_cmd, sizeof(in_cmd) }, + }; + + ngx_memzero(&v, sizeof(v)); + if (ngx_rtmp_receive_amf(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0]))) + { + return NGX_ERROR; + } + + len = ngx_strlen(v.app); + if (len > 10 && !ngx_memcmp(v.app + len - 10, "/_definst_", 10)) { + v.app[len - 10] = 0; + } else if (len && v.app[len - 1] == '/') { + v.app[len - 1] = 0; + } + + ngx_rtmp_cmd_fill_args(v.app, v.args); + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "connect: app='%s' args='%s' flashver='%s' swf_url='%s' " + "tc_url='%s' page_url='%s' acodecs=%uD vcodecs=%uD " + "object_encoding=%ui", + v.app, v.args, v.flashver, v.swf_url, v.tc_url, v.page_url, + (uint32_t)v.acodecs, (uint32_t)v.vcodecs, + (ngx_int_t)v.object_encoding); + + return ngx_rtmp_connect(s, &v); +} + + +static ngx_int_t +ngx_rtmp_cmd_connect(ngx_rtmp_session_t *s, ngx_rtmp_connect_t *v) +{ + ngx_rtmp_core_srv_conf_t *cscf; + ngx_rtmp_core_app_conf_t **cacfp; + ngx_uint_t n; + ngx_rtmp_header_t h; + u_char *p; + + static double trans; + static double capabilities = NGX_RTMP_CAPABILITIES; + static double object_encoding = 0; + + static ngx_rtmp_amf_elt_t out_obj[] = { + + { NGX_RTMP_AMF_STRING, + ngx_string("fmsVer"), + NGX_RTMP_FMS_VERSION, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("capabilities"), + &capabilities, 0 }, + }; + + static ngx_rtmp_amf_elt_t out_inf[] = { + + { NGX_RTMP_AMF_STRING, + ngx_string("level"), + "status", 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_string("code"), + "NetConnection.Connect.Success", 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_string("description"), + "Connection succeeded.", 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("objectEncoding"), + &object_encoding, 0 } + }; + + static ngx_rtmp_amf_elt_t out_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + "_result", 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &trans, 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + out_obj, sizeof(out_obj) }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + out_inf, sizeof(out_inf) }, + }; + + if (s->connected) { + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "connect: duplicate connection"); + return NGX_ERROR; + } + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + trans = v->trans; + + /* fill session parameters */ + s->connected = 1; + + ngx_memzero(&h, sizeof(h)); + h.csid = NGX_RTMP_CSID_AMF_INI; + h.type = NGX_RTMP_MSG_AMF_CMD; + + +#define NGX_RTMP_SET_STRPAR(name) \ + s->name.len = ngx_strlen(v->name); \ + s->name.data = ngx_palloc(s->connection->pool, s->name.len); \ + ngx_memcpy(s->name.data, v->name, s->name.len) + + NGX_RTMP_SET_STRPAR(app); + NGX_RTMP_SET_STRPAR(args); + NGX_RTMP_SET_STRPAR(flashver); + NGX_RTMP_SET_STRPAR(swf_url); + NGX_RTMP_SET_STRPAR(tc_url); + NGX_RTMP_SET_STRPAR(page_url); + +#undef NGX_RTMP_SET_STRPAR + + p = ngx_strlchr(s->app.data, s->app.data + s->app.len, '?'); + if (p) { + s->app.len = (p - s->app.data); + } + + s->acodecs = (uint32_t) v->acodecs; + s->vcodecs = (uint32_t) v->vcodecs; + + /* find application & set app_conf */ + cacfp = cscf->applications.elts; + for(n = 0; n < cscf->applications.nelts; ++n, ++cacfp) { + if ((*cacfp)->name.len == s->app.len && + ngx_strncmp((*cacfp)->name.data, s->app.data, s->app.len) == 0) + { + /* found app! */ + s->app_conf = (*cacfp)->app_conf; + break; + } + } + + if (s->app_conf == NULL) { + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "connect: application not found: '%V'", &s->app); + return NGX_ERROR; + } + + object_encoding = v->object_encoding; + + return ngx_rtmp_send_ack_size(s, cscf->ack_window) != NGX_OK || + ngx_rtmp_send_bandwidth(s, cscf->ack_window, + NGX_RTMP_LIMIT_DYNAMIC) != NGX_OK || + ngx_rtmp_send_chunk_size(s, cscf->chunk_size) != NGX_OK || + ngx_rtmp_send_amf(s, &h, out_elts, + sizeof(out_elts) / sizeof(out_elts[0])) + != NGX_OK ? NGX_ERROR : NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_cmd_create_stream_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + static ngx_rtmp_create_stream_t v; + + static ngx_rtmp_amf_elt_t in_elts[] = { + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &v.trans, sizeof(v.trans) }, + }; + + if (ngx_rtmp_receive_amf(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0]))) + { + return NGX_ERROR; + } + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, "createStream"); + + return ngx_rtmp_create_stream(s, &v); +} + + +static ngx_int_t +ngx_rtmp_cmd_create_stream(ngx_rtmp_session_t *s, ngx_rtmp_create_stream_t *v) +{ + /* support one message stream per connection */ + static double stream; + static double trans; + ngx_rtmp_header_t h; + + static ngx_rtmp_amf_elt_t out_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + "_result", 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &trans, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &stream, sizeof(stream) }, + }; + + trans = v->trans; + stream = NGX_RTMP_MSID; + + ngx_memzero(&h, sizeof(h)); + + h.csid = NGX_RTMP_CSID_AMF_INI; + h.type = NGX_RTMP_MSG_AMF_CMD; + + return ngx_rtmp_send_amf(s, &h, out_elts, + sizeof(out_elts) / sizeof(out_elts[0])) == NGX_OK ? + NGX_DONE : NGX_ERROR; +} + + +static ngx_int_t +ngx_rtmp_cmd_close_stream_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + static ngx_rtmp_close_stream_t v; + + static ngx_rtmp_amf_elt_t in_elts[] = { + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &v.stream, 0 }, + }; + + if (ngx_rtmp_receive_amf(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0]))) + { + return NGX_ERROR; + } + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, "closeStream"); + + return ngx_rtmp_close_stream(s, &v); +} + + +static ngx_int_t +ngx_rtmp_cmd_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v) +{ + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_cmd_delete_stream_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + static ngx_rtmp_delete_stream_t v; + + static ngx_rtmp_amf_elt_t in_elts[] = { + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &v.stream, 0 }, + }; + + if (ngx_rtmp_receive_amf(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0]))) + { + return NGX_ERROR; + } + + return ngx_rtmp_delete_stream(s, &v); +} + + +static ngx_int_t +ngx_rtmp_cmd_delete_stream(ngx_rtmp_session_t *s, ngx_rtmp_delete_stream_t *v) +{ + ngx_rtmp_close_stream_t cv; + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, "deleteStream"); + + cv.stream = 0; + + return ngx_rtmp_close_stream(s, &cv); +} + + +static ngx_int_t +ngx_rtmp_cmd_publish_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + static ngx_rtmp_publish_t v; + + static ngx_rtmp_amf_elt_t in_elts[] = { + + /* transaction is always 0 */ + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + &v.name, sizeof(v.name) }, + + { NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_STRING, + ngx_null_string, + &v.type, sizeof(v.type) }, + }; + + ngx_memzero(&v, sizeof(v)); + + if (ngx_rtmp_receive_amf(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0]))) + { + return NGX_ERROR; + } + + ngx_rtmp_cmd_fill_args(v.name, v.args); + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "publish: name='%s' args='%s' type=%s silent=%d", + v.name, v.args, v.type, v.silent); + + return ngx_rtmp_publish(s, &v); +} + + +static ngx_int_t +ngx_rtmp_cmd_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) +{ + return NGX_OK; +} + +static ngx_int_t +ngx_rtmp_cmd_play_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + static ngx_rtmp_play_t v; + + static ngx_rtmp_amf_elt_t in_elts[] = { + + /* transaction is always 0 */ + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + &v.name, sizeof(v.name) }, + + { NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &v.start, 0 }, + + { NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &v.duration, 0 }, + + { NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_BOOLEAN, + ngx_null_string, + &v.reset, 0 } + }; + + ngx_memzero(&v, sizeof(v)); + + if (ngx_rtmp_receive_amf(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0]))) + { + return NGX_ERROR; + } + + ngx_rtmp_cmd_fill_args(v.name, v.args); + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "play: name='%s' args='%s' start=%i duration=%i " + "reset=%i silent=%i", + v.name, v.args, (ngx_int_t) v.start, + (ngx_int_t) v.duration, (ngx_int_t) v.reset, + (ngx_int_t) v.silent); + + return ngx_rtmp_play(s, &v); +} + + +static ngx_int_t +ngx_rtmp_cmd_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) +{ + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_cmd_play2_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + static ngx_rtmp_play_t v; + static ngx_rtmp_close_stream_t vc; + + static ngx_rtmp_amf_elt_t in_obj[] = { + + { NGX_RTMP_AMF_NUMBER, + ngx_string("start"), + &v.start, 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_string("streamName"), + &v.name, sizeof(v.name) }, + }; + + static ngx_rtmp_amf_elt_t in_elts[] = { + + /* transaction is always 0 */ + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + &in_obj, sizeof(in_obj) } + }; + + ngx_memzero(&v, sizeof(v)); + + if (ngx_rtmp_receive_amf(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0]))) + { + return NGX_ERROR; + } + + ngx_rtmp_cmd_fill_args(v.name, v.args); + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "play2: name='%s' args='%s' start=%i", + v.name, v.args, (ngx_int_t) v.start); + + /* continue from current timestamp */ + + if (v.start < 0) { + v.start = s->current_time; + } + + ngx_memzero(&vc, sizeof(vc)); + + /* close_stream should be synchronous */ + ngx_rtmp_close_stream(s, &vc); + + return ngx_rtmp_play(s, &v); +} + + +static ngx_int_t +ngx_rtmp_cmd_pause_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + static ngx_rtmp_pause_t v; + + static ngx_rtmp_amf_elt_t in_elts[] = { + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_BOOLEAN, + ngx_null_string, + &v.pause, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &v.position, 0 }, + }; + + ngx_memzero(&v, sizeof(v)); + + if (ngx_rtmp_receive_amf(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0]))) + { + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "pause: pause=%i position=%i", + (ngx_int_t) v.pause, (ngx_int_t) v.position); + + return ngx_rtmp_pause(s, &v); +} + + +static ngx_int_t +ngx_rtmp_cmd_pause(ngx_rtmp_session_t *s, ngx_rtmp_pause_t *v) +{ + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_cmd_disconnect_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, "disconnect"); + + return ngx_rtmp_disconnect(s); +} + + +static ngx_int_t +ngx_rtmp_cmd_disconnect(ngx_rtmp_session_t *s) +{ + return ngx_rtmp_delete_stream(s, NULL); +} + + +static ngx_int_t +ngx_rtmp_cmd_seek_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + static ngx_rtmp_seek_t v; + + static ngx_rtmp_amf_elt_t in_elts[] = { + + /* transaction is always 0 */ + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &v.offset, sizeof(v.offset) }, + }; + + ngx_memzero(&v, sizeof(v)); + + if (ngx_rtmp_receive_amf(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0]))) + { + return NGX_ERROR; + } + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "seek: offset=%i", (ngx_int_t) v.offset); + + return ngx_rtmp_seek(s, &v); +} + + +static ngx_int_t +ngx_rtmp_cmd_seek(ngx_rtmp_session_t *s, ngx_rtmp_seek_t *v) +{ + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_cmd_stream_begin(ngx_rtmp_session_t *s, ngx_rtmp_stream_begin_t *v) +{ + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_cmd_stream_eof(ngx_rtmp_session_t *s, ngx_rtmp_stream_eof_t *v) +{ + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_cmd_stream_dry(ngx_rtmp_session_t *s, ngx_rtmp_stream_dry_t *v) +{ + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_cmd_recorded(ngx_rtmp_session_t *s, + ngx_rtmp_recorded_t *v) +{ + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_cmd_set_buflen(ngx_rtmp_session_t *s, ngx_rtmp_set_buflen_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 }, + { ngx_string("closeStream"), ngx_rtmp_cmd_close_stream_init }, + { ngx_string("deleteStream"), ngx_rtmp_cmd_delete_stream_init }, + { ngx_string("publish"), ngx_rtmp_cmd_publish_init }, + { ngx_string("play"), ngx_rtmp_cmd_play_init }, + { ngx_string("play2"), ngx_rtmp_cmd_play2_init }, + { ngx_string("seek"), ngx_rtmp_cmd_seek_init }, + { ngx_string("pause"), ngx_rtmp_cmd_pause_init }, + { ngx_string("pauseraw"), ngx_rtmp_cmd_pause_init }, +}; + + +static ngx_int_t +ngx_rtmp_cmd_postconfiguration(ngx_conf_t *cf) +{ + ngx_rtmp_core_main_conf_t *cmcf; + ngx_rtmp_handler_pt *h; + ngx_rtmp_amf_handler_t *ch, *bh; + size_t n, ncalls; + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + + /* redirect disconnects to deleteStream + * to free client modules from registering + * disconnect callback */ + + h = ngx_array_push(&cmcf->events[NGX_RTMP_DISCONNECT]); + if (h == NULL) { + return NGX_ERROR; + } + + *h = ngx_rtmp_cmd_disconnect_init; + + /* register AMF callbacks */ + + ncalls = sizeof(ngx_rtmp_cmd_map) / sizeof(ngx_rtmp_cmd_map[0]); + + ch = ngx_array_push_n(&cmcf->amf, ncalls); + if (ch == NULL) { + return NGX_ERROR; + } + + bh = ngx_rtmp_cmd_map; + + for(n = 0; n < ncalls; ++n, ++ch, ++bh) { + *ch = *bh; + } + + ngx_rtmp_connect = ngx_rtmp_cmd_connect; + ngx_rtmp_disconnect = ngx_rtmp_cmd_disconnect; + ngx_rtmp_create_stream = ngx_rtmp_cmd_create_stream; + ngx_rtmp_close_stream = ngx_rtmp_cmd_close_stream; + ngx_rtmp_delete_stream = ngx_rtmp_cmd_delete_stream; + ngx_rtmp_publish = ngx_rtmp_cmd_publish; + ngx_rtmp_play = ngx_rtmp_cmd_play; + ngx_rtmp_seek = ngx_rtmp_cmd_seek; + ngx_rtmp_pause = ngx_rtmp_cmd_pause; + + ngx_rtmp_stream_begin = ngx_rtmp_cmd_stream_begin; + ngx_rtmp_stream_eof = ngx_rtmp_cmd_stream_eof; + ngx_rtmp_stream_dry = ngx_rtmp_cmd_stream_dry; + ngx_rtmp_recorded = ngx_rtmp_cmd_recorded; + ngx_rtmp_set_buflen = ngx_rtmp_cmd_set_buflen; + + return NGX_OK; +} diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_cmd_module.h nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_cmd_module.h --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_cmd_module.h 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_cmd_module.h 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,151 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_CMD_H_INCLUDED_ +#define _NGX_RTMP_CMD_H_INCLUDED_ + + +#include +#include +#include +#include "ngx_rtmp.h" + + +#define NGX_RTMP_MAX_NAME 256 +#define NGX_RTMP_MAX_URL 256 +#define NGX_RTMP_MAX_ARGS NGX_RTMP_MAX_NAME + + +/* Basic RTMP call support */ + +typedef struct { + double trans; + u_char app[NGX_RTMP_MAX_NAME]; + u_char args[NGX_RTMP_MAX_ARGS]; + u_char flashver[32]; + u_char swf_url[NGX_RTMP_MAX_URL]; + u_char tc_url[NGX_RTMP_MAX_URL]; + double acodecs; + double vcodecs; + u_char page_url[NGX_RTMP_MAX_URL]; + double object_encoding; +} ngx_rtmp_connect_t; + + +typedef struct { + double trans; + double stream; +} ngx_rtmp_create_stream_t; + + +typedef struct { + double stream; +} ngx_rtmp_delete_stream_t; + + +typedef struct { + double stream; +} ngx_rtmp_close_stream_t; + + +typedef struct { + u_char name[NGX_RTMP_MAX_NAME]; + u_char args[NGX_RTMP_MAX_ARGS]; + u_char type[16]; + int silent; +} ngx_rtmp_publish_t; + + +typedef struct { + u_char name[NGX_RTMP_MAX_NAME]; + u_char args[NGX_RTMP_MAX_ARGS]; + double start; + double duration; + int reset; + int silent; +} ngx_rtmp_play_t; + + +typedef struct { + double offset; +} ngx_rtmp_seek_t; + + +typedef struct { + uint8_t pause; + double position; +} ngx_rtmp_pause_t; + + +typedef struct { + uint32_t msid; +} ngx_rtmp_msid_t; + + +typedef ngx_rtmp_msid_t ngx_rtmp_stream_begin_t; +typedef ngx_rtmp_msid_t ngx_rtmp_stream_eof_t; +typedef ngx_rtmp_msid_t ngx_rtmp_stream_dry_t; +typedef ngx_rtmp_msid_t ngx_rtmp_recorded_t; + + +typedef struct { + uint32_t msid; + uint32_t buflen; +} ngx_rtmp_set_buflen_t; + + +void ngx_rtmp_cmd_fill_args(u_char name[NGX_RTMP_MAX_NAME], + u_char args[NGX_RTMP_MAX_ARGS]); + + +typedef ngx_int_t (*ngx_rtmp_connect_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_connect_t *v); +typedef ngx_int_t (*ngx_rtmp_disconnect_pt)(ngx_rtmp_session_t *s); +typedef ngx_int_t (*ngx_rtmp_create_stream_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_create_stream_t *v); +typedef ngx_int_t (*ngx_rtmp_close_stream_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_close_stream_t *v); +typedef ngx_int_t (*ngx_rtmp_delete_stream_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_delete_stream_t *v); +typedef ngx_int_t (*ngx_rtmp_publish_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_publish_t *v); +typedef ngx_int_t (*ngx_rtmp_play_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_play_t *v); +typedef ngx_int_t (*ngx_rtmp_seek_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_seek_t *v); +typedef ngx_int_t (*ngx_rtmp_pause_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_pause_t *v); + +typedef ngx_int_t (*ngx_rtmp_stream_begin_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_stream_begin_t *v); +typedef ngx_int_t (*ngx_rtmp_stream_eof_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_stream_eof_t *v); +typedef ngx_int_t (*ngx_rtmp_stream_dry_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_stream_dry_t *v); +typedef ngx_int_t (*ngx_rtmp_recorded_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_recorded_t *v); +typedef ngx_int_t (*ngx_rtmp_set_buflen_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_set_buflen_t *v); + + +extern ngx_rtmp_connect_pt ngx_rtmp_connect; +extern ngx_rtmp_disconnect_pt ngx_rtmp_disconnect; +extern ngx_rtmp_create_stream_pt ngx_rtmp_create_stream; +extern ngx_rtmp_close_stream_pt ngx_rtmp_close_stream; +extern ngx_rtmp_delete_stream_pt ngx_rtmp_delete_stream; +extern ngx_rtmp_publish_pt ngx_rtmp_publish; +extern ngx_rtmp_play_pt ngx_rtmp_play; +extern ngx_rtmp_seek_pt ngx_rtmp_seek; +extern ngx_rtmp_pause_pt ngx_rtmp_pause; + +extern ngx_rtmp_stream_begin_pt ngx_rtmp_stream_begin; +extern ngx_rtmp_stream_eof_pt ngx_rtmp_stream_eof; +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; + + +#endif /*_NGX_RTMP_CMD_H_INCLUDED_ */ diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_codec_module.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_codec_module.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_codec_module.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_codec_module.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,956 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp_codec_module.h" +#include "ngx_rtmp_live_module.h" +#include "ngx_rtmp_cmd_module.h" +#include "ngx_rtmp_bitop.h" + + +#define NGX_RTMP_CODEC_META_OFF 0 +#define NGX_RTMP_CODEC_META_ON 1 +#define NGX_RTMP_CODEC_META_COPY 2 + + +static void * ngx_rtmp_codec_create_app_conf(ngx_conf_t *cf); +static char * ngx_rtmp_codec_merge_app_conf(ngx_conf_t *cf, + void *parent, void *child); +static ngx_int_t ngx_rtmp_codec_postconfiguration(ngx_conf_t *cf); +static ngx_int_t ngx_rtmp_codec_reconstruct_meta(ngx_rtmp_session_t *s); +static ngx_int_t ngx_rtmp_codec_copy_meta(ngx_rtmp_session_t *s, + ngx_rtmp_header_t *h, ngx_chain_t *in); +static ngx_int_t ngx_rtmp_codec_prepare_meta(ngx_rtmp_session_t *s, + uint32_t timestamp); +static void ngx_rtmp_codec_parse_aac_header(ngx_rtmp_session_t *s, + ngx_chain_t *in); +static void ngx_rtmp_codec_parse_avc_header(ngx_rtmp_session_t *s, + ngx_chain_t *in); +#if (NGX_DEBUG) +static void ngx_rtmp_codec_dump_header(ngx_rtmp_session_t *s, const char *type, + ngx_chain_t *in); +#endif + + +typedef struct { + ngx_uint_t meta; +} ngx_rtmp_codec_app_conf_t; + + +static ngx_conf_enum_t ngx_rtmp_codec_meta_slots[] = { + { ngx_string("off"), NGX_RTMP_CODEC_META_OFF }, + { ngx_string("on"), NGX_RTMP_CODEC_META_ON }, + { ngx_string("copy"), NGX_RTMP_CODEC_META_COPY }, + { ngx_null_string, 0 } +}; + + +static ngx_command_t ngx_rtmp_codec_commands[] = { + + { ngx_string("meta"), + 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_codec_app_conf_t, meta), + &ngx_rtmp_codec_meta_slots }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_rtmp_codec_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_codec_postconfiguration, /* postconfiguration */ + NULL, /* create main configuration */ + NULL, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + ngx_rtmp_codec_create_app_conf, /* create app configuration */ + ngx_rtmp_codec_merge_app_conf /* merge app configuration */ +}; + + +ngx_module_t ngx_rtmp_codec_module = { + NGX_MODULE_V1, + &ngx_rtmp_codec_module_ctx, /* module context */ + ngx_rtmp_codec_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static const char * +audio_codecs[] = { + "", + "ADPCM", + "MP3", + "LinearLE", + "Nellymoser16", + "Nellymoser8", + "Nellymoser", + "G711A", + "G711U", + "", + "AAC", + "Speex", + "", + "", + "MP3-8K", + "DeviceSpecific", + "Uncompressed" +}; + + +static const char * +video_codecs[] = { + "", + "Jpeg", + "Sorenson-H263", + "ScreenVideo", + "On2-VP6", + "On2-VP6-Alpha", + "ScreenVideo2", + "H264", +}; + + +u_char * +ngx_rtmp_get_audio_codec_name(ngx_uint_t id) +{ + return (u_char *)(id < sizeof(audio_codecs) / sizeof(audio_codecs[0]) + ? audio_codecs[id] + : ""); +} + + +u_char * +ngx_rtmp_get_video_codec_name(ngx_uint_t id) +{ + return (u_char *)(id < sizeof(video_codecs) / sizeof(video_codecs[0]) + ? video_codecs[id] + : ""); +} + + +static ngx_uint_t +ngx_rtmp_codec_get_next_version() +{ + ngx_uint_t v; + static ngx_uint_t version; + + do { + v = ++version; + } while (v == 0); + + return v; +} + + +static ngx_int_t +ngx_rtmp_codec_disconnect(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_rtmp_codec_ctx_t *ctx; + ngx_rtmp_core_srv_conf_t *cscf; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + if (ctx == NULL) { + return NGX_OK; + } + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + if (ctx->avc_header) { + ngx_rtmp_free_shared_chain(cscf, ctx->avc_header); + ctx->avc_header = NULL; + } + + if (ctx->aac_header) { + ngx_rtmp_free_shared_chain(cscf, ctx->aac_header); + ctx->aac_header = NULL; + } + + if (ctx->meta) { + ngx_rtmp_free_shared_chain(cscf, ctx->meta); + ctx->meta = NULL; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_codec_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_rtmp_core_srv_conf_t *cscf; + ngx_rtmp_codec_ctx_t *ctx; + ngx_chain_t **header; + uint8_t fmt; + static ngx_uint_t sample_rates[] = + { 5512, 11025, 22050, 44100 }; + + if (h->type != NGX_RTMP_MSG_AUDIO && h->type != NGX_RTMP_MSG_VIDEO) { + return NGX_OK; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + if (ctx == NULL) { + ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_rtmp_codec_ctx_t)); + ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_codec_module); + } + + /* save codec */ + if (in->buf->last - in->buf->pos < 1) { + return NGX_OK; + } + + fmt = in->buf->pos[0]; + if (h->type == NGX_RTMP_MSG_AUDIO) { + ctx->audio_codec_id = (fmt & 0xf0) >> 4; + ctx->audio_channels = (fmt & 0x01) + 1; + ctx->sample_size = (fmt & 0x02) ? 2 : 1; + + if (ctx->sample_rate == 0) { + ctx->sample_rate = sample_rates[(fmt & 0x0c) >> 2]; + } + } else { + ctx->video_codec_id = (fmt & 0x0f); + } + + /* save AVC/AAC header */ + if (in->buf->last - in->buf->pos < 3) { + return NGX_OK; + } + + /* no conf */ + if (!ngx_rtmp_is_codec_header(in)) { + return NGX_OK; + } + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + header = NULL; + + if (h->type == NGX_RTMP_MSG_AUDIO) { + if (ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC) { + header = &ctx->aac_header; + ngx_rtmp_codec_parse_aac_header(s, in); + } + } else { + if (ctx->video_codec_id == NGX_RTMP_VIDEO_H264) { + header = &ctx->avc_header; + ngx_rtmp_codec_parse_avc_header(s, in); + } + } + + if (header == NULL) { + return NGX_OK; + } + + if (*header) { + ngx_rtmp_free_shared_chain(cscf, *header); + } + + *header = ngx_rtmp_append_shared_bufs(cscf, NULL, in); + + return NGX_OK; +} + + +static void +ngx_rtmp_codec_parse_aac_header(ngx_rtmp_session_t *s, ngx_chain_t *in) +{ + ngx_uint_t idx; + ngx_rtmp_codec_ctx_t *ctx; + ngx_rtmp_bit_reader_t br; + + static ngx_uint_t aac_sample_rates[] = + { 96000, 88200, 64000, 48000, + 44100, 32000, 24000, 22050, + 16000, 12000, 11025, 8000, + 7350, 0, 0, 0 }; + +#if (NGX_DEBUG) + ngx_rtmp_codec_dump_header(s, "aac", in); +#endif + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + ngx_rtmp_bit_init_reader(&br, in->buf->pos, in->buf->last); + + ngx_rtmp_bit_read(&br, 16); + + ctx->aac_profile = (ngx_uint_t) ngx_rtmp_bit_read(&br, 5); + if (ctx->aac_profile == 31) { + ctx->aac_profile = (ngx_uint_t) ngx_rtmp_bit_read(&br, 6) + 32; + } + + idx = (ngx_uint_t) ngx_rtmp_bit_read(&br, 4); + if (idx == 15) { + ctx->sample_rate = (ngx_uint_t) ngx_rtmp_bit_read(&br, 24); + } else { + ctx->sample_rate = aac_sample_rates[idx]; + } + + ctx->aac_chan_conf = (ngx_uint_t) ngx_rtmp_bit_read(&br, 4); + + if (ctx->aac_profile == 5 || ctx->aac_profile == 29) { + + if (ctx->aac_profile == 29) { + ctx->aac_ps = 1; + } + + ctx->aac_sbr = 1; + + idx = (ngx_uint_t) ngx_rtmp_bit_read(&br, 4); + if (idx == 15) { + ctx->sample_rate = (ngx_uint_t) ngx_rtmp_bit_read(&br, 24); + } else { + ctx->sample_rate = aac_sample_rates[idx]; + } + + ctx->aac_profile = (ngx_uint_t) ngx_rtmp_bit_read(&br, 5); + if (ctx->aac_profile == 31) { + ctx->aac_profile = (ngx_uint_t) ngx_rtmp_bit_read(&br, 6) + 32; + } + } + + /* MPEG-4 Audio Specific Config + + 5 bits: object type + if (object type == 31) + 6 bits + 32: object type + 4 bits: frequency index + if (frequency index == 15) + 24 bits: frequency + 4 bits: channel configuration + + if (object_type == 5) + 4 bits: frequency index + if (frequency index == 15) + 24 bits: frequency + 5 bits: object type + if (object type == 31) + 6 bits + 32: object type + + var bits: AOT Specific Config + */ + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "codec: aac header profile=%ui, " + "sample_rate=%ui, chan_conf=%ui", + ctx->aac_profile, ctx->sample_rate, ctx->aac_chan_conf); +} + + +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; + ngx_rtmp_codec_ctx_t *ctx; + ngx_rtmp_bit_reader_t br; + +#if (NGX_DEBUG) + ngx_rtmp_codec_dump_header(s, "avc", in); +#endif + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + ngx_rtmp_bit_init_reader(&br, in->buf->pos, in->buf->last); + + ngx_rtmp_bit_read(&br, 48); + + ctx->avc_profile = (ngx_uint_t) ngx_rtmp_bit_read_8(&br); + ctx->avc_compat = (ngx_uint_t) ngx_rtmp_bit_read_8(&br); + ctx->avc_level = (ngx_uint_t) ngx_rtmp_bit_read_8(&br); + + /* nal bytes */ + ctx->avc_nal_bytes = (ngx_uint_t) ((ngx_rtmp_bit_read_8(&br) & 0x03) + 1); + + /* nnals */ + if ((ngx_rtmp_bit_read_8(&br) & 0x1f) == 0) { + return; + } + + /* nal size */ + ngx_rtmp_bit_read(&br, 16); + + /* nal type */ + if (ngx_rtmp_bit_read_8(&br) != 0x67) { + return; + } + + /* SPS */ + + /* profile idc */ + profile_idc = (ngx_uint_t) ngx_rtmp_bit_read(&br, 8); + + /* flags */ + ngx_rtmp_bit_read(&br, 8); + + /* level idc */ + ngx_rtmp_bit_read(&br, 8); + + /* SPS id */ + ngx_rtmp_bit_read_golomb(&br); + + if (profile_idc == 100 || profile_idc == 110 || + profile_idc == 122 || profile_idc == 244 || profile_idc == 44 || + profile_idc == 83 || profile_idc == 86 || profile_idc == 118) + { + /* chroma format idc */ + cf_idc = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br); + + if (cf_idc == 3) { + + /* separate color plane */ + ngx_rtmp_bit_read(&br, 1); + } + + /* bit depth luma - 8 */ + ngx_rtmp_bit_read_golomb(&br); + + /* bit depth chroma - 8 */ + ngx_rtmp_bit_read_golomb(&br); + + /* qpprime y zero transform bypass */ + ngx_rtmp_bit_read(&br, 1); + + /* seq scaling matrix present */ + if (ngx_rtmp_bit_read(&br, 1)) { + + for (n = 0; n < (cf_idc != 3 ? 8u : 12u); n++) { + + /* seq scaling list present */ + if (ngx_rtmp_bit_read(&br, 1)) { + + /* TODO: scaling_list() + if (n < 6) { + } else { + } + */ + } + } + } + } + + /* log2 max frame num */ + ngx_rtmp_bit_read_golomb(&br); + + /* pic order cnt type */ + switch (ngx_rtmp_bit_read_golomb(&br)) { + case 0: + + /* max pic order cnt */ + ngx_rtmp_bit_read_golomb(&br); + break; + + case 1: + + /* delta pic order alwys zero */ + ngx_rtmp_bit_read(&br, 1); + + /* offset for non-ref pic */ + ngx_rtmp_bit_read_golomb(&br); + + /* offset for top to bottom field */ + ngx_rtmp_bit_read_golomb(&br); + + /* num ref frames in pic order */ + num_ref_frames = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br); + + for (n = 0; n < num_ref_frames; n++) { + + /* offset for ref frame */ + ngx_rtmp_bit_read_golomb(&br); + } + } + + /* num ref frames */ + ctx->avc_ref_frames = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br); + + /* gaps in frame num allowed */ + ngx_rtmp_bit_read(&br, 1); + + /* pic width in mbs - 1 */ + width = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br); + + /* pic height in map units - 1 */ + height = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br); + + /* frame mbs only flag */ + frame_mbs_only = (ngx_uint_t) ngx_rtmp_bit_read(&br, 1); + + if (!frame_mbs_only) { + + /* mbs adaprive frame field */ + ngx_rtmp_bit_read(&br, 1); + } + + /* direct 8x8 inference flag */ + ngx_rtmp_bit_read(&br, 1); + + /* frame cropping */ + if (ngx_rtmp_bit_read(&br, 1)) { + + crop_left = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br); + crop_right = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br); + crop_top = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br); + crop_bottom = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br); + + } else { + + crop_left = 0; + crop_right = 0; + crop_top = 0; + crop_bottom = 0; + } + + ctx->width = (width + 1) * 16 - (crop_left + crop_right) * 2; + ctx->height = (2 - frame_mbs_only) * (height + 1) * 16 - + (crop_top + crop_bottom) * 2; + + ngx_log_debug7(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "codec: avc header " + "profile=%ui, compat=%ui, level=%ui, " + "nal_bytes=%ui, ref_frames=%ui, width=%ui, height=%ui", + ctx->avc_profile, ctx->avc_compat, ctx->avc_level, + ctx->avc_nal_bytes, ctx->avc_ref_frames, + ctx->width, ctx->height); +} + + +#if (NGX_DEBUG) +static void +ngx_rtmp_codec_dump_header(ngx_rtmp_session_t *s, const char *type, + ngx_chain_t *in) +{ + u_char buf[256], *p, *pp; + u_char hex[] = "0123456789abcdef"; + + for (pp = buf, p = in->buf->pos; + p < in->buf->last && pp < buf + sizeof(buf) - 1; + ++p) + { + *pp++ = hex[*p >> 4]; + *pp++ = hex[*p & 0x0f]; + } + + *pp = 0; + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "codec: %s header %s", type, buf); +} +#endif + + +static ngx_int_t +ngx_rtmp_codec_reconstruct_meta(ngx_rtmp_session_t *s) +{ + ngx_rtmp_codec_ctx_t *ctx; + ngx_rtmp_core_srv_conf_t *cscf; + ngx_int_t rc; + + 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, + "onMetaData", 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + out_inf, sizeof(out_inf) }, + }; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + if (ctx == NULL) { + return NGX_OK; + } + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + if (ctx->meta) { + ngx_rtmp_free_shared_chain(cscf, ctx->meta); + ctx->meta = NULL; + } + + v.width = ctx->width; + v.height = ctx->height; + v.duration = ctx->duration; + v.frame_rate = ctx->frame_rate; + v.video_data_rate = ctx->video_data_rate; + v.video_codec_id = ctx->video_codec_id; + v.audio_data_rate = ctx->audio_data_rate; + v.audio_codec_id = ctx->audio_codec_id; + ngx_memcpy(v.profile, ctx->profile, sizeof(ctx->profile)); + ngx_memcpy(v.level, ctx->level, sizeof(ctx->level)); + + rc = ngx_rtmp_append_amf(s, &ctx->meta, NULL, out_elts, + sizeof(out_elts) / sizeof(out_elts[0])); + if (rc != NGX_OK || ctx->meta == NULL) { + return NGX_ERROR; + } + + return ngx_rtmp_codec_prepare_meta(s, 0); +} + + +static ngx_int_t +ngx_rtmp_codec_copy_meta(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_rtmp_codec_ctx_t *ctx; + ngx_rtmp_core_srv_conf_t *cscf; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + if (ctx->meta) { + ngx_rtmp_free_shared_chain(cscf, ctx->meta); + } + + ctx->meta = ngx_rtmp_append_shared_bufs(cscf, NULL, in); + + if (ctx->meta == NULL) { + return NGX_ERROR; + } + + return ngx_rtmp_codec_prepare_meta(s, h->timestamp); +} + + +static ngx_int_t +ngx_rtmp_codec_prepare_meta(ngx_rtmp_session_t *s, uint32_t timestamp) +{ + ngx_rtmp_header_t h; + ngx_rtmp_codec_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + ngx_memzero(&h, sizeof(h)); + h.csid = NGX_RTMP_CSID_AMF; + h.msid = NGX_RTMP_MSID; + h.type = NGX_RTMP_MSG_AMF_META; + h.timestamp = timestamp; + ngx_rtmp_prepare_message(s, &h, NULL, ctx->meta); + + ctx->meta_version = ngx_rtmp_codec_get_next_version(); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_codec_meta_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_rtmp_codec_app_conf_t *cacf; + ngx_rtmp_codec_ctx_t *ctx; + ngx_uint_t skip; + + static struct { + double width; + double height; + double duration; + double frame_rate; + double video_data_rate; + double video_codec_id_n; + u_char video_codec_id_s[32]; + double audio_data_rate; + double audio_codec_id_n; + u_char audio_codec_id_s[32]; + u_char profile[32]; + u_char level[32]; + } v; + + static ngx_rtmp_amf_elt_t in_video_codec_id[] = { + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &v.video_codec_id_n, 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + &v.video_codec_id_s, sizeof(v.video_codec_id_s) }, + }; + + static ngx_rtmp_amf_elt_t in_audio_codec_id[] = { + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &v.audio_codec_id_n, 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + &v.audio_codec_id_s, sizeof(v.audio_codec_id_s) }, + }; + + static ngx_rtmp_amf_elt_t in_inf[] = { + + { 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("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_VARIANT, + ngx_string("videocodecid"), + in_video_codec_id, sizeof(in_video_codec_id) }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("audiodatarate"), + &v.audio_data_rate, 0 }, + + { NGX_RTMP_AMF_VARIANT, + ngx_string("audiocodecid"), + in_audio_codec_id, sizeof(in_audio_codec_id) }, + + { 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 in_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + in_inf, sizeof(in_inf) }, + }; + + cacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_codec_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + if (ctx == NULL) { + ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_rtmp_codec_ctx_t)); + ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_codec_module); + } + + ngx_memzero(&v, sizeof(v)); + + /* use -1 as a sign of unchanged data; + * 0 is a valid value for uncompressed audio */ + v.audio_codec_id_n = -1; + + /* FFmpeg sends a string in front of actal 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, + sizeof(in_elts) / sizeof(in_elts[0]) - skip)) + { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "codec: error parsing data frame"); + 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 + ? 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)); + + ngx_log_debug8(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "codec: data frame: " + "width=%ui height=%ui duration=%ui frame_rate=%ui " + "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), + ctx->video_codec_id, + ngx_rtmp_get_audio_codec_name(ctx->audio_codec_id), + ctx->audio_codec_id); + + switch (cacf->meta) { + case NGX_RTMP_CODEC_META_ON: + return ngx_rtmp_codec_reconstruct_meta(s); + case NGX_RTMP_CODEC_META_COPY: + return ngx_rtmp_codec_copy_meta(s, h, in); + } + + /* NGX_RTMP_CODEC_META_OFF */ + + return NGX_OK; +} + + +static void * +ngx_rtmp_codec_create_app_conf(ngx_conf_t *cf) +{ + ngx_rtmp_codec_app_conf_t *cacf; + + cacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_codec_app_conf_t)); + if (cacf == NULL) { + return NULL; + } + + cacf->meta = NGX_CONF_UNSET_UINT; + + return cacf; +} + + +static char * +ngx_rtmp_codec_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_codec_app_conf_t *prev = parent; + ngx_rtmp_codec_app_conf_t *conf = child; + + ngx_conf_merge_uint_value(conf->meta, prev->meta, NGX_RTMP_CODEC_META_ON); + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_rtmp_codec_postconfiguration(ngx_conf_t *cf) +{ + ngx_rtmp_core_main_conf_t *cmcf; + ngx_rtmp_handler_pt *h; + ngx_rtmp_amf_handler_t *ch; + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + + h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]); + *h = ngx_rtmp_codec_av; + + h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]); + *h = ngx_rtmp_codec_av; + + h = ngx_array_push(&cmcf->events[NGX_RTMP_DISCONNECT]); + *h = ngx_rtmp_codec_disconnect; + + /* register metadata handler */ + ch = ngx_array_push(&cmcf->amf); + if (ch == NULL) { + return NGX_ERROR; + } + ngx_str_set(&ch->name, "@setDataFrame"); + ch->handler = ngx_rtmp_codec_meta_data; + + ch = ngx_array_push(&cmcf->amf); + if (ch == NULL) { + return NGX_ERROR; + } + ngx_str_set(&ch->name, "onMetaData"); + ch->handler = ngx_rtmp_codec_meta_data; + + + return NGX_OK; +} diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_codec_module.h nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_codec_module.h --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_codec_module.h 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_codec_module.h 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,87 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_CODEC_H_INCLUDED_ +#define _NGX_RTMP_CODEC_H_INCLUDED_ + + +#include +#include +#include "ngx_rtmp.h" + + +/* Audio codecs */ +enum { + /* Uncompressed codec id is actually 0, + * but we use another value for consistency */ + NGX_RTMP_AUDIO_UNCOMPRESSED = 16, + NGX_RTMP_AUDIO_ADPCM = 1, + NGX_RTMP_AUDIO_MP3 = 2, + NGX_RTMP_AUDIO_LINEAR_LE = 3, + NGX_RTMP_AUDIO_NELLY16 = 4, + NGX_RTMP_AUDIO_NELLY8 = 5, + NGX_RTMP_AUDIO_NELLY = 6, + NGX_RTMP_AUDIO_G711A = 7, + NGX_RTMP_AUDIO_G711U = 8, + NGX_RTMP_AUDIO_AAC = 10, + NGX_RTMP_AUDIO_SPEEX = 11, + NGX_RTMP_AUDIO_MP3_8 = 14, + NGX_RTMP_AUDIO_DEVSPEC = 15, +}; + + +/* Video codecs */ +enum { + NGX_RTMP_VIDEO_JPEG = 1, + NGX_RTMP_VIDEO_SORENSON_H263 = 2, + NGX_RTMP_VIDEO_SCREEN = 3, + NGX_RTMP_VIDEO_ON2_VP6 = 4, + NGX_RTMP_VIDEO_ON2_VP6_ALPHA = 5, + NGX_RTMP_VIDEO_SCREEN2 = 6, + NGX_RTMP_VIDEO_H264 = 7 +}; + + +u_char * ngx_rtmp_get_audio_codec_name(ngx_uint_t id); +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; + ngx_uint_t video_codec_id; + ngx_uint_t audio_data_rate; + ngx_uint_t audio_codec_id; + ngx_uint_t aac_profile; + ngx_uint_t aac_chan_conf; + ngx_uint_t aac_sbr; + ngx_uint_t aac_ps; + ngx_uint_t avc_profile; + ngx_uint_t avc_compat; + ngx_uint_t avc_level; + ngx_uint_t avc_nal_bytes; + ngx_uint_t avc_ref_frames; + ngx_uint_t sample_rate; /* 5512, 11025, 22050, 44100 */ + ngx_uint_t sample_size; /* 1=8bit, 2=16bit */ + ngx_uint_t audio_channels; /* 1, 2 */ + u_char profile[32]; + u_char level[32]; + + ngx_chain_t *avc_header; + ngx_chain_t *aac_header; + + ngx_chain_t *meta; + ngx_uint_t meta_version; +} ngx_rtmp_codec_ctx_t; + + +extern ngx_module_t ngx_rtmp_codec_module; + + +#endif /* _NGX_RTMP_LIVE_H_INCLUDED_ */ diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_control_module.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_control_module.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_control_module.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_control_module.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,732 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rtmp_live_module.h" +#include "ngx_rtmp_record_module.h" + + +static char *ngx_rtmp_control(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static void * ngx_rtmp_control_create_loc_conf(ngx_conf_t *cf); +static char * ngx_rtmp_control_merge_loc_conf(ngx_conf_t *cf, + void *parent, void *child); + + +typedef const char * (*ngx_rtmp_control_handler_t)(ngx_http_request_t *r, + ngx_rtmp_session_t *); + + +#define NGX_RTMP_CONTROL_ALL 0xff +#define NGX_RTMP_CONTROL_RECORD 0x01 +#define NGX_RTMP_CONTROL_DROP 0x02 +#define NGX_RTMP_CONTROL_REDIRECT 0x04 + + +enum { + NGX_RTMP_CONTROL_FILTER_CLIENT = 0, + NGX_RTMP_CONTROL_FILTER_PUBLISHER, + NGX_RTMP_CONTROL_FILTER_SUBSCRIBER +}; + + +typedef struct { + ngx_uint_t count; + ngx_str_t path; + ngx_uint_t filter; + ngx_str_t method; + ngx_array_t sessions; /* ngx_rtmp_session_t * */ +} ngx_rtmp_control_ctx_t; + + +typedef struct { + ngx_uint_t control; +} ngx_rtmp_control_loc_conf_t; + + +static ngx_conf_bitmask_t ngx_rtmp_control_masks[] = { + { ngx_string("all"), NGX_RTMP_CONTROL_ALL }, + { ngx_string("record"), NGX_RTMP_CONTROL_RECORD }, + { ngx_string("drop"), NGX_RTMP_CONTROL_DROP }, + { ngx_string("redirect"), NGX_RTMP_CONTROL_REDIRECT }, + { ngx_null_string, 0 } +}; + + +static ngx_command_t ngx_rtmp_control_commands[] = { + + { ngx_string("rtmp_control"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, + ngx_rtmp_control, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_rtmp_control_loc_conf_t, control), + ngx_rtmp_control_masks }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_rtmp_control_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + ngx_rtmp_control_create_loc_conf, /* create location configuration */ + ngx_rtmp_control_merge_loc_conf, /* merge location configuration */ +}; + + +ngx_module_t ngx_rtmp_control_module = { + NGX_MODULE_V1, + &ngx_rtmp_control_module_ctx, /* module context */ + ngx_rtmp_control_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static const char * +ngx_rtmp_control_record_handler(ngx_http_request_t *r, ngx_rtmp_session_t *s) +{ + ngx_int_t rc; + ngx_str_t rec; + ngx_uint_t rn; + ngx_rtmp_control_ctx_t *ctx; + ngx_rtmp_core_app_conf_t *cacf; + ngx_rtmp_record_app_conf_t *racf; + + cacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_core_module); + racf = cacf->app_conf[ngx_rtmp_record_module.ctx_index]; + + if (ngx_http_arg(r, (u_char *) "rec", sizeof("rec") - 1, &rec) != NGX_OK) { + rec.len = 0; + } + + rn = ngx_rtmp_record_find(racf, &rec); + if (rn == NGX_CONF_UNSET_UINT) { + return "Recorder not found"; + } + + ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module); + + if (ctx->method.len == sizeof("start") - 1 && + ngx_strncmp(ctx->method.data, "start", ctx->method.len) == 0) + { + rc = ngx_rtmp_record_open(s, rn, &ctx->path); + + } else if (ctx->method.len == sizeof("stop") - 1 && + ngx_strncmp(ctx->method.data, "stop", ctx->method.len) == 0) + { + rc = ngx_rtmp_record_close(s, rn, &ctx->path); + + } else { + return "Undefined method"; + } + + if (rc == NGX_ERROR) { + return "Recorder error"; + } + + return NGX_CONF_OK; +} + + +static const char * +ngx_rtmp_control_drop_handler(ngx_http_request_t *r, ngx_rtmp_session_t *s) +{ + ngx_rtmp_control_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module); + + ngx_rtmp_finalize_session(s); + + ++ctx->count; + + return NGX_CONF_OK; +} + + +static const char * +ngx_rtmp_control_redirect_handler(ngx_http_request_t *r, ngx_rtmp_session_t *s) +{ + ngx_str_t name; + ngx_rtmp_play_t vplay; + ngx_rtmp_publish_t vpublish; + ngx_rtmp_live_ctx_t *lctx; + ngx_rtmp_control_ctx_t *ctx; + ngx_rtmp_close_stream_t vc; + + if (ngx_http_arg(r, (u_char *) "newname", sizeof("newname") - 1, &name) + != NGX_OK) + { + return "newname not specified"; + } + + if (name.len >= NGX_RTMP_MAX_NAME) { + name.len = NGX_RTMP_MAX_NAME - 1; + } + + ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module); + ctx->count++; + + ngx_memzero(&vc, sizeof(ngx_rtmp_close_stream_t)); + + /* close_stream should be synchronous */ + ngx_rtmp_close_stream(s, &vc); + + lctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module); + + if (lctx && lctx->publishing) { + /* publish */ + + ngx_memzero(&vpublish, sizeof(ngx_rtmp_publish_t)); + + ngx_memcpy(vpublish.name, name.data, name.len); + + ngx_rtmp_cmd_fill_args(vpublish.name, vpublish.args); + + if (ngx_rtmp_publish(s, &vpublish) != NGX_OK) { + return "publish failed"; + } + + } else { + /* play */ + + ngx_memzero(&vplay, sizeof(ngx_rtmp_play_t)); + + ngx_memcpy(vplay.name, name.data, name.len); + + ngx_rtmp_cmd_fill_args(vplay.name, vplay.args); + + if (ngx_rtmp_play(s, &vplay) != NGX_OK) { + return "play failed"; + } + } + + return NGX_CONF_OK; +} + + +static const char * +ngx_rtmp_control_walk_session(ngx_http_request_t *r, + ngx_rtmp_live_ctx_t *lctx) +{ + ngx_str_t addr, *paddr, clientid; + ngx_rtmp_session_t *s, **ss; + ngx_rtmp_control_ctx_t *ctx; + + s = lctx->session; + + if (s == NULL || s->connection == NULL) { + return NGX_CONF_OK; + } + + if (ngx_http_arg(r, (u_char *) "addr", sizeof("addr") - 1, &addr) + == NGX_OK) + { + paddr = &s->connection->addr_text; + if (paddr->len != addr.len || + ngx_strncmp(paddr->data, addr.data, addr.len)) + { + return NGX_CONF_OK; + } + } + + if (ngx_http_arg(r, (u_char *) "clientid", sizeof("clientid") - 1, + &clientid) + == NGX_OK) + { + if (s->connection->number != + (ngx_uint_t) ngx_atoi(clientid.data, clientid.len)) + { + return NGX_CONF_OK; + } + } + + ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module); + + switch (ctx->filter) { + case NGX_RTMP_CONTROL_FILTER_PUBLISHER: + if (!lctx->publishing) { + return NGX_CONF_OK; + } + break; + + case NGX_RTMP_CONTROL_FILTER_SUBSCRIBER: + if (lctx->publishing) { + return NGX_CONF_OK; + } + break; + + case NGX_RTMP_CONTROL_FILTER_CLIENT: + break; + } + + ss = ngx_array_push(&ctx->sessions); + if (ss == NULL) { + return "allocation error"; + } + + *ss = s; + + return NGX_CONF_OK; +} + + +static const char * +ngx_rtmp_control_walk_stream(ngx_http_request_t *r, + ngx_rtmp_live_stream_t *ls) +{ + const char *s; + ngx_rtmp_live_ctx_t *lctx; + + for (lctx = ls->ctx; lctx; lctx = lctx->next) { + s = ngx_rtmp_control_walk_session(r, lctx); + if (s != NGX_CONF_OK) { + return s; + } + } + + return NGX_CONF_OK; +} + + +static const char * +ngx_rtmp_control_walk_app(ngx_http_request_t *r, + ngx_rtmp_core_app_conf_t *cacf) +{ + size_t len; + ngx_str_t name; + const char *s; + ngx_uint_t n; + ngx_rtmp_live_stream_t *ls; + ngx_rtmp_live_app_conf_t *lacf; + + lacf = cacf->app_conf[ngx_rtmp_live_module.ctx_index]; + + if (ngx_http_arg(r, (u_char *) "name", sizeof("name") - 1, &name) != NGX_OK) + { + for (n = 0; n < (ngx_uint_t) lacf->nbuckets; ++n) { + for (ls = lacf->streams[n]; ls; ls = ls->next) { + s = ngx_rtmp_control_walk_stream(r, ls); + if (s != NGX_CONF_OK) { + return s; + } + } + } + + return NGX_CONF_OK; + } + + for (ls = lacf->streams[ngx_hash_key(name.data, name.len) % lacf->nbuckets]; + ls; ls = ls->next) + { + len = ngx_strlen(ls->name); + if (name.len != len || ngx_strncmp(name.data, ls->name, name.len)) { + continue; + } + + s = ngx_rtmp_control_walk_stream(r, ls); + if (s != NGX_CONF_OK) { + return s; + } + } + + return NGX_CONF_OK; +} + + +static const char * +ngx_rtmp_control_walk_server(ngx_http_request_t *r, + ngx_rtmp_core_srv_conf_t *cscf) +{ + ngx_str_t app; + ngx_uint_t n; + const char *s; + ngx_rtmp_core_app_conf_t **pcacf; + + if (ngx_http_arg(r, (u_char *) "app", sizeof("app") - 1, &app) != NGX_OK) { + app.len = 0; + } + + pcacf = cscf->applications.elts; + + for (n = 0; n < cscf->applications.nelts; ++n, ++pcacf) { + if (app.len && ((*pcacf)->name.len != app.len || + ngx_strncmp((*pcacf)->name.data, app.data, app.len))) + { + continue; + } + + s = ngx_rtmp_control_walk_app(r, *pcacf); + if (s != NGX_CONF_OK) { + return s; + } + } + + return NGX_CONF_OK; +} + + +static const char * +ngx_rtmp_control_walk(ngx_http_request_t *r, ngx_rtmp_control_handler_t h) +{ + ngx_rtmp_core_main_conf_t *cmcf = ngx_rtmp_core_main_conf; + + ngx_str_t srv; + ngx_uint_t sn, n; + const char *msg; + ngx_rtmp_session_t **s; + ngx_rtmp_control_ctx_t *ctx; + ngx_rtmp_core_srv_conf_t **pcscf; + + sn = 0; + if (ngx_http_arg(r, (u_char *) "srv", sizeof("srv") - 1, &srv) == NGX_OK) { + sn = ngx_atoi(srv.data, srv.len); + } + + if (sn >= cmcf->servers.nelts) { + return "Server index out of range"; + } + + pcscf = cmcf->servers.elts; + pcscf += sn; + + msg = ngx_rtmp_control_walk_server(r, *pcscf); + if (msg != NGX_CONF_OK) { + return msg; + } + + ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module); + + s = ctx->sessions.elts; + for (n = 0; n < ctx->sessions.nelts; n++) { + msg = h(r, s[n]); + if (msg != NGX_CONF_OK) { + return msg; + } + } + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_rtmp_control_record(ngx_http_request_t *r, ngx_str_t *method) +{ + ngx_buf_t *b; + const char *msg; + ngx_chain_t cl; + ngx_rtmp_control_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module); + ctx->filter = NGX_RTMP_CONTROL_FILTER_PUBLISHER; + + msg = ngx_rtmp_control_walk(r, ngx_rtmp_control_record_handler); + if (msg != NGX_CONF_OK) { + goto error; + } + + if (ctx->path.len == 0) { + return NGX_HTTP_NO_CONTENT; + } + + /* output record path */ + + r->headers_out.status = NGX_HTTP_OK; + r->headers_out.content_length_n = ctx->path.len; + + b = ngx_create_temp_buf(r->pool, ctx->path.len); + if (b == NULL) { + goto error; + } + + ngx_memzero(&cl, sizeof(cl)); + cl.buf = b; + + b->last = ngx_cpymem(b->pos, ctx->path.data, ctx->path.len); + b->last_buf = 1; + + ngx_http_send_header(r); + + return ngx_http_output_filter(r, &cl); + +error: + return NGX_HTTP_INTERNAL_SERVER_ERROR; +} + + +static ngx_int_t +ngx_rtmp_control_drop(ngx_http_request_t *r, ngx_str_t *method) +{ + size_t len; + u_char *p; + ngx_buf_t *b; + ngx_chain_t cl; + const char *msg; + ngx_rtmp_control_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module); + + if (ctx->method.len == sizeof("publisher") - 1 && + ngx_memcmp(ctx->method.data, "publisher", ctx->method.len) == 0) + { + ctx->filter = NGX_RTMP_CONTROL_FILTER_PUBLISHER; + + } else if (ctx->method.len == sizeof("subscriber") - 1 && + ngx_memcmp(ctx->method.data, "subscriber", ctx->method.len) + == 0) + { + ctx->filter = NGX_RTMP_CONTROL_FILTER_SUBSCRIBER; + + } else if (method->len == sizeof("client") - 1 && + ngx_memcmp(ctx->method.data, "client", ctx->method.len) == 0) + { + ctx->filter = NGX_RTMP_CONTROL_FILTER_CLIENT; + + } else { + msg = "Undefined filter"; + goto error; + } + + msg = ngx_rtmp_control_walk(r, ngx_rtmp_control_drop_handler); + if (msg != NGX_CONF_OK) { + goto error; + } + + /* output count */ + + len = NGX_INT_T_LEN; + + p = ngx_palloc(r->connection->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + len = (size_t) (ngx_snprintf(p, len, "%ui", ctx->count) - p); + + r->headers_out.status = NGX_HTTP_OK; + r->headers_out.content_length_n = len; + + b = ngx_calloc_buf(r->pool); + if (b == NULL) { + goto error; + } + + b->start = b->pos = p; + b->end = b->last = p + len; + b->temporary = 1; + b->last_buf = 1; + + ngx_memzero(&cl, sizeof(cl)); + cl.buf = b; + + ngx_http_send_header(r); + + return ngx_http_output_filter(r, &cl); + +error: + return NGX_HTTP_INTERNAL_SERVER_ERROR; +} + + +static ngx_int_t +ngx_rtmp_control_redirect(ngx_http_request_t *r, ngx_str_t *method) +{ + size_t len; + u_char *p; + ngx_buf_t *b; + ngx_chain_t cl; + const char *msg; + ngx_rtmp_control_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module); + + if (ctx->method.len == sizeof("publisher") - 1 && + ngx_memcmp(ctx->method.data, "publisher", ctx->method.len) == 0) + { + ctx->filter = NGX_RTMP_CONTROL_FILTER_PUBLISHER; + + } else if (ctx->method.len == sizeof("subscriber") - 1 && + ngx_memcmp(ctx->method.data, "subscriber", ctx->method.len) + == 0) + { + ctx->filter = NGX_RTMP_CONTROL_FILTER_SUBSCRIBER; + + } else if (ctx->method.len == sizeof("client") - 1 && + ngx_memcmp(ctx->method.data, "client", ctx->method.len) == 0) + { + ctx->filter = NGX_RTMP_CONTROL_FILTER_CLIENT; + + } else { + msg = "Undefined filter"; + goto error; + } + + msg = ngx_rtmp_control_walk(r, ngx_rtmp_control_redirect_handler); + if (msg != NGX_CONF_OK) { + goto error; + } + + /* output count */ + + len = NGX_INT_T_LEN; + + p = ngx_palloc(r->connection->pool, len); + if (p == NULL) { + goto error; + } + + len = (size_t) (ngx_snprintf(p, len, "%ui", ctx->count) - p); + + r->headers_out.status = NGX_HTTP_OK; + r->headers_out.content_length_n = len; + + b = ngx_calloc_buf(r->pool); + if (b == NULL) { + goto error; + } + + b->start = b->pos = p; + b->end = b->last = p + len; + b->temporary = 1; + b->last_buf = 1; + + ngx_memzero(&cl, sizeof(cl)); + cl.buf = b; + + ngx_http_send_header(r); + + return ngx_http_output_filter(r, &cl); + +error: + return NGX_HTTP_INTERNAL_SERVER_ERROR; +} + + +static ngx_int_t +ngx_rtmp_control_handler(ngx_http_request_t *r) +{ + u_char *p; + ngx_str_t section, method; + ngx_uint_t n; + ngx_rtmp_control_ctx_t *ctx; + ngx_rtmp_control_loc_conf_t *llcf; + + llcf = ngx_http_get_module_loc_conf(r, ngx_rtmp_control_module); + if (llcf->control == 0) { + return NGX_DECLINED; + } + + /* uri format: .../section/method?args */ + + ngx_str_null(§ion); + ngx_str_null(&method); + + for (n = r->uri.len; n; --n) { + p = &r->uri.data[n - 1]; + + if (*p != '/') { + continue; + } + + if (method.data) { + section.data = p + 1; + section.len = method.data - section.data - 1; + break; + } + + method.data = p + 1; + method.len = r->uri.data + r->uri.len - method.data; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, r->connection->log, 0, + "rtmp_control: section='%V' method='%V'", + §ion, &method); + + ctx = ngx_pcalloc(r->pool, sizeof(ngx_rtmp_control_ctx_t)); + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_http_set_ctx(r, ctx, ngx_rtmp_control_module); + + if (ngx_array_init(&ctx->sessions, r->pool, 1, sizeof(void *)) != NGX_OK) { + return NGX_ERROR; + } + + ctx->method = method; + +#define NGX_RTMP_CONTROL_SECTION(flag, secname) \ + if (llcf->control & NGX_RTMP_CONTROL_##flag && \ + section.len == sizeof(#secname) - 1 && \ + ngx_strncmp(section.data, #secname, sizeof(#secname) - 1) == 0) \ + { \ + return ngx_rtmp_control_##secname(r, &method); \ + } + + NGX_RTMP_CONTROL_SECTION(RECORD, record); + NGX_RTMP_CONTROL_SECTION(DROP, drop); + NGX_RTMP_CONTROL_SECTION(REDIRECT, redirect); + +#undef NGX_RTMP_CONTROL_SECTION + + return NGX_DECLINED; +} + + +static void * +ngx_rtmp_control_create_loc_conf(ngx_conf_t *cf) +{ + ngx_rtmp_control_loc_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_control_loc_conf_t)); + if (conf == NULL) { + return NULL; + } + + conf->control = 0; + + return conf; +} + + +static char * +ngx_rtmp_control_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_control_loc_conf_t *prev = parent; + ngx_rtmp_control_loc_conf_t *conf = child; + + ngx_conf_merge_bitmask_value(conf->control, prev->control, 0); + + return NGX_CONF_OK; +} + + +static char * +ngx_rtmp_control(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_core_loc_conf_t *clcf; + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_rtmp_control_handler; + + return ngx_conf_set_bitmask_slot(cf, cmd, conf); +} diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_core_module.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_core_module.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_core_module.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_core_module.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,733 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include +#include +#include "ngx_rtmp.h" + + +static void *ngx_rtmp_core_create_main_conf(ngx_conf_t *cf); +static void *ngx_rtmp_core_create_srv_conf(ngx_conf_t *cf); +static char *ngx_rtmp_core_merge_srv_conf(ngx_conf_t *cf, void *parent, + void *child); +static void *ngx_rtmp_core_create_app_conf(ngx_conf_t *cf); +static char *ngx_rtmp_core_merge_app_conf(ngx_conf_t *cf, void *parent, + void *child); +static char *ngx_rtmp_core_server(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_rtmp_core_application(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + + +ngx_rtmp_core_main_conf_t *ngx_rtmp_core_main_conf; + + +static ngx_conf_deprecated_t ngx_conf_deprecated_so_keepalive = { + ngx_conf_deprecated, "so_keepalive", + "so_keepalive\" parameter of the \"listen" +}; + + +static ngx_command_t ngx_rtmp_core_commands[] = { + + { ngx_string("server"), + NGX_RTMP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, + ngx_rtmp_core_server, + 0, + 0, + NULL }, + + { ngx_string("listen"), + NGX_RTMP_SRV_CONF|NGX_CONF_TAKE12, + ngx_rtmp_core_listen, + NGX_RTMP_SRV_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("application"), + NGX_RTMP_SRV_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE1, + ngx_rtmp_core_application, + NGX_RTMP_SRV_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("so_keepalive"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, so_keepalive), + &ngx_conf_deprecated_so_keepalive }, + + { ngx_string("timeout"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, timeout), + NULL }, + + { ngx_string("ping"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, ping), + NULL }, + + { ngx_string("ping_timeout"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, ping_timeout), + NULL }, + + { ngx_string("max_streams"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, max_streams), + NULL }, + + { ngx_string("ack_window"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, ack_window), + NULL }, + + { ngx_string("chunk_size"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, chunk_size), + NULL }, + + { ngx_string("max_message"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, max_message), + NULL }, + + { ngx_string("out_queue"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, out_queue), + NULL }, + + { ngx_string("out_cork"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, out_cork), + NULL }, + + { ngx_string("busy"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, busy), + NULL }, + + /* time fixes are needed for flash clients */ + { ngx_string("play_time_fix"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, play_time_fix), + NULL }, + + { ngx_string("publish_time_fix"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, publish_time_fix), + NULL }, + + { ngx_string("buflen"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_core_srv_conf_t, buflen), + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_rtmp_core_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + ngx_rtmp_core_create_main_conf, /* create main configuration */ + NULL, /* init main configuration */ + ngx_rtmp_core_create_srv_conf, /* create server configuration */ + ngx_rtmp_core_merge_srv_conf, /* merge server configuration */ + ngx_rtmp_core_create_app_conf, /* create app configuration */ + ngx_rtmp_core_merge_app_conf /* merge app configuration */ +}; + + +ngx_module_t ngx_rtmp_core_module = { + NGX_MODULE_V1, + &ngx_rtmp_core_module_ctx, /* module context */ + ngx_rtmp_core_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_rtmp_core_create_main_conf(ngx_conf_t *cf) +{ + ngx_rtmp_core_main_conf_t *cmcf; + + cmcf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_core_main_conf_t)); + if (cmcf == NULL) { + return NULL; + } + + ngx_rtmp_core_main_conf = cmcf; + + if (ngx_array_init(&cmcf->servers, cf->pool, 4, + sizeof(ngx_rtmp_core_srv_conf_t *)) + != NGX_OK) + { + return NULL; + } + + if (ngx_array_init(&cmcf->listen, cf->pool, 4, sizeof(ngx_rtmp_listen_t)) + != NGX_OK) + { + return NULL; + } + + return cmcf; +} + + +static void * +ngx_rtmp_core_create_srv_conf(ngx_conf_t *cf) +{ + ngx_rtmp_core_srv_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_core_srv_conf_t)); + if (conf == NULL) { + return NULL; + } + + if (ngx_array_init(&conf->applications, cf->pool, 4, + sizeof(ngx_rtmp_core_app_conf_t *)) + != NGX_OK) + { + return NULL; + } + + conf->timeout = NGX_CONF_UNSET_MSEC; + conf->ping = NGX_CONF_UNSET_MSEC; + conf->ping_timeout = NGX_CONF_UNSET_MSEC; + conf->so_keepalive = NGX_CONF_UNSET; + conf->max_streams = NGX_CONF_UNSET; + conf->chunk_size = NGX_CONF_UNSET; + conf->ack_window = NGX_CONF_UNSET_UINT; + conf->max_message = NGX_CONF_UNSET_SIZE; + conf->out_queue = NGX_CONF_UNSET_SIZE; + conf->out_cork = NGX_CONF_UNSET_SIZE; + conf->play_time_fix = NGX_CONF_UNSET; + conf->publish_time_fix = NGX_CONF_UNSET; + conf->buflen = NGX_CONF_UNSET_MSEC; + conf->busy = NGX_CONF_UNSET; + + return conf; +} + + +static char * +ngx_rtmp_core_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_core_srv_conf_t *prev = parent; + ngx_rtmp_core_srv_conf_t *conf = child; + + ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000); + ngx_conf_merge_msec_value(conf->ping, prev->ping, 60000); + ngx_conf_merge_msec_value(conf->ping_timeout, prev->ping_timeout, 30000); + + ngx_conf_merge_value(conf->so_keepalive, prev->so_keepalive, 0); + ngx_conf_merge_value(conf->max_streams, prev->max_streams, 32); + ngx_conf_merge_value(conf->chunk_size, prev->chunk_size, 4096); + ngx_conf_merge_uint_value(conf->ack_window, prev->ack_window, 5000000); + ngx_conf_merge_size_value(conf->max_message, prev->max_message, + 1 * 1024 * 1024); + ngx_conf_merge_size_value(conf->out_queue, prev->out_queue, 256); + ngx_conf_merge_size_value(conf->out_cork, prev->out_cork, + conf->out_queue / 8); + ngx_conf_merge_value(conf->play_time_fix, prev->play_time_fix, 1); + ngx_conf_merge_value(conf->publish_time_fix, prev->publish_time_fix, 1); + ngx_conf_merge_msec_value(conf->buflen, prev->buflen, 1000); + ngx_conf_merge_value(conf->busy, prev->busy, 0); + + if (prev->pool == NULL) { + prev->pool = ngx_create_pool(4096, &cf->cycle->new_log); + if (prev->pool == NULL) { + return NGX_CONF_ERROR; + } + } + + conf->pool = prev->pool; + + return NGX_CONF_OK; +} + + +static void * +ngx_rtmp_core_create_app_conf(ngx_conf_t *cf) +{ + ngx_rtmp_core_app_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_core_app_conf_t)); + if (conf == NULL) { + return NULL; + } + + if (ngx_array_init(&conf->applications, cf->pool, 1, + sizeof(ngx_rtmp_core_app_conf_t *)) + != NGX_OK) + { + return NULL; + } + + return conf; +} + + +static char * +ngx_rtmp_core_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_core_app_conf_t *prev = parent; + ngx_rtmp_core_app_conf_t *conf = child; + + (void)prev; + (void)conf; + + return NGX_CONF_OK; +} + + +static char * +ngx_rtmp_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + char *rv; + void *mconf; + ngx_uint_t m; + ngx_conf_t pcf; + ngx_rtmp_module_t *module; + ngx_rtmp_conf_ctx_t *ctx, *rtmp_ctx; + ngx_rtmp_core_srv_conf_t *cscf, **cscfp; + ngx_rtmp_core_main_conf_t *cmcf; + + ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_conf_ctx_t)); + if (ctx == NULL) { + return NGX_CONF_ERROR; + } + + rtmp_ctx = cf->ctx; + ctx->main_conf = rtmp_ctx->main_conf; + + /* the server{}'s srv_conf */ + + ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module); + if (ctx->srv_conf == NULL) { + return NGX_CONF_ERROR; + } + + ctx->app_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module); + if (ctx->app_conf == NULL) { + return NGX_CONF_ERROR; + } + + for (m = 0; ngx_modules[m]; m++) { + if (ngx_modules[m]->type != NGX_RTMP_MODULE) { + continue; + } + + module = ngx_modules[m]->ctx; + + if (module->create_srv_conf) { + mconf = module->create_srv_conf(cf); + if (mconf == NULL) { + return NGX_CONF_ERROR; + } + + ctx->srv_conf[ngx_modules[m]->ctx_index] = mconf; + } + + if (module->create_app_conf) { + mconf = module->create_app_conf(cf); + if (mconf == NULL) { + return NGX_CONF_ERROR; + } + + ctx->app_conf[ngx_modules[m]->ctx_index] = mconf; + } + } + + /* the server configuration context */ + + cscf = ctx->srv_conf[ngx_rtmp_core_module.ctx_index]; + cscf->ctx = ctx; + + cmcf = ctx->main_conf[ngx_rtmp_core_module.ctx_index]; + + cscfp = ngx_array_push(&cmcf->servers); + if (cscfp == NULL) { + return NGX_CONF_ERROR; + } + + *cscfp = cscf; + + + /* parse inside server{} */ + + pcf = *cf; + cf->ctx = ctx; + cf->cmd_type = NGX_RTMP_SRV_CONF; + + rv = ngx_conf_parse(cf, NULL); + + *cf = pcf; + + return rv; +} + + +static char * +ngx_rtmp_core_application(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + char *rv; + ngx_int_t i; + ngx_str_t *value; + ngx_conf_t save; + ngx_rtmp_module_t *module; + ngx_rtmp_conf_ctx_t *ctx, *pctx; + ngx_rtmp_core_srv_conf_t *cscf; + ngx_rtmp_core_app_conf_t *cacf, **cacfp; + + ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_conf_ctx_t)); + if (ctx == NULL) { + return NGX_CONF_ERROR; + } + + pctx = cf->ctx; + ctx->main_conf = pctx->main_conf; + ctx->srv_conf = pctx->srv_conf; + + ctx->app_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module); + if (ctx->app_conf == NULL) { + return NGX_CONF_ERROR; + } + + for (i = 0; ngx_modules[i]; i++) { + if (ngx_modules[i]->type != NGX_RTMP_MODULE) { + continue; + } + + module = ngx_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) { + return NGX_CONF_ERROR; + } + } + } + + cacf = ctx->app_conf[ngx_rtmp_core_module.ctx_index]; + cacf->app_conf = ctx->app_conf; + + value = cf->args->elts; + + cacf->name = value[1]; + cscf = pctx->srv_conf[ngx_rtmp_core_module.ctx_index]; + + cacfp = ngx_array_push(&cscf->applications); + if (cacfp == NULL) { + return NGX_CONF_ERROR; + } + + *cacfp = cacf; + + save = *cf; + cf->ctx = ctx; + cf->cmd_type = NGX_RTMP_APP_CONF; + + rv = ngx_conf_parse(cf, NULL); + + *cf= save; + + return rv; +} + + +static char * +ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + size_t len, off; + in_port_t port; + ngx_str_t *value; + ngx_url_t u; + ngx_uint_t i, m; + struct sockaddr *sa; + ngx_rtmp_listen_t *ls; + struct sockaddr_in *sin; + ngx_rtmp_core_main_conf_t *cmcf; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; +#endif + + value = cf->args->elts; + + ngx_memzero(&u, sizeof(ngx_url_t)); + + u.url = value[1]; + u.listen = 1; + + if (ngx_parse_url(cf->pool, &u) != NGX_OK) { + if (u.err) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "%s in \"%V\" of the \"listen\" directive", + u.err, &u.url); + } + + return NGX_CONF_ERROR; + } + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + + ls = cmcf->listen.elts; + + for (i = 0; i < cmcf->listen.nelts; i++) { + + sa = (struct sockaddr *) ls[i].sockaddr; + + if (sa->sa_family != u.family) { + continue; + } + + switch (sa->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + off = offsetof(struct sockaddr_in6, sin6_addr); + len = 16; + sin6 = (struct sockaddr_in6 *) sa; + port = sin6->sin6_port; + break; +#endif + + default: /* AF_INET */ + off = offsetof(struct sockaddr_in, sin_addr); + len = 4; + sin = (struct sockaddr_in *) sa; + port = sin->sin_port; + break; + } + + if (ngx_memcmp(ls[i].sockaddr + off, u.sockaddr + off, len) != 0) { + continue; + } + + if (port != u.port) { + continue; + } + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "duplicate \"%V\" address and port pair", &u.url); + return NGX_CONF_ERROR; + } + + ls = ngx_array_push(&cmcf->listen); + if (ls == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memzero(ls, sizeof(ngx_rtmp_listen_t)); + + ngx_memcpy(ls->sockaddr, u.sockaddr, u.socklen); + + 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) { + ls->bind = 1; + continue; + } + + 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; + + if (sa->sa_family == AF_INET6) { + + if (ngx_strcmp(&value[i].data[10], "n") == 0) { + ls->ipv6only = 1; + + } else if (ngx_strcmp(&value[i].data[10], "ff") == 0) { + ls->ipv6only = 0; + + } else { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid ipv6only flags \"%s\"", + &value[i].data[9]); + return NGX_CONF_ERROR; + } + + ls->bind = 1; + + } else { + len = ngx_sock_ntop(sa, +#if (nginx_version >= 1005003) + ls->socklen, +#endif + buf, NGX_SOCKADDR_STRLEN, 1); + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "ipv6only is not supported " + "on addr \"%*s\", ignored", len, buf); + } + + continue; +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "bind ipv6only is not supported " + "on this platform"); + return NGX_CONF_ERROR; +#endif + } + + if (ngx_strncmp(value[i].data, "so_keepalive=", 13) == 0) { + + if (ngx_strcmp(&value[i].data[13], "on") == 0) { + ls->so_keepalive = 1; + + } else if (ngx_strcmp(&value[i].data[13], "off") == 0) { + ls->so_keepalive = 2; + + } else { + +#if (NGX_HAVE_KEEPALIVE_TUNABLE) + u_char *p, *end; + ngx_str_t s; + + end = value[i].data + value[i].len; + s.data = value[i].data + 13; + + p = ngx_strlchr(s.data, end, ':'); + if (p == NULL) { + p = end; + } + + if (p > s.data) { + s.len = p - s.data; + + ls->tcp_keepidle = ngx_parse_time(&s, 1); + if (ls->tcp_keepidle == (time_t) NGX_ERROR) { + goto invalid_so_keepalive; + } + } + + s.data = (p < end) ? (p + 1) : end; + + p = ngx_strlchr(s.data, end, ':'); + if (p == NULL) { + p = end; + } + + if (p > s.data) { + s.len = p - s.data; + + ls->tcp_keepintvl = ngx_parse_time(&s, 1); + if (ls->tcp_keepintvl == (time_t) NGX_ERROR) { + goto invalid_so_keepalive; + } + } + + s.data = (p < end) ? (p + 1) : end; + + if (s.data < end) { + s.len = end - s.data; + + ls->tcp_keepcnt = ngx_atoi(s.data, s.len); + if (ls->tcp_keepcnt == NGX_ERROR) { + goto invalid_so_keepalive; + } + } + + if (ls->tcp_keepidle == 0 && ls->tcp_keepintvl == 0 + && ls->tcp_keepcnt == 0) + { + goto invalid_so_keepalive; + } + + ls->so_keepalive = 1; + +#else + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the \"so_keepalive\" parameter accepts " + "only \"on\" or \"off\" on this platform"); + return NGX_CONF_ERROR; + +#endif + } + + ls->bind = 1; + + continue; + +#if (NGX_HAVE_KEEPALIVE_TUNABLE) + invalid_so_keepalive: + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid so_keepalive value: \"%s\"", + &value[i].data[13]); + return NGX_CONF_ERROR; +#endif + } + + if (ngx_strcmp(value[i].data, "proxy_protocol") == 0) { + ls->proxy_protocol = 1; + continue; + } + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the invalid \"%V\" parameter", &value[i]); + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_eval.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_eval.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_eval.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_eval.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,282 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp_eval.h" + + +#define NGX_RTMP_EVAL_BUFLEN 16 + + +static void +ngx_rtmp_eval_session_str(void *ctx, ngx_rtmp_eval_t *e, ngx_str_t *ret) +{ + *ret = *(ngx_str_t *) ((u_char *) ctx + e->offset); +} + + +static void +ngx_rtmp_eval_connection_str(void *ctx, ngx_rtmp_eval_t *e, ngx_str_t *ret) +{ + ngx_rtmp_session_t *s = ctx; + + *ret = *(ngx_str_t *) ((u_char *) s->connection + e->offset); +} + + +ngx_rtmp_eval_t ngx_rtmp_eval_session[] = { + + { ngx_string("app"), + ngx_rtmp_eval_session_str, + offsetof(ngx_rtmp_session_t, app) }, + + { ngx_string("flashver"), + ngx_rtmp_eval_session_str, + offsetof(ngx_rtmp_session_t, flashver) }, + + { ngx_string("swfurl"), + ngx_rtmp_eval_session_str, + offsetof(ngx_rtmp_session_t, swf_url) }, + + { ngx_string("tcurl"), + ngx_rtmp_eval_session_str, + offsetof(ngx_rtmp_session_t, tc_url) }, + + { ngx_string("pageurl"), + ngx_rtmp_eval_session_str, + offsetof(ngx_rtmp_session_t, page_url) }, + + { ngx_string("addr"), + ngx_rtmp_eval_connection_str, + offsetof(ngx_connection_t, addr_text) }, + + ngx_rtmp_null_eval +}; + + +static void +ngx_rtmp_eval_append(ngx_buf_t *b, void *data, size_t len, ngx_log_t *log) +{ + size_t buf_len; + + if (b->last + len > b->end) { + buf_len = 2 * (b->last - b->pos) + len; + + b->start = ngx_alloc(buf_len, log); + if (b->start == NULL) { + return; + } + + b->last = ngx_cpymem(b->start, b->pos, b->last - b->pos); + b->pos = b->start; + b->end = b->start + buf_len; + } + + b->last = ngx_cpymem(b->last, data, len); +} + + +static void +ngx_rtmp_eval_append_var(void *ctx, ngx_buf_t *b, ngx_rtmp_eval_t **e, + ngx_str_t *name, ngx_log_t *log) +{ + ngx_uint_t k; + ngx_str_t v; + ngx_rtmp_eval_t *ee; + + for (; *e; ++e) { + for (k = 0, ee = *e; ee->handler; ++k, ++ee) { + if (ee->name.len == name->len && + ngx_memcmp(ee->name.data, name->data, name->len) == 0) + { + ee->handler(ctx, ee, &v); + ngx_rtmp_eval_append(b, v.data, v.len, log); + } + } + } +} + + +ngx_int_t +ngx_rtmp_eval(void *ctx, ngx_str_t *in, ngx_rtmp_eval_t **e, ngx_str_t *out, + ngx_log_t *log) +{ + u_char c, *p; + ngx_str_t name; + ngx_buf_t b; + ngx_uint_t n; + + enum { + NORMAL, + ESCAPE, + NAME, + SNAME + } state = NORMAL; + + b.pos = b.last = b.start = ngx_alloc(NGX_RTMP_EVAL_BUFLEN, log); + if (b.pos == NULL) { + return NGX_ERROR; + } + + b.end = b.pos + NGX_RTMP_EVAL_BUFLEN; + name.data = NULL; + + for (n = 0; n < in->len; ++n) { + p = &in->data[n]; + c = *p; + + switch (state) { + case SNAME: + if (c != '}') { + continue; + } + + name.len = p - name.data; + ngx_rtmp_eval_append_var(ctx, &b, e, &name, log); + + state = NORMAL; + + continue; + + case NAME: + if (c == '{' && name.data == p) { + ++name.data; + state = SNAME; + continue; + } + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + continue; + } + + name.len = p - name.data; + ngx_rtmp_eval_append_var(ctx, &b, e, &name, log); + + case NORMAL: + switch (c) { + case '$': + name.data = p + 1; + state = NAME; + continue; + case '\\': + state = ESCAPE; + continue; + } + + case ESCAPE: + ngx_rtmp_eval_append(&b, &c, 1, log); + state = NORMAL; + break; + + } + } + + if (state == NAME) { + p = &in->data[n]; + name.len = p - name.data; + ngx_rtmp_eval_append_var(ctx, &b, e, &name, log); + } + + c = 0; + ngx_rtmp_eval_append(&b, &c, 1, log); + + out->data = b.pos; + out->len = b.last - b.pos - 1; + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_eval_streams(ngx_str_t *in) +{ +#if !(NGX_WIN32) + ngx_int_t mode, create, v, close_src; + ngx_fd_t dst, src; + u_char *path; + + path = in->data; + + while (*path >= '0' && *path <= '9') { + path++; + } + + switch ((char) *path) { + + case '>': + + v = (path == in->data ? 1 : ngx_atoi(in->data, path - in->data)); + if (v == NGX_ERROR) { + return NGX_ERROR; + } + + dst = (ngx_fd_t) v; + mode = NGX_FILE_WRONLY; + create = NGX_FILE_TRUNCATE; + path++; + + if (*path == (u_char) '>') { + mode = NGX_FILE_APPEND; + create = NGX_FILE_CREATE_OR_OPEN; + path++; + } + + break; + + case '<': + + v = (path == in->data ? 0 : ngx_atoi(in->data, path - in->data)); + if (v == NGX_ERROR) { + return NGX_ERROR; + } + + dst = (ngx_fd_t) v; + mode = NGX_FILE_RDONLY; + create = NGX_FILE_OPEN; + path++; + + break; + + default: + + return NGX_DONE; + } + + if (*path == (u_char) '&') { + + path++; + v = ngx_atoi(path, in->data + in->len - path); + if (v == NGX_ERROR) { + return NGX_ERROR; + } + src = (ngx_fd_t) v; + close_src = 0; + + } else { + + src = ngx_open_file(path, mode, create, NGX_FILE_DEFAULT_ACCESS); + if (src == NGX_INVALID_FILE) { + return NGX_ERROR; + } + close_src = 1; + + } + + if (src == dst) { + return NGX_OK; + } + + dup2(src, dst); + + if (close_src) { + ngx_close_file(src); + } + return NGX_OK; + +#else + return NGX_DONE; +#endif +} diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_eval.h nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_eval.h --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_eval.h 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_eval.h 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,44 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_EVAL_H_INCLUDED_ +#define _NGX_RTMP_EVAL_H_INCLUDED_ + + +#include +#include +#include "ngx_rtmp.h" + + +typedef struct ngx_rtmp_eval_s ngx_rtmp_eval_t; + + +typedef void (* ngx_rtmp_eval_pt)(void *ctx, ngx_rtmp_eval_t *e, + ngx_str_t *ret); + + +struct ngx_rtmp_eval_s { + ngx_str_t name; + ngx_rtmp_eval_pt handler; + ngx_uint_t offset; +}; + + +#define ngx_rtmp_null_eval { ngx_null_string, NULL, 0 } + + +/* standard session eval variables */ +extern ngx_rtmp_eval_t ngx_rtmp_eval_session[]; + + +ngx_int_t ngx_rtmp_eval(void *ctx, ngx_str_t *in, ngx_rtmp_eval_t **e, + ngx_str_t *out, ngx_log_t *log); + + +ngx_int_t ngx_rtmp_eval_streams(ngx_str_t *in); + + +#endif /* _NGX_RTMP_EVAL_H_INCLUDED_ */ diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_exec_module.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_exec_module.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_exec_module.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_exec_module.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,1604 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp_cmd_module.h" +#include "ngx_rtmp_record_module.h" +#include "ngx_rtmp_eval.h" +#include + +#ifdef NGX_LINUX +#include +#endif + + +#if !(NGX_WIN32) +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_done_pt next_record_done; +#endif + + +static ngx_int_t ngx_rtmp_exec_init_process(ngx_cycle_t *cycle); +static ngx_int_t ngx_rtmp_exec_postconfiguration(ngx_conf_t *cf); +static void * ngx_rtmp_exec_create_main_conf(ngx_conf_t *cf); +static char * ngx_rtmp_exec_init_main_conf(ngx_conf_t *cf, void *conf); +static void * ngx_rtmp_exec_create_app_conf(ngx_conf_t *cf); +static char * ngx_rtmp_exec_merge_app_conf(ngx_conf_t *cf, + void *parent, void *child); +/*static char * ngx_rtmp_exec_block(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf);*/ +static char * ngx_rtmp_exec_conf(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_rtmp_exec_kill_signal(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + + +#define NGX_RTMP_EXEC_RESPAWN 0x01 +#define NGX_RTMP_EXEC_KILL 0x02 + + +#define NGX_RTMP_EXEC_PUBLISHING 0x01 +#define NGX_RTMP_EXEC_PLAYING 0x02 + + +enum { + NGX_RTMP_EXEC_PUSH, + NGX_RTMP_EXEC_PULL, + + NGX_RTMP_EXEC_PUBLISH, + NGX_RTMP_EXEC_PUBLISH_DONE, + NGX_RTMP_EXEC_PLAY, + NGX_RTMP_EXEC_PLAY_DONE, + NGX_RTMP_EXEC_RECORD_DONE, + + NGX_RTMP_EXEC_MAX, + + NGX_RTMP_EXEC_STATIC +}; + + +typedef struct { + ngx_str_t id; + ngx_uint_t type; + ngx_str_t cmd; + ngx_array_t args; /* ngx_str_t */ + ngx_array_t names; +} ngx_rtmp_exec_conf_t; + + +typedef struct { + ngx_rtmp_exec_conf_t *conf; + ngx_log_t *log; + ngx_rtmp_eval_t **eval; + void *eval_ctx; + unsigned active:1; + unsigned managed:1; + ngx_pid_t pid; + ngx_pid_t *save_pid; + int pipefd; + ngx_connection_t dummy_conn; /*needed by ngx_xxx_event*/ + ngx_event_t read_evt, write_evt; + ngx_event_t respawn_evt; + ngx_msec_t respawn_timeout; + ngx_int_t kill_signal; +} ngx_rtmp_exec_t; + + +typedef struct { + ngx_array_t static_conf; /* ngx_rtmp_exec_conf_t */ + ngx_array_t static_exec; /* ngx_rtmp_exec_t */ + ngx_msec_t respawn_timeout; + ngx_int_t kill_signal; + ngx_log_t *log; +} ngx_rtmp_exec_main_conf_t; + + +typedef struct ngx_rtmp_exec_pull_ctx_s ngx_rtmp_exec_pull_ctx_t; + +struct ngx_rtmp_exec_pull_ctx_s { + ngx_pool_t *pool; + ngx_uint_t counter; + ngx_str_t name; + ngx_str_t app; + ngx_array_t pull_exec; /* ngx_rtmp_exec_t */ + ngx_rtmp_exec_pull_ctx_t *next; +}; + + +typedef struct { + ngx_int_t active; + ngx_array_t conf[NGX_RTMP_EXEC_MAX]; + /* ngx_rtmp_exec_conf_t */ + ngx_flag_t respawn; + ngx_flag_t options; + ngx_uint_t nbuckets; + ngx_rtmp_exec_pull_ctx_t **pull; +} ngx_rtmp_exec_app_conf_t; + + +typedef struct { + ngx_uint_t flags; + ngx_str_t path; /* /tmp/rec/myfile-123.flv */ + ngx_str_t filename; /* myfile-123.flv */ + ngx_str_t basename; /* myfile-123 */ + ngx_str_t dirname; /* /tmp/rec */ + ngx_str_t recorder; + u_char name[NGX_RTMP_MAX_NAME]; + u_char args[NGX_RTMP_MAX_ARGS]; + ngx_array_t push_exec; /* ngx_rtmp_exec_t */ + ngx_rtmp_exec_pull_ctx_t *pull; +} ngx_rtmp_exec_ctx_t; + + +#if !(NGX_WIN32) +static void ngx_rtmp_exec_respawn(ngx_event_t *ev); +static ngx_int_t ngx_rtmp_exec_kill(ngx_rtmp_exec_t *e, ngx_int_t kill_signal); +static ngx_int_t ngx_rtmp_exec_run(ngx_rtmp_exec_t *e); +#endif + + +static ngx_command_t ngx_rtmp_exec_commands[] = { +/* + { ngx_string("exec_block"), + NGX_RTMP_APP_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS|NGX_CONF_TAKE1, + ngx_rtmp_exec_block, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, +*/ + { ngx_string("exec"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_exec_conf, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_exec_app_conf_t, conf) + + NGX_RTMP_EXEC_PUSH * sizeof(ngx_array_t), + NULL }, + + { ngx_string("exec_push"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_exec_conf, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_exec_app_conf_t, conf) + + NGX_RTMP_EXEC_PUSH * sizeof(ngx_array_t), + NULL }, + + { ngx_string("exec_pull"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_exec_conf, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_exec_app_conf_t, conf) + + NGX_RTMP_EXEC_PULL * sizeof(ngx_array_t), + NULL }, + + { ngx_string("exec_publish"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_exec_conf, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_exec_app_conf_t, conf) + + NGX_RTMP_EXEC_PUBLISH * sizeof(ngx_array_t), + NULL }, + + { ngx_string("exec_publish_done"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_exec_conf, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_exec_app_conf_t, conf) + + NGX_RTMP_EXEC_PUBLISH_DONE * sizeof(ngx_array_t), + NULL }, + + { ngx_string("exec_play"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_exec_conf, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_exec_app_conf_t, conf) + + NGX_RTMP_EXEC_PLAY * sizeof(ngx_array_t), + NULL }, + + { ngx_string("exec_play_done"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_exec_conf, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_exec_app_conf_t, conf) + + NGX_RTMP_EXEC_PLAY_DONE * 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, + ngx_rtmp_exec_conf, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_exec_app_conf_t, conf) + + NGX_RTMP_EXEC_RECORD_DONE * sizeof(ngx_array_t), + NULL }, + + { ngx_string("exec_static"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_exec_conf, + NGX_RTMP_MAIN_CONF_OFFSET, + offsetof(ngx_rtmp_exec_main_conf_t, static_conf), + NULL }, + + { ngx_string("respawn"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_exec_app_conf_t, respawn), + NULL }, + + { ngx_string("respawn_timeout"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_MAIN_CONF_OFFSET, + offsetof(ngx_rtmp_exec_main_conf_t, respawn_timeout), + NULL }, + + { ngx_string("exec_kill_signal"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_rtmp_exec_kill_signal, + NGX_RTMP_MAIN_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("exec_options"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_exec_app_conf_t, options), + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_rtmp_exec_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_exec_postconfiguration, /* postconfiguration */ + ngx_rtmp_exec_create_main_conf, /* create main configuration */ + ngx_rtmp_exec_init_main_conf, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + ngx_rtmp_exec_create_app_conf, /* create app configuration */ + ngx_rtmp_exec_merge_app_conf /* merge app configuration */ +}; + + +ngx_module_t ngx_rtmp_exec_module = { + NGX_MODULE_V1, + &ngx_rtmp_exec_module_ctx, /* module context */ + ngx_rtmp_exec_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + ngx_rtmp_exec_init_process, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void +ngx_rtmp_exec_eval_ctx_cstr(void *sctx, ngx_rtmp_eval_t *e, ngx_str_t *ret) +{ + ngx_rtmp_session_t *s = sctx; + + ngx_rtmp_exec_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_exec_module); + if (ctx == NULL) { + ret->len = 0; + return; + } + + ret->data = (u_char *) ctx + e->offset; + ret->len = ngx_strlen(ret->data); +} + + +static void +ngx_rtmp_exec_eval_ctx_str(void *sctx, ngx_rtmp_eval_t *e, ngx_str_t *ret) +{ + ngx_rtmp_session_t *s = sctx; + + ngx_rtmp_exec_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_exec_module); + if (ctx == NULL) { + ret->len = 0; + return; + } + + *ret = * (ngx_str_t *) ((u_char *) ctx + e->offset); +} + + +static void +ngx_rtmp_exec_eval_pctx_str(void *ctx, ngx_rtmp_eval_t *e, ngx_str_t *ret) +{ + *ret = *(ngx_str_t *) ((u_char *) ctx + e->offset); +} + + +static ngx_rtmp_eval_t ngx_rtmp_exec_push_specific_eval[] = { + + { ngx_string("name"), + ngx_rtmp_exec_eval_ctx_cstr, + offsetof(ngx_rtmp_exec_ctx_t, name) }, + + { ngx_string("args"), + ngx_rtmp_exec_eval_ctx_cstr, + offsetof(ngx_rtmp_exec_ctx_t, args) }, + + ngx_rtmp_null_eval +}; + + +static ngx_rtmp_eval_t * ngx_rtmp_exec_push_eval[] = { + ngx_rtmp_eval_session, + ngx_rtmp_exec_push_specific_eval, + NULL +}; + + +static ngx_rtmp_eval_t ngx_rtmp_exec_pull_specific_eval[] = { + + { ngx_string("name"), + ngx_rtmp_exec_eval_pctx_str, + offsetof(ngx_rtmp_exec_pull_ctx_t, name) }, + + { ngx_string("app"), + ngx_rtmp_exec_eval_pctx_str, + offsetof(ngx_rtmp_exec_pull_ctx_t, app) }, + + ngx_rtmp_null_eval +}; + + +static ngx_rtmp_eval_t * ngx_rtmp_exec_pull_eval[] = { + ngx_rtmp_exec_pull_specific_eval, + NULL +}; + + +static ngx_rtmp_eval_t ngx_rtmp_exec_event_specific_eval[] = { + + { ngx_string("name"), + ngx_rtmp_exec_eval_ctx_cstr, + offsetof(ngx_rtmp_exec_ctx_t, name) }, + + { ngx_string("args"), + ngx_rtmp_exec_eval_ctx_cstr, + offsetof(ngx_rtmp_exec_ctx_t, args) }, + + { ngx_string("path"), + ngx_rtmp_exec_eval_ctx_str, + offsetof(ngx_rtmp_exec_ctx_t, path) }, + + { ngx_string("filename"), + ngx_rtmp_exec_eval_ctx_str, + offsetof(ngx_rtmp_exec_ctx_t, filename) }, + + { ngx_string("basename"), + ngx_rtmp_exec_eval_ctx_str, + offsetof(ngx_rtmp_exec_ctx_t, basename) }, + + { ngx_string("dirname"), + ngx_rtmp_exec_eval_ctx_str, + offsetof(ngx_rtmp_exec_ctx_t, dirname) }, + + { ngx_string("recorder"), + ngx_rtmp_exec_eval_ctx_str, + offsetof(ngx_rtmp_exec_ctx_t, recorder) }, + + ngx_rtmp_null_eval +}; + + +static ngx_rtmp_eval_t * ngx_rtmp_exec_event_eval[] = { + ngx_rtmp_eval_session, + ngx_rtmp_exec_event_specific_eval, + NULL +}; + + +static void * +ngx_rtmp_exec_create_main_conf(ngx_conf_t *cf) +{ + ngx_rtmp_exec_main_conf_t *emcf; + + emcf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_exec_main_conf_t)); + if (emcf == NULL) { + return NULL; + } + + emcf->respawn_timeout = NGX_CONF_UNSET_MSEC; + emcf->kill_signal = NGX_CONF_UNSET; + + if (ngx_array_init(&emcf->static_conf, cf->pool, 1, + sizeof(ngx_rtmp_exec_conf_t)) != NGX_OK) + { + return NULL; + } + + return emcf; +} + + +static char * +ngx_rtmp_exec_init_main_conf(ngx_conf_t *cf, void *conf) +{ + ngx_rtmp_exec_main_conf_t *emcf = conf; + ngx_rtmp_exec_conf_t *ec; + ngx_rtmp_exec_t *e; + ngx_uint_t n; + + if (emcf->respawn_timeout == NGX_CONF_UNSET_MSEC) { + emcf->respawn_timeout = 5000; + } + +#if !(NGX_WIN32) + if (emcf->kill_signal == NGX_CONF_UNSET) { + emcf->kill_signal = SIGKILL; + } +#endif + + if (ngx_array_init(&emcf->static_exec, cf->pool, + emcf->static_conf.nelts, + sizeof(ngx_rtmp_exec_t)) != NGX_OK) + { + return NGX_CONF_ERROR; + } + + e = ngx_array_push_n(&emcf->static_exec, emcf->static_conf.nelts); + if (e == NULL) { + return NGX_CONF_ERROR; + } + + emcf->log = &cf->cycle->new_log; + + ec = emcf->static_conf.elts; + + for (n = 0; n < emcf->static_conf.nelts; n++, e++, ec++) { + ngx_memzero(e, sizeof(*e)); + e->conf = ec; + e->managed = 1; + e->log = emcf->log; + e->respawn_timeout = emcf->respawn_timeout; + e->kill_signal = emcf->kill_signal; + } + + return NGX_CONF_OK; +} + + +static void * +ngx_rtmp_exec_create_app_conf(ngx_conf_t *cf) +{ + ngx_rtmp_exec_app_conf_t *eacf; + + eacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_exec_app_conf_t)); + if (eacf == NULL) { + return NULL; + } + + eacf->respawn = NGX_CONF_UNSET; + eacf->options = NGX_CONF_UNSET; + eacf->nbuckets = NGX_CONF_UNSET_UINT; + + return eacf; +} + + +static ngx_int_t +ngx_rtmp_exec_merge_confs(ngx_array_t *conf, ngx_array_t *prev) +{ + size_t n; + ngx_rtmp_exec_conf_t *ec, *pec; + + if (prev->nelts == 0) { + return NGX_OK; + } + + if (conf->nelts == 0) { + *conf = *prev; + return NGX_OK; + } + + ec = ngx_array_push_n(conf, prev->nelts); + if (ec == NULL) { + return NGX_ERROR; + } + + pec = prev->elts; + for (n = 0; n < prev->nelts; n++, ec++, pec++) { + *ec = *pec; + } + + return NGX_OK; +} + + +static char * +ngx_rtmp_exec_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_exec_app_conf_t *prev = parent; + ngx_rtmp_exec_app_conf_t *conf = child; + + ngx_uint_t n; + + ngx_conf_merge_value(conf->respawn, prev->respawn, 1); + ngx_conf_merge_uint_value(conf->nbuckets, prev->nbuckets, 1024); + + for (n = 0; n < NGX_RTMP_EXEC_MAX; n++) { + if (ngx_rtmp_exec_merge_confs(&conf->conf[n], &prev->conf[n]) != NGX_OK) + { + return NGX_CONF_ERROR; + } + + if (conf->conf[n].nelts) { + conf->active = 1; + prev->active = 1; + } + } + + if (conf->conf[NGX_RTMP_EXEC_PULL].nelts > 0) { + conf->pull = ngx_pcalloc(cf->pool, sizeof(void *) * conf->nbuckets); + if (conf->pull == NULL) { + return NGX_CONF_ERROR; + } + } + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_rtmp_exec_init_process(ngx_cycle_t *cycle) +{ +#if !(NGX_WIN32) + ngx_rtmp_core_main_conf_t *cmcf = ngx_rtmp_core_main_conf; + ngx_rtmp_core_srv_conf_t **cscf; + ngx_rtmp_conf_ctx_t *cctx; + ngx_rtmp_exec_main_conf_t *emcf; + ngx_rtmp_exec_t *e; + ngx_uint_t n; + + if (cmcf == NULL || cmcf->servers.nelts == 0) { + return NGX_OK; + } + + /* execs are always started by the first worker */ + if (ngx_process_slot) { + return NGX_OK; + } + + cscf = cmcf->servers.elts; + cctx = (*cscf)->ctx; + emcf = cctx->main_conf[ngx_rtmp_exec_module.ctx_index]; + + /* FreeBSD note: + * When worker is restarted, child process (ffmpeg) will + * not be terminated if it's connected to another + * (still alive) worker. That leads to starting + * another instance of exec_static process. + * Need to kill previously started processes. + * + * On Linux "prctl" syscall is used to kill child + * when nginx worker is terminated. + */ + + e = emcf->static_exec.elts; + for (n = 0; n < emcf->static_exec.nelts; ++n, ++e) { + e->respawn_evt.data = e; + e->respawn_evt.log = e->log; + e->respawn_evt.handler = ngx_rtmp_exec_respawn; + ngx_post_event((&e->respawn_evt), &ngx_rtmp_init_queue); + } +#endif + + return NGX_OK; +} + + +#if !(NGX_WIN32) +static void +ngx_rtmp_exec_respawn(ngx_event_t *ev) +{ + ngx_rtmp_exec_run((ngx_rtmp_exec_t *) ev->data); +} + + +static void +ngx_rtmp_exec_child_dead(ngx_event_t *ev) +{ + ngx_connection_t *dummy_conn = ev->data; + ngx_rtmp_exec_t *e; + + e = dummy_conn->data; + + ngx_log_error(NGX_LOG_INFO, e->log, 0, + "exec: child %ui exited; %s", (ngx_int_t) e->pid, + e->respawn_timeout == NGX_CONF_UNSET_MSEC ? "respawning" : + "ignoring"); + + ngx_rtmp_exec_kill(e, 0); + + if (e->respawn_timeout == NGX_CONF_UNSET_MSEC) { + return; + } + + if (e->respawn_timeout == 0) { + ngx_rtmp_exec_run(e); + return; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, e->log, 0, + "exec: shedule respawn %Mmsec", e->respawn_timeout); + + e->respawn_evt.data = e; + e->respawn_evt.log = e->log; + e->respawn_evt.handler = ngx_rtmp_exec_respawn; + + ngx_add_timer(&e->respawn_evt, e->respawn_timeout); +} + + +static ngx_int_t +ngx_rtmp_exec_kill(ngx_rtmp_exec_t *e, ngx_int_t kill_signal) +{ + if (e->respawn_evt.timer_set) { + ngx_del_timer(&e->respawn_evt); + } + + if (e->read_evt.active) { + ngx_del_event(&e->read_evt, NGX_READ_EVENT, 0); + } + + if (e->active == 0) { + return NGX_OK; + } + + ngx_log_error(NGX_LOG_INFO, e->log, 0, + "exec: terminating child %ui", (ngx_int_t) e->pid); + + e->active = 0; + close(e->pipefd); + if (e->save_pid) { + *e->save_pid = NGX_INVALID_PID; + } + + if (kill_signal == 0) { + return NGX_OK; + } + + if (kill(e->pid, kill_signal) == -1) { + ngx_log_error(NGX_LOG_INFO, e->log, ngx_errno, + "exec: kill failed pid=%i", (ngx_int_t) e->pid); + } else { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, e->log, 0, + "exec: killed pid=%i", (ngx_int_t) e->pid); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_exec_run(ngx_rtmp_exec_t *e) +{ + int fd, ret, maxfd, pipefd[2]; + char **args, **arg_out; + ngx_pid_t pid; + ngx_str_t *arg_in, a; + ngx_uint_t n; + ngx_rtmp_exec_conf_t *ec; + + ec = e->conf; + + ngx_log_error(NGX_LOG_INFO, e->log, 0, + "exec: starting %s child '%V'", + e->managed ? "managed" : "unmanaged", &ec->cmd); + + pipefd[0] = -1; + pipefd[1] = -1; + + if (e->managed) { + + if (e->active) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, e->log, 0, + "exec: already active '%V'", &ec->cmd); + return NGX_OK; + } + + if (pipe(pipefd) == -1) { + ngx_log_error(NGX_LOG_INFO, e->log, ngx_errno, + "exec: pipe failed"); + return NGX_ERROR; + } + + /* make pipe write end survive through exec */ + + ret = fcntl(pipefd[1], F_GETFD); + + if (ret != -1) { + ret &= ~FD_CLOEXEC; + ret = fcntl(pipefd[1], F_SETFD, ret); + } + + if (ret == -1) { + + close(pipefd[0]); + close(pipefd[1]); + + ngx_log_error(NGX_LOG_INFO, e->log, ngx_errno, + "exec: fcntl failed"); + + return NGX_ERROR; + } + } + + pid = fork(); + + switch (pid) { + + case -1: + + /* failure */ + + if (pipefd[0] != -1) { + close(pipefd[0]); + } + + if (pipefd[1] != -1) { + close(pipefd[1]); + } + + ngx_log_error(NGX_LOG_INFO, e->log, ngx_errno, + "exec: fork failed"); + + return NGX_ERROR; + + case 0: + + /* child */ + +#if (NGX_LINUX) + if (e->managed) { + prctl(PR_SET_PDEATHSIG, e->kill_signal, 0, 0, 0); + } +#endif + + /* close all descriptors but pipe write end */ + + maxfd = sysconf(_SC_OPEN_MAX); + for (fd = 0; fd < maxfd; ++fd) { + if (fd == pipefd[1]) { + continue; + } + + close(fd); + } + + fd = open("/dev/null", O_RDWR); + + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + + args = ngx_alloc((ec->args.nelts + 2) * sizeof(char *), e->log); + if (args == NULL) { + exit(1); + } + + arg_in = ec->args.elts; + arg_out = args; + *arg_out++ = (char *) ec->cmd.data; + + for (n = 0; n < ec->args.nelts; n++, ++arg_in) { + + if (e->eval == NULL) { + a = *arg_in; + } else { + ngx_rtmp_eval(e->eval_ctx, arg_in, e->eval, &a, e->log); + } + + if (ngx_rtmp_eval_streams(&a) != NGX_DONE) { + continue; + } + + *arg_out++ = (char *) a.data; + } + + *arg_out = NULL; + +#if (NGX_DEBUG) + { + char **p; + + for (p = args; *p; p++) { + ngx_write_fd(STDERR_FILENO, "'", 1); + ngx_write_fd(STDERR_FILENO, *p, strlen(*p)); + ngx_write_fd(STDERR_FILENO, "' ", 2); + } + + ngx_write_fd(STDERR_FILENO, "\n", 1); + } +#endif + + if (execvp((char *) ec->cmd.data, args) == -1) { + char *msg; + + msg = strerror(errno); + + ngx_write_fd(STDERR_FILENO, "execvp error: ", 14); + ngx_write_fd(STDERR_FILENO, msg, strlen(msg)); + ngx_write_fd(STDERR_FILENO, "\n", 1); + + exit(1); + } + + break; + + default: + + /* parent */ + + if (pipefd[1] != -1) { + close(pipefd[1]); + } + + if (pipefd[0] != -1) { + + e->active = 1; + e->pid = pid; + e->pipefd = pipefd[0]; + + if (e->save_pid) { + *e->save_pid = pid; + } + + e->dummy_conn.fd = e->pipefd; + e->dummy_conn.data = e; + e->dummy_conn.read = &e->read_evt; + e->dummy_conn.write = &e->write_evt; + e->read_evt.data = &e->dummy_conn; + e->write_evt.data = &e->dummy_conn; + + e->read_evt.log = e->log; + e->read_evt.handler = ngx_rtmp_exec_child_dead; + + if (ngx_add_event(&e->read_evt, NGX_READ_EVENT, 0) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, e->log, ngx_errno, + "exec: failed to add child control event"); + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, e->log, 0, + "exec: child '%V' started pid=%i", + &ec->cmd, (ngx_int_t) pid); + break; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_exec_init_ctx(ngx_rtmp_session_t *s, u_char name[NGX_RTMP_MAX_NAME], + u_char args[NGX_RTMP_MAX_ARGS], ngx_uint_t flags) +{ + ngx_uint_t n; + ngx_array_t *push_conf; + ngx_rtmp_exec_t *e; + ngx_rtmp_exec_ctx_t *ctx; + ngx_rtmp_exec_conf_t *ec; + ngx_rtmp_exec_app_conf_t *eacf; + ngx_rtmp_exec_main_conf_t *emcf; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_exec_module); + + if (ctx != NULL) { + goto done; + } + + ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_rtmp_exec_ctx_t)); + + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_exec_module); + + eacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_exec_module); + + emcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_exec_module); + + push_conf = &eacf->conf[NGX_RTMP_EXEC_PUSH]; + + if (push_conf->nelts > 0) { + + if (ngx_array_init(&ctx->push_exec, s->connection->pool, + push_conf->nelts, + sizeof(ngx_rtmp_exec_t)) != NGX_OK) + { + return NGX_ERROR; + } + + e = ngx_array_push_n(&ctx->push_exec, push_conf->nelts); + + if (e == NULL) { + return NGX_ERROR; + } + + ec = push_conf->elts; + + for (n = 0; n < push_conf->nelts; n++, e++, ec++) { + ngx_memzero(e, sizeof(*e)); + e->conf = ec; + e->managed = 1; + e->log = s->connection->log; + e->eval = ngx_rtmp_exec_push_eval; + e->eval_ctx = s; + e->kill_signal = emcf->kill_signal; + e->respawn_timeout = (eacf->respawn ? emcf->respawn_timeout : + NGX_CONF_UNSET_MSEC); + } + } + +done: + + ngx_memcpy(ctx->name, name, NGX_RTMP_MAX_NAME); + ngx_memcpy(ctx->args, args, NGX_RTMP_MAX_ARGS); + + ctx->flags |= flags; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_exec_init_pull_ctx(ngx_rtmp_session_t *s, + u_char name[NGX_RTMP_MAX_NAME]) +{ + size_t len; + ngx_uint_t n; + ngx_pool_t *pool; + ngx_array_t *pull_conf; + ngx_rtmp_exec_t *e; + ngx_rtmp_exec_ctx_t *ctx; + ngx_rtmp_exec_conf_t *ec; + ngx_rtmp_exec_pull_ctx_t *pctx, **ppctx; + ngx_rtmp_exec_app_conf_t *eacf; + ngx_rtmp_exec_main_conf_t *emcf; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_exec_module); + if (ctx->pull != NULL) { + return NGX_OK; + } + + eacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_exec_module); + + pull_conf = &eacf->conf[NGX_RTMP_EXEC_PULL]; + + if (pull_conf->nelts == 0) { + return NGX_OK; + } + + emcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_exec_module); + + len = ngx_strlen(name); + + ppctx = &eacf->pull[ngx_hash_key(name, len) % eacf->nbuckets]; + + for (; *ppctx; ppctx = &(*ppctx)->next) { + pctx = *ppctx; + + if (pctx->name.len == len && + ngx_strncmp(name, pctx->name.data, len) == 0) + { + goto done; + } + } + + pool = ngx_create_pool(4096, emcf->log); + if (pool == NULL) { + return NGX_ERROR; + } + + pctx = ngx_pcalloc(pool, sizeof(ngx_rtmp_exec_pull_ctx_t)); + if (pctx == NULL) { + goto error; + } + + pctx->pool = pool; + pctx->name.len = len; + pctx->name.data = ngx_palloc(pool, len); + + if (pctx->name.data == NULL) { + goto error; + } + + ngx_memcpy(pctx->name.data, name, len); + + pctx->app.len = s->app.len; + pctx->app.data = ngx_palloc(pool, s->app.len); + + if (pctx->app.data == NULL) { + goto error; + } + + ngx_memcpy(pctx->app.data, s->app.data, s->app.len); + + if (ngx_array_init(&pctx->pull_exec, pool, pull_conf->nelts, + sizeof(ngx_rtmp_exec_t)) != NGX_OK) + { + goto error; + } + + e = ngx_array_push_n(&pctx->pull_exec, pull_conf->nelts); + if (e == NULL) { + goto error; + } + + ec = pull_conf->elts; + for (n = 0; n < pull_conf->nelts; n++, e++, ec++) { + ngx_memzero(e, sizeof(*e)); + e->conf = ec; + e->managed = 1; + e->log = emcf->log; + e->eval = ngx_rtmp_exec_pull_eval; + e->eval_ctx = pctx; + e->kill_signal = emcf->kill_signal; + e->respawn_timeout = (eacf->respawn ? emcf->respawn_timeout : + NGX_CONF_UNSET_MSEC); + } + + *ppctx = pctx; + +done: + + ctx->pull = pctx; + ctx->pull->counter++; + + return NGX_OK; + +error: + + ngx_destroy_pool(pool); + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_rtmp_exec_filter(ngx_rtmp_session_t *s, ngx_rtmp_exec_conf_t *ec) +{ + size_t len; + ngx_str_t *v; + ngx_uint_t n; + ngx_rtmp_exec_ctx_t *ctx; + + if (ec->names.nelts == 0) { + return NGX_OK; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_exec_module); + + len = ngx_strlen(ctx->name); + + v = ec->names.elts; + for (n = 0; n < ec->names.nelts; n++, s++) { + if (v->len == len && ngx_strncmp(v->data, ctx->name, len) == 0) { + return NGX_OK; + } + } + + return NGX_DECLINED; +} + + +static void +ngx_rtmp_exec_unmanaged(ngx_rtmp_session_t *s, ngx_array_t *e, const char *op) +{ + ngx_uint_t n; + ngx_rtmp_exec_t en; + ngx_rtmp_exec_conf_t *ec; + + if (e->nelts == 0) { + return; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "exec: %s %uz unmanaged command(s)", op, e->nelts); + + ec = e->elts; + for (n = 0; n < e->nelts; n++, ec++) { + if (ngx_rtmp_exec_filter(s, ec) != NGX_OK) { + continue; + } + + ngx_memzero(&en, sizeof(ngx_rtmp_exec_t)); + + en.conf = ec; + en.eval = ngx_rtmp_exec_event_eval; + en.eval_ctx = s; + en.log = s->connection->log; + + ngx_rtmp_exec_run(&en); + } +} + + +static void +ngx_rtmp_exec_managed(ngx_rtmp_session_t *s, ngx_array_t *e, const char *op) +{ + ngx_uint_t n; + ngx_rtmp_exec_t *en; + + if (e->nelts == 0) { + return; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "exec: %s %uz managed command(s)", op, e->nelts); + + en = e->elts; + for (n = 0; n < e->nelts; n++, en++) { + if (ngx_rtmp_exec_filter(s, en->conf) == NGX_OK) { + ngx_rtmp_exec_run(en); + } + } +} + + +static ngx_int_t +ngx_rtmp_exec_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) +{ + ngx_rtmp_exec_ctx_t *ctx; + ngx_rtmp_exec_app_conf_t *eacf; + + eacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_exec_module); + + if (eacf == NULL || !eacf->active) { + goto next; + } + + if (s->auto_pushed) { + goto next; + } + + if (ngx_rtmp_exec_init_ctx(s, v->name, v->args, NGX_RTMP_EXEC_PUBLISHING) + != NGX_OK) + { + goto next; + } + + ngx_rtmp_exec_unmanaged(s, &eacf->conf[NGX_RTMP_EXEC_PUBLISH], "publish"); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_exec_module); + + ngx_rtmp_exec_managed(s, &ctx->push_exec, "push"); + +next: + return next_publish(s, v); +} + + +static ngx_int_t +ngx_rtmp_exec_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) +{ + ngx_rtmp_exec_ctx_t *ctx; + ngx_rtmp_exec_pull_ctx_t *pctx; + ngx_rtmp_exec_app_conf_t *eacf; + + eacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_exec_module); + + if (eacf == NULL || !eacf->active) { + goto next; + } + + if (ngx_rtmp_exec_init_ctx(s, v->name, v->args, NGX_RTMP_EXEC_PLAYING) + != NGX_OK) + { + goto next; + } + + ngx_rtmp_exec_unmanaged(s, &eacf->conf[NGX_RTMP_EXEC_PLAY], "play"); + + if (ngx_rtmp_exec_init_pull_ctx(s, v->name) != NGX_OK) { + goto next; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_exec_module); + pctx = ctx->pull; + + if (pctx && pctx->counter == 1) { + ngx_rtmp_exec_managed(s, &pctx->pull_exec, "pull"); + } + +next: + return next_play(s, v); +} + + +static ngx_int_t +ngx_rtmp_exec_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v) +{ + size_t n; + ngx_rtmp_exec_t *e; + ngx_rtmp_exec_ctx_t *ctx; + ngx_rtmp_exec_pull_ctx_t *pctx, **ppctx; + ngx_rtmp_exec_app_conf_t *eacf; + + eacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_exec_module); + if (eacf == NULL) { + goto next; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_exec_module); + if (ctx == NULL) { + goto next; + } + + if (ctx->flags & NGX_RTMP_EXEC_PUBLISHING) { + ngx_rtmp_exec_unmanaged(s, &eacf->conf[NGX_RTMP_EXEC_PUBLISH_DONE], + "publish_done"); + } + + if (ctx->flags & NGX_RTMP_EXEC_PLAYING) { + ngx_rtmp_exec_unmanaged(s, &eacf->conf[NGX_RTMP_EXEC_PLAY_DONE], + "play_done"); + } + + ctx->flags = 0; + + if (ctx->push_exec.nelts > 0) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "exec: delete %uz push command(s)", + ctx->push_exec.nelts); + + e = ctx->push_exec.elts; + for (n = 0; n < ctx->push_exec.nelts; n++, e++) { + ngx_rtmp_exec_kill(e, e->kill_signal); + } + } + + pctx = ctx->pull; + + if (pctx && --pctx->counter == 0) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "exec: delete %uz pull command(s)", + pctx->pull_exec.nelts); + + e = pctx->pull_exec.elts; + for (n = 0; n < pctx->pull_exec.nelts; n++, e++) { + ngx_rtmp_exec_kill(e, e->kill_signal); + } + + ppctx = &eacf->pull[ngx_hash_key(pctx->name.data, pctx->name.len) % + eacf->nbuckets]; + + for (; *ppctx; ppctx = &(*ppctx)->next) { + if (pctx == *ppctx) { + *ppctx = pctx->next; + break; + } + } + + ngx_destroy_pool(pctx->pool); + } + + ctx->pull = NULL; + +next: + return next_close_stream(s, v); +} + + +static ngx_int_t +ngx_rtmp_exec_record_done(ngx_rtmp_session_t *s, ngx_rtmp_record_done_t *v) +{ + u_char c; + ngx_uint_t ext, dir; + ngx_rtmp_exec_ctx_t *ctx; + 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; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_exec_module); + if (ctx == NULL) { + goto next; + } + + ctx->recorder = v->recorder; + ctx->path = v->path; + + ctx->dirname.data = ctx->path.data; + ctx->dirname.len = 0; + + for (dir = ctx->path.len; dir > 0; dir--) { + c = ctx->path.data[dir - 1]; + if (c == '/' || c == '\\') { + ctx->dirname.len = dir - 1; + break; + } + } + + ctx->filename.data = ctx->path.data + dir; + ctx->filename.len = ctx->path.len - dir; + + ctx->basename = ctx->filename; + + for (ext = ctx->filename.len; ext > 0; ext--) { + if (ctx->filename.data[ext - 1] == '.') { + ctx->basename.len = ext - 1; + break; + } + } + + ngx_rtmp_exec_unmanaged(s, &eacf->conf[NGX_RTMP_EXEC_RECORD_DONE], + "record_done"); + + ngx_str_null(&v->recorder); + ngx_str_null(&v->path); + +next: + return next_record_done(s, v); +} +#endif /* NGX_WIN32 */ + + +static char * +ngx_rtmp_exec_conf(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + char *p = conf; + + size_t n, nargs; + ngx_str_t *s, *value, v; + ngx_array_t *confs; + ngx_rtmp_exec_conf_t *ec; + ngx_rtmp_exec_app_conf_t *eacf; + + confs = (ngx_array_t *) (p + cmd->offset); + + eacf = ngx_rtmp_conf_get_module_app_conf(cf, ngx_rtmp_exec_module); + + if (confs->nalloc == 0 && ngx_array_init(confs, cf->pool, 1, + sizeof(ngx_rtmp_exec_conf_t)) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + value = cf->args->elts; + + ec = ngx_array_push(confs); + if (ec == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memzero(ec, sizeof(ngx_rtmp_exec_conf_t)); + + /* type is undefined for explicit execs */ + + ec->type = NGX_CONF_UNSET_UINT; + ec->cmd = value[1]; + + if (ngx_array_init(&ec->names, cf->pool, 1, sizeof(ngx_str_t)) != NGX_OK) { + return NGX_CONF_ERROR; + } + + if (cf->args->nelts == 2) { + return NGX_CONF_OK; + } + + nargs = cf->args->nelts - 2; + if (ngx_array_init(&ec->args, cf->pool, nargs, sizeof(ngx_str_t)) != NGX_OK) + { + return NGX_CONF_ERROR; + } + + for (n = 2; n < cf->args->nelts; n++) { + + v = value[n]; + + if (eacf->options == 1) { + + if (v.len >= 5 && ngx_strncmp(v.data, "name=", 5) == 0) { + + s = ngx_array_push(&ec->names); + if (s == NULL) { + return NGX_CONF_ERROR; + } + + v.data += 5; + v.len -= 5; + + *s = v; + + continue; + } + } + + s = ngx_array_push(&ec->args); + if (s == NULL) { + return NGX_CONF_ERROR; + } + + *s = v; + } + + return NGX_CONF_OK; +} + +/* +static char * +ngx_rtmp_exec_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + char *rv; + ngx_str_t *value; + ngx_conf_t save; + ngx_array_t *confs; + ngx_rtmp_conf_ctx_t *ctx, *pctx; + ngx_rtmp_exec_conf_t *ec, *eec; + ngx_rtmp_exec_app_conf_t *eacf; + ngx_rtmp_exec_main_conf_t *emcf; + + value = cf->args->elts; + + eacf = ngx_rtmp_conf_get_module_app_conf(cf, ngx_rtmp_exec_module); + + emcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_exec_module); + + ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_conf_ctx_t)); + if (ctx == NULL) { + return NGX_CONF_ERROR; + } + + pctx = cf->ctx; + + ctx->main_conf = pctx->main_conf; + ctx->srv_conf = pctx->srv_conf; + + ctx->app_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module); + if (ctx->app_conf == NULL) { + return NGX_CONF_ERROR; + } + + ec = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_exec_conf_t)); + if (ec == NULL) { + return NGX_CONF_ERROR; + } + + ec->id = value[1]; + ec->type = NGX_CONF_UNSET_UINT; + + ctx->app_conf[ngx_rtmp_exec_module.ctx_index] = ec; + + save = *cf; + + cf->ctx = ctx; + cf->cmd_type = NGX_RTMP_EXEC_CONF; + + rv = ngx_conf_parse(cf, NULL); + *cf= save; + + switch (ec->type) { + + case NGX_RTMP_EXEC_STATIC: + confs = &emcf->static_conf; + break; + + case NGX_CONF_UNSET_UINT: + return "unspecified exec type"; + + default: + confs = &eacf->conf[ec->type]; + } + + if (confs->nalloc == 0 && ngx_array_init(confs, cf->pool, 1, + sizeof(ngx_rtmp_exec_conf_t)) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + eec = ngx_array_push(confs); + if (eec == NULL) { + return NGX_CONF_ERROR; + } + + *eec = *ec; + + return rv; +} +*/ + +static char * +ngx_rtmp_exec_kill_signal(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_rtmp_exec_main_conf_t *emcf = conf; + + ngx_str_t *value; + + value = cf->args->elts; + value++; + + emcf->kill_signal = ngx_atoi(value->data, value->len); + if (emcf->kill_signal != NGX_ERROR) { + return NGX_CONF_OK; + } + +#define NGX_RMTP_EXEC_SIGNAL(name) \ + if (value->len == sizeof(#name) - 1 && \ + ngx_strncasecmp(value->data, (u_char *) #name, value->len) == 0) \ + { \ + emcf->kill_signal = SIG##name; \ + return NGX_CONF_OK; \ + } + + /* POSIX.1-1990 signals */ + +#if !(NGX_WIN32) + NGX_RMTP_EXEC_SIGNAL(HUP); + NGX_RMTP_EXEC_SIGNAL(INT); + NGX_RMTP_EXEC_SIGNAL(QUIT); + NGX_RMTP_EXEC_SIGNAL(ILL); + NGX_RMTP_EXEC_SIGNAL(ABRT); + NGX_RMTP_EXEC_SIGNAL(FPE); + NGX_RMTP_EXEC_SIGNAL(KILL); + NGX_RMTP_EXEC_SIGNAL(SEGV); + NGX_RMTP_EXEC_SIGNAL(PIPE); + NGX_RMTP_EXEC_SIGNAL(ALRM); + NGX_RMTP_EXEC_SIGNAL(TERM); + NGX_RMTP_EXEC_SIGNAL(USR1); + NGX_RMTP_EXEC_SIGNAL(USR2); + NGX_RMTP_EXEC_SIGNAL(CHLD); + NGX_RMTP_EXEC_SIGNAL(CONT); + NGX_RMTP_EXEC_SIGNAL(STOP); + NGX_RMTP_EXEC_SIGNAL(TSTP); + NGX_RMTP_EXEC_SIGNAL(TTIN); + NGX_RMTP_EXEC_SIGNAL(TTOU); +#endif + +#undef NGX_RMTP_EXEC_SIGNAL + + return "unknown signal"; +} + + +static ngx_int_t +ngx_rtmp_exec_postconfiguration(ngx_conf_t *cf) +{ +#if !(NGX_WIN32) + + next_publish = ngx_rtmp_publish; + ngx_rtmp_publish = ngx_rtmp_exec_publish; + + next_play = ngx_rtmp_play; + ngx_rtmp_play = ngx_rtmp_exec_play; + + next_close_stream = ngx_rtmp_close_stream; + ngx_rtmp_close_stream = ngx_rtmp_exec_close_stream; + + next_record_done = ngx_rtmp_record_done; + ngx_rtmp_record_done = ngx_rtmp_exec_record_done; + +#endif /* NGX_WIN32 */ + + return NGX_OK; +} diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_flv_module.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_flv_module.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_flv_module.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_flv_module.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,675 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp_play_module.h" +#include "ngx_rtmp_codec_module.h" +#include "ngx_rtmp_streams.h" + + +static ngx_int_t ngx_rtmp_flv_postconfiguration(ngx_conf_t *cf); +static void ngx_rtmp_flv_read_meta(ngx_rtmp_session_t *s, ngx_file_t *f); +static ngx_int_t ngx_rtmp_flv_timestamp_to_offset(ngx_rtmp_session_t *s, + ngx_file_t *f, ngx_int_t timestamp); +static ngx_int_t ngx_rtmp_flv_init(ngx_rtmp_session_t *s, ngx_file_t *f, + ngx_int_t aindex, ngx_int_t vindex); +static ngx_int_t ngx_rtmp_flv_start(ngx_rtmp_session_t *s, ngx_file_t *f); +static ngx_int_t ngx_rtmp_flv_seek(ngx_rtmp_session_t *s, ngx_file_t *f, + ngx_uint_t offset); +static ngx_int_t ngx_rtmp_flv_stop(ngx_rtmp_session_t *s, ngx_file_t *f); +static ngx_int_t ngx_rtmp_flv_send(ngx_rtmp_session_t *s, ngx_file_t *f, + ngx_uint_t *ts); + + +typedef struct { + ngx_uint_t nelts; + ngx_uint_t offset; +} ngx_rtmp_flv_index_t; + + +typedef struct { + ngx_int_t offset; + ngx_int_t start_timestamp; + ngx_event_t write_evt; + uint32_t last_audio; + uint32_t last_video; + ngx_uint_t msg_mask; + uint32_t epoch; + + unsigned meta_read:1; + ngx_rtmp_flv_index_t filepositions; + ngx_rtmp_flv_index_t times; +} ngx_rtmp_flv_ctx_t; + + +#define NGX_RTMP_FLV_BUFFER (1024*1024) +#define NGX_RTMP_FLV_BUFLEN_ADDON 1000 +#define NGX_RTMP_FLV_TAG_HEADER 11 +#define NGX_RTMP_FLV_DATA_OFFSET 13 + + +static u_char ngx_rtmp_flv_buffer[ + NGX_RTMP_FLV_BUFFER]; +static u_char ngx_rtmp_flv_header[ + NGX_RTMP_FLV_TAG_HEADER]; + + +static ngx_rtmp_module_t ngx_rtmp_flv_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_flv_postconfiguration, /* postconfiguration */ + NULL, /* create main configuration */ + NULL, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + NULL, /* create app configuration */ + NULL /* merge app configuration */ +}; + + +ngx_module_t ngx_rtmp_flv_module = { + NGX_MODULE_V1, + &ngx_rtmp_flv_module_ctx, /* module context */ + NULL, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_int_t +ngx_rtmp_flv_fill_index(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_flv_index_t *idx) +{ + uint32_t nelts; + ngx_buf_t *b; + + /* we have AMF array pointed by context; + * need to extract its size (4 bytes) & + * save offset of actual array data */ + + b = ctx->link->buf; + + if (b->last - b->pos < (ngx_int_t) ctx->offset + 4) { + return NGX_ERROR; + } + + ngx_rtmp_rmemcpy(&nelts, b->pos + ctx->offset, 4); + + idx->nelts = nelts; + idx->offset = ctx->offset + 4; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_flv_init_index(ngx_rtmp_session_t *s, ngx_chain_t *in) +{ + ngx_rtmp_flv_ctx_t *ctx; + + static ngx_rtmp_amf_ctx_t filepositions_ctx; + static ngx_rtmp_amf_ctx_t times_ctx; + + static ngx_rtmp_amf_elt_t in_keyframes[] = { + + { NGX_RTMP_AMF_ARRAY | NGX_RTMP_AMF_CONTEXT, + ngx_string("filepositions"), + &filepositions_ctx, 0 }, + + { NGX_RTMP_AMF_ARRAY | NGX_RTMP_AMF_CONTEXT, + ngx_string("times"), + ×_ctx, 0 } + }; + + static ngx_rtmp_amf_elt_t in_inf[] = { + + { NGX_RTMP_AMF_OBJECT, + ngx_string("keyframes"), + in_keyframes, sizeof(in_keyframes) } + }; + + static ngx_rtmp_amf_elt_t in_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + in_inf, sizeof(in_inf) }, + }; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module); + + if (ctx == NULL || in == NULL) { + return NGX_OK; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: init index"); + + ngx_memzero(&filepositions_ctx, sizeof(filepositions_ctx)); + ngx_memzero(×_ctx, sizeof(times_ctx)); + + if (ngx_rtmp_receive_amf(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0]))) + { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "flv: init index error"); + return NGX_OK; + } + + if (filepositions_ctx.link && ngx_rtmp_flv_fill_index(&filepositions_ctx, + &ctx->filepositions) + != NGX_OK) + { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "flv: failed to init filepositions"); + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: filepositions nelts=%ui offset=%ui", + ctx->filepositions.nelts, ctx->filepositions.offset); + + if (times_ctx.link && ngx_rtmp_flv_fill_index(×_ctx, + &ctx->times) + != NGX_OK) + { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "flv: failed to init times"); + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: times nelts=%ui offset=%ui", + ctx->times.nelts, ctx->times.offset); + + return NGX_OK; +} + + +static double +ngx_rtmp_flv_index_value(void *src) +{ + double v; + + ngx_rtmp_rmemcpy(&v, src, 8); + + return v; +} + + +static ngx_int_t +ngx_rtmp_flv_timestamp_to_offset(ngx_rtmp_session_t *s, ngx_file_t *f, + ngx_int_t timestamp) +{ + ngx_rtmp_flv_ctx_t *ctx; + ssize_t n, size; + ngx_uint_t offset, index, ret, nelts; + double v; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module); + + if (ctx == NULL) { + goto rewind; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: lookup index start timestamp=%i", + timestamp); + + if (ctx->meta_read == 0) { + ngx_rtmp_flv_read_meta(s, f); + ctx->meta_read = 1; + } + + if (timestamp <= 0 || ctx->filepositions.nelts == 0 + || ctx->times.nelts == 0) + { + goto rewind; + } + + /* read index table from file given offset */ + offset = NGX_RTMP_FLV_DATA_OFFSET + NGX_RTMP_FLV_TAG_HEADER + + ctx->times.offset; + + /* index should fit in the buffer */ + nelts = ngx_min(ctx->times.nelts, sizeof(ngx_rtmp_flv_buffer) / 9); + size = nelts * 9; + + n = ngx_read_file(f, ngx_rtmp_flv_buffer, size, offset); + + if (n != size) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "flv: could not read times index"); + goto rewind; + } + + /*TODO: implement binary search */ + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: lookup times nelts=%ui", nelts); + + for (index = 0; index < nelts - 1; ++index) { + v = ngx_rtmp_flv_index_value(ngx_rtmp_flv_buffer + + index * 9 + 1) * 1000; + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: lookup times index=%ui value=%ui", + index, (ngx_uint_t) v); + + if (timestamp < v) { + break; + } + } + + if (index >= ctx->filepositions.nelts) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "flv: index out of bounds: %ui>=%ui", + index, ctx->filepositions.nelts); + goto rewind; + } + + /* take value from filepositions */ + offset = NGX_RTMP_FLV_DATA_OFFSET + NGX_RTMP_FLV_TAG_HEADER + + ctx->filepositions.offset + index * 9; + + n = ngx_read_file(f, ngx_rtmp_flv_buffer, 8, offset + 1); + + if (n != 8) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "flv: could not read filepositions index"); + goto rewind; + } + + ret = (ngx_uint_t) ngx_rtmp_flv_index_value(ngx_rtmp_flv_buffer); + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: lookup index timestamp=%i offset=%ui", + timestamp, ret); + + return ret; + +rewind: + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: lookup index timestamp=%i offset=begin", + timestamp); + + return NGX_RTMP_FLV_DATA_OFFSET; +} + + +static void +ngx_rtmp_flv_read_meta(ngx_rtmp_session_t *s, ngx_file_t *f) +{ + ngx_rtmp_flv_ctx_t *ctx; + ssize_t n; + ngx_rtmp_header_t h; + ngx_chain_t *out, in; + ngx_buf_t in_buf; + ngx_rtmp_core_srv_conf_t *cscf; + uint32_t size; + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module); + + if (ctx == NULL) { + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: read meta"); + + /* read tag header */ + n = ngx_read_file(f, ngx_rtmp_flv_header, sizeof(ngx_rtmp_flv_header), + NGX_RTMP_FLV_DATA_OFFSET); + + if (n != sizeof(ngx_rtmp_flv_header)) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "flv: could not read metadata tag header"); + return; + } + + if (ngx_rtmp_flv_header[0] != NGX_RTMP_MSG_AMF_META) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: first tag is not metadata, giving up"); + return; + } + + ngx_memzero(&h, sizeof(h)); + + h.type = NGX_RTMP_MSG_AMF_META; + h.msid = NGX_RTMP_MSID; + h.csid = NGX_RTMP_CSID_AMF; + + size = 0; + ngx_rtmp_rmemcpy(&size, ngx_rtmp_flv_header + 1, 3); + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: metadata size=%D", size); + + if (size > sizeof(ngx_rtmp_flv_buffer)) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "flv: too big metadata"); + return; + } + + /* read metadata */ + n = ngx_read_file(f, ngx_rtmp_flv_buffer, size, + sizeof(ngx_rtmp_flv_header) + + NGX_RTMP_FLV_DATA_OFFSET); + + if (n != (ssize_t) size) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "flv: could not read metadata"); + return; + } + + /* prepare input chain */ + ngx_memzero(&in, sizeof(in)); + ngx_memzero(&in_buf, sizeof(in_buf)); + + in.buf = &in_buf; + in_buf.pos = ngx_rtmp_flv_buffer; + in_buf.last = ngx_rtmp_flv_buffer + size; + + ngx_rtmp_flv_init_index(s, &in); + + /* output chain */ + out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in); + + ngx_rtmp_prepare_message(s, &h, NULL, out); + ngx_rtmp_send_message(s, out, 0); + ngx_rtmp_free_shared_chain(cscf, out); +} + + +static ngx_int_t +ngx_rtmp_flv_send(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_uint_t *ts) +{ + ngx_rtmp_flv_ctx_t *ctx; + uint32_t last_timestamp; + ngx_rtmp_header_t h, lh; + ngx_rtmp_core_srv_conf_t *cscf; + ngx_chain_t *out, in; + ngx_buf_t in_buf; + ngx_int_t rc; + ssize_t n; + uint32_t buflen, end_timestamp, size; + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module); + + if (ctx == NULL) { + return NGX_ERROR; + } + + if (ctx->offset == -1) { + ctx->offset = ngx_rtmp_flv_timestamp_to_offset(s, f, + ctx->start_timestamp); + ctx->start_timestamp = -1; /* set later from actual timestamp */ + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: read tag at offset=%i", ctx->offset); + + /* read tag header */ + n = ngx_read_file(f, ngx_rtmp_flv_header, + sizeof(ngx_rtmp_flv_header), ctx->offset); + + if (n != sizeof(ngx_rtmp_flv_header)) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "flv: could not read flv tag header"); + return NGX_DONE; + } + + /* parse header fields */ + ngx_memzero(&h, sizeof(h)); + + h.msid = NGX_RTMP_MSID; + h.type = ngx_rtmp_flv_header[0]; + + size = 0; + + ngx_rtmp_rmemcpy(&size, ngx_rtmp_flv_header + 1, 3); + ngx_rtmp_rmemcpy(&h.timestamp, ngx_rtmp_flv_header + 4, 3); + + ((u_char *) &h.timestamp)[3] = ngx_rtmp_flv_header[7]; + + ctx->offset += (sizeof(ngx_rtmp_flv_header) + size + 4); + + last_timestamp = 0; + + switch (h.type) { + + case NGX_RTMP_MSG_AUDIO: + h.csid = NGX_RTMP_CSID_AUDIO; + last_timestamp = ctx->last_audio; + ctx->last_audio = h.timestamp; + break; + + case NGX_RTMP_MSG_VIDEO: + h.csid = NGX_RTMP_CSID_VIDEO; + last_timestamp = ctx->last_video; + ctx->last_video = h.timestamp; + break; + + default: + return NGX_OK; + } + + ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: read tag type=%i size=%uD timestamp=%uD " + "last_timestamp=%uD", + (ngx_int_t) h.type,size, h.timestamp, last_timestamp); + + lh = h; + lh.timestamp = last_timestamp; + + if (size > sizeof(ngx_rtmp_flv_buffer)) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "flv: too big message: %D>%uz", size, + sizeof(ngx_rtmp_flv_buffer)); + goto next; + } + + /* read tag body */ + n = ngx_read_file(f, ngx_rtmp_flv_buffer, size, + ctx->offset - size - 4); + + if (n != (ssize_t) size) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "flv: could not read flv tag"); + return NGX_ERROR; + } + + /* prepare input chain */ + ngx_memzero(&in, sizeof(in)); + ngx_memzero(&in_buf, sizeof(in_buf)); + + in.buf = &in_buf; + in_buf.pos = ngx_rtmp_flv_buffer; + in_buf.last = ngx_rtmp_flv_buffer + size; + + /* output chain */ + out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in); + + ngx_rtmp_prepare_message(s, &h, ctx->msg_mask & (1 << h.type) ? + &lh : NULL, out); + rc = ngx_rtmp_send_message(s, out, 0); + ngx_rtmp_free_shared_chain(cscf, out); + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + if (rc != NGX_OK) { + return NGX_ERROR; + } + + ctx->msg_mask |= (1 << h.type); + +next: + if (ctx->start_timestamp == -1) { + ctx->start_timestamp = h.timestamp; + ctx->epoch = ngx_current_msec; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: start_timestamp=%i", ctx->start_timestamp); + return NGX_OK; + } + + buflen = s->buflen + NGX_RTMP_FLV_BUFLEN_ADDON; + + end_timestamp = (ngx_current_msec - ctx->epoch) + + ctx->start_timestamp + buflen; + + ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: %s wait=%D timestamp=%D end_timestamp=%D bufen=%i", + h.timestamp > end_timestamp ? "schedule" : "advance", + h.timestamp > end_timestamp ? h.timestamp - end_timestamp : 0, + h.timestamp, end_timestamp, (ngx_int_t) buflen); + + s->current_time = h.timestamp; + + /* too much data sent; schedule timeout */ + if (h.timestamp > end_timestamp) { + return h.timestamp - end_timestamp; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_flv_init(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_int_t aindex, + ngx_int_t vindex) +{ + ngx_rtmp_flv_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module); + + if (ctx == NULL) { + ctx = ngx_palloc(s->connection->pool, sizeof(ngx_rtmp_flv_ctx_t)); + + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_flv_module); + } + + ngx_memzero(ctx, sizeof(*ctx)); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_flv_start(ngx_rtmp_session_t *s, ngx_file_t *f) +{ + ngx_rtmp_flv_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module); + + if (ctx == NULL) { + return NGX_OK; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: start"); + + ctx->offset = -1; + ctx->msg_mask = 0; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_flv_seek(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_uint_t timestamp) +{ + ngx_rtmp_flv_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module); + + if (ctx == NULL) { + return NGX_OK; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: seek timestamp=%ui", timestamp); + + ctx->start_timestamp = timestamp; + ctx->epoch = ngx_current_msec; + ctx->offset = -1; + ctx->msg_mask = 0; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_flv_stop(ngx_rtmp_session_t *s, ngx_file_t *f) +{ + ngx_rtmp_flv_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module); + + if (ctx == NULL) { + return NGX_OK; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "flv: stop"); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_flv_postconfiguration(ngx_conf_t *cf) +{ + ngx_rtmp_play_main_conf_t *pmcf; + ngx_rtmp_play_fmt_t **pfmt, *fmt; + + pmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_play_module); + + pfmt = ngx_array_push(&pmcf->fmts); + + if (pfmt == NULL) { + return NGX_ERROR; + } + + fmt = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_play_fmt_t)); + + if (fmt == NULL) { + return NGX_ERROR; + } + + *pfmt = fmt; + + ngx_str_set(&fmt->name, "flv-format"); + + ngx_str_null(&fmt->pfx); /* default fmt */ + ngx_str_set(&fmt->sfx, ".flv"); + + fmt->init = ngx_rtmp_flv_init; + fmt->start = ngx_rtmp_flv_start; + fmt->seek = ngx_rtmp_flv_seek; + fmt->stop = ngx_rtmp_flv_stop; + fmt->send = ngx_rtmp_flv_send; + + return NGX_OK; +} diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp.h nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp.h --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp.h 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp.h 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,622 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_H_INCLUDED_ +#define _NGX_RTMP_H_INCLUDED_ + + +#include +#include +#include +#include +#include + +#include "ngx_rtmp_amf.h" +#include "ngx_rtmp_bandwidth.h" + + +#if (NGX_WIN32) +typedef __int8 int8_t; +typedef unsigned __int8 uint8_t; +#endif + + +typedef struct { + void **main_conf; + void **srv_conf; + void **app_conf; +} ngx_rtmp_conf_ctx_t; + + +typedef struct { + u_char sockaddr[NGX_SOCKADDRLEN]; + socklen_t socklen; + + /* server ctx */ + ngx_rtmp_conf_ctx_t *ctx; + + unsigned bind:1; + unsigned wildcard:1; +#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY) + unsigned ipv6only:2; +#endif + unsigned so_keepalive:2; + unsigned proxy_protocol:1; +#if (NGX_HAVE_KEEPALIVE_TUNABLE) + int tcp_keepidle; + int tcp_keepintvl; + int tcp_keepcnt; +#endif +} ngx_rtmp_listen_t; + + +typedef struct { + ngx_rtmp_conf_ctx_t *ctx; + ngx_str_t addr_text; + unsigned proxy_protocol:1; +} ngx_rtmp_addr_conf_t; + +typedef struct { + in_addr_t addr; + ngx_rtmp_addr_conf_t conf; +} ngx_rtmp_in_addr_t; + + +#if (NGX_HAVE_INET6) + +typedef struct { + struct in6_addr addr6; + ngx_rtmp_addr_conf_t conf; +} ngx_rtmp_in6_addr_t; + +#endif + + +typedef struct { + void *addrs; + ngx_uint_t naddrs; +} ngx_rtmp_port_t; + + +typedef struct { + int family; + in_port_t port; + ngx_array_t addrs; /* array of ngx_rtmp_conf_addr_t */ +} ngx_rtmp_conf_port_t; + + +typedef struct { + struct sockaddr *sockaddr; + socklen_t socklen; + + ngx_rtmp_conf_ctx_t *ctx; + + unsigned bind:1; + unsigned wildcard:1; +#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY) + unsigned ipv6only:2; +#endif + unsigned so_keepalive:2; + unsigned proxy_protocol:1; +#if (NGX_HAVE_KEEPALIVE_TUNABLE) + int tcp_keepidle; + int tcp_keepintvl; + int tcp_keepcnt; +#endif +} ngx_rtmp_conf_addr_t; + + +#define NGX_RTMP_VERSION 3 + +#define NGX_LOG_DEBUG_RTMP NGX_LOG_DEBUG_CORE + +#define NGX_RTMP_DEFAULT_CHUNK_SIZE 128 + + +/* RTMP message types */ +#define NGX_RTMP_MSG_CHUNK_SIZE 1 +#define NGX_RTMP_MSG_ABORT 2 +#define NGX_RTMP_MSG_ACK 3 +#define NGX_RTMP_MSG_USER 4 +#define NGX_RTMP_MSG_ACK_SIZE 5 +#define NGX_RTMP_MSG_BANDWIDTH 6 +#define NGX_RTMP_MSG_EDGE 7 +#define NGX_RTMP_MSG_AUDIO 8 +#define NGX_RTMP_MSG_VIDEO 9 +#define NGX_RTMP_MSG_AMF3_META 15 +#define NGX_RTMP_MSG_AMF3_SHARED 16 +#define NGX_RTMP_MSG_AMF3_CMD 17 +#define NGX_RTMP_MSG_AMF_META 18 +#define NGX_RTMP_MSG_AMF_SHARED 19 +#define NGX_RTMP_MSG_AMF_CMD 20 +#define NGX_RTMP_MSG_AGGREGATE 22 +#define NGX_RTMP_MSG_MAX 22 + +#define NGX_RTMP_CONNECT NGX_RTMP_MSG_MAX + 1 +#define NGX_RTMP_DISCONNECT NGX_RTMP_MSG_MAX + 2 +#define NGX_RTMP_HANDSHAKE_DONE NGX_RTMP_MSG_MAX + 3 +#define NGX_RTMP_MAX_EVENT NGX_RTMP_MSG_MAX + 4 + + +/* RMTP control message types */ +#define NGX_RTMP_USER_STREAM_BEGIN 0 +#define NGX_RTMP_USER_STREAM_EOF 1 +#define NGX_RTMP_USER_STREAM_DRY 2 +#define NGX_RTMP_USER_SET_BUFLEN 3 +#define NGX_RTMP_USER_RECORDED 4 +#define NGX_RTMP_USER_PING_REQUEST 6 +#define NGX_RTMP_USER_PING_RESPONSE 7 +#define NGX_RTMP_USER_UNKNOWN 8 +#define NGX_RTMP_USER_BUFFER_END 31 + + +/* Chunk header: + * max 3 basic header + * + max 11 message header + * + max 4 extended header (timestamp) */ +#define NGX_RTMP_MAX_CHUNK_HEADER 18 + + +typedef struct { + uint32_t csid; /* chunk stream id */ + uint32_t timestamp; /* timestamp (delta) */ + uint32_t mlen; /* message length */ + uint8_t type; /* message type id */ + uint32_t msid; /* message stream id */ +} ngx_rtmp_header_t; + + +typedef struct { + ngx_rtmp_header_t hdr; + uint32_t dtime; + uint32_t len; /* current fragment length */ + uint8_t ext; + ngx_chain_t *in; +} ngx_rtmp_stream_t; + + +/* disable zero-sized array warning by msvc */ + +#if (NGX_WIN32) +#pragma warning(push) +#pragma warning(disable:4200) +#endif + + +typedef struct { + uint32_t signature; /* "RTMP" */ /* <-- FIXME wtf */ + + ngx_event_t close; + + void **ctx; + void **main_conf; + void **srv_conf; + void **app_conf; + + ngx_str_t *addr_text; + int connected; + +#if (nginx_version >= 1007005) + ngx_queue_t posted_dry_events; +#else + ngx_event_t *posted_dry_events; +#endif + + /* client buffer time in msec */ + uint32_t buflen; + uint32_t ack_size; + + /* connection parameters */ + ngx_str_t app; + ngx_str_t args; + ngx_str_t flashver; + ngx_str_t swf_url; + ngx_str_t tc_url; + uint32_t acodecs; + uint32_t vcodecs; + ngx_str_t page_url; + + /* handshake data */ + ngx_buf_t *hs_buf; + u_char *hs_digest; + unsigned hs_old:1; + ngx_uint_t hs_stage; + + /* connection timestamps */ + ngx_msec_t epoch; + ngx_msec_t peer_epoch; + ngx_msec_t base_time; + uint32_t current_time; + + /* ping */ + ngx_event_t ping_evt; + unsigned ping_active:1; + unsigned ping_reset:1; + + /* auto-pushed? */ + unsigned auto_pushed:1; + unsigned relay:1; + unsigned static_relay:1; + + /* input stream 0 (reserved by RTMP spec) + * is used as free chain link */ + + ngx_rtmp_stream_t *in_streams; + uint32_t in_csid; + ngx_uint_t in_chunk_size; + ngx_pool_t *in_pool; + uint32_t in_bytes; + uint32_t in_last_ack; + + ngx_pool_t *in_old_pool; + ngx_int_t in_chunk_size_changing; + + ngx_connection_t *connection; + + /* circular buffer of RTMP message pointers */ + ngx_msec_t timeout; + uint32_t out_bytes; + size_t out_pos, out_last; + ngx_chain_t *out_chain; + u_char *out_bpos; + unsigned out_buffer:1; + size_t out_queue; + size_t out_cork; + ngx_chain_t *out[0]; +} ngx_rtmp_session_t; + + +#if (NGX_WIN32) +#pragma warning(pop) +#endif + + +/* handler result code: + * NGX_ERROR - error + * NGX_OK - success, may continue + * NGX_DONE - success, input parsed, reply sent; need no + * more calls on this event */ +typedef ngx_int_t (*ngx_rtmp_handler_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_header_t *h, ngx_chain_t *in); + + +typedef struct { + ngx_str_t name; + ngx_rtmp_handler_pt handler; +} ngx_rtmp_amf_handler_t; + + +typedef struct { + ngx_array_t servers; /* ngx_rtmp_core_srv_conf_t */ + ngx_array_t listen; /* ngx_rtmp_listen_t */ + + ngx_array_t events[NGX_RTMP_MAX_EVENT]; + + ngx_hash_t amf_hash; + ngx_array_t amf_arrays; + ngx_array_t amf; +} ngx_rtmp_core_main_conf_t; + + +/* global main conf for stats */ +extern ngx_rtmp_core_main_conf_t *ngx_rtmp_core_main_conf; + + +typedef struct ngx_rtmp_core_srv_conf_s { + ngx_array_t applications; /* ngx_rtmp_core_app_conf_t */ + + ngx_msec_t timeout; + ngx_msec_t ping; + ngx_msec_t ping_timeout; + ngx_flag_t so_keepalive; + ngx_int_t max_streams; + + ngx_uint_t ack_window; + + ngx_int_t chunk_size; + ngx_pool_t *pool; + ngx_chain_t *free; + ngx_chain_t *free_hs; + size_t max_message; + ngx_flag_t play_time_fix; + ngx_flag_t publish_time_fix; + ngx_flag_t busy; + size_t out_queue; + size_t out_cork; + ngx_msec_t buflen; + + ngx_rtmp_conf_ctx_t *ctx; +} ngx_rtmp_core_srv_conf_t; + + +typedef struct { + ngx_array_t applications; /* ngx_rtmp_core_app_conf_t */ + ngx_str_t name; + void **app_conf; +} ngx_rtmp_core_app_conf_t; + + +typedef struct { + ngx_str_t *client; + ngx_rtmp_session_t *session; +} ngx_rtmp_error_log_ctx_t; + + +typedef struct { + ngx_int_t (*preconfiguration)(ngx_conf_t *cf); + ngx_int_t (*postconfiguration)(ngx_conf_t *cf); + + void *(*create_main_conf)(ngx_conf_t *cf); + char *(*init_main_conf)(ngx_conf_t *cf, void *conf); + + void *(*create_srv_conf)(ngx_conf_t *cf); + char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, + void *conf); + + void *(*create_app_conf)(ngx_conf_t *cf); + char *(*merge_app_conf)(ngx_conf_t *cf, void *prev, + void *conf); +} ngx_rtmp_module_t; + +#define NGX_RTMP_MODULE 0x504D5452 /* "RTMP" */ + +#define NGX_RTMP_MAIN_CONF 0x02000000 +#define NGX_RTMP_SRV_CONF 0x04000000 +#define NGX_RTMP_APP_CONF 0x08000000 +#define NGX_RTMP_REC_CONF 0x10000000 + + +#define NGX_RTMP_MAIN_CONF_OFFSET offsetof(ngx_rtmp_conf_ctx_t, main_conf) +#define NGX_RTMP_SRV_CONF_OFFSET offsetof(ngx_rtmp_conf_ctx_t, srv_conf) +#define NGX_RTMP_APP_CONF_OFFSET offsetof(ngx_rtmp_conf_ctx_t, app_conf) + + +#define ngx_rtmp_get_module_ctx(s, module) (s)->ctx[module.ctx_index] +#define ngx_rtmp_set_ctx(s, c, module) s->ctx[module.ctx_index] = c; +#define ngx_rtmp_delete_ctx(s, module) s->ctx[module.ctx_index] = NULL; + + +#define ngx_rtmp_get_module_main_conf(s, module) \ + (s)->main_conf[module.ctx_index] +#define ngx_rtmp_get_module_srv_conf(s, module) (s)->srv_conf[module.ctx_index] +#define ngx_rtmp_get_module_app_conf(s, module) ((s)->app_conf ? \ + (s)->app_conf[module.ctx_index] : NULL) + +#define ngx_rtmp_conf_get_module_main_conf(cf, module) \ + ((ngx_rtmp_conf_ctx_t *) cf->ctx)->main_conf[module.ctx_index] +#define ngx_rtmp_conf_get_module_srv_conf(cf, module) \ + ((ngx_rtmp_conf_ctx_t *) cf->ctx)->srv_conf[module.ctx_index] +#define ngx_rtmp_conf_get_module_app_conf(cf, module) \ + ((ngx_rtmp_conf_ctx_t *) cf->ctx)->app_conf[module.ctx_index] + + +#ifdef NGX_DEBUG +char* ngx_rtmp_message_type(uint8_t type); +char* ngx_rtmp_user_message_type(uint16_t evt); +#endif + +void ngx_rtmp_init_connection(ngx_connection_t *c); +ngx_rtmp_session_t * ngx_rtmp_init_session(ngx_connection_t *c, + ngx_rtmp_addr_conf_t *addr_conf); +void ngx_rtmp_finalize_session(ngx_rtmp_session_t *s); +void ngx_rtmp_handshake(ngx_rtmp_session_t *s); +void ngx_rtmp_client_handshake(ngx_rtmp_session_t *s, unsigned async); +void ngx_rtmp_free_handshake_buffers(ngx_rtmp_session_t *s); +void ngx_rtmp_cycle(ngx_rtmp_session_t *s); +void ngx_rtmp_reset_ping(ngx_rtmp_session_t *s); +ngx_int_t ngx_rtmp_fire_event(ngx_rtmp_session_t *s, ngx_uint_t evt, + ngx_rtmp_header_t *h, ngx_chain_t *in); + + +ngx_int_t ngx_rtmp_set_chunk_size(ngx_rtmp_session_t *s, ngx_uint_t size); + + +/* Bit reverse: we need big-endians in many places */ +void * ngx_rtmp_rmemcpy(void *dst, const void* src, size_t n); + +#define ngx_rtmp_rcpymem(dst, src, n) \ + (((u_char*)ngx_rtmp_rmemcpy(dst, src, n)) + (n)) + + +static ngx_inline uint16_t +ngx_rtmp_r16(uint16_t n) +{ + return (n << 8) | (n >> 8); +} + + +static ngx_inline uint32_t +ngx_rtmp_r32(uint32_t n) +{ + return (n << 24) | ((n << 8) & 0xff0000) | ((n >> 8) & 0xff00) | (n >> 24); +} + + +static ngx_inline uint64_t +ngx_rtmp_r64(uint64_t n) +{ + return (uint64_t) ngx_rtmp_r32((uint32_t) n) << 32 | + ngx_rtmp_r32((uint32_t) (n >> 32)); +} + + +/* Receiving messages */ +ngx_int_t ngx_rtmp_receive_message(ngx_rtmp_session_t *s, + ngx_rtmp_header_t *h, ngx_chain_t *in); +ngx_int_t ngx_rtmp_protocol_message_handler(ngx_rtmp_session_t *s, + ngx_rtmp_header_t *h, ngx_chain_t *in); +ngx_int_t ngx_rtmp_user_message_handler(ngx_rtmp_session_t *s, + ngx_rtmp_header_t *h, ngx_chain_t *in); +ngx_int_t ngx_rtmp_aggregate_message_handler(ngx_rtmp_session_t *s, + ngx_rtmp_header_t *h, ngx_chain_t *in); +ngx_int_t ngx_rtmp_amf_message_handler(ngx_rtmp_session_t *s, + ngx_rtmp_header_t *h, ngx_chain_t *in); +ngx_int_t ngx_rtmp_amf_shared_object_handler(ngx_rtmp_session_t *s, + ngx_rtmp_header_t *h, ngx_chain_t *in); + + +/* Shared output buffers */ + +/* Store refcount in negative bytes of shared buffer */ + +#define NGX_RTMP_REFCOUNT_TYPE uint32_t +#define NGX_RTMP_REFCOUNT_BYTES sizeof(NGX_RTMP_REFCOUNT_TYPE) + +#define ngx_rtmp_ref(b) \ + *((NGX_RTMP_REFCOUNT_TYPE*)(b) - 1) + +#define ngx_rtmp_ref_set(b, v) \ + ngx_rtmp_ref(b) = v + +#define ngx_rtmp_ref_get(b) \ + ++ngx_rtmp_ref(b) + +#define ngx_rtmp_ref_put(b) \ + --ngx_rtmp_ref(b) + +ngx_chain_t * ngx_rtmp_alloc_shared_buf(ngx_rtmp_core_srv_conf_t *cscf); +void ngx_rtmp_free_shared_chain(ngx_rtmp_core_srv_conf_t *cscf, + ngx_chain_t *in); +ngx_chain_t * ngx_rtmp_append_shared_bufs(ngx_rtmp_core_srv_conf_t *cscf, + ngx_chain_t *head, ngx_chain_t *in); + +#define ngx_rtmp_acquire_shared_chain(in) \ + ngx_rtmp_ref_get(in); \ + + +/* Sending messages */ +void ngx_rtmp_prepare_message(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_rtmp_header_t *lh, ngx_chain_t *out); +ngx_int_t ngx_rtmp_send_message(ngx_rtmp_session_t *s, ngx_chain_t *out, + ngx_uint_t priority); + +/* Note on priorities: + * the bigger value the lower the priority. + * priority=0 is the highest */ + + +#define NGX_RTMP_LIMIT_SOFT 0 +#define NGX_RTMP_LIMIT_HARD 1 +#define NGX_RTMP_LIMIT_DYNAMIC 2 + +/* Protocol control messages */ +ngx_chain_t * ngx_rtmp_create_chunk_size(ngx_rtmp_session_t *s, + uint32_t chunk_size); +ngx_chain_t * ngx_rtmp_create_abort(ngx_rtmp_session_t *s, + uint32_t csid); +ngx_chain_t * ngx_rtmp_create_ack(ngx_rtmp_session_t *s, + uint32_t seq); +ngx_chain_t * ngx_rtmp_create_ack_size(ngx_rtmp_session_t *s, + uint32_t ack_size); +ngx_chain_t * ngx_rtmp_create_bandwidth(ngx_rtmp_session_t *s, + uint32_t ack_size, uint8_t limit_type); + +ngx_int_t ngx_rtmp_send_chunk_size(ngx_rtmp_session_t *s, + uint32_t chunk_size); +ngx_int_t ngx_rtmp_send_abort(ngx_rtmp_session_t *s, + uint32_t csid); +ngx_int_t ngx_rtmp_send_ack(ngx_rtmp_session_t *s, + uint32_t seq); +ngx_int_t ngx_rtmp_send_ack_size(ngx_rtmp_session_t *s, + uint32_t ack_size); +ngx_int_t ngx_rtmp_send_bandwidth(ngx_rtmp_session_t *s, + uint32_t ack_size, uint8_t limit_type); + +/* User control messages */ +ngx_chain_t * ngx_rtmp_create_stream_begin(ngx_rtmp_session_t *s, + uint32_t msid); +ngx_chain_t * ngx_rtmp_create_stream_eof(ngx_rtmp_session_t *s, + uint32_t msid); +ngx_chain_t * ngx_rtmp_create_stream_dry(ngx_rtmp_session_t *s, + uint32_t msid); +ngx_chain_t * ngx_rtmp_create_set_buflen(ngx_rtmp_session_t *s, + uint32_t msid, uint32_t buflen_msec); +ngx_chain_t * ngx_rtmp_create_recorded(ngx_rtmp_session_t *s, + uint32_t msid); +ngx_chain_t * ngx_rtmp_create_ping_request(ngx_rtmp_session_t *s, + uint32_t timestamp); +ngx_chain_t * ngx_rtmp_create_ping_response(ngx_rtmp_session_t *s, + uint32_t timestamp); + +ngx_int_t ngx_rtmp_send_stream_begin(ngx_rtmp_session_t *s, + uint32_t msid); +ngx_int_t ngx_rtmp_send_stream_eof(ngx_rtmp_session_t *s, + uint32_t msid); +ngx_int_t ngx_rtmp_send_stream_dry(ngx_rtmp_session_t *s, + uint32_t msid); +ngx_int_t ngx_rtmp_send_set_buflen(ngx_rtmp_session_t *s, + uint32_t msid, uint32_t buflen_msec); +ngx_int_t ngx_rtmp_send_recorded(ngx_rtmp_session_t *s, + uint32_t msid); +ngx_int_t ngx_rtmp_send_ping_request(ngx_rtmp_session_t *s, + uint32_t timestamp); +ngx_int_t ngx_rtmp_send_ping_response(ngx_rtmp_session_t *s, + uint32_t timestamp); + +/* AMF sender/receiver */ +ngx_int_t ngx_rtmp_append_amf(ngx_rtmp_session_t *s, + ngx_chain_t **first, ngx_chain_t **last, + ngx_rtmp_amf_elt_t *elts, size_t nelts); +ngx_int_t ngx_rtmp_receive_amf(ngx_rtmp_session_t *s, ngx_chain_t *in, + ngx_rtmp_amf_elt_t *elts, size_t nelts); + +ngx_chain_t * ngx_rtmp_create_amf(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_rtmp_amf_elt_t *elts, size_t nelts); +ngx_int_t ngx_rtmp_send_amf(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_rtmp_amf_elt_t *elts, size_t nelts); + +/* AMF status sender */ +ngx_chain_t * ngx_rtmp_create_status(ngx_rtmp_session_t *s, char *code, + char* level, char *desc); +ngx_chain_t * ngx_rtmp_create_play_status(ngx_rtmp_session_t *s, char *code, + char* level, ngx_uint_t duration, ngx_uint_t bytes); +ngx_chain_t * ngx_rtmp_create_sample_access(ngx_rtmp_session_t *s); + +ngx_int_t ngx_rtmp_send_status(ngx_rtmp_session_t *s, char *code, + char* level, char *desc); +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); + + +/* Frame types */ +#define NGX_RTMP_VIDEO_KEY_FRAME 1 +#define NGX_RTMP_VIDEO_INTER_FRAME 2 +#define NGX_RTMP_VIDEO_DISPOSABLE_FRAME 3 + + +static ngx_inline ngx_int_t +ngx_rtmp_get_video_frame_type(ngx_chain_t *in) +{ + return (in->buf->pos[0] & 0xf0) >> 4; +} + + +static ngx_inline ngx_int_t +ngx_rtmp_is_codec_header(ngx_chain_t *in) +{ + return in->buf->pos + 1 < in->buf->last && in->buf->pos[1] == 0; +} + + +extern ngx_rtmp_bandwidth_t ngx_rtmp_bw_out; +extern ngx_rtmp_bandwidth_t ngx_rtmp_bw_in; + + +extern ngx_uint_t ngx_rtmp_naccepted; +#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; +#endif + +extern ngx_uint_t ngx_rtmp_max_module; +extern ngx_module_t ngx_rtmp_core_module; + + +#endif /* _NGX_RTMP_H_INCLUDED_ */ diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_handler.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_handler.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_handler.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_handler.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,895 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rtmp_amf.h" + + +static void ngx_rtmp_recv(ngx_event_t *rev); +static void ngx_rtmp_send(ngx_event_t *rev); +static void ngx_rtmp_ping(ngx_event_t *rev); +static ngx_int_t ngx_rtmp_finalize_set_chunk_size(ngx_rtmp_session_t *s); + + +ngx_uint_t ngx_rtmp_naccepted; + + +ngx_rtmp_bandwidth_t ngx_rtmp_bw_out; +ngx_rtmp_bandwidth_t ngx_rtmp_bw_in; + + +#ifdef NGX_DEBUG +char* +ngx_rtmp_message_type(uint8_t type) +{ + static char* types[] = { + "?", + "chunk_size", + "abort", + "ack", + "user", + "ack_size", + "bandwidth", + "edge", + "audio", + "video", + "?", + "?", + "?", + "?", + "?", + "amf3_meta", + "amf3_shared", + "amf3_cmd", + "amf_meta", + "amf_shared", + "amf_cmd", + "?", + "aggregate" + }; + + return type < sizeof(types) / sizeof(types[0]) + ? types[type] + : "?"; +} + + +char* +ngx_rtmp_user_message_type(uint16_t evt) +{ + static char* evts[] = { + "stream_begin", + "stream_eof", + "stream dry", + "set_buflen", + "recorded", + "", + "ping_request", + "ping_response", + }; + + return evt < sizeof(evts) / sizeof(evts[0]) + ? evts[evt] + : "?"; +} +#endif + + +void +ngx_rtmp_cycle(ngx_rtmp_session_t *s) +{ + ngx_connection_t *c; + + c = s->connection; + c->read->handler = ngx_rtmp_recv; + c->write->handler = ngx_rtmp_send; + + s->ping_evt.data = c; + s->ping_evt.log = c->log; + s->ping_evt.handler = ngx_rtmp_ping; + ngx_rtmp_reset_ping(s); + + ngx_rtmp_recv(c->read); +} + + +static ngx_chain_t * +ngx_rtmp_alloc_in_buf(ngx_rtmp_session_t *s) +{ + ngx_chain_t *cl; + ngx_buf_t *b; + size_t size; + + if ((cl = ngx_alloc_chain_link(s->in_pool)) == NULL + || (cl->buf = ngx_calloc_buf(s->in_pool)) == NULL) + { + return NULL; + } + + cl->next = NULL; + b = cl->buf; + size = s->in_chunk_size + NGX_RTMP_MAX_CHUNK_HEADER; + + b->start = b->last = b->pos = ngx_palloc(s->in_pool, size); + if (b->start == NULL) { + return NULL; + } + b->end = b->start + size; + + return cl; +} + + +void +ngx_rtmp_reset_ping(ngx_rtmp_session_t *s) +{ + ngx_rtmp_core_srv_conf_t *cscf; + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + if (cscf->ping == 0) { + return; + } + + s->ping_active = 0; + s->ping_reset = 0; + ngx_add_timer(&s->ping_evt, cscf->ping); + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "ping: wait %Mms", cscf->ping); +} + + +static void +ngx_rtmp_ping(ngx_event_t *pev) +{ + ngx_connection_t *c; + ngx_rtmp_session_t *s; + ngx_rtmp_core_srv_conf_t *cscf; + + c = pev->data; + s = c->data; + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + /* i/o event has happened; no need to ping */ + if (s->ping_reset) { + ngx_rtmp_reset_ping(s); + return; + } + + if (s->ping_active) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "ping: unresponded"); + ngx_rtmp_finalize_session(s); + return; + } + + if (cscf->busy) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "ping: not busy between pings"); + ngx_rtmp_finalize_session(s); + return; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "ping: schedule %Mms", cscf->ping_timeout); + + if (ngx_rtmp_send_ping_request(s, (uint32_t)ngx_current_msec) != NGX_OK) { + ngx_rtmp_finalize_session(s); + return; + } + + s->ping_active = 1; + ngx_add_timer(pev, cscf->ping_timeout); +} + + +static void +ngx_rtmp_recv(ngx_event_t *rev) +{ + ngx_int_t n; + ngx_connection_t *c; + ngx_rtmp_session_t *s; + ngx_rtmp_core_srv_conf_t *cscf; + ngx_rtmp_header_t *h; + ngx_rtmp_stream_t *st, *st0; + ngx_chain_t *in, *head; + ngx_buf_t *b; + u_char *p, *pp, *old_pos; + size_t size, fsize, old_size; + uint8_t fmt, ext; + uint32_t csid, timestamp; + + c = rev->data; + s = c->data; + b = NULL; + old_pos = NULL; + old_size = 0; + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + if (c->destroyed) { + return; + } + + for( ;; ) { + + st = &s->in_streams[s->in_csid]; + + /* allocate new buffer */ + if (st->in == NULL) { + st->in = ngx_rtmp_alloc_in_buf(s); + if (st->in == NULL) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "in buf alloc failed"); + ngx_rtmp_finalize_session(s); + return; + } + } + + h = &st->hdr; + in = st->in; + b = in->buf; + + if (old_size) { + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, c->log, 0, + "reusing formerly read data: %d", old_size); + + b->pos = b->start; + b->last = ngx_movemem(b->pos, old_pos, old_size); + + if (s->in_chunk_size_changing) { + ngx_rtmp_finalize_set_chunk_size(s); + } + + } else { + + if (old_pos) { + b->pos = b->last = b->start; + } + + n = c->recv(c, b->last, b->end - b->last); + + if (n == NGX_ERROR || n == 0) { + ngx_rtmp_finalize_session(s); + return; + } + + if (n == NGX_AGAIN) { + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + ngx_rtmp_finalize_session(s); + } + return; + } + + s->ping_reset = 1; + ngx_rtmp_update_bandwidth(&ngx_rtmp_bw_in, n); + b->last += n; + s->in_bytes += n; + + if (s->in_bytes >= 0xf0000000) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, c->log, 0, + "resetting byte counter"); + s->in_bytes = 0; + s->in_last_ack = 0; + } + + if (s->ack_size && s->in_bytes - s->in_last_ack >= s->ack_size) { + + s->in_last_ack = s->in_bytes; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, c->log, 0, + "sending RTMP ACK(%uD)", s->in_bytes); + + if (ngx_rtmp_send_ack(s, s->in_bytes)) { + ngx_rtmp_finalize_session(s); + return; + } + } + } + + old_pos = NULL; + old_size = 0; + + /* parse headers */ + if (b->pos == b->start) { + p = b->pos; + + /* chunk basic header */ + fmt = (*p >> 6) & 0x03; + csid = *p++ & 0x3f; + + if (csid == 0) { + if (b->last - p < 1) + continue; + csid = 64; + csid += *(uint8_t*)p++; + + } else if (csid == 1) { + if (b->last - p < 2) + continue; + csid = 64; + csid += *(uint8_t*)p++; + csid += (uint32_t)256 * (*(uint8_t*)p++); + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, c->log, 0, + "RTMP bheader fmt=%d csid=%D", + (int)fmt, csid); + + if (csid >= (uint32_t)cscf->max_streams) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "RTMP in chunk stream too big: %D >= %D", + csid, cscf->max_streams); + ngx_rtmp_finalize_session(s); + return; + } + + /* link orphan */ + if (s->in_csid == 0) { + + /* unlink from stream #0 */ + st->in = st->in->next; + + /* link to new stream */ + s->in_csid = csid; + st = &s->in_streams[csid]; + if (st->in == NULL) { + in->next = in; + } else { + in->next = st->in->next; + st->in->next = in; + } + st->in = in; + h = &st->hdr; + h->csid = csid; + } + + ext = st->ext; + timestamp = st->dtime; + if (fmt <= 2 ) { + if (b->last - p < 3) + continue; + /* timestamp: + * big-endian 3b -> little-endian 4b */ + pp = (u_char*)×tamp; + pp[2] = *p++; + pp[1] = *p++; + pp[0] = *p++; + pp[3] = 0; + + ext = (timestamp == 0x00ffffff); + + if (fmt <= 1) { + if (b->last - p < 4) + continue; + /* size: + * big-endian 3b -> little-endian 4b + * type: + * 1b -> 1b*/ + pp = (u_char*)&h->mlen; + pp[2] = *p++; + pp[1] = *p++; + pp[0] = *p++; + pp[3] = 0; + h->type = *(uint8_t*)p++; + + if (fmt == 0) { + if (b->last - p < 4) + continue; + /* stream: + * little-endian 4b -> little-endian 4b */ + pp = (u_char*)&h->msid; + pp[0] = *p++; + pp[1] = *p++; + pp[2] = *p++; + pp[3] = *p++; + } + } + } + + /* extended header */ + if (ext) { + if (b->last - p < 4) + continue; + pp = (u_char*)×tamp; + pp[3] = *p++; + pp[2] = *p++; + pp[1] = *p++; + pp[0] = *p++; + } + + if (st->len == 0) { + /* Messages with type=3 should + * never have ext timestamp field + * according to standard. + * However that's not always the case + * in real life */ + st->ext = (ext && cscf->publish_time_fix); + if (fmt) { + st->dtime = timestamp; + } else { + h->timestamp = timestamp; + st->dtime = 0; + } + } + + ngx_log_debug8(NGX_LOG_DEBUG_RTMP, c->log, 0, + "RTMP mheader fmt=%d %s (%d) " + "time=%uD+%uD mlen=%D len=%D msid=%D", + (int)fmt, ngx_rtmp_message_type(h->type), (int)h->type, + h->timestamp, st->dtime, h->mlen, st->len, h->msid); + + /* header done */ + b->pos = p; + + if (h->mlen > cscf->max_message) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "too big message: %uz", cscf->max_message); + ngx_rtmp_finalize_session(s); + return; + } + } + + size = b->last - b->pos; + fsize = h->mlen - st->len; + + if (size < ngx_min(fsize, s->in_chunk_size)) + continue; + + /* buffer is ready */ + + if (fsize > s->in_chunk_size) { + /* collect fragmented chunks */ + st->len += s->in_chunk_size; + b->last = b->pos + s->in_chunk_size; + old_pos = b->last; + old_size = size - s->in_chunk_size; + + } else { + /* handle! */ + head = st->in->next; + st->in->next = NULL; + b->last = b->pos + fsize; + old_pos = b->last; + old_size = size - fsize; + st->len = 0; + h->timestamp += st->dtime; + + if (ngx_rtmp_receive_message(s, h, head) != NGX_OK) { + ngx_rtmp_finalize_session(s); + return; + } + + if (s->in_chunk_size_changing) { + /* copy old data to a new buffer */ + if (!old_size) { + ngx_rtmp_finalize_set_chunk_size(s); + } + + } else { + /* add used bufs to stream #0 */ + st0 = &s->in_streams[0]; + st->in->next = st0->in; + st0->in = head; + st->in = NULL; + } + } + + s->in_csid = 0; + } +} + + +static void +ngx_rtmp_send(ngx_event_t *wev) +{ + ngx_connection_t *c; + ngx_rtmp_session_t *s; + ngx_int_t n; + ngx_rtmp_core_srv_conf_t *cscf; + + c = wev->data; + s = c->data; + + if (c->destroyed) { + return; + } + + if (wev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, + "client timed out"); + c->timedout = 1; + ngx_rtmp_finalize_session(s); + return; + } + + if (wev->timer_set) { + ngx_del_timer(wev); + } + + if (s->out_chain == NULL && s->out_pos != s->out_last) { + s->out_chain = s->out[s->out_pos]; + s->out_bpos = s->out_chain->buf->pos; + } + + while (s->out_chain) { + n = c->send(c, s->out_bpos, s->out_chain->buf->last - s->out_bpos); + + if (n == NGX_AGAIN || n == 0) { + ngx_add_timer(c->write, s->timeout); + if (ngx_handle_write_event(c->write, 0) != NGX_OK) { + ngx_rtmp_finalize_session(s); + } + return; + } + + if (n < 0) { + ngx_rtmp_finalize_session(s); + return; + } + + s->out_bytes += n; + s->ping_reset = 1; + ngx_rtmp_update_bandwidth(&ngx_rtmp_bw_out, n); + s->out_bpos += n; + if (s->out_bpos == s->out_chain->buf->last) { + s->out_chain = s->out_chain->next; + if (s->out_chain == NULL) { + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + ngx_rtmp_free_shared_chain(cscf, s->out[s->out_pos]); + ++s->out_pos; + s->out_pos %= s->out_queue; + if (s->out_pos == s->out_last) { + break; + } + s->out_chain = s->out[s->out_pos]; + } + s->out_bpos = s->out_chain->buf->pos; + } + } + + if (wev->active) { + ngx_del_event(wev, NGX_WRITE_EVENT, 0); + } + + ngx_event_process_posted((ngx_cycle_t *) ngx_cycle, &s->posted_dry_events); +} + + +void +ngx_rtmp_prepare_message(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_rtmp_header_t *lh, ngx_chain_t *out) +{ + ngx_chain_t *l; + u_char *p, *pp; + ngx_int_t hsize, thsize, nbufs; + uint32_t mlen, timestamp, ext_timestamp; + static uint8_t hdrsize[] = { 12, 8, 4, 1 }; + u_char th[7]; + ngx_rtmp_core_srv_conf_t *cscf; + uint8_t fmt; + ngx_connection_t *c; + + c = s->connection; + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + if (h->csid >= (uint32_t)cscf->max_streams) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "RTMP out chunk stream too big: %D >= %D", + h->csid, cscf->max_streams); + ngx_rtmp_finalize_session(s); + return; + } + + /* detect packet size */ + mlen = 0; + nbufs = 0; + for(l = out; l; l = l->next) { + mlen += (l->buf->last - l->buf->pos); + ++nbufs; + } + + fmt = 0; + if (lh && lh->csid && h->msid == lh->msid) { + ++fmt; + if (h->type == lh->type && mlen && mlen == lh->mlen) { + ++fmt; + if (h->timestamp == lh->timestamp) { + ++fmt; + } + } + timestamp = h->timestamp - lh->timestamp; + } else { + timestamp = h->timestamp; + } + + /*if (lh) { + *lh = *h; + lh->mlen = mlen; + }*/ + + hsize = hdrsize[fmt]; + + ngx_log_debug8(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "RTMP prep %s (%d) fmt=%d csid=%uD timestamp=%uD " + "mlen=%uD msid=%uD nbufs=%d", + ngx_rtmp_message_type(h->type), (int)h->type, (int)fmt, + h->csid, timestamp, mlen, h->msid, nbufs); + + ext_timestamp = 0; + if (timestamp >= 0x00ffffff) { + ext_timestamp = timestamp; + timestamp = 0x00ffffff; + hsize += 4; + } + + if (h->csid >= 64) { + ++hsize; + if (h->csid >= 320) { + ++hsize; + } + } + + /* fill initial header */ + out->buf->pos -= hsize; + p = out->buf->pos; + + /* basic header */ + *p = (fmt << 6); + if (h->csid >= 2 && h->csid <= 63) { + *p++ |= (((uint8_t)h->csid) & 0x3f); + } else if (h->csid >= 64 && h->csid < 320) { + ++p; + *p++ = (uint8_t)(h->csid - 64); + } else { + *p++ |= 1; + *p++ = (uint8_t)(h->csid - 64); + *p++ = (uint8_t)((h->csid - 64) >> 8); + } + + /* create fmt3 header for successive fragments */ + thsize = p - out->buf->pos; + ngx_memcpy(th, out->buf->pos, thsize); + th[0] |= 0xc0; + + /* message header */ + if (fmt <= 2) { + pp = (u_char*)×tamp; + *p++ = pp[2]; + *p++ = pp[1]; + *p++ = pp[0]; + if (fmt <= 1) { + pp = (u_char*)&mlen; + *p++ = pp[2]; + *p++ = pp[1]; + *p++ = pp[0]; + *p++ = h->type; + if (fmt == 0) { + pp = (u_char*)&h->msid; + *p++ = pp[0]; + *p++ = pp[1]; + *p++ = pp[2]; + *p++ = pp[3]; + } + } + } + + /* extended header */ + if (ext_timestamp) { + pp = (u_char*)&ext_timestamp; + *p++ = pp[3]; + *p++ = pp[2]; + *p++ = pp[1]; + *p++ = pp[0]; + + /* This CONTRADICTS the standard + * but that's the way flash client + * wants data to be encoded; + * ffmpeg complains */ + if (cscf->play_time_fix) { + ngx_memcpy(&th[thsize], p - 4, 4); + thsize += 4; + } + } + + /* append headers to successive fragments */ + for(out = out->next; out; out = out->next) { + out->buf->pos -= thsize; + ngx_memcpy(out->buf->pos, th, thsize); + } +} + + +ngx_int_t +ngx_rtmp_send_message(ngx_rtmp_session_t *s, ngx_chain_t *out, + ngx_uint_t priority) +{ + ngx_uint_t nmsg; + + nmsg = (s->out_last - s->out_pos) % s->out_queue + 1; + + if (priority > 3) { + priority = 3; + } + + /* drop packet? + * Note we always leave 1 slot free */ + if (nmsg + priority * s->out_queue / 4 >= s->out_queue) { + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "RTMP drop message bufs=%ui, priority=%ui", + nmsg, priority); + return NGX_AGAIN; + } + + s->out[s->out_last++] = out; + s->out_last %= s->out_queue; + + ngx_rtmp_acquire_shared_chain(out); + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "RTMP send nmsg=%ui, priority=%ui #%ui", + nmsg, priority, s->out_last); + + if (priority && s->out_buffer && nmsg < s->out_cork) { + return NGX_OK; + } + + if (!s->connection->write->active) { + ngx_rtmp_send(s->connection->write); + /*return ngx_add_event(s->connection->write, NGX_WRITE_EVENT, NGX_CLEAR_EVENT);*/ + } + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_receive_message(ngx_rtmp_session_t *s, + ngx_rtmp_header_t *h, ngx_chain_t *in) +{ + ngx_rtmp_core_main_conf_t *cmcf; + ngx_array_t *evhs; + size_t n; + ngx_rtmp_handler_pt *evh; + + cmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_core_module); + +#ifdef NGX_DEBUG + { + int nbufs; + ngx_chain_t *ch; + + for(nbufs = 1, ch = in; + ch->next; + ch = ch->next, ++nbufs); + + ngx_log_debug7(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "RTMP recv %s (%d) csid=%D timestamp=%D " + "mlen=%D msid=%D nbufs=%d", + ngx_rtmp_message_type(h->type), (int)h->type, + h->csid, h->timestamp, h->mlen, h->msid, nbufs); + } +#endif + + if (h->type > NGX_RTMP_MSG_MAX) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "unexpected RTMP message type: %d", (int)h->type); + return NGX_OK; + } + + evhs = &cmcf->events[h->type]; + evh = evhs->elts; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "nhandlers: %d", evhs->nelts); + + for(n = 0; n < evhs->nelts; ++n, ++evh) { + if (!evh) { + continue; + } + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "calling handler %d", n); + + switch ((*evh)(s, h, in)) { + case NGX_ERROR: + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "handler %d failed", n); + return NGX_ERROR; + case NGX_DONE: + return NGX_OK; + } + } + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_set_chunk_size(ngx_rtmp_session_t *s, ngx_uint_t size) +{ + ngx_rtmp_core_srv_conf_t *cscf; + ngx_chain_t *li, *fli, *lo, *flo; + ngx_buf_t *bi, *bo; + ngx_int_t n; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "setting chunk_size=%ui", size); + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + s->in_old_pool = s->in_pool; + s->in_chunk_size = size; + s->in_pool = ngx_create_pool(4096, s->connection->log); + + /* copy existing chunk data */ + if (s->in_old_pool) { + s->in_chunk_size_changing = 1; + s->in_streams[0].in = NULL; + + for(n = 1; n < cscf->max_streams; ++n) { + /* stream buffer is circular + * for all streams except for the current one + * (which caused this chunk size change); + * we can simply ignore it */ + li = s->in_streams[n].in; + if (li == NULL || li->next == NULL) { + s->in_streams[n].in = NULL; + continue; + } + /* move from last to the first */ + li = li->next; + fli = li; + lo = ngx_rtmp_alloc_in_buf(s); + if (lo == NULL) { + return NGX_ERROR; + } + flo = lo; + for ( ;; ) { + bi = li->buf; + bo = lo->buf; + + if (bo->end - bo->last >= bi->last - bi->pos) { + bo->last = ngx_cpymem(bo->last, bi->pos, + bi->last - bi->pos); + li = li->next; + if (li == fli) { + lo->next = flo; + s->in_streams[n].in = lo; + break; + } + continue; + } + + bi->pos += (ngx_cpymem(bo->last, bi->pos, + bo->end - bo->last) - bo->last); + lo->next = ngx_rtmp_alloc_in_buf(s); + lo = lo->next; + if (lo == NULL) { + return NGX_ERROR; + } + } + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_finalize_set_chunk_size(ngx_rtmp_session_t *s) +{ + if (s->in_chunk_size_changing && s->in_old_pool) { + ngx_destroy_pool(s->in_old_pool); + s->in_old_pool = NULL; + s->in_chunk_size_changing = 0; + } + return NGX_OK; +} + + diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_handshake.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_handshake.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_handshake.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_handshake.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,624 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp.h" + +#include +#include + + +static void ngx_rtmp_handshake_send(ngx_event_t *wev); +static void ngx_rtmp_handshake_recv(ngx_event_t *rev); +static void ngx_rtmp_handshake_done(ngx_rtmp_session_t *s); + + +/* RTMP handshake : + * + * =peer1= =peer2= + * challenge ----> (.....[digest1]......) ----> 1537 bytes + * response <---- (...........[digest2]) <---- 1536 bytes + * + * + * - both packets contain random bytes except for digests + * - digest1 position is calculated on random packet bytes + * - digest2 is always at the end of the packet + * + * digest1: HMAC_SHA256(packet, peer1_partial_key) + * digest2: HMAC_SHA256(packet, HMAC_SHA256(digest1, peer2_full_key)) + */ + + +/* Handshake keys */ +static u_char +ngx_rtmp_server_key[] = { + 'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ', + 'F', 'l', 'a', 's', 'h', ' ', 'M', 'e', 'd', 'i', 'a', ' ', + 'S', 'e', 'r', 'v', 'e', 'r', ' ', + '0', '0', '1', + + 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, + 0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, + 0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE +}; + + +static u_char +ngx_rtmp_client_key[] = { + 'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ', + 'F', 'l', 'a', 's', 'h', ' ', 'P', 'l', 'a', 'y', 'e', 'r', ' ', + '0', '0', '1', + + 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, + 0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, + 0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE +}; + + +static const u_char +ngx_rtmp_server_version[4] = { + 0x0D, 0x0E, 0x0A, 0x0D +}; + + +static const u_char +ngx_rtmp_client_version[4] = { + 0x0C, 0x00, 0x0D, 0x0E +}; + + +#define NGX_RTMP_HANDSHAKE_KEYLEN SHA256_DIGEST_LENGTH +#define NGX_RTMP_HANDSHAKE_BUFSIZE 1537 + + +#define NGX_RTMP_HANDSHAKE_SERVER_RECV_CHALLENGE 1 +#define NGX_RTMP_HANDSHAKE_SERVER_SEND_CHALLENGE 2 +#define NGX_RTMP_HANDSHAKE_SERVER_SEND_RESPONSE 3 +#define NGX_RTMP_HANDSHAKE_SERVER_RECV_RESPONSE 4 +#define NGX_RTMP_HANDSHAKE_SERVER_DONE 5 + + +#define NGX_RTMP_HANDSHAKE_CLIENT_SEND_CHALLENGE 6 +#define NGX_RTMP_HANDSHAKE_CLIENT_RECV_CHALLENGE 7 +#define NGX_RTMP_HANDSHAKE_CLIENT_RECV_RESPONSE 8 +#define NGX_RTMP_HANDSHAKE_CLIENT_SEND_RESPONSE 9 +#define NGX_RTMP_HANDSHAKE_CLIENT_DONE 10 + + +static ngx_str_t ngx_rtmp_server_full_key + = { sizeof(ngx_rtmp_server_key), ngx_rtmp_server_key }; +static ngx_str_t ngx_rtmp_server_partial_key + = { 36, ngx_rtmp_server_key }; + +static ngx_str_t ngx_rtmp_client_full_key + = { sizeof(ngx_rtmp_client_key), ngx_rtmp_client_key }; +static ngx_str_t ngx_rtmp_client_partial_key + = { 30, ngx_rtmp_client_key }; + + +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; + unsigned int len; + + if (!hmac_initialized) { + HMAC_CTX_init(&hmac); + hmac_initialized = 1; + } + + 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); + } + if (src->last != 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_Final(&hmac, dst, &len); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_find_digest(ngx_buf_t *b, ngx_str_t *key, size_t base, ngx_log_t *log) +{ + size_t n, offs; + u_char digest[NGX_RTMP_HANDSHAKE_KEYLEN]; + u_char *p; + + offs = 0; + for (n = 0; n < 4; ++n) { + offs += b->pos[base + n]; + } + offs = (offs % 728) + base + 4; + p = b->pos + offs; + + if (ngx_rtmp_make_digest(key, b, p, digest, log) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_memcmp(digest, p, NGX_RTMP_HANDSHAKE_KEYLEN) == 0) { + return offs; + } + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_rtmp_write_digest(ngx_buf_t *b, ngx_str_t *key, size_t base, + ngx_log_t *log) +{ + size_t n, offs; + u_char *p; + + offs = 0; + for (n = 8; n < 12; ++n) { + offs += b->pos[base + n]; + } + offs = (offs % 728) + base + 12; + p = b->pos + offs; + + if (ngx_rtmp_make_digest(key, b, p, p, log) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static void +ngx_rtmp_fill_random_buffer(ngx_buf_t *b) +{ + for (; b->last != b->end; ++b->last) { + *b->last = (u_char) rand(); + } +} + + +static ngx_buf_t * +ngx_rtmp_alloc_handshake_buffer(ngx_rtmp_session_t *s) +{ + ngx_rtmp_core_srv_conf_t *cscf; + ngx_chain_t *cl; + ngx_buf_t *b; + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "handshake: allocating buffer"); + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + if (cscf->free_hs) { + cl = cscf->free_hs; + b = cl->buf; + cscf->free_hs = cl->next; + ngx_free_chain(cscf->pool, cl); + + } else { + b = ngx_pcalloc(cscf->pool, sizeof(ngx_buf_t)); + if (b == NULL) { + return NULL; + } + b->memory = 1; + b->start = ngx_pcalloc(cscf->pool, NGX_RTMP_HANDSHAKE_BUFSIZE); + if (b->start == NULL) { + return NULL; + } + b->end = b->start + NGX_RTMP_HANDSHAKE_BUFSIZE; + } + + b->pos = b->last = b->start; + + return b; +} + + +void +ngx_rtmp_free_handshake_buffers(ngx_rtmp_session_t *s) +{ + ngx_rtmp_core_srv_conf_t *cscf; + ngx_chain_t *cl; + + if (s->hs_buf == NULL) { + return; + } + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + cl = ngx_alloc_chain_link(cscf->pool); + if (cl == NULL) { + return; + } + cl->buf = s->hs_buf; + cl->next = cscf->free_hs; + cscf->free_hs = cl; + s->hs_buf = NULL; +} + + +static ngx_int_t +ngx_rtmp_handshake_create_challenge(ngx_rtmp_session_t *s, + const u_char version[4], ngx_str_t *key) +{ + ngx_buf_t *b; + + b = s->hs_buf; + b->last = b->pos = b->start; + *b->last++ = '\x03'; + b->last = ngx_rtmp_rcpymem(b->last, &s->epoch, 4); + b->last = ngx_cpymem(b->last, version, 4); + ngx_rtmp_fill_random_buffer(b); + ++b->pos; + if (ngx_rtmp_write_digest(b, key, 0, s->connection->log) != NGX_OK) { + return NGX_ERROR; + } + --b->pos; + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_handshake_parse_challenge(ngx_rtmp_session_t *s, + ngx_str_t *peer_key, ngx_str_t *key) +{ + ngx_buf_t *b; + u_char *p; + ngx_int_t offs; + + b = s->hs_buf; + if (*b->pos != '\x03') { + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "handshake: unexpected RTMP version: %i", + (ngx_int_t)*b->pos); + return NGX_ERROR; + } + ++b->pos; + s->peer_epoch = 0; + ngx_rtmp_rmemcpy(&s->peer_epoch, b->pos, 4); + + p = b->pos + 4; + ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "handshake: peer version=%i.%i.%i.%i epoch=%uD", + (ngx_int_t)p[3], (ngx_int_t)p[2], + (ngx_int_t)p[1], (ngx_int_t)p[0], + (uint32_t)s->peer_epoch); + if (*(uint32_t *)p == 0) { + s->hs_old = 1; + return NGX_OK; + } + + offs = ngx_rtmp_find_digest(b, peer_key, 772, s->connection->log); + if (offs == NGX_ERROR) { + offs = ngx_rtmp_find_digest(b, peer_key, 8, s->connection->log); + } + if (offs == NGX_ERROR) { + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "handshake: digest not found"); + s->hs_old = 1; + return NGX_OK; + } + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "handshake: digest found at pos=%i", offs); + b->pos += offs; + b->last = b->pos + NGX_RTMP_HANDSHAKE_KEYLEN; + s->hs_digest = ngx_palloc(s->connection->pool, NGX_RTMP_HANDSHAKE_KEYLEN); + if (ngx_rtmp_make_digest(key, b, NULL, s->hs_digest, s->connection->log) + != NGX_OK) + { + return NGX_ERROR; + } + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_handshake_create_response(ngx_rtmp_session_t *s) +{ + ngx_buf_t *b; + u_char *p; + ngx_str_t key; + + b = s->hs_buf; + b->pos = b->last = b->start + 1; + ngx_rtmp_fill_random_buffer(b); + if (s->hs_digest) { + p = b->last - NGX_RTMP_HANDSHAKE_KEYLEN; + key.data = s->hs_digest; + key.len = NGX_RTMP_HANDSHAKE_KEYLEN; + if (ngx_rtmp_make_digest(&key, b, p, p, s->connection->log) != NGX_OK) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static void +ngx_rtmp_handshake_done(ngx_rtmp_session_t *s) +{ + ngx_rtmp_free_handshake_buffers(s); + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "handshake: done"); + + if (ngx_rtmp_fire_event(s, NGX_RTMP_HANDSHAKE_DONE, + NULL, NULL) != NGX_OK) + { + ngx_rtmp_finalize_session(s); + return; + } + + ngx_rtmp_cycle(s); +} + + +static void +ngx_rtmp_handshake_recv(ngx_event_t *rev) +{ + ssize_t n; + ngx_connection_t *c; + ngx_rtmp_session_t *s; + ngx_buf_t *b; + + c = rev->data; + s = c->data; + + if (c->destroyed) { + return; + } + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, + "handshake: recv: client timed out"); + c->timedout = 1; + ngx_rtmp_finalize_session(s); + return; + } + + if (rev->timer_set) { + ngx_del_timer(rev); + } + + b = s->hs_buf; + + while (b->last != b->end) { + n = c->recv(c, b->last, b->end - b->last); + + if (n == NGX_ERROR || n == 0) { + ngx_rtmp_finalize_session(s); + return; + } + + if (n == NGX_AGAIN) { + ngx_add_timer(rev, s->timeout); + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + ngx_rtmp_finalize_session(s); + } + return; + } + + b->last += n; + } + + if (rev->active) { + ngx_del_event(rev, NGX_READ_EVENT, 0); + } + + ++s->hs_stage; + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "handshake: stage %ui", s->hs_stage); + + switch (s->hs_stage) { + case NGX_RTMP_HANDSHAKE_SERVER_SEND_CHALLENGE: + if (ngx_rtmp_handshake_parse_challenge(s, + &ngx_rtmp_client_partial_key, + &ngx_rtmp_server_full_key) != NGX_OK) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "handshake: error parsing challenge"); + ngx_rtmp_finalize_session(s); + return; + } + if (s->hs_old) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "handshake: old-style challenge"); + s->hs_buf->pos = s->hs_buf->start; + s->hs_buf->last = s->hs_buf->end; + } else if (ngx_rtmp_handshake_create_challenge(s, + ngx_rtmp_server_version, + &ngx_rtmp_server_partial_key) != NGX_OK) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "handshake: error creating challenge"); + ngx_rtmp_finalize_session(s); + return; + } + ngx_rtmp_handshake_send(c->write); + break; + + case NGX_RTMP_HANDSHAKE_SERVER_DONE: + ngx_rtmp_handshake_done(s); + break; + + case NGX_RTMP_HANDSHAKE_CLIENT_RECV_RESPONSE: + if (ngx_rtmp_handshake_parse_challenge(s, + &ngx_rtmp_server_partial_key, + &ngx_rtmp_client_full_key) != NGX_OK) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "handshake: error parsing challenge"); + ngx_rtmp_finalize_session(s); + return; + } + s->hs_buf->pos = s->hs_buf->last = s->hs_buf->start + 1; + ngx_rtmp_handshake_recv(c->read); + break; + + case NGX_RTMP_HANDSHAKE_CLIENT_SEND_RESPONSE: + if (ngx_rtmp_handshake_create_response(s) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "handshake: response error"); + ngx_rtmp_finalize_session(s); + return; + } + ngx_rtmp_handshake_send(c->write); + break; + } +} + + +static void +ngx_rtmp_handshake_send(ngx_event_t *wev) +{ + ngx_int_t n; + ngx_connection_t *c; + ngx_rtmp_session_t *s; + ngx_buf_t *b; + + c = wev->data; + s = c->data; + + if (c->destroyed) { + return; + } + + if (wev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, + "handshake: send: client timed out"); + c->timedout = 1; + ngx_rtmp_finalize_session(s); + return; + } + + if (wev->timer_set) { + ngx_del_timer(wev); + } + + b = s->hs_buf; + + while(b->pos != b->last) { + n = c->send(c, b->pos, b->last - b->pos); + + if (n == NGX_ERROR) { + ngx_rtmp_finalize_session(s); + return; + } + + if (n == NGX_AGAIN || n == 0) { + ngx_add_timer(c->write, s->timeout); + if (ngx_handle_write_event(c->write, 0) != NGX_OK) { + ngx_rtmp_finalize_session(s); + } + return; + } + + b->pos += n; + } + + if (wev->active) { + ngx_del_event(wev, NGX_WRITE_EVENT, 0); + } + + ++s->hs_stage; + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "handshake: stage %ui", s->hs_stage); + + switch (s->hs_stage) { + case NGX_RTMP_HANDSHAKE_SERVER_SEND_RESPONSE: + if (s->hs_old) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "handshake: old-style response"); + s->hs_buf->pos = s->hs_buf->start + 1; + s->hs_buf->last = s->hs_buf->end; + } else if (ngx_rtmp_handshake_create_response(s) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "handshake: response error"); + ngx_rtmp_finalize_session(s); + return; + } + ngx_rtmp_handshake_send(wev); + break; + + case NGX_RTMP_HANDSHAKE_SERVER_RECV_RESPONSE: + s->hs_buf->pos = s->hs_buf->last = s->hs_buf->start + 1; + ngx_rtmp_handshake_recv(c->read); + break; + + case NGX_RTMP_HANDSHAKE_CLIENT_RECV_CHALLENGE: + s->hs_buf->pos = s->hs_buf->last = s->hs_buf->start; + ngx_rtmp_handshake_recv(c->read); + break; + + case NGX_RTMP_HANDSHAKE_CLIENT_DONE: + ngx_rtmp_handshake_done(s); + break; + } +} + + +void +ngx_rtmp_handshake(ngx_rtmp_session_t *s) +{ + ngx_connection_t *c; + + c = s->connection; + c->read->handler = ngx_rtmp_handshake_recv; + c->write->handler = ngx_rtmp_handshake_send; + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "handshake: start server handshake"); + + s->hs_buf = ngx_rtmp_alloc_handshake_buffer(s); + s->hs_stage = NGX_RTMP_HANDSHAKE_SERVER_RECV_CHALLENGE; + + ngx_rtmp_handshake_recv(c->read); +} + + +void +ngx_rtmp_client_handshake(ngx_rtmp_session_t *s, unsigned async) +{ + ngx_connection_t *c; + + c = s->connection; + c->read->handler = ngx_rtmp_handshake_recv; + c->write->handler = ngx_rtmp_handshake_send; + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "handshake: start client handshake"); + + s->hs_buf = ngx_rtmp_alloc_handshake_buffer(s); + s->hs_stage = NGX_RTMP_HANDSHAKE_CLIENT_SEND_CHALLENGE; + + if (ngx_rtmp_handshake_create_challenge(s, + ngx_rtmp_client_version, + &ngx_rtmp_client_partial_key) != NGX_OK) + { + ngx_rtmp_finalize_session(s); + return; + } + + if (async) { + ngx_add_timer(c->write, s->timeout); + if (ngx_handle_write_event(c->write, 0) != NGX_OK) { + ngx_rtmp_finalize_session(s); + } + return; + } + + ngx_rtmp_handshake_send(c->write); +} + diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_init.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_init.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_init.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_init.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,329 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rtmp_proxy_protocol.h" + + +static void ngx_rtmp_close_connection(ngx_connection_t *c); +static u_char * ngx_rtmp_log_error(ngx_log_t *log, u_char *buf, size_t len); + + +void +ngx_rtmp_init_connection(ngx_connection_t *c) +{ + ngx_uint_t i; + ngx_rtmp_port_t *port; + struct sockaddr *sa; + struct sockaddr_in *sin; + ngx_rtmp_in_addr_t *addr; + ngx_rtmp_session_t *s; + ngx_rtmp_addr_conf_t *addr_conf; + ngx_int_t unix_socket; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; + ngx_rtmp_in6_addr_t *addr6; +#endif + + ++ngx_rtmp_naccepted; + + /* find the server configuration for the address:port */ + + /* AF_INET only */ + + port = c->listening->servers; + unix_socket = 0; + + if (port->naddrs > 1) { + + /* + * There are several addresses on this port and one of them + * is the "*:port" wildcard so getsockname() is needed to determine + * the server address. + * + * AcceptEx() already gave this address. + */ + + if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) { + ngx_rtmp_close_connection(c); + return; + } + + sa = c->local_sockaddr; + + switch (sa->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) sa; + + addr6 = port->addrs; + + /* the last address is "*" */ + + for (i = 0; i < port->naddrs - 1; i++) { + if (ngx_memcmp(&addr6[i].addr6, &sin6->sin6_addr, 16) == 0) { + break; + } + } + + addr_conf = &addr6[i].conf; + + break; +#endif + + case AF_UNIX: + unix_socket = 1; + + default: /* AF_INET */ + sin = (struct sockaddr_in *) sa; + + addr = port->addrs; + + /* the last address is "*" */ + + for (i = 0; i < port->naddrs - 1; i++) { + if (addr[i].addr == sin->sin_addr.s_addr) { + break; + } + } + + addr_conf = &addr[i].conf; + + break; + } + + } else { + switch (c->local_sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + addr6 = port->addrs; + addr_conf = &addr6[0].conf; + break; +#endif + + case AF_UNIX: + unix_socket = 1; + + default: /* AF_INET */ + addr = port->addrs; + addr_conf = &addr[0].conf; + break; + } + } + + ngx_log_error(NGX_LOG_INFO, c->log, 0, "*%ui client connected '%V'", + c->number, &c->addr_text); + + s = ngx_rtmp_init_session(c, addr_conf); + if (s == NULL) { + return; + } + + /* only auto-pushed connections are + * done through unix socket */ + + s->auto_pushed = unix_socket; + + if (addr_conf->proxy_protocol) { + ngx_rtmp_proxy_protocol(s); + + } else { + ngx_rtmp_handshake(s); + } +} + + +ngx_rtmp_session_t * +ngx_rtmp_init_session(ngx_connection_t *c, ngx_rtmp_addr_conf_t *addr_conf) +{ + ngx_rtmp_session_t *s; + ngx_rtmp_core_srv_conf_t *cscf; + ngx_rtmp_error_log_ctx_t *ctx; + + s = ngx_pcalloc(c->pool, sizeof(ngx_rtmp_session_t) + + sizeof(ngx_chain_t *) * ((ngx_rtmp_core_srv_conf_t *) + addr_conf->ctx-> srv_conf[ngx_rtmp_core_module + .ctx_index])->out_queue); + if (s == NULL) { + ngx_rtmp_close_connection(c); + return NULL; + } + + s->main_conf = addr_conf->ctx->main_conf; + s->srv_conf = addr_conf->ctx->srv_conf; + + s->addr_text = &addr_conf->addr_text; + + c->data = s; + s->connection = c; + + ctx = ngx_palloc(c->pool, sizeof(ngx_rtmp_error_log_ctx_t)); + if (ctx == NULL) { + ngx_rtmp_close_connection(c); + return NULL; + } + + ctx->client = &c->addr_text; + ctx->session = s; + + c->log->connection = c->number; + c->log->handler = ngx_rtmp_log_error; + c->log->data = ctx; + c->log->action = NULL; + + c->log_error = NGX_ERROR_INFO; + + s->ctx = ngx_pcalloc(c->pool, sizeof(void *) * ngx_rtmp_max_module); + if (s->ctx == NULL) { + ngx_rtmp_close_connection(c); + return NULL; + } + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + s->out_queue = cscf->out_queue; + s->out_cork = cscf->out_cork; + s->in_streams = ngx_pcalloc(c->pool, sizeof(ngx_rtmp_stream_t) + * cscf->max_streams); + if (s->in_streams == NULL) { + ngx_rtmp_close_connection(c); + return NULL; + } + +#if (nginx_version >= 1007005) + ngx_queue_init(&s->posted_dry_events); +#endif + + s->epoch = ngx_current_msec; + s->timeout = cscf->timeout; + s->buflen = cscf->buflen; + ngx_rtmp_set_chunk_size(s, NGX_RTMP_DEFAULT_CHUNK_SIZE); + + + if (ngx_rtmp_fire_event(s, NGX_RTMP_CONNECT, NULL, NULL) != NGX_OK) { + ngx_rtmp_finalize_session(s); + return NULL; + } + + return s; +} + + +static u_char * +ngx_rtmp_log_error(ngx_log_t *log, u_char *buf, size_t len) +{ + u_char *p; + ngx_rtmp_session_t *s; + ngx_rtmp_error_log_ctx_t *ctx; + + if (log->action) { + p = ngx_snprintf(buf, len, " while %s", log->action); + len -= p - buf; + buf = p; + } + + ctx = log->data; + + p = ngx_snprintf(buf, len, ", client: %V", ctx->client); + len -= p - buf; + buf = p; + + s = ctx->session; + + if (s == NULL) { + return p; + } + + p = ngx_snprintf(buf, len, ", server: %V", s->addr_text); + len -= p - buf; + buf = p; + + return p; +} + + +static void +ngx_rtmp_close_connection(ngx_connection_t *c) +{ + ngx_pool_t *pool; + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, c->log, 0, "close connection"); + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, -1); +#endif + + pool = c->pool; + ngx_close_connection(c); + ngx_destroy_pool(pool); +} + + +static void +ngx_rtmp_close_session_handler(ngx_event_t *e) +{ + ngx_rtmp_session_t *s; + ngx_connection_t *c; + ngx_rtmp_core_srv_conf_t *cscf; + + s = e->data; + c = s->connection; + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, c->log, 0, "close session"); + + ngx_rtmp_fire_event(s, NGX_RTMP_DISCONNECT, NULL, NULL); + + if (s->ping_evt.timer_set) { + ngx_del_timer(&s->ping_evt); + } + + if (s->in_old_pool) { + ngx_destroy_pool(s->in_old_pool); + } + + if (s->in_pool) { + ngx_destroy_pool(s->in_pool); + } + + ngx_rtmp_free_handshake_buffers(s); + + while (s->out_pos != s->out_last) { + ngx_rtmp_free_shared_chain(cscf, s->out[s->out_pos++]); + s->out_pos %= s->out_queue; + } + + ngx_rtmp_close_connection(c); +} + + +void +ngx_rtmp_finalize_session(ngx_rtmp_session_t *s) +{ + ngx_event_t *e; + ngx_connection_t *c; + + c = s->connection; + if (c->destroyed) { + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, c->log, 0, "finalize session"); + + c->destroyed = 1; + e = &s->close; + e->data = s; + e->handler = ngx_rtmp_close_session_handler; + e->log = c->log; + + ngx_post_event(e, &ngx_posted_events); +} + diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_limit_module.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_limit_module.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_limit_module.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_limit_module.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,205 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp.h" + + +typedef struct { + ngx_int_t max_conn; + ngx_shm_zone_t *shm_zone; +} ngx_rtmp_limit_main_conf_t; + + +static ngx_str_t shm_name = ngx_string("rtmp_limit"); + + +static ngx_int_t ngx_rtmp_limit_postconfiguration(ngx_conf_t *cf); +static void *ngx_rtmp_limit_create_main_conf(ngx_conf_t *cf); + + +static ngx_command_t ngx_rtmp_limit_commands[] = { + + { ngx_string("max_connections"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_RTMP_MAIN_CONF_OFFSET, + offsetof(ngx_rtmp_limit_main_conf_t, max_conn), + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_rtmp_limit_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_limit_postconfiguration, /* postconfiguration */ + ngx_rtmp_limit_create_main_conf, /* 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_limit_module = { + NGX_MODULE_V1, + &ngx_rtmp_limit_module_ctx, /* module context */ + ngx_rtmp_limit_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_rtmp_limit_create_main_conf(ngx_conf_t *cf) +{ + ngx_rtmp_limit_main_conf_t *lmcf; + + lmcf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_limit_main_conf_t)); + if (lmcf == NULL) { + return NULL; + } + + lmcf->max_conn = NGX_CONF_UNSET; + + return lmcf; +} + + +static ngx_int_t +ngx_rtmp_limit_connect(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_rtmp_limit_main_conf_t *lmcf; + ngx_slab_pool_t *shpool; + ngx_shm_zone_t *shm_zone; + uint32_t *nconn, n; + ngx_int_t rc; + + lmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_limit_module); + if (lmcf->max_conn == NGX_CONF_UNSET) { + return NGX_OK; + } + + shm_zone = lmcf->shm_zone; + shpool = (ngx_slab_pool_t *) shm_zone->shm.addr; + nconn = shm_zone->data; + + ngx_shmtx_lock(&shpool->mutex); + n = ++*nconn; + ngx_shmtx_unlock(&shpool->mutex); + + rc = n > (ngx_uint_t) lmcf->max_conn ? NGX_ERROR : NGX_OK; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "limit: inc conection counter: %uD", n); + + if (rc != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "limit: too many connections: %uD > %i", + n, lmcf->max_conn); + } + + return rc; +} + + +static ngx_int_t +ngx_rtmp_limit_disconnect(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_rtmp_limit_main_conf_t *lmcf; + ngx_slab_pool_t *shpool; + ngx_shm_zone_t *shm_zone; + uint32_t *nconn, n; + + lmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_limit_module); + if (lmcf->max_conn == NGX_CONF_UNSET) { + return NGX_OK; + } + + shm_zone = lmcf->shm_zone; + shpool = (ngx_slab_pool_t *) shm_zone->shm.addr; + nconn = shm_zone->data; + + ngx_shmtx_lock(&shpool->mutex); + n = --*nconn; + ngx_shmtx_unlock(&shpool->mutex); + + (void) n; + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "limit: dec conection counter: %uD", n); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_limit_shm_init(ngx_shm_zone_t *shm_zone, void *data) +{ + ngx_slab_pool_t *shpool; + uint32_t *nconn; + + if (data) { + shm_zone->data = data; + return NGX_OK; + } + + shpool = (ngx_slab_pool_t *) shm_zone->shm.addr; + + nconn = ngx_slab_alloc(shpool, 4); + if (nconn == NULL) { + return NGX_ERROR; + } + + *nconn = 0; + + shm_zone->data = nconn; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_limit_postconfiguration(ngx_conf_t *cf) +{ + ngx_rtmp_core_main_conf_t *cmcf; + ngx_rtmp_limit_main_conf_t *lmcf; + ngx_rtmp_handler_pt *h; + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + + h = ngx_array_push(&cmcf->events[NGX_RTMP_CONNECT]); + *h = ngx_rtmp_limit_connect; + + h = ngx_array_push(&cmcf->events[NGX_RTMP_DISCONNECT]); + *h = ngx_rtmp_limit_disconnect; + + lmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_limit_module); + if (lmcf->max_conn == NGX_CONF_UNSET) { + return NGX_OK; + } + + lmcf->shm_zone = ngx_shared_memory_add(cf, &shm_name, ngx_pagesize * 2, + &ngx_rtmp_limit_module); + if (lmcf->shm_zone == NULL) { + return NGX_ERROR; + } + + lmcf->shm_zone->init = ngx_rtmp_limit_shm_init; + + return NGX_OK; +} diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_live_module.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_live_module.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_live_module.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_live_module.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,1153 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp_live_module.h" +#include "ngx_rtmp_cmd_module.h" +#include "ngx_rtmp_codec_module.h" + + +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_pause_pt next_pause; +static ngx_rtmp_stream_begin_pt next_stream_begin; +static ngx_rtmp_stream_eof_pt next_stream_eof; + + +static ngx_int_t ngx_rtmp_live_postconfiguration(ngx_conf_t *cf); +static void * ngx_rtmp_live_create_app_conf(ngx_conf_t *cf); +static char * ngx_rtmp_live_merge_app_conf(ngx_conf_t *cf, + void *parent, void *child); +static char *ngx_rtmp_live_set_msec_slot(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static void ngx_rtmp_live_start(ngx_rtmp_session_t *s); +static void ngx_rtmp_live_stop(ngx_rtmp_session_t *s); + + +static ngx_command_t ngx_rtmp_live_commands[] = { + + { ngx_string("live"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_live_app_conf_t, live), + NULL }, + + { 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_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_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_live_app_conf_t, buflen), + NULL }, + + { ngx_string("sync"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_rtmp_live_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_live_app_conf_t, sync), + NULL }, + + { ngx_string("interleave"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_live_app_conf_t, interleave), + NULL }, + + { ngx_string("wait_key"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_live_app_conf_t, wait_key), + NULL }, + + { ngx_string("wait_video"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_live_app_conf_t, wait_video), + NULL }, + + { ngx_string("publish_notify"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_live_app_conf_t, publish_notify), + NULL }, + + { ngx_string("play_restart"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_live_app_conf_t, play_restart), + NULL }, + + { ngx_string("idle_streams"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_live_app_conf_t, idle_streams), + NULL }, + + { ngx_string("drop_idle_publisher"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_rtmp_live_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_live_app_conf_t, idle_timeout), + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_rtmp_live_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_live_postconfiguration, /* postconfiguration */ + NULL, /* create main configuration */ + NULL, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + ngx_rtmp_live_create_app_conf, /* create app configuration */ + ngx_rtmp_live_merge_app_conf /* merge app configuration */ +}; + + +ngx_module_t ngx_rtmp_live_module = { + NGX_MODULE_V1, + &ngx_rtmp_live_module_ctx, /* module context */ + ngx_rtmp_live_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_rtmp_live_create_app_conf(ngx_conf_t *cf) +{ + ngx_rtmp_live_app_conf_t *lacf; + + lacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_live_app_conf_t)); + if (lacf == NULL) { + return NULL; + } + + lacf->live = NGX_CONF_UNSET; + lacf->nbuckets = NGX_CONF_UNSET; + lacf->buflen = NGX_CONF_UNSET_MSEC; + lacf->sync = NGX_CONF_UNSET_MSEC; + lacf->idle_timeout = NGX_CONF_UNSET_MSEC; + lacf->interleave = NGX_CONF_UNSET; + lacf->wait_key = NGX_CONF_UNSET; + lacf->wait_video = NGX_CONF_UNSET; + lacf->publish_notify = NGX_CONF_UNSET; + lacf->play_restart = NGX_CONF_UNSET; + lacf->idle_streams = NGX_CONF_UNSET; + + return lacf; +} + + +static char * +ngx_rtmp_live_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_live_app_conf_t *prev = parent; + ngx_rtmp_live_app_conf_t *conf = 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_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_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); + ngx_conf_merge_value(conf->idle_streams, prev->idle_streams, 1); + + conf->pool = ngx_create_pool(4096, &cf->cycle->new_log); + if (conf->pool == NULL) { + return NGX_CONF_ERROR; + } + + conf->streams = ngx_pcalloc(cf->pool, + sizeof(ngx_rtmp_live_stream_t *) * conf->nbuckets); + + return NGX_CONF_OK; +} + + +static char * +ngx_rtmp_live_set_msec_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + char *p = conf; + ngx_str_t *value; + ngx_msec_t *msp; + + msp = (ngx_msec_t *) (p + cmd->offset); + + value = cf->args->elts; + + if (value[1].len == sizeof("off") - 1 && + ngx_strncasecmp(value[1].data, (u_char *) "off", value[1].len) == 0) + { + *msp = 0; + return NGX_CONF_OK; + } + + return ngx_conf_set_msec_slot(cf, cmd, conf); +} + + +static ngx_rtmp_live_stream_t ** +ngx_rtmp_live_get_stream(ngx_rtmp_session_t *s, u_char *name, int create) +{ + ngx_rtmp_live_app_conf_t *lacf; + ngx_rtmp_live_stream_t **stream; + size_t len; + + lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module); + if (lacf == NULL) { + return NULL; + } + + len = ngx_strlen(name); + stream = &lacf->streams[ngx_hash_key(name, len) % lacf->nbuckets]; + + for (; *stream; stream = &(*stream)->next) { + if (ngx_strcmp(name, (*stream)->name) == 0) { + return stream; + } + } + + if (!create) { + return NULL; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "live: create stream '%s'", name); + + if (lacf->free_streams) { + *stream = lacf->free_streams; + lacf->free_streams = lacf->free_streams->next; + } else { + *stream = ngx_palloc(lacf->pool, sizeof(ngx_rtmp_live_stream_t)); + } + ngx_memzero(*stream, sizeof(ngx_rtmp_live_stream_t)); + ngx_memcpy((*stream)->name, name, + ngx_min(sizeof((*stream)->name) - 1, len)); + (*stream)->epoch = ngx_current_msec; + + return stream; +} + + +static void +ngx_rtmp_live_idle(ngx_event_t *pev) +{ + ngx_connection_t *c; + ngx_rtmp_session_t *s; + + c = pev->data; + s = c->data; + + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "live: drop idle publisher"); + + ngx_rtmp_finalize_session(s); +} + + +static void +ngx_rtmp_live_set_status(ngx_rtmp_session_t *s, ngx_chain_t *control, + ngx_chain_t **status, size_t nstatus, + unsigned active) +{ + ngx_rtmp_live_app_conf_t *lacf; + ngx_rtmp_live_ctx_t *ctx, *pctx; + ngx_chain_t **cl; + ngx_event_t *e; + size_t n; + + lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module); + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "live: set active=%ui", active); + + if (ctx->active == active) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "live: unchanged active=%ui", active); + return; + } + + ctx->active = active; + + if (ctx->publishing) { + + /* publisher */ + + if (lacf->idle_timeout) { + e = &ctx->idle_evt; + + if (active && !ctx->idle_evt.timer_set) { + e->data = s->connection; + e->log = s->connection->log; + e->handler = ngx_rtmp_live_idle; + + ngx_add_timer(e, lacf->idle_timeout); + + } else if (!active && ctx->idle_evt.timer_set) { + ngx_del_timer(e); + } + } + + ctx->stream->active = active; + + for (pctx = ctx->stream->ctx; pctx; pctx = pctx->next) { + if (pctx->publishing == 0) { + ngx_rtmp_live_set_status(pctx->session, control, status, + nstatus, active); + } + } + + return; + } + + /* subscriber */ + + if (control && ngx_rtmp_send_message(s, control, 0) != NGX_OK) { + ngx_rtmp_finalize_session(s); + return; + } + + if (!ctx->silent) { + cl = status; + + for (n = 0; n < nstatus; ++n, ++cl) { + if (*cl && ngx_rtmp_send_message(s, *cl, 0) != NGX_OK) { + ngx_rtmp_finalize_session(s); + return; + } + } + } + + ctx->cs[0].active = 0; + ctx->cs[0].dropped = 0; + + ctx->cs[1].active = 0; + ctx->cs[1].dropped = 0; +} + + +static void +ngx_rtmp_live_start(ngx_rtmp_session_t *s) +{ + ngx_rtmp_core_srv_conf_t *cscf; + ngx_rtmp_live_app_conf_t *lacf; + ngx_chain_t *control; + ngx_chain_t *status[3]; + size_t n, nstatus; + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module); + + control = ngx_rtmp_create_stream_begin(s, NGX_RTMP_MSID); + + nstatus = 0; + + if (lacf->play_restart) { + status[nstatus++] = ngx_rtmp_create_status(s, "NetStream.Play.Start", + "status", "Start live"); + status[nstatus++] = ngx_rtmp_create_sample_access(s); + } + + if (lacf->publish_notify) { + status[nstatus++] = ngx_rtmp_create_status(s, + "NetStream.Play.PublishNotify", + "status", "Start publishing"); + } + + ngx_rtmp_live_set_status(s, control, status, nstatus, 1); + + if (control) { + ngx_rtmp_free_shared_chain(cscf, control); + } + + for (n = 0; n < nstatus; ++n) { + ngx_rtmp_free_shared_chain(cscf, status[n]); + } +} + + +static void +ngx_rtmp_live_stop(ngx_rtmp_session_t *s) +{ + ngx_rtmp_core_srv_conf_t *cscf; + ngx_rtmp_live_app_conf_t *lacf; + ngx_chain_t *control; + ngx_chain_t *status[3]; + size_t n, nstatus; + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module); + + control = ngx_rtmp_create_stream_eof(s, NGX_RTMP_MSID); + + nstatus = 0; + + if (lacf->play_restart) { + status[nstatus++] = ngx_rtmp_create_status(s, "NetStream.Play.Stop", + "status", "Stop live"); + } + + if (lacf->publish_notify) { + status[nstatus++] = ngx_rtmp_create_status(s, + "NetStream.Play.UnpublishNotify", + "status", "Stop publishing"); + } + + ngx_rtmp_live_set_status(s, control, status, nstatus, 0); + + if (control) { + ngx_rtmp_free_shared_chain(cscf, control); + } + + for (n = 0; n < nstatus; ++n) { + ngx_rtmp_free_shared_chain(cscf, status[n]); + } +} + + +static ngx_int_t +ngx_rtmp_live_stream_begin(ngx_rtmp_session_t *s, ngx_rtmp_stream_begin_t *v) +{ + ngx_rtmp_live_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module); + + if (ctx == NULL || ctx->stream == NULL || !ctx->publishing) { + goto next; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "live: stream_begin"); + + ngx_rtmp_live_start(s); + +next: + return next_stream_begin(s, v); +} + + +static ngx_int_t +ngx_rtmp_live_stream_eof(ngx_rtmp_session_t *s, ngx_rtmp_stream_eof_t *v) +{ + ngx_rtmp_live_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module); + + if (ctx == NULL || ctx->stream == NULL || !ctx->publishing) { + goto next; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "live: stream_eof"); + + ngx_rtmp_live_stop(s); + +next: + return next_stream_eof(s, v); +} + + +static void +ngx_rtmp_live_join(ngx_rtmp_session_t *s, u_char *name, unsigned publisher) +{ + ngx_rtmp_live_ctx_t *ctx; + ngx_rtmp_live_stream_t **stream; + ngx_rtmp_live_app_conf_t *lacf; + + lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module); + if (lacf == NULL) { + return; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module); + if (ctx && ctx->stream) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "live: already joined"); + return; + } + + if (ctx == NULL) { + ctx = ngx_palloc(s->connection->pool, sizeof(ngx_rtmp_live_ctx_t)); + ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_live_module); + } + + ngx_memzero(ctx, sizeof(*ctx)); + + ctx->session = s; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "live: join '%s'", name); + + stream = ngx_rtmp_live_get_stream(s, name, publisher || lacf->idle_streams); + + if (stream == NULL || + !(publisher || (*stream)->publishing || lacf->idle_streams)) + { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "live: stream not found"); + + ngx_rtmp_send_status(s, "NetStream.Play.StreamNotFound", "error", + "No such stream"); + + ngx_rtmp_finalize_session(s); + + return; + } + + if (publisher) { + if ((*stream)->publishing) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "live: already publishing"); + + ngx_rtmp_send_status(s, "NetStream.Publish.BadName", "error", + "Already publishing"); + + return; + } + + (*stream)->publishing = 1; + } + + ctx->stream = *stream; + ctx->publishing = publisher; + ctx->next = (*stream)->ctx; + + (*stream)->ctx = ctx; + + if (lacf->buflen) { + s->out_buffer = 1; + } + + ctx->cs[0].csid = NGX_RTMP_CSID_VIDEO; + ctx->cs[1].csid = NGX_RTMP_CSID_AUDIO; + + if (!ctx->publishing && ctx->stream->active) { + ngx_rtmp_live_start(s); + } +} + + +static ngx_int_t +ngx_rtmp_live_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v) +{ + ngx_rtmp_session_t *ss; + ngx_rtmp_live_ctx_t *ctx, **cctx, *pctx; + ngx_rtmp_live_stream_t **stream; + ngx_rtmp_live_app_conf_t *lacf; + + lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module); + if (lacf == NULL) { + goto next; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module); + if (ctx == NULL) { + goto next; + } + + if (ctx->stream == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "live: not joined"); + goto next; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "live: leave '%s'", ctx->stream->name); + + if (ctx->stream->publishing && ctx->publishing) { + ctx->stream->publishing = 0; + } + + for (cctx = &ctx->stream->ctx; *cctx; cctx = &(*cctx)->next) { + if (*cctx == ctx) { + *cctx = ctx->next; + break; + } + } + + if (ctx->publishing || ctx->stream->active) { + ngx_rtmp_live_stop(s); + } + + if (ctx->publishing) { + ngx_rtmp_send_status(s, "NetStream.Unpublish.Success", + "status", "Stop publishing"); + if (!lacf->idle_streams) { + for (pctx = ctx->stream->ctx; pctx; pctx = pctx->next) { + if (pctx->publishing == 0) { + ss = pctx->session; + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0, + "live: no publisher"); + ngx_rtmp_finalize_session(ss); + } + } + } + } + + if (ctx->stream->ctx) { + ctx->stream = NULL; + goto next; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "live: delete empty stream '%s'", + ctx->stream->name); + + stream = ngx_rtmp_live_get_stream(s, ctx->stream->name, 0); + if (stream == NULL) { + goto next; + } + *stream = (*stream)->next; + + ctx->stream->next = lacf->free_streams; + lacf->free_streams = ctx->stream; + ctx->stream = NULL; + + if (!ctx->silent && !ctx->publishing && !lacf->play_restart) { + ngx_rtmp_send_status(s, "NetStream.Play.Stop", "status", "Stop live"); + } + +next: + return next_close_stream(s, v); +} + + +static ngx_int_t +ngx_rtmp_live_pause(ngx_rtmp_session_t *s, ngx_rtmp_pause_t *v) +{ + ngx_rtmp_live_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module); + + if (ctx == NULL || ctx->stream == NULL) { + goto next; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "live: pause=%i timestamp=%f", + (ngx_int_t) v->pause, v->position); + + if (v->pause) { + if (ngx_rtmp_send_status(s, "NetStream.Pause.Notify", "status", + "Paused live") + != NGX_OK) + { + return NGX_ERROR; + } + + ctx->paused = 1; + + ngx_rtmp_live_stop(s); + + } else { + if (ngx_rtmp_send_status(s, "NetStream.Unpause.Notify", "status", + "Unpaused live") + != NGX_OK) + { + return NGX_ERROR; + } + + ctx->paused = 0; + + ngx_rtmp_live_start(s); + } + +next: + return next_pause(s, v); +} + +static ngx_int_t +ngx_rtmp_live_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_rtmp_live_ctx_t *ctx, *pctx; + ngx_rtmp_codec_ctx_t *codec_ctx; + ngx_chain_t *header, *coheader, *meta, + *apkt, *aapkt, *acopkt, *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, lh, clh; + ngx_int_t rc, mandatory, dummy_audio; + ngx_uint_t prio; + ngx_uint_t peers; + ngx_uint_t meta_version; + ngx_uint_t csidx; + uint32_t delta; + ngx_rtmp_live_chunk_stream_t *cs; +#ifdef NGX_DEBUG + const char *type_s; + + type_s = (h->type == NGX_RTMP_MSG_VIDEO ? "video" : "audio"); +#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", type_s); + return NGX_OK; + } + + if (!ctx->stream->active) { + ngx_rtmp_live_start(s); + } + + if (ctx->idle_evt.timer_set) { + ngx_add_timer(&ctx->idle_evt, lacf->idle_timeout); + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "live: %s packet timestamp=%uD", + type_s, h->timestamp); + + s->current_time = h->timestamp; + + peers = 0; + apkt = NULL; + aapkt = NULL; + acopkt = NULL; + header = NULL; + coheader = NULL; + meta = NULL; + meta_version = 0; + mandatory = 0; + + prio = (h->type == NGX_RTMP_MSG_VIDEO ? + ngx_rtmp_get_video_frame_type(in) : 0); + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + csidx = !(lacf->interleave || h->type == NGX_RTMP_MSG_VIDEO); + + cs = &ctx->cs[csidx]; + + ngx_memzero(&ch, sizeof(ch)); + + ch.timestamp = h->timestamp; + ch.msid = NGX_RTMP_MSID; + ch.csid = cs->csid; + ch.type = h->type; + + lh = ch; + + if (cs->active) { + lh.timestamp = cs->timestamp; + } + + clh = lh; + clh.type = (h->type == NGX_RTMP_MSG_AUDIO ? NGX_RTMP_MSG_VIDEO : + NGX_RTMP_MSG_AUDIO); + + cs->active = 1; + cs->timestamp = ch.timestamp; + + delta = ch.timestamp - lh.timestamp; +/* + if (delta >> 31) { + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "live: clipping non-monotonical timestamp %uD->%uD", + lh.timestamp, ch.timestamp); + + delta = 0; + + ch.timestamp = lh.timestamp; + } +*/ + rpkt = ngx_rtmp_append_shared_bufs(cscf, NULL, in); + + ngx_rtmp_prepare_message(s, &ch, &lh, rpkt); + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + + if (codec_ctx) { + + if (h->type == NGX_RTMP_MSG_AUDIO) { + header = codec_ctx->aac_header; + + if (lacf->interleave) { + coheader = codec_ctx->avc_header; + } + + if (codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC && + ngx_rtmp_is_codec_header(in)) + { + prio = 0; + mandatory = 1; + } + + } else { + header = codec_ctx->avc_header; + + if (lacf->interleave) { + coheader = codec_ctx->aac_header; + } + + if (codec_ctx->video_codec_id == NGX_RTMP_VIDEO_H264 && + ngx_rtmp_is_codec_header(in)) + { + prio = 0; + mandatory = 1; + } + } + + if (codec_ctx->meta) { + meta = codec_ctx->meta; + meta_version = codec_ctx->meta_version; + } + } + + /* broadcast to all subscribers */ + + for (pctx = ctx->stream->ctx; pctx; pctx = pctx->next) { + if (pctx == ctx || pctx->paused) { + continue; + } + + ss = pctx->session; + cs = &pctx->cs[csidx]; + + /* send metadata */ + + if (meta && meta_version != pctx->meta_version) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0, + "live: meta"); + + if (ngx_rtmp_send_message(ss, meta, 0) == NGX_OK) { + pctx->meta_version = meta_version; + } + } + + /* sync stream */ + + if (cs->active && (lacf->sync && cs->dropped > lacf->sync)) { + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0, + "live: sync %s dropped=%uD", type_s, cs->dropped); + + cs->active = 0; + cs->dropped = 0; + } + + /* absolute packet */ + + if (!cs->active) { + + if (mandatory) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0, + "live: skipping header"); + continue; + } + + if (lacf->wait_video && h->type == NGX_RTMP_MSG_AUDIO && + !pctx->cs[0].active) + { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0, + "live: waiting for video"); + continue; + } + + if (lacf->wait_key && prio != NGX_RTMP_VIDEO_KEY_FRAME && + (lacf->interleave || h->type == NGX_RTMP_MSG_VIDEO)) + { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0, + "live: skip non-key"); + continue; + } + + dummy_audio = 0; + if (lacf->wait_video && h->type == NGX_RTMP_MSG_VIDEO && + !pctx->cs[1].active) + { + dummy_audio = 1; + if (aapkt == NULL) { + aapkt = ngx_rtmp_alloc_shared_buf(cscf); + ngx_rtmp_prepare_message(s, &clh, NULL, aapkt); + } + } + + if (header || coheader) { + + /* send absolute codec header */ + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0, + "live: abs %s header timestamp=%uD", + type_s, lh.timestamp); + + if (header) { + if (apkt == NULL) { + apkt = ngx_rtmp_append_shared_bufs(cscf, NULL, header); + ngx_rtmp_prepare_message(s, &lh, NULL, apkt); + } + + rc = ngx_rtmp_send_message(ss, apkt, 0); + if (rc != NGX_OK) { + continue; + } + } + + if (coheader) { + if (acopkt == NULL) { + acopkt = ngx_rtmp_append_shared_bufs(cscf, NULL, coheader); + ngx_rtmp_prepare_message(s, &clh, NULL, acopkt); + } + + rc = ngx_rtmp_send_message(ss, acopkt, 0); + if (rc != NGX_OK) { + continue; + } + + } else if (dummy_audio) { + ngx_rtmp_send_message(ss, aapkt, 0); + } + + cs->timestamp = lh.timestamp; + cs->active = 1; + ss->current_time = cs->timestamp; + + } else { + + /* send absolute packet */ + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0, + "live: abs %s packet timestamp=%uD", + type_s, ch.timestamp); + + if (apkt == NULL) { + apkt = ngx_rtmp_append_shared_bufs(cscf, NULL, in); + ngx_rtmp_prepare_message(s, &ch, NULL, apkt); + } + + rc = ngx_rtmp_send_message(ss, apkt, prio); + if (rc != NGX_OK) { + continue; + } + + cs->timestamp = ch.timestamp; + cs->active = 1; + ss->current_time = cs->timestamp; + + ++peers; + + if (dummy_audio) { + ngx_rtmp_send_message(ss, aapkt, 0); + } + + continue; + } + } + + /* send relative packet */ + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0, + "live: rel %s packet delta=%uD", + type_s, delta); + + if (ngx_rtmp_send_message(ss, rpkt, prio) != NGX_OK) { + ++pctx->ndropped; + + cs->dropped += delta; + + if (mandatory) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0, + "live: mandatory packet failed"); + ngx_rtmp_finalize_session(ss); + } + + continue; + } + + cs->timestamp += delta; + ++peers; + ss->current_time = cs->timestamp; + } + + if (rpkt) { + ngx_rtmp_free_shared_chain(cscf, rpkt); + } + + if (apkt) { + ngx_rtmp_free_shared_chain(cscf, apkt); + } + + if (aapkt) { + ngx_rtmp_free_shared_chain(cscf, aapkt); + } + + if (acopkt) { + ngx_rtmp_free_shared_chain(cscf, acopkt); + } + + 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(h->type == NGX_RTMP_MSG_AUDIO ? + &ctx->stream->bw_in_audio : + &ctx->stream->bw_in_video, + h->mlen); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_live_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) +{ + ngx_rtmp_live_app_conf_t *lacf; + ngx_rtmp_live_ctx_t *ctx; + + lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module); + + if (lacf == NULL || !lacf->live) { + goto next; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "live: publish: name='%s' type='%s'", + v->name, v->type); + + /* join stream as publisher */ + + ngx_rtmp_live_join(s, v->name, 1); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module); + if (ctx == NULL || !ctx->publishing) { + goto next; + } + + ctx->silent = v->silent; + + if (!ctx->silent) { + ngx_rtmp_send_status(s, "NetStream.Publish.Start", + "status", "Start publishing"); + } + +next: + return next_publish(s, v); +} + + +static ngx_int_t +ngx_rtmp_live_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) +{ + ngx_rtmp_live_app_conf_t *lacf; + ngx_rtmp_live_ctx_t *ctx; + + lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module); + + if (lacf == NULL || !lacf->live) { + goto next; + } + + ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "live: play: name='%s' start=%uD duration=%uD reset=%d", + v->name, (uint32_t) v->start, + (uint32_t) v->duration, (uint32_t) v->reset); + + /* join stream as subscriber */ + + ngx_rtmp_live_join(s, v->name, 0); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module); + if (ctx == NULL) { + goto next; + } + + ctx->silent = v->silent; + + if (!ctx->silent && !lacf->play_restart) { + ngx_rtmp_send_status(s, "NetStream.Play.Start", + "status", "Start live"); + ngx_rtmp_send_sample_access(s); + } + +next: + return next_play(s, v); +} + + +static ngx_int_t +ngx_rtmp_live_postconfiguration(ngx_conf_t *cf) +{ + ngx_rtmp_core_main_conf_t *cmcf; + ngx_rtmp_handler_pt *h; + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + + /* register raw event handlers */ + + h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]); + *h = ngx_rtmp_live_av; + + h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]); + *h = ngx_rtmp_live_av; + + /* chain handlers */ + + next_publish = ngx_rtmp_publish; + ngx_rtmp_publish = ngx_rtmp_live_publish; + + next_play = ngx_rtmp_play; + ngx_rtmp_play = ngx_rtmp_live_play; + + next_close_stream = ngx_rtmp_close_stream; + ngx_rtmp_close_stream = ngx_rtmp_live_close_stream; + + next_pause = ngx_rtmp_pause; + ngx_rtmp_pause = ngx_rtmp_live_pause; + + next_stream_begin = ngx_rtmp_stream_begin; + ngx_rtmp_stream_begin = ngx_rtmp_live_stream_begin; + + next_stream_eof = ngx_rtmp_stream_eof; + ngx_rtmp_stream_eof = ngx_rtmp_live_stream_eof; + + return NGX_OK; +} diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_live_module.h nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_live_module.h --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_live_module.h 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_live_module.h 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,83 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_LIVE_H_INCLUDED_ +#define _NGX_RTMP_LIVE_H_INCLUDED_ + + +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rtmp_cmd_module.h" +#include "ngx_rtmp_bandwidth.h" +#include "ngx_rtmp_streams.h" + + +typedef struct ngx_rtmp_live_ctx_s ngx_rtmp_live_ctx_t; +typedef struct ngx_rtmp_live_stream_s ngx_rtmp_live_stream_t; + + +typedef struct { + unsigned active:1; + uint32_t timestamp; + uint32_t csid; + uint32_t dropped; +} ngx_rtmp_live_chunk_stream_t; + + +struct ngx_rtmp_live_ctx_s { + ngx_rtmp_session_t *session; + 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_uint_t meta_version; + ngx_event_t idle_evt; + unsigned active:1; + unsigned publishing:1; + unsigned silent:1; + unsigned paused:1; +}; + + +struct ngx_rtmp_live_stream_s { + u_char name[NGX_RTMP_MAX_NAME]; + ngx_rtmp_live_stream_t *next; + ngx_rtmp_live_ctx_t *ctx; + 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_out; + ngx_msec_t epoch; + unsigned active:1; + unsigned publishing:1; +}; + + +typedef struct { + ngx_int_t nbuckets; + ngx_rtmp_live_stream_t **streams; + ngx_flag_t live; + ngx_flag_t meta; + ngx_msec_t sync; + ngx_msec_t idle_timeout; + ngx_flag_t atc; + ngx_flag_t interleave; + ngx_flag_t wait_key; + ngx_flag_t wait_video; + ngx_flag_t publish_notify; + ngx_flag_t play_restart; + ngx_flag_t idle_streams; + ngx_msec_t buflen; + ngx_pool_t *pool; + ngx_rtmp_live_stream_t *free_streams; +} ngx_rtmp_live_app_conf_t; + + +extern ngx_module_t ngx_rtmp_live_module; + + +#endif /* _NGX_RTMP_LIVE_H_INCLUDED_ */ diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_log_module.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_log_module.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_log_module.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_log_module.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,1016 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp_cmd_module.h" + + +static ngx_rtmp_publish_pt next_publish; +static ngx_rtmp_play_pt next_play; + + +static ngx_int_t ngx_rtmp_log_postconfiguration(ngx_conf_t *cf); +static void *ngx_rtmp_log_create_main_conf(ngx_conf_t *cf); +static void * ngx_rtmp_log_create_app_conf(ngx_conf_t *cf); +static char * ngx_rtmp_log_merge_app_conf(ngx_conf_t *cf, + void *parent, void *child); +static char * ngx_rtmp_log_set_log(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char * ngx_rtmp_log_set_format(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char * ngx_rtmp_log_compile_format(ngx_conf_t *cf, ngx_array_t *ops, + ngx_array_t *args, ngx_uint_t s); + + +typedef struct ngx_rtmp_log_op_s ngx_rtmp_log_op_t; + + +typedef size_t (*ngx_rtmp_log_op_getlen_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_log_op_t *op); +typedef u_char * (*ngx_rtmp_log_op_getdata_pt)(ngx_rtmp_session_t *s, + u_char *buf, ngx_rtmp_log_op_t *log); + + +struct ngx_rtmp_log_op_s { + ngx_rtmp_log_op_getlen_pt getlen; + ngx_rtmp_log_op_getdata_pt getdata; + ngx_str_t value; + ngx_uint_t offset; +}; + + +typedef struct { + ngx_str_t name; + ngx_rtmp_log_op_getlen_pt getlen; + ngx_rtmp_log_op_getdata_pt getdata; + ngx_uint_t offset; +} ngx_rtmp_log_var_t; + + +typedef struct { + ngx_str_t name; + ngx_array_t *ops; /* ngx_rtmp_log_op_t */ +} ngx_rtmp_log_fmt_t; + + +typedef struct { + ngx_open_file_t *file; + time_t disk_full_time; + time_t error_log_time; + ngx_rtmp_log_fmt_t *format; +} ngx_rtmp_log_t; + + +typedef struct { + ngx_array_t *logs; /* ngx_rtmp_log_t */ + ngx_uint_t off; +} ngx_rtmp_log_app_conf_t; + + +typedef struct { + ngx_array_t formats; /* ngx_rtmp_log_fmt_t */ + ngx_uint_t combined_used; +} ngx_rtmp_log_main_conf_t; + + +typedef struct { + unsigned play:1; + unsigned publish:1; + u_char name[NGX_RTMP_MAX_NAME]; + u_char args[NGX_RTMP_MAX_ARGS]; +} ngx_rtmp_log_ctx_t; + + +static ngx_str_t ngx_rtmp_access_log = ngx_string(NGX_HTTP_LOG_PATH); + + +static ngx_command_t ngx_rtmp_log_commands[] = { + + { ngx_string("access_log"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE12, + ngx_rtmp_log_set_log, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("log_format"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_2MORE, + ngx_rtmp_log_set_format, + NGX_RTMP_MAIN_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_rtmp_log_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_log_postconfiguration, /* postconfiguration */ + ngx_rtmp_log_create_main_conf, /* create main configuration */ + NULL, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + ngx_rtmp_log_create_app_conf, /* create app configuration */ + ngx_rtmp_log_merge_app_conf /* merge app configuration */ +}; + + +ngx_module_t ngx_rtmp_log_module = { + NGX_MODULE_V1, + &ngx_rtmp_log_module_ctx, /* module context */ + ngx_rtmp_log_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_str_t ngx_rtmp_combined_fmt = + ngx_string("$remote_addr [$time_local] $command " + "\"$app\" \"$name\" \"$args\" - " + "$bytes_received $bytes_sent " + "\"$pageurl\" \"$flashver\" ($session_readable_time)"); + + +static size_t +ngx_rtmp_log_var_default_getlen(ngx_rtmp_session_t *s, ngx_rtmp_log_op_t *op) +{ + return op->value.len; +} + + +static u_char * +ngx_rtmp_log_var_default_getdata(ngx_rtmp_session_t *s, u_char *buf, + ngx_rtmp_log_op_t *op) +{ + return ngx_cpymem(buf, op->value.data, op->value.len); +} + + +static size_t +ngx_rtmp_log_var_connection_getlen(ngx_rtmp_session_t *s, ngx_rtmp_log_op_t *op) +{ + return NGX_INT_T_LEN; +} + +static u_char * +ngx_rtmp_log_var_connection_getdata(ngx_rtmp_session_t *s, u_char *buf, + ngx_rtmp_log_op_t *op) +{ + return ngx_sprintf(buf, "%ui", (ngx_uint_t) s->connection->number); +} + + +static size_t +ngx_rtmp_log_var_remote_addr_getlen(ngx_rtmp_session_t *s, + ngx_rtmp_log_op_t *op) +{ + return s->connection->addr_text.len; +} + + +static u_char * +ngx_rtmp_log_var_remote_addr_getdata(ngx_rtmp_session_t *s, u_char *buf, + ngx_rtmp_log_op_t *op) +{ + return ngx_cpymem(buf, s->connection->addr_text.data, + s->connection->addr_text.len); +} + + +static size_t +ngx_rtmp_log_var_msec_getlen(ngx_rtmp_session_t *s, + ngx_rtmp_log_op_t *op) +{ + return NGX_TIME_T_LEN + 4; +} + + +static u_char * +ngx_rtmp_log_var_msec_getdata(ngx_rtmp_session_t *s, u_char *buf, + ngx_rtmp_log_op_t *op) +{ + ngx_time_t *tp; + + tp = ngx_timeofday(); + + return ngx_sprintf(buf, "%T.%03M", tp->sec, tp->msec); +} + + +static size_t +ngx_rtmp_log_var_session_string_getlen(ngx_rtmp_session_t *s, + ngx_rtmp_log_op_t *op) +{ + return ((ngx_str_t *) ((u_char *) s + op->offset))->len; +} + + +static u_char * +ngx_rtmp_log_var_session_string_getdata(ngx_rtmp_session_t *s, u_char *buf, + ngx_rtmp_log_op_t *op) +{ + ngx_str_t *str; + + str = (ngx_str_t *) ((u_char *) s + op->offset); + + return ngx_cpymem(buf, str->data, str->len); +} + + +static size_t +ngx_rtmp_log_var_command_getlen(ngx_rtmp_session_t *s, + ngx_rtmp_log_op_t *op) +{ + return sizeof("PLAY+PUBLISH") - 1; +} + + +static u_char * +ngx_rtmp_log_var_command_getdata(ngx_rtmp_session_t *s, u_char *buf, + ngx_rtmp_log_op_t *op) +{ + ngx_rtmp_log_ctx_t *ctx; + ngx_str_t *cmd; + ngx_uint_t n; + + static ngx_str_t commands[] = { + ngx_string("NONE"), + ngx_string("PLAY"), + ngx_string("PUBLISH"), + ngx_string("PLAY+PUBLISH") + }; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_log_module); + + n = ctx ? (ctx->play + ctx->publish * 2) : 0; + + cmd = &commands[n]; + + return ngx_cpymem(buf, cmd->data, cmd->len); +} + + +static size_t +ngx_rtmp_log_var_context_cstring_getlen(ngx_rtmp_session_t *s, + ngx_rtmp_log_op_t *op) +{ + return ngx_max(NGX_RTMP_MAX_NAME, NGX_RTMP_MAX_ARGS); +} + + +static u_char * +ngx_rtmp_log_var_context_cstring_getdata(ngx_rtmp_session_t *s, u_char *buf, + ngx_rtmp_log_op_t *op) +{ + ngx_rtmp_log_ctx_t *ctx; + u_char *p; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_log_module); + if (ctx == NULL) { + return buf; + } + + p = (u_char *) ctx + op->offset; + while (*p) { + *buf++ = *p++; + } + + return buf; +} + + +static size_t +ngx_rtmp_log_var_session_uint32_getlen(ngx_rtmp_session_t *s, + ngx_rtmp_log_op_t *op) +{ + return NGX_INT32_LEN; +} + + +static u_char * +ngx_rtmp_log_var_session_uint32_getdata(ngx_rtmp_session_t *s, u_char *buf, + ngx_rtmp_log_op_t *op) +{ + uint32_t *v; + + v = (uint32_t *) ((uint8_t *) s + op->offset); + + return ngx_sprintf(buf, "%uD", *v); +} + + +static size_t +ngx_rtmp_log_var_time_local_getlen(ngx_rtmp_session_t *s, + ngx_rtmp_log_op_t *op) +{ + return ngx_cached_http_log_time.len; +} + + +static u_char * +ngx_rtmp_log_var_time_local_getdata(ngx_rtmp_session_t *s, u_char *buf, + ngx_rtmp_log_op_t *op) +{ + return ngx_cpymem(buf, ngx_cached_http_log_time.data, + ngx_cached_http_log_time.len); +} + + +static size_t +ngx_rtmp_log_var_session_time_getlen(ngx_rtmp_session_t *s, + ngx_rtmp_log_op_t *op) +{ + return NGX_INT64_LEN; +} + + +static u_char * +ngx_rtmp_log_var_session_time_getdata(ngx_rtmp_session_t *s, u_char *buf, + ngx_rtmp_log_op_t *op) +{ + return ngx_sprintf(buf, "%L", + (int64_t) (ngx_current_msec - s->epoch) / 1000); +} + + +static size_t +ngx_rtmp_log_var_session_readable_time_getlen(ngx_rtmp_session_t *s, + ngx_rtmp_log_op_t *op) +{ + return NGX_INT_T_LEN + sizeof("d 23h 59m 59s") - 1; +} + + +static u_char * +ngx_rtmp_log_var_session_readable_time_getdata(ngx_rtmp_session_t *s, + u_char *buf, ngx_rtmp_log_op_t *op) +{ + int64_t v; + ngx_uint_t days, hours, minutes, seconds; + + v = (ngx_current_msec - s->epoch) / 1000; + + days = (ngx_uint_t) (v / (60 * 60 * 24)); + hours = (ngx_uint_t) (v / (60 * 60) % 24); + minutes = (ngx_uint_t) (v / 60 % 60); + seconds = (ngx_uint_t) (v % 60); + + if (days) { + buf = ngx_sprintf(buf, "%uid ", days); + } + + if (days || hours) { + buf = ngx_sprintf(buf, "%uih ", hours); + } + + if (days || hours || minutes) { + buf = ngx_sprintf(buf, "%uim ", minutes); + } + + buf = ngx_sprintf(buf, "%uis", seconds); + + return buf; +} + + +static ngx_rtmp_log_var_t ngx_rtmp_log_vars[] = { + { ngx_string("connection"), + ngx_rtmp_log_var_connection_getlen, + ngx_rtmp_log_var_connection_getdata, + 0 }, + + { ngx_string("remote_addr"), + ngx_rtmp_log_var_remote_addr_getlen, + ngx_rtmp_log_var_remote_addr_getdata, + 0 }, + + { ngx_string("app"), + ngx_rtmp_log_var_session_string_getlen, + ngx_rtmp_log_var_session_string_getdata, + offsetof(ngx_rtmp_session_t, app) }, + + { ngx_string("flashver"), + ngx_rtmp_log_var_session_string_getlen, + ngx_rtmp_log_var_session_string_getdata, + offsetof(ngx_rtmp_session_t, flashver) }, + + { ngx_string("swfurl"), + ngx_rtmp_log_var_session_string_getlen, + ngx_rtmp_log_var_session_string_getdata, + offsetof(ngx_rtmp_session_t, swf_url) }, + + { ngx_string("tcurl"), + ngx_rtmp_log_var_session_string_getlen, + ngx_rtmp_log_var_session_string_getdata, + offsetof(ngx_rtmp_session_t, tc_url) }, + + { ngx_string("pageurl"), + ngx_rtmp_log_var_session_string_getlen, + ngx_rtmp_log_var_session_string_getdata, + offsetof(ngx_rtmp_session_t, page_url) }, + + { ngx_string("command"), + ngx_rtmp_log_var_command_getlen, + ngx_rtmp_log_var_command_getdata, + 0 }, + + { ngx_string("name"), + ngx_rtmp_log_var_context_cstring_getlen, + ngx_rtmp_log_var_context_cstring_getdata, + offsetof(ngx_rtmp_log_ctx_t, name) }, + + { ngx_string("args"), + ngx_rtmp_log_var_context_cstring_getlen, + ngx_rtmp_log_var_context_cstring_getdata, + offsetof(ngx_rtmp_log_ctx_t, args) }, + + { ngx_string("bytes_sent"), + ngx_rtmp_log_var_session_uint32_getlen, + ngx_rtmp_log_var_session_uint32_getdata, + offsetof(ngx_rtmp_session_t, out_bytes) }, + + { ngx_string("bytes_received"), + ngx_rtmp_log_var_session_uint32_getlen, + ngx_rtmp_log_var_session_uint32_getdata, + offsetof(ngx_rtmp_session_t, in_bytes) }, + + { ngx_string("time_local"), + ngx_rtmp_log_var_time_local_getlen, + ngx_rtmp_log_var_time_local_getdata, + 0 }, + + { ngx_string("msec"), + ngx_rtmp_log_var_msec_getlen, + ngx_rtmp_log_var_msec_getdata, + 0 }, + + { ngx_string("session_time"), + ngx_rtmp_log_var_session_time_getlen, + ngx_rtmp_log_var_session_time_getdata, + 0 }, + + { ngx_string("session_readable_time"), + ngx_rtmp_log_var_session_readable_time_getlen, + ngx_rtmp_log_var_session_readable_time_getdata, + 0 }, + + { ngx_null_string, NULL, NULL, 0 } +}; + + +static void * +ngx_rtmp_log_create_main_conf(ngx_conf_t *cf) +{ + ngx_rtmp_log_main_conf_t *lmcf; + ngx_rtmp_log_fmt_t *fmt; + + lmcf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_log_main_conf_t)); + if (lmcf == NULL) { + return NULL; + } + + if (ngx_array_init(&lmcf->formats, cf->pool, 4, sizeof(ngx_rtmp_log_fmt_t)) + != NGX_OK) + { + return NULL; + } + + fmt = ngx_array_push(&lmcf->formats); + if (fmt == NULL) { + return NULL; + } + + ngx_str_set(&fmt->name, "combined"); + + fmt->ops = ngx_array_create(cf->pool, 16, sizeof(ngx_rtmp_log_op_t)); + if (fmt->ops == NULL) { + return NULL; + } + + return lmcf; + +} + + +static void * +ngx_rtmp_log_create_app_conf(ngx_conf_t *cf) +{ + ngx_rtmp_log_app_conf_t *lacf; + + lacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_log_app_conf_t)); + if (lacf == NULL) { + return NULL; + } + + return lacf; +} + + +static char * +ngx_rtmp_log_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_log_app_conf_t *prev = parent; + ngx_rtmp_log_app_conf_t *conf = child; + ngx_rtmp_log_main_conf_t *lmcf; + ngx_rtmp_log_fmt_t *fmt; + ngx_rtmp_log_t *log; + + if (conf->logs || conf->off) { + return NGX_OK; + } + + conf->logs = prev->logs; + conf->off = prev->off; + + if (conf->logs || conf->off) { + return NGX_OK; + } + + conf->logs = ngx_array_create(cf->pool, 2, sizeof(ngx_rtmp_log_t)); + if (conf->logs == NULL) { + return NGX_CONF_ERROR; + } + + log = ngx_array_push(conf->logs); + if (log == NULL) { + return NGX_CONF_ERROR; + } + + log->file = ngx_conf_open_file(cf->cycle, &ngx_rtmp_access_log); + if (log->file == NULL) { + return NGX_CONF_ERROR; + } + + log->disk_full_time = 0; + log->error_log_time = 0; + + lmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_log_module); + fmt = lmcf->formats.elts; + + log->format = &fmt[0]; + lmcf->combined_used = 1; + + return NGX_CONF_OK; +} + + +static char * +ngx_rtmp_log_set_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_rtmp_log_app_conf_t *lacf = conf; + + ngx_rtmp_log_main_conf_t *lmcf; + ngx_rtmp_log_fmt_t *fmt; + ngx_rtmp_log_t *log; + ngx_str_t *value, name; + ngx_uint_t n; + + value = cf->args->elts; + + if (ngx_strcmp(value[1].data, "off") == 0) { + lacf->off = 1; + return NGX_CONF_OK; + } + + if (lacf->logs == NULL) { + lacf->logs = ngx_array_create(cf->pool, 2, sizeof(ngx_rtmp_log_t)); + if (lacf->logs == NULL) { + return NGX_CONF_ERROR; + } + } + + log = ngx_array_push(lacf->logs); + if (log == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memzero(log, sizeof(*log)); + + lmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_log_module); + + log->file = ngx_conf_open_file(cf->cycle, &value[1]); + if (log->file == NULL) { + return NGX_CONF_ERROR; + } + + if (cf->args->nelts == 2) { + ngx_str_set(&name, "combined"); + lmcf->combined_used = 1; + + } else { + name = value[2]; + if (ngx_strcmp(name.data, "combined") == 0) { + lmcf->combined_used = 1; + } + } + + fmt = lmcf->formats.elts; + for (n = 0; n < lmcf->formats.nelts; ++n, ++fmt) { + if (fmt->name.len == name.len && + ngx_strncasecmp(fmt->name.data, name.data, name.len) == 0) + { + log->format = fmt; + break; + } + } + + if (log->format == NULL) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "unknown log format \"%V\"", + &name); + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_rtmp_log_set_format(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_rtmp_log_main_conf_t *lmcf = conf; + ngx_rtmp_log_fmt_t *fmt; + ngx_str_t *value; + ngx_uint_t i; + + value = cf->args->elts; + + if (cf->cmd_type != NGX_RTMP_MAIN_CONF) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "\"log_format\" directive can only be used on " + "\"rtmp\" level"); + } + + fmt = lmcf->formats.elts; + for (i = 0; i < lmcf->formats.nelts; i++) { + if (fmt[i].name.len == value[1].len && + ngx_strcmp(fmt[i].name.data, value[1].data) == 0) + { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "duplicate \"log_format\" name \"%V\"", + &value[1]); + return NGX_CONF_ERROR; + } + } + + fmt = ngx_array_push(&lmcf->formats); + if (fmt == NULL) { + return NGX_CONF_ERROR; + } + + fmt->name = value[1]; + + fmt->ops = ngx_array_create(cf->pool, 16, sizeof(ngx_rtmp_log_op_t)); + if (fmt->ops == NULL) { + return NGX_CONF_ERROR; + } + + return ngx_rtmp_log_compile_format(cf, fmt->ops, cf->args, 2); +} + + +static char * +ngx_rtmp_log_compile_format(ngx_conf_t *cf, ngx_array_t *ops, ngx_array_t *args, + ngx_uint_t s) +{ + size_t i, len; + u_char *data, *d, c; + ngx_uint_t bracket; + ngx_str_t *value, var; + ngx_rtmp_log_op_t *op; + ngx_rtmp_log_var_t *v; + + value = args->elts; + + for (; s < args->nelts; ++s) { + i = 0; + + len = value[s].len; + d = value[s].data; + + while (i < len) { + + op = ngx_array_push(ops); + if (op == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memzero(op, sizeof(*op)); + + data = &d[i]; + + if (d[i] == '$') { + if (++i == len) { + goto invalid; + } + + if (d[i] == '{') { + bracket = 1; + if (++i == len) { + goto invalid; + } + } else { + bracket = 0; + } + + var.data = &d[i]; + + for (var.len = 0; i < len; ++i, ++var.len) { + c = d[i]; + + if (c == '}' && bracket) { + ++i; + bracket = 0; + break; + } + + if ((c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || + (c == '_')) + { + continue; + } + + break; + } + + if (bracket) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "missing closing bracket in \"%V\"", + &var); + return NGX_CONF_ERROR; + } + + if (var.len == 0) { + goto invalid; + } + + for (v = ngx_rtmp_log_vars; v->name.len; ++v) { + if (v->name.len == var.len && + ngx_strncmp(v->name.data, var.data, var.len) == 0) + { + op->getlen = v->getlen; + op->getdata = v->getdata; + op->offset = v->offset; + break; + } + } + + if (v->name.len == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "unknown variable \"%V\"", &var); + return NGX_CONF_ERROR; + } + + continue; + } + + ++i; + + while (i < len && d[i] != '$') { + ++i; + } + + op->getlen = ngx_rtmp_log_var_default_getlen; + op->getdata = ngx_rtmp_log_var_default_getdata; + + op->value.len = &d[i] - data; + + op->value.data = ngx_pnalloc(cf->pool, op->value.len); + if (op->value.data == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memcpy(op->value.data, data, op->value.len); + } + } + + return NGX_CONF_OK; + +invalid: + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%s\"", data); + + return NGX_CONF_ERROR; +} + + +static ngx_rtmp_log_ctx_t * +ngx_rtmp_log_set_names(ngx_rtmp_session_t *s, u_char *name, u_char *args) +{ + ngx_rtmp_log_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_log_module); + if (ctx == NULL) { + ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_rtmp_log_ctx_t)); + if (ctx == NULL) { + return NULL; + } + + ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_log_module); + } + + ngx_memcpy(ctx->name, name, NGX_RTMP_MAX_NAME); + ngx_memcpy(ctx->args, args, NGX_RTMP_MAX_ARGS); + + return ctx; +} + + +static ngx_int_t +ngx_rtmp_log_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) +{ + ngx_rtmp_log_ctx_t *ctx; + + if (s->auto_pushed || s->relay) { + goto next; + } + + ctx = ngx_rtmp_log_set_names(s, v->name, v->args); + if (ctx == NULL) { + goto next; + } + + ctx->publish = 1; + +next: + return next_publish(s, v); +} + + +static ngx_int_t +ngx_rtmp_log_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) +{ + ngx_rtmp_log_ctx_t *ctx; + + if (s->auto_pushed || s->relay) { + goto next; + } + + ctx = ngx_rtmp_log_set_names(s, v->name, v->args); + if (ctx == NULL) { + goto next; + } + + ctx->play = 1; + +next: + return next_play(s, v); +} + + +static void +ngx_rtmp_log_write(ngx_rtmp_session_t *s, ngx_rtmp_log_t *log, u_char *buf, + size_t len) +{ + u_char *name; + time_t now; + ssize_t n; + int err; + + err = 0; + name = log->file->name.data; + n = ngx_write_fd(log->file->fd, buf, len); + + if (n == (ssize_t) len) { + return; + } + + now = ngx_time(); + + if (n == -1) { + err = ngx_errno; + + if (err == NGX_ENOSPC) { + log->disk_full_time = now; + } + + if (now - log->error_log_time > 59) { + ngx_log_error(NGX_LOG_ALERT, s->connection->log, err, + ngx_write_fd_n " to \"%s\" failed", name); + log->error_log_time = now; + } + } + + if (now - log->error_log_time > 59) { + ngx_log_error(NGX_LOG_ALERT, s->connection->log, err, + ngx_write_fd_n " to \"%s\" was incomplete: %z of %uz", + name, n, len); + log->error_log_time = now; + } +} + + +static ngx_int_t +ngx_rtmp_log_disconnect(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_rtmp_log_app_conf_t *lacf; + ngx_rtmp_log_t *log; + ngx_rtmp_log_op_t *op; + ngx_uint_t n, i; + u_char *line, *p; + size_t len; + + if (s->auto_pushed || s->relay) { + return NGX_OK; + } + + lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_log_module); + if (lacf == NULL || lacf->off || lacf->logs == NULL) { + return NGX_OK; + } + + log = lacf->logs->elts; + for (i = 0; i < lacf->logs->nelts; ++i, ++log) { + + if (ngx_time() == log->disk_full_time) { + /* FreeBSD full disk protection; + * nginx http logger does the same */ + continue; + } + + len = 0; + op = log->format->ops->elts; + for (n = 0; n < log->format->ops->nelts; ++n, ++op) { + len += op->getlen(s, op); + } + + len += NGX_LINEFEED_SIZE; + + line = ngx_palloc(s->connection->pool, len); + if (line == NULL) { + return NGX_OK; + } + + p = line; + op = log->format->ops->elts; + for (n = 0; n < log->format->ops->nelts; ++n, ++op) { + p = op->getdata(s, p, op); + } + + ngx_linefeed(p); + + ngx_rtmp_log_write(s, log, line, p - line); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_log_postconfiguration(ngx_conf_t *cf) +{ + ngx_rtmp_core_main_conf_t *cmcf; + ngx_rtmp_handler_pt *h; + ngx_rtmp_log_main_conf_t *lmcf; + ngx_array_t a; + ngx_rtmp_log_fmt_t *fmt; + ngx_str_t *value; + + lmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_log_module); + if (lmcf->combined_used) { + if (ngx_array_init(&a, cf->pool, 1, sizeof(ngx_str_t)) != NGX_OK) { + return NGX_ERROR; + } + + value = ngx_array_push(&a); + if (value == NULL) { + return NGX_ERROR; + } + + *value = ngx_rtmp_combined_fmt; + fmt = lmcf->formats.elts; + + if (ngx_rtmp_log_compile_format(cf, fmt->ops, &a, 0) + != NGX_CONF_OK) + { + return NGX_ERROR; + } + } + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + + h = ngx_array_push(&cmcf->events[NGX_RTMP_DISCONNECT]); + *h = ngx_rtmp_log_disconnect; + + next_publish = ngx_rtmp_publish; + ngx_rtmp_publish = ngx_rtmp_log_publish; + + next_play = ngx_rtmp_play; + ngx_rtmp_play = ngx_rtmp_log_play; + + return NGX_OK; +} diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_mp4_module.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_mp4_module.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_mp4_module.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_mp4_module.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,2591 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp_play_module.h" +#include "ngx_rtmp_codec_module.h" +#include "ngx_rtmp_streams.h" + + +static ngx_int_t ngx_rtmp_mp4_postconfiguration(ngx_conf_t *cf); +static ngx_int_t ngx_rtmp_mp4_init(ngx_rtmp_session_t *s, ngx_file_t *f, + ngx_int_t aindex, ngx_int_t vindex); +static ngx_int_t ngx_rtmp_mp4_done(ngx_rtmp_session_t *s, ngx_file_t *f); +static ngx_int_t ngx_rtmp_mp4_start(ngx_rtmp_session_t *s, ngx_file_t *f); +static ngx_int_t ngx_rtmp_mp4_seek(ngx_rtmp_session_t *s, ngx_file_t *f, + ngx_uint_t offset); +static ngx_int_t ngx_rtmp_mp4_stop(ngx_rtmp_session_t *s, ngx_file_t *f); +static ngx_int_t ngx_rtmp_mp4_send(ngx_rtmp_session_t *s, ngx_file_t *f, + ngx_uint_t *ts); +static ngx_int_t ngx_rtmp_mp4_reset(ngx_rtmp_session_t *s); + + +#define NGX_RTMP_MP4_MAX_FRAMES 8 + + +#pragma pack(push,4) + + +/* disable zero-sized array warning by msvc */ + +#if (NGX_WIN32) +#pragma warning(push) +#pragma warning(disable:4200) +#endif + + +typedef struct { + uint32_t first_chunk; + uint32_t samples_per_chunk; + uint32_t sample_descrption_index; +} ngx_rtmp_mp4_chunk_entry_t; + + +typedef struct { + uint32_t version_flags; + uint32_t entry_count; + ngx_rtmp_mp4_chunk_entry_t entries[0]; +} ngx_rtmp_mp4_chunks_t; + + +typedef struct { + uint32_t sample_count; + uint32_t sample_delta; +} ngx_rtmp_mp4_time_entry_t; + + +typedef struct { + uint32_t version_flags; + uint32_t entry_count; + ngx_rtmp_mp4_time_entry_t entries[0]; +} ngx_rtmp_mp4_times_t; + + +typedef struct { + uint32_t sample_count; + uint32_t sample_offset; +} ngx_rtmp_mp4_delay_entry_t; + + +typedef struct { + uint32_t version_flags; + uint32_t entry_count; + ngx_rtmp_mp4_delay_entry_t entries[0]; +} ngx_rtmp_mp4_delays_t; + + +typedef struct { + uint32_t version_flags; + uint32_t entry_count; + uint32_t entries[0]; +} ngx_rtmp_mp4_keys_t; + + +typedef struct { + uint32_t version_flags; + uint32_t sample_size; + uint32_t sample_count; + uint32_t entries[0]; +} ngx_rtmp_mp4_sizes_t; + + +typedef struct { + uint32_t version_flags; + uint32_t field_size; + uint32_t sample_count; + uint32_t entries[0]; +} ngx_rtmp_mp4_sizes2_t; + + +typedef struct { + uint32_t version_flags; + uint32_t entry_count; + uint32_t entries[0]; +} ngx_rtmp_mp4_offsets_t; + + +typedef struct { + uint32_t version_flags; + uint32_t entry_count; + uint64_t entries[0]; +} ngx_rtmp_mp4_offsets64_t; + + +#if (NGX_WIN32) +#pragma warning(pop) +#endif + + +#pragma pack(pop) + + +typedef struct { + uint32_t timestamp; + uint32_t last_timestamp; + off_t offset; + size_t size; + ngx_int_t key; + uint32_t delay; + + unsigned not_first:1; + unsigned valid:1; + + ngx_uint_t pos; + + ngx_uint_t key_pos; + + ngx_uint_t chunk; + ngx_uint_t chunk_pos; + ngx_uint_t chunk_count; + + ngx_uint_t time_pos; + ngx_uint_t time_count; + + ngx_uint_t delay_pos; + ngx_uint_t delay_count; + + ngx_uint_t size_pos; +} ngx_rtmp_mp4_cursor_t; + + +typedef struct { + ngx_uint_t id; + + ngx_int_t type; + ngx_int_t codec; + uint32_t csid; + u_char fhdr; + ngx_int_t time_scale; + uint64_t duration; + + u_char *header; + size_t header_size; + unsigned header_sent:1; + + ngx_rtmp_mp4_times_t *times; + ngx_rtmp_mp4_delays_t *delays; + ngx_rtmp_mp4_keys_t *keys; + ngx_rtmp_mp4_chunks_t *chunks; + ngx_rtmp_mp4_sizes_t *sizes; + ngx_rtmp_mp4_sizes2_t *sizes2; + ngx_rtmp_mp4_offsets_t *offsets; + ngx_rtmp_mp4_offsets64_t *offsets64; + ngx_rtmp_mp4_cursor_t cursor; +} ngx_rtmp_mp4_track_t; + + +typedef struct { + void *mmaped; + size_t mmaped_size; + ngx_fd_t extra; + + unsigned meta_sent:1; + + ngx_rtmp_mp4_track_t tracks[2]; + ngx_rtmp_mp4_track_t *track; + ngx_uint_t ntracks; + + ngx_uint_t width; + ngx_uint_t height; + ngx_uint_t nchannels; + ngx_uint_t sample_size; + ngx_uint_t sample_rate; + + ngx_int_t atracks, vtracks; + ngx_int_t aindex, vindex; + + uint32_t start_timestamp, epoch; +} ngx_rtmp_mp4_ctx_t; + + +#define ngx_rtmp_mp4_make_tag(a, b, c, d) \ + ((uint32_t)d << 24 | (uint32_t)c << 16 | (uint32_t)b << 8 | (uint32_t)a) + + +static ngx_inline uint32_t +ngx_rtmp_mp4_to_rtmp_timestamp(ngx_rtmp_mp4_track_t *t, uint64_t ts) +{ + return (uint32_t) (ts * 1000 / t->time_scale); +} + + +static ngx_inline uint32_t +ngx_rtmp_mp4_from_rtmp_timestamp(ngx_rtmp_mp4_track_t *t, uint32_t ts) +{ + return (uint64_t) ts * t->time_scale / 1000; +} + + +#define NGX_RTMP_MP4_BUFLEN_ADDON 1000 + + +static u_char ngx_rtmp_mp4_buffer[1024*1024]; + + +#if (NGX_WIN32) +static void * +ngx_rtmp_mp4_mmap(ngx_fd_t fd, size_t size, off_t offset, ngx_fd_t *extra) +{ + void *data; + + *extra = CreateFileMapping(fd, NULL, PAGE_READONLY, + (DWORD) ((uint64_t) size >> 32), + (DWORD) (size & 0xffffffff), + NULL); + if (*extra == NULL) { + return NULL; + } + + data = MapViewOfFile(*extra, FILE_MAP_READ, + (DWORD) ((uint64_t) offset >> 32), + (DWORD) (offset & 0xffffffff), + size); + + if (data == NULL) { + CloseHandle(*extra); + } + + /* + * non-NULL result means map view handle is open + * and should be closed later + */ + + return data; +} + + +static ngx_int_t +ngx_rtmp_mp4_munmap(void *data, size_t size, ngx_fd_t *extra) +{ + ngx_int_t rc; + + rc = NGX_OK; + + if (UnmapViewOfFile(data) == 0) { + rc = NGX_ERROR; + } + + if (CloseHandle(*extra) == 0) { + rc = NGX_ERROR; + } + + return rc; +} + +#else + +static void * +ngx_rtmp_mp4_mmap(ngx_fd_t fd, size_t size, off_t offset, ngx_fd_t *extra) +{ + void *data; + + data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, offset); + + /* valid address is never NULL since there's no MAP_FIXED */ + + return data == MAP_FAILED ? NULL : data; +} + + +static ngx_int_t +ngx_rtmp_mp4_munmap(void *data, size_t size, ngx_fd_t *extra) +{ + return munmap(data, size); +} + +#endif + + +static ngx_int_t ngx_rtmp_mp4_parse(ngx_rtmp_session_t *s, u_char *pos, + u_char *last); +static ngx_int_t ngx_rtmp_mp4_parse_trak(ngx_rtmp_session_t *s, u_char *pos, + u_char *last); +static ngx_int_t ngx_rtmp_mp4_parse_mdhd(ngx_rtmp_session_t *s, u_char *pos, + u_char *last); +static ngx_int_t ngx_rtmp_mp4_parse_hdlr(ngx_rtmp_session_t *s, u_char *pos, + u_char *last); +static ngx_int_t ngx_rtmp_mp4_parse_stsd(ngx_rtmp_session_t *s, u_char *pos, + u_char *last); +static ngx_int_t ngx_rtmp_mp4_parse_stsc(ngx_rtmp_session_t *s, u_char *pos, + u_char *last); +static ngx_int_t ngx_rtmp_mp4_parse_stts(ngx_rtmp_session_t *s, u_char *pos, + u_char *last); +static ngx_int_t ngx_rtmp_mp4_parse_ctts(ngx_rtmp_session_t *s, u_char *pos, + u_char *last); +static ngx_int_t ngx_rtmp_mp4_parse_stss(ngx_rtmp_session_t *s, u_char *pos, + u_char *last); +static ngx_int_t ngx_rtmp_mp4_parse_stsz(ngx_rtmp_session_t *s, u_char *pos, + u_char *last); +static ngx_int_t ngx_rtmp_mp4_parse_stz2(ngx_rtmp_session_t *s, u_char *pos, + u_char *last); +static ngx_int_t ngx_rtmp_mp4_parse_stco(ngx_rtmp_session_t *s, u_char *pos, + u_char *last); +static ngx_int_t ngx_rtmp_mp4_parse_co64(ngx_rtmp_session_t *s, u_char *pos, + u_char *last); +static ngx_int_t ngx_rtmp_mp4_parse_avc1(ngx_rtmp_session_t *s, u_char *pos, + u_char *last); +static ngx_int_t ngx_rtmp_mp4_parse_avcC(ngx_rtmp_session_t *s, u_char *pos, + u_char *last); +static ngx_int_t ngx_rtmp_mp4_parse_mp4a(ngx_rtmp_session_t *s, u_char *pos, + u_char *last); +static ngx_int_t ngx_rtmp_mp4_parse_mp4v(ngx_rtmp_session_t *s, u_char *pos, + u_char *last); +static ngx_int_t ngx_rtmp_mp4_parse_esds(ngx_rtmp_session_t *s, u_char *pos, + u_char *last); +static ngx_int_t ngx_rtmp_mp4_parse_mp3(ngx_rtmp_session_t *s, u_char *pos, + u_char *last); +static ngx_int_t ngx_rtmp_mp4_parse_nmos(ngx_rtmp_session_t *s, u_char *pos, + u_char *last); +static ngx_int_t ngx_rtmp_mp4_parse_spex(ngx_rtmp_session_t *s, u_char *pos, + u_char *last); + + +typedef ngx_int_t (*ngx_rtmp_mp4_box_pt)(ngx_rtmp_session_t *s, u_char *pos, + u_char *last); + +typedef struct { + uint32_t tag; + ngx_rtmp_mp4_box_pt handler; +} ngx_rtmp_mp4_box_t; + + +static ngx_rtmp_mp4_box_t ngx_rtmp_mp4_boxes[] = { + { ngx_rtmp_mp4_make_tag('t','r','a','k'), ngx_rtmp_mp4_parse_trak }, + { ngx_rtmp_mp4_make_tag('m','d','i','a'), ngx_rtmp_mp4_parse }, + { ngx_rtmp_mp4_make_tag('m','d','h','d'), ngx_rtmp_mp4_parse_mdhd }, + { ngx_rtmp_mp4_make_tag('h','d','l','r'), ngx_rtmp_mp4_parse_hdlr }, + { ngx_rtmp_mp4_make_tag('m','i','n','f'), ngx_rtmp_mp4_parse }, + { ngx_rtmp_mp4_make_tag('s','t','b','l'), ngx_rtmp_mp4_parse }, + { ngx_rtmp_mp4_make_tag('s','t','s','d'), ngx_rtmp_mp4_parse_stsd }, + { ngx_rtmp_mp4_make_tag('s','t','s','c'), ngx_rtmp_mp4_parse_stsc }, + { ngx_rtmp_mp4_make_tag('s','t','t','s'), ngx_rtmp_mp4_parse_stts }, + { ngx_rtmp_mp4_make_tag('c','t','t','s'), ngx_rtmp_mp4_parse_ctts }, + { ngx_rtmp_mp4_make_tag('s','t','s','s'), ngx_rtmp_mp4_parse_stss }, + { ngx_rtmp_mp4_make_tag('s','t','s','z'), ngx_rtmp_mp4_parse_stsz }, + { ngx_rtmp_mp4_make_tag('s','t','z','2'), ngx_rtmp_mp4_parse_stz2 }, + { ngx_rtmp_mp4_make_tag('s','t','c','o'), ngx_rtmp_mp4_parse_stco }, + { ngx_rtmp_mp4_make_tag('c','o','6','4'), ngx_rtmp_mp4_parse_co64 }, + { ngx_rtmp_mp4_make_tag('a','v','c','1'), ngx_rtmp_mp4_parse_avc1 }, + { ngx_rtmp_mp4_make_tag('a','v','c','C'), ngx_rtmp_mp4_parse_avcC }, + { ngx_rtmp_mp4_make_tag('m','p','4','a'), ngx_rtmp_mp4_parse_mp4a }, + { ngx_rtmp_mp4_make_tag('m','p','4','v'), ngx_rtmp_mp4_parse_mp4v }, + { ngx_rtmp_mp4_make_tag('e','s','d','s'), ngx_rtmp_mp4_parse_esds }, + { ngx_rtmp_mp4_make_tag('.','m','p','3'), ngx_rtmp_mp4_parse_mp3 }, + { ngx_rtmp_mp4_make_tag('n','m','o','s'), ngx_rtmp_mp4_parse_nmos }, + { ngx_rtmp_mp4_make_tag('s','p','e','x'), ngx_rtmp_mp4_parse_spex }, + { ngx_rtmp_mp4_make_tag('w','a','v','e'), ngx_rtmp_mp4_parse } +}; + + +static ngx_int_t ngx_rtmp_mp4_parse_descr(ngx_rtmp_session_t *s, u_char *pos, + u_char *last); +static ngx_int_t ngx_rtmp_mp4_parse_es(ngx_rtmp_session_t *s, u_char *pos, + u_char *last); +static ngx_int_t ngx_rtmp_mp4_parse_dc(ngx_rtmp_session_t *s, u_char *pos, + u_char *last); +static ngx_int_t ngx_rtmp_mp4_parse_ds(ngx_rtmp_session_t *s, u_char *pos, + u_char *last); + + +typedef ngx_int_t (*ngx_rtmp_mp4_descriptor_pt)(ngx_rtmp_session_t *s, + u_char *pos, u_char *last); + +typedef struct { + uint8_t tag; + ngx_rtmp_mp4_descriptor_pt handler; +} ngx_rtmp_mp4_descriptor_t; + + +static ngx_rtmp_mp4_descriptor_t ngx_rtmp_mp4_descriptors[] = { + { 0x03, ngx_rtmp_mp4_parse_es }, /* MPEG ES Descriptor */ + { 0x04, ngx_rtmp_mp4_parse_dc }, /* MPEG DecoderConfig Descriptor */ + { 0x05, ngx_rtmp_mp4_parse_ds } /* MPEG DecoderSpec Descriptor */ +}; + + +static ngx_rtmp_module_t ngx_rtmp_mp4_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_mp4_postconfiguration, /* postconfiguration */ + NULL, /* create main configuration */ + NULL, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + NULL, /* create app configuration */ + NULL /* merge app configuration */ +}; + + +ngx_module_t ngx_rtmp_mp4_module = { + NGX_MODULE_V1, + &ngx_rtmp_mp4_module_ctx, /* module context */ + NULL, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_int_t +ngx_rtmp_mp4_parse_trak(ngx_rtmp_session_t *s, u_char *pos, u_char *last) +{ + ngx_rtmp_mp4_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); + + if (ctx->track) { + return NGX_OK; + } + + ctx->track = (ctx->ntracks == sizeof(ctx->tracks) / sizeof(ctx->tracks[0])) + ? NULL : &ctx->tracks[ctx->ntracks]; + + if (ctx->track) { + ngx_memzero(ctx->track, sizeof(*ctx->track)); + ctx->track->id = ctx->ntracks; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: trying track %ui", ctx->ntracks); + } + + if (ngx_rtmp_mp4_parse(s, pos, last) != NGX_OK) { + return NGX_ERROR; + } + + if (ctx->track && ctx->track->type && + (ctx->ntracks == 0 || + ctx->tracks[0].type != ctx->tracks[ctx->ntracks].type)) + { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: adding track %ui", ctx->ntracks); + + if (ctx->track->type == NGX_RTMP_MSG_AUDIO) { + if (ctx->atracks++ != ctx->aindex) { + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: skipping audio track %ui!=%ui", + ctx->atracks - 1, ctx->aindex); + ctx->track = NULL; + return NGX_OK; + } + + } else { + if (ctx->vtracks++ != ctx->vindex) { + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: skipping video track %i!=%i", + ctx->vtracks - 1, ctx->vindex); + ctx->track = NULL; + return NGX_OK; + } + } + + ++ctx->ntracks; + + } else { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: ignoring track %ui", ctx->ntracks); + } + + ctx->track = NULL; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_parse_mdhd(ngx_rtmp_session_t *s, u_char *pos, u_char *last) +{ + ngx_rtmp_mp4_ctx_t *ctx; + ngx_rtmp_mp4_track_t *t; + uint8_t version; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); + + if (ctx->track == NULL) { + return NGX_OK; + } + + t = ctx->track; + + if (pos + 1 > last) { + return NGX_ERROR; + } + + version = *(uint8_t *) pos; + + switch (version) { + case 0: + if (pos + 20 > last) { + return NGX_ERROR; + } + + pos += 12; + t->time_scale = ngx_rtmp_r32(*(uint32_t *) pos); + pos += 4; + t->duration = ngx_rtmp_r32(*(uint32_t *) pos); + break; + + case 1: + if (pos + 28 > last) { + return NGX_ERROR; + } + + pos += 20; + t->time_scale = ngx_rtmp_r32(*(uint32_t *) pos); + pos += 4; + t->duration = ngx_rtmp_r64(*(uint64_t *) pos); + break; + + default: + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: duration time_scale=%ui duration=%uL", + t->time_scale, t->duration); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_parse_hdlr(ngx_rtmp_session_t *s, u_char *pos, u_char *last) +{ + ngx_rtmp_mp4_ctx_t *ctx; + uint32_t type; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); + + if (ctx->track == NULL) { + return NGX_OK; + } + + if (pos + 12 > last) { + return NGX_ERROR; + } + + type = *(uint32_t *)(pos + 8); + + if (type == ngx_rtmp_mp4_make_tag('v','i','d','e')) { + ctx->track->type = NGX_RTMP_MSG_VIDEO; + ctx->track->csid = NGX_RTMP_CSID_VIDEO; + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: video track"); + + } else if (type == ngx_rtmp_mp4_make_tag('s','o','u','n')) { + ctx->track->type = NGX_RTMP_MSG_AUDIO; + ctx->track->csid = NGX_RTMP_CSID_AUDIO; + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: audio track"); + } else { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: unknown track"); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_parse_video(ngx_rtmp_session_t *s, u_char *pos, u_char *last, + ngx_int_t codec) +{ + ngx_rtmp_mp4_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); + + if (ctx->track == NULL) { + return NGX_OK; + } + + ctx->track->codec = codec; + + if (pos + 78 > last) { + return NGX_ERROR; + } + + pos += 24; + + ctx->width = ngx_rtmp_r16(*(uint16_t *) pos); + + pos += 2; + + ctx->height = ngx_rtmp_r16(*(uint16_t *) pos); + + pos += 52; + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: video settings codec=%i, width=%ui, height=%ui", + codec, ctx->width, ctx->height); + + if (ngx_rtmp_mp4_parse(s, pos, last) != NGX_OK) { + return NGX_ERROR; + } + + ctx->track->fhdr = (u_char) ctx->track->codec; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_parse_audio(ngx_rtmp_session_t *s, u_char *pos, u_char *last, + ngx_int_t codec) +{ + ngx_rtmp_mp4_ctx_t *ctx; + u_char *p; + ngx_uint_t version; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); + + if (ctx->track == NULL) { + return NGX_OK; + } + + ctx->track->codec = codec; + + if (pos + 28 > last) { + return NGX_ERROR; + } + + pos += 8; + + version = ngx_rtmp_r16(*(uint16_t *) pos); + + pos += 8; + + ctx->nchannels = ngx_rtmp_r16(*(uint16_t *) pos); + + pos += 2; + + ctx->sample_size = ngx_rtmp_r16(*(uint16_t *) pos); + + pos += 6; + + ctx->sample_rate = ngx_rtmp_r16(*(uint16_t *) pos); + + pos += 4; + + p = &ctx->track->fhdr; + + *p = 0; + + if (ctx->nchannels == 2) { + *p |= 0x01; + } + + if (ctx->sample_size == 16) { + *p |= 0x02; + } + + switch (ctx->sample_rate) { + case 5512: + break; + + case 11025: + *p |= 0x04; + break; + + case 22050: + *p |= 0x08; + break; + + default: /*44100 etc */ + *p |= 0x0c; + break; + } + + ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: audio settings version=%ui, codec=%i, nchannels==%ui, " + "sample_size=%ui, sample_rate=%ui", + version, codec, ctx->nchannels, ctx->sample_size, + ctx->sample_rate); + + switch (version) { + case 1: + pos += 16; + break; + + case 2: + pos += 36; + } + + if (pos > last) { + return NGX_ERROR; + } + + if (ngx_rtmp_mp4_parse(s, pos, last) != NGX_OK) { + return NGX_ERROR; + } + + *p |= (ctx->track->codec << 4); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_parse_avc1(ngx_rtmp_session_t *s, u_char *pos, u_char *last) +{ + return ngx_rtmp_mp4_parse_video(s, pos, last, NGX_RTMP_VIDEO_H264); +} + + +static ngx_int_t +ngx_rtmp_mp4_parse_mp4v(ngx_rtmp_session_t *s, u_char *pos, u_char *last) +{ + return ngx_rtmp_mp4_parse_video(s, pos, last, NGX_RTMP_VIDEO_H264); +} + + +static ngx_int_t +ngx_rtmp_mp4_parse_avcC(ngx_rtmp_session_t *s, u_char *pos, u_char *last) +{ + ngx_rtmp_mp4_ctx_t *ctx; + + if (pos == last) { + return NGX_OK; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); + + if (ctx->track == NULL || ctx->track->codec != NGX_RTMP_VIDEO_H264) { + return NGX_OK; + } + + ctx->track->header = pos; + ctx->track->header_size = (size_t) (last - pos); + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: video h264 header size=%uz", + ctx->track->header_size); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_parse_mp4a(ngx_rtmp_session_t *s, u_char *pos, u_char *last) +{ + return ngx_rtmp_mp4_parse_audio(s, pos, last, NGX_RTMP_AUDIO_MP3); +} + + +static ngx_int_t +ngx_rtmp_mp4_parse_ds(ngx_rtmp_session_t *s, u_char *pos, u_char *last) +{ + ngx_rtmp_mp4_ctx_t *ctx; + ngx_rtmp_mp4_track_t *t; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); + + t = ctx->track; + + if (t == NULL) { + return NGX_OK; + } + + t->header = pos; + t->header_size = (size_t) (last - pos); + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: decoder header size=%uz", t->header_size); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_parse_dc(ngx_rtmp_session_t *s, u_char *pos, u_char *last) +{ + uint8_t id; + ngx_rtmp_mp4_ctx_t *ctx; + ngx_int_t *pc; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); + + if (ctx->track == NULL) { + return NGX_OK; + } + + if (pos + 13 > last) { + return NGX_ERROR; + } + + id = * (uint8_t *) pos; + pos += 13; + pc = &ctx->track->codec; + + switch (id) { + case 0x21: + *pc = NGX_RTMP_VIDEO_H264; + break; + + case 0x40: + case 0x66: + case 0x67: + case 0x68: + *pc = NGX_RTMP_AUDIO_AAC; + break; + + case 0x69: + case 0x6b: + *pc = NGX_RTMP_AUDIO_MP3; + break; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: decoder descriptor id=%i codec=%i", + (ngx_int_t) id, *pc); + + return ngx_rtmp_mp4_parse_descr(s, pos, last); +} + + +static ngx_int_t +ngx_rtmp_mp4_parse_es(ngx_rtmp_session_t *s, u_char *pos, u_char *last) +{ + uint16_t id; + uint8_t flags; + + if (pos + 3 > last) { + return NGX_ERROR; + } + + id = ngx_rtmp_r16(*(uint16_t *) pos); + pos += 2; + + flags = *(uint8_t *) pos; + ++pos; + + if (flags & 0x80) { /* streamDependenceFlag */ + pos += 2; + } + + if (flags & 0x40) { /* URL_FLag */ + return NGX_OK; + } + + if (flags & 0x20) { /* OCRstreamFlag */ + pos += 2; + } + + if (pos > last) { + return NGX_ERROR; + } + + (void) id; + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: es descriptor es id=%i flags=%i", + (ngx_int_t) id, (ngx_int_t) flags); + + return ngx_rtmp_mp4_parse_descr(s, pos, last); +} + + +static ngx_int_t +ngx_rtmp_mp4_parse_descr(ngx_rtmp_session_t *s, u_char *pos, u_char *last) +{ + uint8_t tag, v; + uint32_t size; + ngx_uint_t n, ndesc; + ngx_rtmp_mp4_descriptor_t *ds; + + ndesc = sizeof(ngx_rtmp_mp4_descriptors) + / sizeof(ngx_rtmp_mp4_descriptors[0]); + + while (pos < last) { + tag = *(uint8_t *) pos++; + + for (size = 0, n = 0; n < 4; ++n) { + if (pos == last) { + return NGX_ERROR; + } + + v = *(uint8_t *) pos++; + + size = (size << 7) | (v & 0x7f); + + if (!(v & 0x80)) { + break; + } + } + + if (pos + size > last) { + return NGX_ERROR; + } + + ds = ngx_rtmp_mp4_descriptors;; + + for (n = 0; n < ndesc; ++n, ++ds) { + if (tag == ds->tag) { + break; + } + } + + if (n == ndesc) { + ds = NULL; + } + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: descriptor%s tag=%i size=%uD", + ds ? "" : " unhandled", (ngx_int_t) tag, size); + + if (ds && ds->handler(s, pos, pos + size) != NGX_OK) { + return NGX_ERROR; + } + + pos += size; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_parse_esds(ngx_rtmp_session_t *s, u_char *pos, u_char *last) +{ + if (pos + 4 > last) { + return NGX_ERROR; + } + + pos += 4; /* version */ + + return ngx_rtmp_mp4_parse_descr(s, pos, last); +} + + +static ngx_int_t +ngx_rtmp_mp4_parse_mp3(ngx_rtmp_session_t *s, u_char *pos, u_char *last) +{ + return ngx_rtmp_mp4_parse_audio(s, pos, last, NGX_RTMP_AUDIO_MP3); +} + + +static ngx_int_t +ngx_rtmp_mp4_parse_nmos(ngx_rtmp_session_t *s, u_char *pos, u_char *last) +{ + return ngx_rtmp_mp4_parse_audio(s, pos, last, NGX_RTMP_AUDIO_NELLY); +} + + +static ngx_int_t +ngx_rtmp_mp4_parse_spex(ngx_rtmp_session_t *s, u_char *pos, u_char *last) +{ + return ngx_rtmp_mp4_parse_audio(s, pos, last, NGX_RTMP_AUDIO_SPEEX); +} + + +static ngx_int_t +ngx_rtmp_mp4_parse_stsd(ngx_rtmp_session_t *s, u_char *pos, u_char *last) +{ + if (pos + 8 > last) { + return NGX_ERROR; + } + + pos += 8; + + ngx_rtmp_mp4_parse(s, pos, last); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_parse_stsc(ngx_rtmp_session_t *s, u_char *pos, u_char *last) +{ + ngx_rtmp_mp4_ctx_t *ctx; + ngx_rtmp_mp4_track_t *t; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); + + t = ctx->track; + + if (t == NULL) { + return NGX_OK; + } + + t->chunks = (ngx_rtmp_mp4_chunks_t *) pos; + + if (pos + sizeof(*t->chunks) + ngx_rtmp_r32(t->chunks->entry_count) * + sizeof(t->chunks->entries[0]) + <= last) + { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: chunks entries=%uD", + ngx_rtmp_r32(t->chunks->entry_count)); + return NGX_OK; + } + + t->chunks = NULL; + return NGX_ERROR; +} + + +static ngx_int_t +ngx_rtmp_mp4_parse_stts(ngx_rtmp_session_t *s, u_char *pos, u_char *last) +{ + ngx_rtmp_mp4_ctx_t *ctx; + ngx_rtmp_mp4_track_t *t; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); + + t = ctx->track; + + if (t == NULL) { + return NGX_OK; + } + + t->times = (ngx_rtmp_mp4_times_t *) pos; + + if (pos + sizeof(*t->times) + ngx_rtmp_r32(t->times->entry_count) * + sizeof(t->times->entries[0]) + <= last) + { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: times entries=%uD", + ngx_rtmp_r32(t->times->entry_count)); + return NGX_OK; + } + + t->times = NULL; + return NGX_ERROR; +} + + +static ngx_int_t +ngx_rtmp_mp4_parse_ctts(ngx_rtmp_session_t *s, u_char *pos, u_char *last) +{ + ngx_rtmp_mp4_ctx_t *ctx; + ngx_rtmp_mp4_track_t *t; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); + + t = ctx->track; + + if (t == NULL) { + return NGX_OK; + } + + t->delays = (ngx_rtmp_mp4_delays_t *) pos; + + if (pos + sizeof(*t->delays) + ngx_rtmp_r32(t->delays->entry_count) * + sizeof(t->delays->entries[0]) + <= last) + { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: delays entries=%uD", + ngx_rtmp_r32(t->delays->entry_count)); + return NGX_OK; + } + + t->delays = NULL; + return NGX_ERROR; +} + + +static ngx_int_t +ngx_rtmp_mp4_parse_stss(ngx_rtmp_session_t *s, u_char *pos, u_char *last) +{ + ngx_rtmp_mp4_ctx_t *ctx; + ngx_rtmp_mp4_track_t *t; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); + + t = ctx->track; + + if (t == NULL) { + return NGX_OK; + } + + t->keys = (ngx_rtmp_mp4_keys_t *) pos; + + if (pos + sizeof(*t->keys) + ngx_rtmp_r32(t->keys->entry_count) * + sizeof(t->keys->entries[0]) + <= last) + { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: keys entries=%uD", + ngx_rtmp_r32(t->keys->entry_count)); + return NGX_OK; + } + + t->keys = NULL; + return NGX_ERROR; +} + + +static ngx_int_t +ngx_rtmp_mp4_parse_stsz(ngx_rtmp_session_t *s, u_char *pos, u_char *last) +{ + ngx_rtmp_mp4_ctx_t *ctx; + ngx_rtmp_mp4_track_t *t; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); + + t = ctx->track; + + if (t == NULL) { + return NGX_OK; + } + + t->sizes = (ngx_rtmp_mp4_sizes_t *) pos; + + if (pos + sizeof(*t->sizes) <= last && t->sizes->sample_size) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: sizes size=%uD", + ngx_rtmp_r32(t->sizes->sample_size)); + return NGX_OK; + } + + if (pos + sizeof(*t->sizes) + ngx_rtmp_r32(t->sizes->sample_count) * + sizeof(t->sizes->entries[0]) + <= last) + + { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: sizes entries=%uD", + ngx_rtmp_r32(t->sizes->sample_count)); + return NGX_OK; + } + + t->sizes = NULL; + return NGX_ERROR; +} + + +static ngx_int_t +ngx_rtmp_mp4_parse_stz2(ngx_rtmp_session_t *s, u_char *pos, u_char *last) +{ + ngx_rtmp_mp4_ctx_t *ctx; + ngx_rtmp_mp4_track_t *t; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); + + t = ctx->track; + + if (t == NULL) { + return NGX_OK; + } + + t->sizes2 = (ngx_rtmp_mp4_sizes2_t *) pos; + + if (pos + sizeof(*t->sizes) + ngx_rtmp_r32(t->sizes2->sample_count) * + ngx_rtmp_r32(t->sizes2->field_size) / 8 + <= last) + { + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: sizes2 field_size=%uD entries=%uD", + ngx_rtmp_r32(t->sizes2->field_size), + ngx_rtmp_r32(t->sizes2->sample_count)); + return NGX_OK; + } + + t->sizes2 = NULL; + return NGX_ERROR; +} + + +static ngx_int_t +ngx_rtmp_mp4_parse_stco(ngx_rtmp_session_t *s, u_char *pos, u_char *last) +{ + ngx_rtmp_mp4_ctx_t *ctx; + ngx_rtmp_mp4_track_t *t; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); + + t = ctx->track; + + if (t == NULL) { + return NGX_OK; + } + + t->offsets = (ngx_rtmp_mp4_offsets_t *) pos; + + if (pos + sizeof(*t->offsets) + ngx_rtmp_r32(t->offsets->entry_count) * + sizeof(t->offsets->entries[0]) + <= last) + { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: offsets entries=%uD", + ngx_rtmp_r32(t->offsets->entry_count)); + return NGX_OK; + } + + t->offsets = NULL; + return NGX_ERROR; +} + + +static ngx_int_t +ngx_rtmp_mp4_parse_co64(ngx_rtmp_session_t *s, u_char *pos, u_char *last) +{ + ngx_rtmp_mp4_ctx_t *ctx; + ngx_rtmp_mp4_track_t *t; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); + + t = ctx->track; + + if (t == NULL) { + return NGX_OK; + } + + t->offsets64 = (ngx_rtmp_mp4_offsets64_t *) pos; + + if (pos + sizeof(*t->offsets64) + ngx_rtmp_r32(t->offsets64->entry_count) * + sizeof(t->offsets64->entries[0]) + <= last) + { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: offsets64 entries=%uD", + ngx_rtmp_r32(t->offsets64->entry_count)); + return NGX_OK; + } + + t->offsets64 = NULL; + return NGX_ERROR; +} + + +static ngx_int_t +ngx_rtmp_mp4_parse(ngx_rtmp_session_t *s, u_char *pos, u_char *last) +{ + uint32_t *hdr, tag; + size_t size, nboxes; + ngx_uint_t n; + ngx_rtmp_mp4_box_t *b; + + while (pos != last) { + if (pos + 8 > last) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: too small box: size=%i", last - pos); + return NGX_ERROR; + } + + hdr = (uint32_t *) pos; + size = ngx_rtmp_r32(hdr[0]); + tag = hdr[1]; + + if (pos + size > last) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "mp4: too big box '%*s': size=%uz", + 4, &tag, size); + return NGX_ERROR; + } + + b = ngx_rtmp_mp4_boxes; + nboxes = sizeof(ngx_rtmp_mp4_boxes) / sizeof(ngx_rtmp_mp4_boxes[0]); + + for (n = 0; n < nboxes && b->tag != tag; ++n, ++b); + + if (n == nboxes) { + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: box unhandled '%*s'", 4, &tag); + } else { + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: box '%*s'", 4, &tag); + b->handler(s, pos + 8, pos + size); + } + + pos += size; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_next_time(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t) +{ + ngx_rtmp_mp4_cursor_t *cr; + ngx_rtmp_mp4_time_entry_t *te; + + if (t->times == NULL) { + return NGX_ERROR; + } + + cr = &t->cursor; + + if (cr->time_pos >= ngx_rtmp_r32(t->times->entry_count)) { + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui time[%ui/%uD] overflow", + t->id, cr->time_pos, + ngx_rtmp_r32(t->times->entry_count)); + + return NGX_ERROR; + } + + te = &t->times->entries[cr->time_pos]; + + cr->last_timestamp = cr->timestamp; + cr->timestamp += ngx_rtmp_r32(te->sample_delta); + + cr->not_first = 1; + + ngx_log_debug8(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui time[%ui] [%ui/%uD][%ui/%uD]=%uD t=%uD", + t->id, cr->pos, cr->time_pos, + ngx_rtmp_r32(t->times->entry_count), + cr->time_count, ngx_rtmp_r32(te->sample_count), + ngx_rtmp_r32(te->sample_delta), + cr->timestamp); + + cr->time_count++; + cr->pos++; + + if (cr->time_count >= ngx_rtmp_r32(te->sample_count)) { + cr->time_pos++; + cr->time_count = 0; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_seek_time(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t, + uint32_t timestamp) +{ + ngx_rtmp_mp4_cursor_t *cr; + ngx_rtmp_mp4_time_entry_t *te; + uint32_t dt; + + if (t->times == NULL) { + return NGX_ERROR; + } + + cr = &t->cursor; + + te = t->times->entries; + + while (cr->time_pos < ngx_rtmp_r32(t->times->entry_count)) { + dt = ngx_rtmp_r32(te->sample_delta) * ngx_rtmp_r32(te->sample_count); + + if (cr->timestamp + dt >= timestamp) { + if (te->sample_delta == 0) { + return NGX_ERROR; + } + + cr->time_count = (timestamp - cr->timestamp) / + ngx_rtmp_r32(te->sample_delta); + cr->timestamp += ngx_rtmp_r32(te->sample_delta) * cr->time_count; + cr->pos += cr->time_count; + + break; + } + + cr->timestamp += dt; + cr->pos += ngx_rtmp_r32(te->sample_count); + cr->time_pos++; + te++; + } + + if (cr->time_pos >= ngx_rtmp_r32(t->times->entry_count)) { + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui seek time[%ui/%uD] overflow", + t->id, cr->time_pos, + ngx_rtmp_r32(t->times->entry_count)); + + return NGX_ERROR; + } + + ngx_log_debug8(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui seek time[%ui] [%ui/%uD][%ui/%uD]=%uD " + "t=%uD", + t->id, cr->pos, cr->time_pos, + ngx_rtmp_r32(t->times->entry_count), + cr->time_count, + ngx_rtmp_r32(te->sample_count), + ngx_rtmp_r32(te->sample_delta), + cr->timestamp); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_update_offset(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t) +{ + ngx_rtmp_mp4_cursor_t *cr; + ngx_uint_t chunk; + + cr = &t->cursor; + + if (cr->chunk < 1) { + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui offset[%ui] underflow", + t->id, cr->chunk); + return NGX_ERROR; + } + + chunk = cr->chunk - 1; + + if (t->offsets) { + if (chunk >= ngx_rtmp_r32(t->offsets->entry_count)) { + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui offset[%ui/%uD] overflow", + t->id, cr->chunk, + ngx_rtmp_r32(t->offsets->entry_count)); + + return NGX_ERROR; + } + + cr->offset = (off_t) ngx_rtmp_r32(t->offsets->entries[chunk]); + cr->size = 0; + + ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui offset[%ui/%uD]=%O", + t->id, cr->chunk, + ngx_rtmp_r32(t->offsets->entry_count), + cr->offset); + + return NGX_OK; + } + + if (t->offsets64) { + if (chunk >= ngx_rtmp_r32(t->offsets64->entry_count)) { + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui offset64[%ui/%uD] overflow", + t->id, cr->chunk, + ngx_rtmp_r32(t->offsets->entry_count)); + + return NGX_ERROR; + } + + cr->offset = (off_t) ngx_rtmp_r64(t->offsets64->entries[chunk]); + cr->size = 0; + + ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui offset64[%ui/%uD]=%O", + t->id, cr->chunk, + ngx_rtmp_r32(t->offsets->entry_count), + cr->offset); + + return NGX_OK; + } + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_rtmp_mp4_next_chunk(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t) +{ + ngx_rtmp_mp4_cursor_t *cr; + ngx_rtmp_mp4_chunk_entry_t *ce, *nce; + ngx_int_t new_chunk; + + if (t->chunks == NULL) { + return NGX_OK; + } + + cr = &t->cursor; + + if (cr->chunk_pos >= ngx_rtmp_r32(t->chunks->entry_count)) { + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui chunk[%ui/%uD] overflow", + t->id, cr->chunk_pos, + ngx_rtmp_r32(t->chunks->entry_count)); + + return NGX_ERROR; + } + + ce = &t->chunks->entries[cr->chunk_pos]; + + cr->chunk_count++; + + if (cr->chunk_count >= ngx_rtmp_r32(ce->samples_per_chunk)) { + cr->chunk_count = 0; + cr->chunk++; + + if (cr->chunk_pos + 1 < ngx_rtmp_r32(t->chunks->entry_count)) { + nce = ce + 1; + if (cr->chunk >= ngx_rtmp_r32(nce->first_chunk)) { + cr->chunk_pos++; + ce = nce; + } + } + + new_chunk = 1; + + } else { + new_chunk = 0; + } + + ngx_log_debug7(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui chunk[%ui/%uD][%uD..%ui][%ui/%uD]", + t->id, cr->chunk_pos, + ngx_rtmp_r32(t->chunks->entry_count), + ngx_rtmp_r32(ce->first_chunk), + cr->chunk, cr->chunk_count, + ngx_rtmp_r32(ce->samples_per_chunk)); + + + if (new_chunk) { + return ngx_rtmp_mp4_update_offset(s, t); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_seek_chunk(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t) +{ + ngx_rtmp_mp4_cursor_t *cr; + ngx_rtmp_mp4_chunk_entry_t *ce, *nce; + ngx_uint_t pos, dpos, dchunk; + + cr = &t->cursor; + + if (t->chunks == NULL || t->chunks->entry_count == 0) { + cr->chunk = 1; + return NGX_OK; + } + + ce = t->chunks->entries; + pos = 0; + + while (cr->chunk_pos + 1 < ngx_rtmp_r32(t->chunks->entry_count)) { + nce = ce + 1; + + dpos = (ngx_rtmp_r32(nce->first_chunk) - + ngx_rtmp_r32(ce->first_chunk)) * + ngx_rtmp_r32(ce->samples_per_chunk); + + if (pos + dpos > cr->pos) { + break; + } + + pos += dpos; + ce++; + cr->chunk_pos++; + } + + if (ce->samples_per_chunk == 0) { + return NGX_ERROR; + } + + dchunk = (cr->pos - pos) / ngx_rtmp_r32(ce->samples_per_chunk); + + cr->chunk = ngx_rtmp_r32(ce->first_chunk) + dchunk; + cr->chunk_pos = (ngx_uint_t) (ce - t->chunks->entries); + cr->chunk_count = (ngx_uint_t) (cr->pos - pos - dchunk * + ngx_rtmp_r32(ce->samples_per_chunk)); + + ngx_log_debug7(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui seek chunk[%ui/%uD][%uD..%ui][%ui/%uD]", + t->id, cr->chunk_pos, + ngx_rtmp_r32(t->chunks->entry_count), + ngx_rtmp_r32(ce->first_chunk), + cr->chunk, cr->chunk_count, + ngx_rtmp_r32(ce->samples_per_chunk)); + + return ngx_rtmp_mp4_update_offset(s, t); +} + + +static ngx_int_t +ngx_rtmp_mp4_next_size(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t) +{ + ngx_rtmp_mp4_cursor_t *cr; + + cr = &t->cursor; + + cr->offset += cr->size; + + if (t->sizes) { + if (t->sizes->sample_size) { + cr->size = ngx_rtmp_r32(t->sizes->sample_size); + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui size fix=%uz", + t->id, cr->size); + + return NGX_OK; + } + + cr->size_pos++; + + if (cr->size_pos >= ngx_rtmp_r32(t->sizes->sample_count)) { + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui size[%ui/%uD] overflow", + t->id, cr->size_pos, + ngx_rtmp_r32(t->sizes->sample_count)); + + return NGX_ERROR; + } + + cr->size = ngx_rtmp_r32(t->sizes->entries[cr->size_pos]); + + ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui size[%ui/%uD]=%uz", + t->id, cr->size_pos, + ngx_rtmp_r32(t->sizes->sample_count), + cr->size); + + return NGX_OK; + } + + if (t->sizes2) { + if (cr->size_pos >= ngx_rtmp_r32(t->sizes2->sample_count)) { + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui size[%ui/%uD] overflow", + t->id, cr->size_pos, + ngx_rtmp_r32(t->sizes2->sample_count)); + + return NGX_ERROR; + } + + /*TODO*/ + + return NGX_OK; + } + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_rtmp_mp4_seek_size(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t) +{ + ngx_rtmp_mp4_cursor_t *cr; + ngx_uint_t pos; + + cr = &t->cursor; + + if (cr->chunk_count > cr->pos) { + return NGX_ERROR; + } + + if (t->sizes) { + if (t->sizes->sample_size) { + cr->size = ngx_rtmp_r32(t->sizes->sample_size); + + cr->offset += cr->size * cr->chunk_count; + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui seek size fix=%uz", + t->id, cr->size); + + return NGX_OK; + } + + if (cr->pos >= ngx_rtmp_r32(t->sizes->sample_count)) { + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui seek size[%ui/%uD] overflow", + t->id, cr->pos, + ngx_rtmp_r32(t->sizes->sample_count)); + + return NGX_ERROR; + } + + for (pos = 1; pos <= cr->chunk_count; ++pos) { + cr->offset += ngx_rtmp_r32(t->sizes->entries[cr->pos - pos]); + } + + cr->size_pos = cr->pos; + cr->size = ngx_rtmp_r32(t->sizes->entries[cr->size_pos]); + + ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui seek size[%ui/%uD]=%uz", + t->id, cr->size_pos, + ngx_rtmp_r32(t->sizes->sample_count), + cr->size); + + return NGX_OK; + } + + if (t->sizes2) { + if (cr->size_pos >= ngx_rtmp_r32(t->sizes2->sample_count)) { + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui seek size2[%ui/%uD] overflow", + t->id, cr->size_pos, + ngx_rtmp_r32(t->sizes->sample_count)); + + return NGX_ERROR; + } + + cr->size_pos = cr->pos; + + /* TODO */ + return NGX_OK; + } + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_rtmp_mp4_next_key(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t) +{ + ngx_rtmp_mp4_cursor_t *cr; + uint32_t *ke; + + cr = &t->cursor; + + if (t->keys == NULL) { + return NGX_OK; + } + + if (cr->key) { + cr->key_pos++; + } + + if (cr->key_pos >= ngx_rtmp_r32(t->keys->entry_count)) { + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui key[%ui/%uD] overflow", + t->id, cr->key_pos, + ngx_rtmp_r32(t->keys->entry_count)); + + cr->key = 0; + + return NGX_OK; + } + + ke = &t->keys->entries[cr->key_pos]; + cr->key = (cr->pos + 1 == ngx_rtmp_r32(*ke)); + + ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui key[%ui/%uD][%ui/%uD]=%s", + t->id, cr->key_pos, + ngx_rtmp_r32(t->keys->entry_count), + cr->pos, ngx_rtmp_r32(*ke), + cr->key ? "match" : "miss"); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_seek_key(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t) +{ + ngx_rtmp_mp4_cursor_t *cr; + uint32_t *ke; + ngx_int_t dpos; + + cr = &t->cursor; + + if (t->keys == NULL) { + return NGX_OK; + } + + while (cr->key_pos < ngx_rtmp_r32(t->keys->entry_count)) { + if (ngx_rtmp_r32(t->keys->entries[cr->key_pos]) > cr->pos) { + break; + } + + cr->key_pos++; + } + + if (cr->key_pos >= ngx_rtmp_r32(t->keys->entry_count)) { + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui seek key[%ui/%uD] overflow", + t->id, cr->key_pos, + ngx_rtmp_r32(t->keys->entry_count)); + return NGX_OK; + } + + ke = &t->keys->entries[cr->key_pos]; + /*cr->key = (cr->pos + 1 == ngx_rtmp_r32(*ke));*/ + + /* distance to the next keyframe */ + dpos = ngx_rtmp_r32(*ke) - cr->pos - 1; + cr->key = 1; + + /* TODO: range version needed */ + for (; dpos > 0; --dpos) { + ngx_rtmp_mp4_next_time(s, t); + } + +/* cr->key = (cr->pos + 1 == ngx_rtmp_r32(*ke));*/ + + ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui seek key[%ui/%uD][%ui/%uD]=%s", + t->id, cr->key_pos, + ngx_rtmp_r32(t->keys->entry_count), + cr->pos, ngx_rtmp_r32(*ke), + cr->key ? "match" : "miss"); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_next_delay(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t) +{ + ngx_rtmp_mp4_cursor_t *cr; + ngx_rtmp_mp4_delay_entry_t *de; + + cr = &t->cursor; + + if (t->delays == NULL) { + return NGX_OK; + } + + if (cr->delay_pos >= ngx_rtmp_r32(t->delays->entry_count)) { + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui delay[%ui/%uD] overflow", + t->id, cr->delay_pos, + ngx_rtmp_r32(t->delays->entry_count)); + + return NGX_OK; + } + + cr->delay_count++; + de = &t->delays->entries[cr->delay_pos]; + + if (cr->delay_count >= ngx_rtmp_r32(de->sample_count)) { + cr->delay_pos++; + de++; + cr->delay_count = 0; + } + + if (cr->delay_pos >= ngx_rtmp_r32(t->delays->entry_count)) { + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui delay[%ui/%uD] overflow", + t->id, cr->delay_pos, + ngx_rtmp_r32(t->delays->entry_count)); + + return NGX_OK; + } + + cr->delay = ngx_rtmp_r32(de->sample_offset); + + ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui delay[%ui/%uD][%ui/%uD]=%ui", + t->id, cr->delay_pos, + ngx_rtmp_r32(t->delays->entry_count), + cr->delay_count, + ngx_rtmp_r32(de->sample_count), cr->delay); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_seek_delay(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t) +{ + ngx_rtmp_mp4_cursor_t *cr; + ngx_rtmp_mp4_delay_entry_t *de; + uint32_t pos, dpos; + + cr = &t->cursor; + + if (t->delays == NULL) { + return NGX_OK; + } + + pos = 0; + de = t->delays->entries; + + while (cr->delay_pos < ngx_rtmp_r32(t->delays->entry_count)) { + dpos = ngx_rtmp_r32(de->sample_count); + + if (pos + dpos > cr->pos) { + cr->delay_count = cr->pos - pos; + cr->delay = ngx_rtmp_r32(de->sample_offset); + break; + } + + cr->delay_pos++; + pos += dpos; + de++; + } + + if (cr->delay_pos >= ngx_rtmp_r32(t->delays->entry_count)) { + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui seek delay[%ui/%uD] overflow", + t->id, cr->delay_pos, + ngx_rtmp_r32(t->delays->entry_count)); + + return NGX_OK; + } + + ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui seek delay[%ui/%uD][%ui/%uD]=%ui", + t->id, cr->delay_pos, + ngx_rtmp_r32(t->delays->entry_count), + cr->delay_count, + ngx_rtmp_r32(de->sample_count), cr->delay); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_next(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t) +{ + if (ngx_rtmp_mp4_next_time(s, t) != NGX_OK || + ngx_rtmp_mp4_next_key(s, t) != NGX_OK || + ngx_rtmp_mp4_next_chunk(s, t) != NGX_OK || + ngx_rtmp_mp4_next_size(s, t) != NGX_OK || + ngx_rtmp_mp4_next_delay(s, t) != NGX_OK) + { + t->cursor.valid = 0; + return NGX_ERROR; + } + + t->cursor.valid = 1; + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_send_meta(ngx_rtmp_session_t *s) +{ + ngx_rtmp_mp4_ctx_t *ctx; + ngx_rtmp_core_srv_conf_t *cscf; + ngx_int_t rc; + ngx_uint_t n; + ngx_rtmp_header_t h; + ngx_chain_t *out; + ngx_rtmp_mp4_track_t *t; + double d; + + static struct { + double width; + double height; + double duration; + double video_codec_id; + double audio_codec_id; + double audio_sample_rate; + } v; + + static ngx_rtmp_amf_elt_t out_inf[] = { + + { 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("videocodecid"), + &v.video_codec_id, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("audiocodecid"), + &v.audio_codec_id, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("audiosamplerate"), + &v.audio_sample_rate, 0 }, + }; + + static ngx_rtmp_amf_elt_t out_elts[] = { + + { 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_mp4_module); + if (ctx == NULL) { + return NGX_OK; + } + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + ngx_memzero(&v, sizeof(v)); + + v.width = ctx->width; + v.height = ctx->height; + v.audio_sample_rate = ctx->sample_rate; + + t = &ctx->tracks[0]; + for (n = 0; n < ctx->ntracks; ++n, ++t) { + d = ngx_rtmp_mp4_to_rtmp_timestamp(t, t->duration) / 1000.; + + if (v.duration < d) { + v.duration = d; + } + + switch (t->type) { + case NGX_RTMP_MSG_AUDIO: + v.audio_codec_id = t->codec; + break; + case NGX_RTMP_MSG_VIDEO: + v.video_codec_id = t->codec; + break; + } + } + + out = NULL; + rc = ngx_rtmp_append_amf(s, &out, NULL, out_elts, + sizeof(out_elts) / sizeof(out_elts[0])); + if (rc != NGX_OK || out == NULL) { + return NGX_ERROR; + } + + ngx_memzero(&h, sizeof(h)); + + h.csid = NGX_RTMP_CSID_AMF; + h.msid = NGX_RTMP_MSID; + h.type = NGX_RTMP_MSG_AMF_META; + + ngx_rtmp_prepare_message(s, &h, NULL, out); + rc = ngx_rtmp_send_message(s, out, 0); + ngx_rtmp_free_shared_chain(cscf, out); + + return rc; +} + + +static ngx_int_t +ngx_rtmp_mp4_seek_track(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t, + ngx_int_t timestamp) +{ + ngx_rtmp_mp4_cursor_t *cr; + + cr = &t->cursor; + ngx_memzero(cr, sizeof(*cr)); + + if (ngx_rtmp_mp4_seek_time(s, t, ngx_rtmp_mp4_from_rtmp_timestamp( + t, timestamp)) != NGX_OK || + ngx_rtmp_mp4_seek_key(s, t) != NGX_OK || + ngx_rtmp_mp4_seek_chunk(s, t) != NGX_OK || + ngx_rtmp_mp4_seek_size(s, t) != NGX_OK || + ngx_rtmp_mp4_seek_delay(s, t) != NGX_OK) + { + return NGX_ERROR; + } + + cr->valid = 1; + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_send(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_uint_t *ts) +{ + ngx_rtmp_mp4_ctx_t *ctx; + ngx_buf_t in_buf; + ngx_rtmp_header_t h, lh; + ngx_rtmp_core_srv_conf_t *cscf; + ngx_chain_t *out, in; + ngx_rtmp_mp4_track_t *t, *cur_t; + ngx_rtmp_mp4_cursor_t *cr, *cur_cr; + uint32_t buflen, end_timestamp, + timestamp, last_timestamp, rdelay, + cur_timestamp; + ssize_t ret; + u_char fhdr[5]; + size_t fhdr_size; + ngx_int_t rc; + ngx_uint_t n, counter; + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); + + if (ctx == NULL) { + return NGX_ERROR; + } + + if (!ctx->meta_sent) { + rc = ngx_rtmp_mp4_send_meta(s); + + if (rc == NGX_OK) { + ctx->meta_sent = 1; + } + + return rc; + } + + buflen = s->buflen + NGX_RTMP_MP4_BUFLEN_ADDON; + + counter = 0; + last_timestamp = 0; + end_timestamp = ctx->start_timestamp + + (ngx_current_msec - ctx->epoch) + buflen; + + for ( ;; ) { + counter++; + if (counter > NGX_RTMP_MP4_MAX_FRAMES) { + return NGX_OK; + } + + timestamp = 0; + t = NULL; + + for (n = 0; n < ctx->ntracks; n++) { + cur_t = &ctx->tracks[n]; + cur_cr = &cur_t->cursor; + + if (!cur_cr->valid) { + continue; + } + + cur_timestamp = ngx_rtmp_mp4_to_rtmp_timestamp(cur_t, + cur_cr->timestamp); + + if (t == NULL || cur_timestamp < timestamp) { + timestamp = cur_timestamp; + t = cur_t; + } + } + + if (t == NULL) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "mp4: no track"); + return NGX_DONE; + } + + if (timestamp > end_timestamp) { + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui ahead %uD > %uD", + t->id, timestamp, end_timestamp); + + if (ts) { + *ts = last_timestamp; + } + + return (uint32_t) (timestamp - end_timestamp); + } + + cr = &t->cursor; + + last_timestamp = ngx_rtmp_mp4_to_rtmp_timestamp(t, cr->last_timestamp); + + ngx_memzero(&h, sizeof(h)); + + h.msid = NGX_RTMP_MSID; + h.type = (uint8_t) t->type; + h.csid = t->csid; + + lh = h; + + h.timestamp = timestamp; + lh.timestamp = last_timestamp; + + ngx_memzero(&in, sizeof(in)); + ngx_memzero(&in_buf, sizeof(in_buf)); + + if (t->header && !t->header_sent) { + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui sending header of size=%uz", + t->id, t->header_size); + + fhdr[0] = t->fhdr; + fhdr[1] = 0; + + if (t->type == NGX_RTMP_MSG_VIDEO) { + fhdr[0] |= 0x10; + fhdr[2] = fhdr[3] = fhdr[4] = 0; + fhdr_size = 5; + } else { + fhdr_size = 2; + } + + in.buf = &in_buf; + in_buf.pos = fhdr; + in_buf.last = fhdr + fhdr_size; + + out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in); + + in.buf = &in_buf; + in_buf.pos = t->header; + in_buf.last = t->header + t->header_size; + + ngx_rtmp_append_shared_bufs(cscf, out, &in); + + ngx_rtmp_prepare_message(s, &h, NULL, out); + rc = ngx_rtmp_send_message(s, out, 0); + ngx_rtmp_free_shared_chain(cscf, out); + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + t->header_sent = 1; + } + + ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui read frame offset=%O, size=%uz, " + "timestamp=%uD, last_timestamp=%uD", + t->id, cr->offset, cr->size, timestamp, + last_timestamp); + + ngx_rtmp_mp4_buffer[0] = t->fhdr; + fhdr_size = 1; + + if (t->type == NGX_RTMP_MSG_VIDEO) { + if (cr->key) { + ngx_rtmp_mp4_buffer[0] |= 0x10; + } else if (cr->delay) { + ngx_rtmp_mp4_buffer[0] |= 0x20; + } else { + ngx_rtmp_mp4_buffer[0] |= 0x30; + } + + if (t->header) { + fhdr_size = 5; + + rdelay = ngx_rtmp_mp4_to_rtmp_timestamp(t, cr->delay); + + ngx_rtmp_mp4_buffer[1] = 1; + ngx_rtmp_mp4_buffer[2] = (rdelay >> 16) & 0xff; + ngx_rtmp_mp4_buffer[3] = (rdelay >> 8) & 0xff; + ngx_rtmp_mp4_buffer[4] = rdelay & 0xff; + } + + } else { /* NGX_RTMP_MSG_AUDIO */ + if (t->header) { + fhdr_size = 2; + ngx_rtmp_mp4_buffer[1] = 1; + } + } + + if (cr->size + fhdr_size > sizeof(ngx_rtmp_mp4_buffer)) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "mp4: track#%ui too big frame: %D>%uz", + t->id, cr->size, sizeof(ngx_rtmp_mp4_buffer)); + goto next; + } + + ret = ngx_read_file(f, ngx_rtmp_mp4_buffer + fhdr_size, + cr->size, cr->offset); + + if (ret != (ssize_t) cr->size) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "mp4: track#%ui could not read frame", t->id); + goto next; + } + + in.buf = &in_buf; + in_buf.pos = ngx_rtmp_mp4_buffer; + in_buf.last = ngx_rtmp_mp4_buffer + cr->size + fhdr_size; + + out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in); + + ngx_rtmp_prepare_message(s, &h, cr->not_first ? &lh : NULL, out); + rc = ngx_rtmp_send_message(s, out, 0); + ngx_rtmp_free_shared_chain(cscf, out); + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + s->current_time = timestamp; + +next: + if (ngx_rtmp_mp4_next(s, t) != NGX_OK) { + return NGX_DONE; + } + } +} + + +static ngx_int_t +ngx_rtmp_mp4_init(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_int_t aindex, + ngx_int_t vindex) +{ + ngx_rtmp_mp4_ctx_t *ctx; + uint32_t hdr[2]; + ssize_t n; + size_t offset, page_offset, size, shift; + uint64_t extended_size; + ngx_file_info_t fi; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); + + if (ctx == NULL) { + ctx = ngx_palloc(s->connection->pool, sizeof(ngx_rtmp_mp4_ctx_t)); + + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_mp4_module); + } + + ngx_memzero(ctx, sizeof(*ctx)); + + ctx->aindex = aindex; + ctx->vindex = vindex; + + offset = 0; + size = 0; + + for ( ;; ) { + n = ngx_read_file(f, (u_char *) &hdr, sizeof(hdr), offset); + + if (n != sizeof(hdr)) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "mp4: error reading file at offset=%uz " + "while searching for moov box", offset); + return NGX_ERROR; + } + + size = (size_t) ngx_rtmp_r32(hdr[0]); + shift = sizeof(hdr); + + if (size == 1) { + n = ngx_read_file(f, (u_char *) &extended_size, + sizeof(extended_size), offset + sizeof(hdr)); + + if (n != sizeof(extended_size)) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "mp4: error reading file at offset=%uz " + "while searching for moov box", offset + 8); + return NGX_ERROR; + } + + size = (size_t) ngx_rtmp_r64(extended_size); + shift += sizeof(extended_size); + + } else if (size == 0) { + if (ngx_fd_info(f->fd, &fi) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "mp4: " ngx_fd_info_n " failed"); + return NGX_ERROR; + } + size = ngx_file_size(&fi) - offset; + } + + if (hdr[1] == ngx_rtmp_mp4_make_tag('m','o','o','v')) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: found moov box"); + break; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: skipping box '%*s'", 4, hdr + 1); + + offset += size; + } + + if (size < shift) { + return NGX_ERROR; + } + + size -= shift; + offset += shift; + + page_offset = offset & (ngx_pagesize - 1); + ctx->mmaped_size = page_offset + size; + + ctx->mmaped = ngx_rtmp_mp4_mmap(f->fd, ctx->mmaped_size, + offset - page_offset, &ctx->extra); + if (ctx->mmaped == NULL) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "mp4: mmap failed at offset=%ui, size=%uz", + offset, size); + return NGX_ERROR; + } + + return ngx_rtmp_mp4_parse(s, (u_char *) ctx->mmaped + page_offset, + (u_char *) ctx->mmaped + page_offset + size); +} + + +static ngx_int_t +ngx_rtmp_mp4_done(ngx_rtmp_session_t *s, ngx_file_t *f) +{ + ngx_rtmp_mp4_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); + + if (ctx == NULL || ctx->mmaped == NULL) { + return NGX_OK; + } + + if (ngx_rtmp_mp4_munmap(ctx->mmaped, ctx->mmaped_size, &ctx->extra) + != NGX_OK) + { + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "mp4: munmap failed"); + return NGX_ERROR; + } + + ctx->mmaped = NULL; + ctx->mmaped_size = 0; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_seek(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_uint_t timestamp) +{ + ngx_rtmp_mp4_ctx_t *ctx; + ngx_rtmp_mp4_track_t *t; + ngx_uint_t n; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); + + if (ctx == NULL) { + return NGX_OK; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: seek timestamp=%ui", timestamp); + + for (n = 0; n < ctx->ntracks; ++n) { + t = &ctx->tracks[n]; + + if (t->type != NGX_RTMP_MSG_VIDEO) { + continue; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui seek video", n); + + ngx_rtmp_mp4_seek_track(s, t, timestamp); + + timestamp = ngx_rtmp_mp4_to_rtmp_timestamp(t, t->cursor.timestamp); + + break; + } + + for (n = 0; n < ctx->ntracks; ++n) { + t = &ctx->tracks[n]; + + if (t->type == NGX_RTMP_MSG_VIDEO) { + continue; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: track#%ui seek", n); + + ngx_rtmp_mp4_seek_track(s, &ctx->tracks[n], timestamp); + } + + ctx->start_timestamp = timestamp; + ctx->epoch = ngx_current_msec; + + return ngx_rtmp_mp4_reset(s); +} + + +static ngx_int_t +ngx_rtmp_mp4_start(ngx_rtmp_session_t *s, ngx_file_t *f) +{ + ngx_rtmp_mp4_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); + + if (ctx == NULL) { + return NGX_OK; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: start timestamp=%uD", ctx->start_timestamp); + + ctx->epoch = ngx_current_msec; + + return NGX_OK;/*ngx_rtmp_mp4_reset(s);*/ +} + + +static ngx_int_t +ngx_rtmp_mp4_reset(ngx_rtmp_session_t *s) +{ + ngx_rtmp_mp4_ctx_t *ctx; + ngx_rtmp_mp4_cursor_t *cr; + ngx_rtmp_mp4_track_t *t; + ngx_uint_t n; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); + + if (ctx == NULL) { + return NGX_OK; + } + + t = &ctx->tracks[0]; + for (n = 0; n < ctx->ntracks; ++n, ++t) { + cr = &t->cursor; + cr->not_first = 0; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_mp4_stop(ngx_rtmp_session_t *s, ngx_file_t *f) +{ + ngx_rtmp_mp4_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); + + if (ctx == NULL) { + return NGX_OK; + } + + ctx->start_timestamp += (ngx_current_msec - ctx->epoch); + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "mp4: stop timestamp=%uD", ctx->start_timestamp); + + return NGX_OK;/*ngx_rtmp_mp4_reset(s);*/ +} + + +static ngx_int_t +ngx_rtmp_mp4_postconfiguration(ngx_conf_t *cf) +{ + ngx_rtmp_play_main_conf_t *pmcf; + ngx_rtmp_play_fmt_t **pfmt, *fmt; + + pmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_play_module); + + pfmt = ngx_array_push(&pmcf->fmts); + + if (pfmt == NULL) { + return NGX_ERROR; + } + + fmt = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_play_fmt_t)); + + if (fmt == NULL) { + return NGX_ERROR; + } + + *pfmt = fmt; + + ngx_str_set(&fmt->name, "mp4-format"); + + ngx_str_set(&fmt->pfx, "mp4:"); + ngx_str_set(&fmt->sfx, ".mp4"); + + fmt->init = ngx_rtmp_mp4_init; + fmt->done = ngx_rtmp_mp4_done; + fmt->seek = ngx_rtmp_mp4_seek; + fmt->start = ngx_rtmp_mp4_start; + fmt->stop = ngx_rtmp_mp4_stop; + fmt->send = ngx_rtmp_mp4_send; + + return NGX_OK; +} diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_netcall_module.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_netcall_module.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_netcall_module.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_netcall_module.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,725 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp_netcall_module.h" + + +static ngx_int_t ngx_rtmp_netcall_postconfiguration(ngx_conf_t *cf); +static void * ngx_rtmp_netcall_create_srv_conf(ngx_conf_t *cf); +static char * ngx_rtmp_netcall_merge_srv_conf(ngx_conf_t *cf, + void *parent, void *child); + +static void ngx_rtmp_netcall_close(ngx_connection_t *cc); +static void ngx_rtmp_netcall_detach(ngx_connection_t *cc); + +static void ngx_rtmp_netcall_recv(ngx_event_t *rev); +static void ngx_rtmp_netcall_send(ngx_event_t *wev); + + +typedef struct { + ngx_msec_t timeout; + size_t bufsize; + ngx_log_t *log; +} ngx_rtmp_netcall_srv_conf_t; + + +typedef struct ngx_rtmp_netcall_session_s { + ngx_rtmp_session_t *session; + ngx_peer_connection_t *pc; + ngx_url_t *url; + struct ngx_rtmp_netcall_session_s *next; + void *arg; + ngx_rtmp_netcall_handle_pt handle; + ngx_rtmp_netcall_filter_pt filter; + ngx_rtmp_netcall_sink_pt sink; + ngx_chain_t *in; + ngx_chain_t *inlast; + ngx_chain_t *out; + ngx_msec_t timeout; + unsigned detached:1; + size_t bufsize; +} ngx_rtmp_netcall_session_t; + + +typedef struct { + ngx_rtmp_netcall_session_t *cs; +} ngx_rtmp_netcall_ctx_t; + + +static ngx_command_t ngx_rtmp_netcall_commands[] = { + + { ngx_string("netcall_timeout"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_netcall_srv_conf_t, timeout), + NULL }, + + { ngx_string("netcall_buffer"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_RTMP_SRV_CONF_OFFSET, + offsetof(ngx_rtmp_netcall_srv_conf_t, bufsize), + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_rtmp_netcall_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_netcall_postconfiguration, /* postconfiguration */ + NULL, /* create main configuration */ + NULL, /* init main configuration */ + ngx_rtmp_netcall_create_srv_conf, /* create server configuration */ + ngx_rtmp_netcall_merge_srv_conf, /* merge server configuration */ + NULL, /* create app configuration */ + NULL /* merge app configuration */ +}; + + +ngx_module_t ngx_rtmp_netcall_module = { + NGX_MODULE_V1, + &ngx_rtmp_netcall_module_ctx, /* module context */ + ngx_rtmp_netcall_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_rtmp_netcall_create_srv_conf(ngx_conf_t *cf) +{ + ngx_rtmp_netcall_srv_conf_t *nscf; + + nscf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_netcall_srv_conf_t)); + if (nscf == NULL) { + return NULL; + } + + nscf->timeout = NGX_CONF_UNSET_MSEC; + nscf->bufsize = NGX_CONF_UNSET_SIZE; + + nscf->log = &cf->cycle->new_log; + + return nscf; +} + + +static char * +ngx_rtmp_netcall_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_netcall_srv_conf_t *prev = parent; + ngx_rtmp_netcall_srv_conf_t *conf = child; + + ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 10000); + ngx_conf_merge_size_value(conf->bufsize, prev->bufsize, 1024); + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_rtmp_netcall_disconnect(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_rtmp_netcall_ctx_t *ctx; + ngx_rtmp_netcall_session_t *cs; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_netcall_module); + + if (ctx) { + for (cs = ctx->cs; cs; cs = cs->next) { + ngx_rtmp_netcall_detach(cs->pc->connection); + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_netcall_get_peer(ngx_peer_connection_t *pc, void *data) +{ + ngx_rtmp_netcall_session_t *cs = data; + + pc->sockaddr =(struct sockaddr *)&cs->url->sockaddr; + pc->socklen = cs->url->socklen; + pc->name = &cs->url->host; + + return NGX_OK; +} + + +static void +ngx_rtmp_netcall_free_peer(ngx_peer_connection_t *pc, void *data, + ngx_uint_t state) +{ +} + + +ngx_int_t +ngx_rtmp_netcall_create(ngx_rtmp_session_t *s, ngx_rtmp_netcall_init_t *ci) +{ + ngx_rtmp_netcall_ctx_t *ctx; + ngx_peer_connection_t *pc; + ngx_rtmp_netcall_session_t *cs; + ngx_rtmp_netcall_srv_conf_t *nscf; + ngx_connection_t *c, *cc; + ngx_pool_t *pool; + ngx_int_t rc; + + pool = NULL; + c = s->connection; + + nscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_netcall_module); + if (nscf == NULL) { + goto error; + } + + /* get module context */ + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_netcall_module); + if (ctx == NULL) { + ctx = ngx_pcalloc(c->pool, + sizeof(ngx_rtmp_netcall_ctx_t)); + if (ctx == NULL) { + return NGX_ERROR; + } + ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_netcall_module); + } + + /* Create netcall pool, connection, session. + * Note we use shared (app-wide) log because + * s->connection->log might be unavailable + * in detached netcall when it's being closed */ + pool = ngx_create_pool(4096, nscf->log); + if (pool == NULL) { + goto error; + } + + pc = ngx_pcalloc(pool, sizeof(ngx_peer_connection_t)); + if (pc == NULL) { + goto error; + } + + cs = ngx_pcalloc(pool, sizeof(ngx_rtmp_netcall_session_t)); + if (cs == NULL) { + goto error; + } + + /* copy arg to connection pool */ + if (ci->argsize) { + cs->arg = ngx_pcalloc(pool, ci->argsize); + if (cs->arg == NULL) { + goto error; + } + ngx_memcpy(cs->arg, ci->arg, ci->argsize); + } + + cs->timeout = nscf->timeout; + cs->bufsize = nscf->bufsize; + cs->url = ci->url; + cs->session = s; + cs->filter = ci->filter; + cs->sink = ci->sink; + cs->handle = ci->handle; + if (cs->handle == NULL) { + cs->detached = 1; + } + + pc->log = nscf->log; + pc->get = ngx_rtmp_netcall_get_peer; + pc->free = ngx_rtmp_netcall_free_peer; + pc->data = cs; + + /* connect */ + rc = ngx_event_connect_peer(pc); + if (rc != NGX_OK && rc != NGX_AGAIN ) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "netcall: connection failed"); + goto error; + } + + cc = pc->connection; + cc->data = cs; + cc->pool = pool; + cs->pc = pc; + + cs->out = ci->create(s, ci->arg, pool); + if (cs->out == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "netcall: creation failed"); + ngx_close_connection(pc->connection); + goto error; + } + + cc->write->handler = ngx_rtmp_netcall_send; + cc->read->handler = ngx_rtmp_netcall_recv; + + if (!cs->detached) { + cs->next = ctx->cs; + ctx->cs = cs; + } + + ngx_rtmp_netcall_send(cc->write); + + return c->destroyed ? NGX_ERROR : NGX_OK; + +error: + if (pool) { + ngx_destroy_pool(pool); + } + + return NGX_ERROR; +} + + +static void +ngx_rtmp_netcall_close(ngx_connection_t *cc) +{ + ngx_rtmp_netcall_session_t *cs, **css; + ngx_pool_t *pool; + ngx_rtmp_session_t *s; + ngx_rtmp_netcall_ctx_t *ctx; + ngx_buf_t *b; + + cs = cc->data; + + if (cc->destroyed) { + return; + } + + cc->destroyed = 1; + + if (!cs->detached) { + s = cs->session; + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_netcall_module); + + if (cs->in && cs->sink) { + cs->sink(cs->session, cs->in); + + b = cs->in->buf; + b->pos = b->last = b->start; + + } + + for(css = &ctx->cs; *css; css = &((*css)->next)) { + if (*css == cs) { + *css = cs->next; + break; + } + } + + if (cs->handle && cs->handle(s, cs->arg, cs->in) != NGX_OK) { + ngx_rtmp_finalize_session(s); + } + } + + pool = cc->pool; + ngx_close_connection(cc); + ngx_destroy_pool(pool); +} + + +static void +ngx_rtmp_netcall_detach(ngx_connection_t *cc) +{ + ngx_rtmp_netcall_session_t *cs; + + cs = cc->data; + cs->detached = 1; +} + + +static void +ngx_rtmp_netcall_recv(ngx_event_t *rev) +{ + ngx_rtmp_netcall_session_t *cs; + ngx_connection_t *cc; + ngx_chain_t *cl; + ngx_int_t n; + ngx_buf_t *b; + + cc = rev->data; + cs = cc->data; + + if (cc->destroyed) { + return; + } + + if (rev->timedout) { + cc->timedout = 1; + ngx_rtmp_netcall_close(cc); + return; + } + + if (rev->timer_set) { + ngx_del_timer(rev); + } + + for ( ;; ) { + + if (cs->inlast == NULL || + cs->inlast->buf->last == cs->inlast->buf->end) + { + if (cs->in && cs->sink) { + if (!cs->detached) { + if (cs->sink(cs->session, cs->in) != NGX_OK) { + ngx_rtmp_netcall_close(cc); + return; + } + } + + b = cs->in->buf; + b->pos = b->last = b->start; + + } else { + cl = ngx_alloc_chain_link(cc->pool); + if (cl == NULL) { + ngx_rtmp_netcall_close(cc); + return; + } + + cl->next = NULL; + + cl->buf = ngx_create_temp_buf(cc->pool, cs->bufsize); + if (cl->buf == NULL) { + ngx_rtmp_netcall_close(cc); + return; + } + + if (cs->in == NULL) { + cs->in = cl; + } else { + cs->inlast->next = cl; + } + + cs->inlast = cl; + } + } + + b = cs->inlast->buf; + + n = cc->recv(cc, b->last, b->end - b->last); + + if (n == NGX_ERROR || n == 0) { + ngx_rtmp_netcall_close(cc); + return; + } + + if (n == NGX_AGAIN) { + if (cs->filter && cs->in + && cs->filter(cs->in) != NGX_AGAIN) + { + ngx_rtmp_netcall_close(cc); + return; + } + + ngx_add_timer(rev, cs->timeout); + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_rtmp_netcall_close(cc); + } + return; + } + + b->last += n; + } +} + + +static void +ngx_rtmp_netcall_send(ngx_event_t *wev) +{ + ngx_rtmp_netcall_session_t *cs; + ngx_connection_t *cc; + ngx_chain_t *cl; + + cc = wev->data; + cs = cc->data; + + if (cc->destroyed) { + return; + } + + if (wev->timedout) { + ngx_log_error(NGX_LOG_INFO, cc->log, NGX_ETIMEDOUT, + "netcall: client send timed out"); + cc->timedout = 1; + ngx_rtmp_netcall_close(cc); + return; + } + + if (wev->timer_set) { + ngx_del_timer(wev); + } + + cl = cc->send_chain(cc, cs->out, 0); + + if (cl == NGX_CHAIN_ERROR) { + ngx_rtmp_netcall_close(cc); + return; + } + + cs->out = cl; + + /* more data to send? */ + if (cl) { + ngx_add_timer(wev, cs->timeout); + if (ngx_handle_write_event(wev, 0) != NGX_OK) { + ngx_rtmp_netcall_close(cc); + } + return; + } + + /* we've sent everything we had. + * now receive reply */ + ngx_del_event(wev, NGX_WRITE_EVENT, 0); + + ngx_rtmp_netcall_recv(cc->read); +} + + +ngx_chain_t * +ngx_rtmp_netcall_http_format_request(ngx_int_t method, ngx_str_t *host, + ngx_str_t *uri, ngx_chain_t *args, + ngx_chain_t *body, ngx_pool_t *pool, + ngx_str_t *content_type) +{ + ngx_chain_t *al, *bl, *ret; + ngx_buf_t *b; + size_t content_length; + static const char *methods[2] = { "GET", "POST" }; + static const char rq_tmpl[] = " HTTP/1.0\r\n" + "Host: %V\r\n" + "Content-Type: %V\r\n" + "Connection: Close\r\n" + "Content-Length: %uz\r\n" + "\r\n"; + + content_length = 0; + for (al = body; al; al = al->next) { + b = al->buf; + content_length += (b->last - b->pos); + } + + /* create first buffer */ + + al = ngx_alloc_chain_link(pool); + if (al == NULL) { + return NULL; + } + + b = ngx_create_temp_buf(pool, sizeof("POST") + /* longest method + 1 */ + uri->len); + if (b == NULL) { + return NULL; + } + + b->last = ngx_snprintf(b->last, b->end - b->last, "%s %V", + methods[method], uri); + + al->buf = b; + + ret = al; + + if (args) { + *b->last++ = '?'; + al->next = args; + for (al = args; al->next; al = al->next); + } + + /* create second buffer */ + + bl = ngx_alloc_chain_link(pool); + if (bl == NULL) { + return NULL; + } + + b = ngx_create_temp_buf(pool, sizeof(rq_tmpl) + host->len + + content_type->len + NGX_SIZE_T_LEN); + if (b == NULL) { + return NULL; + } + + bl->buf = b; + + b->last = ngx_snprintf(b->last, b->end - b->last, rq_tmpl, + host, content_type, content_length); + + al->next = bl; + bl->next = body; + + return ret; +} + + +ngx_chain_t * +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; + + addr_text = &s->connection->addr_text; + + cl = ngx_alloc_chain_link(pool); + if (cl == NULL) { + 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 + ); + + if (b == NULL) { + return NULL; + } + + 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); + + b->last = ngx_cpymem(b->last, (u_char*) "&addr=", sizeof("&addr=") - 1); + b->last = (u_char*) ngx_escape_uri(b->last, addr_text->data, + addr_text->len, NGX_ESCAPE_ARGS); + + b->last = ngx_cpymem(b->last, (u_char*) "&clientid=", + sizeof("&clientid=") - 1); + b->last = ngx_sprintf(b->last, "%ui", (ngx_uint_t) s->connection->number); + + return cl; +} + + +ngx_chain_t * +ngx_rtmp_netcall_http_skip_header(ngx_chain_t *in) +{ + ngx_buf_t *b; + + /* find \n[\r]\n */ + enum { + normal, + lf, + lfcr + } state = normal; + + if (in == NULL) { + return NULL; + } + + b = in->buf; + + for ( ;; ) { + + while (b->pos == b->last) { + in = in->next; + if (in == NULL) { + return NULL; + } + b = in->buf; + } + + switch (*b->pos++) { + case '\r': + state = (state == lf) ? lfcr : normal; + break; + + case '\n': + if (state != normal) { + return in; + } + state = lf; + break; + + default: + state = normal; + } + } +} + + +ngx_chain_t * +ngx_rtmp_netcall_memcache_set(ngx_rtmp_session_t *s, ngx_pool_t *pool, + ngx_str_t *key, ngx_str_t *value, ngx_uint_t flags, ngx_uint_t sec) +{ + ngx_chain_t *cl; + ngx_buf_t *b; + + cl = ngx_alloc_chain_link(pool); + if (cl == NULL) { + return NULL; + } + + b = ngx_create_temp_buf(pool, sizeof("set ") - 1 + key->len + + (1 + NGX_INT_T_LEN) * 3 + + (sizeof("\r\n") - 1) * 2 + value->len); + + if (b == NULL) { + return NULL; + } + + cl->next = NULL; + cl->buf = b; + + b->last = ngx_sprintf(b->pos, "set %V %ui %ui %ui\r\n%V\r\n", + key, flags, sec, (ngx_uint_t) value->len, value); + + return cl; +} + + +static ngx_int_t +ngx_rtmp_netcall_postconfiguration(ngx_conf_t *cf) +{ + ngx_rtmp_core_main_conf_t *cmcf; + ngx_rtmp_handler_pt *h; + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + + h = ngx_array_push(&cmcf->events[NGX_RTMP_DISCONNECT]); + *h = ngx_rtmp_netcall_disconnect; + + return NGX_OK; +} + diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_netcall_module.h nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_netcall_module.h --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_netcall_module.h 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_netcall_module.h 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,67 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_NETCALL_H_INCLUDED_ +#define _NGX_RTMP_NETCALL_H_INCLUDED_ + + +#include +#include +#include "ngx_rtmp.h" + + +typedef ngx_chain_t * (*ngx_rtmp_netcall_create_pt)(ngx_rtmp_session_t *s, + void *arg, ngx_pool_t *pool); +typedef ngx_int_t (*ngx_rtmp_netcall_filter_pt)(ngx_chain_t *in); +typedef ngx_int_t (*ngx_rtmp_netcall_sink_pt)(ngx_rtmp_session_t *s, + ngx_chain_t *in); +typedef ngx_int_t (*ngx_rtmp_netcall_handle_pt)(ngx_rtmp_session_t *s, + void *arg, ngx_chain_t *in); + +#define NGX_RTMP_NETCALL_HTTP_GET 0 +#define NGX_RTMP_NETCALL_HTTP_POST 1 + + +/* If handle is NULL then netcall is created detached + * which means it's completely independent of RTMP + * session and its result is never visible to anyone. + * + * WARNING: It's not recommended to create non-detached + * netcalls from disconect handlers. Netcall disconnect + * handler which detaches active netcalls is executed + * BEFORE your handler. It leads to a crash + * after netcall connection is closed */ +typedef struct { + ngx_url_t *url; + ngx_rtmp_netcall_create_pt create; + ngx_rtmp_netcall_filter_pt filter; + ngx_rtmp_netcall_sink_pt sink; + ngx_rtmp_netcall_handle_pt handle; + void *arg; + size_t argsize; +} ngx_rtmp_netcall_init_t; + + +ngx_int_t ngx_rtmp_netcall_create(ngx_rtmp_session_t *s, + ngx_rtmp_netcall_init_t *ci); + + +/* HTTP handling */ +ngx_chain_t * ngx_rtmp_netcall_http_format_session(ngx_rtmp_session_t *s, + ngx_pool_t *pool); +ngx_chain_t * ngx_rtmp_netcall_http_format_request(ngx_int_t method, + ngx_str_t *host, ngx_str_t *uri, ngx_chain_t *args, ngx_chain_t *body, + ngx_pool_t *pool, ngx_str_t *content_type); +ngx_chain_t * ngx_rtmp_netcall_http_skip_header(ngx_chain_t *in); + + +/* Memcache handling */ +ngx_chain_t * ngx_rtmp_netcall_memcache_set(ngx_rtmp_session_t *s, + ngx_pool_t *pool, ngx_str_t *key, ngx_str_t *value, + ngx_uint_t flags, ngx_uint_t sec); + + +#endif /* _NGX_RTMP_NETCALL_H_INCLUDED_ */ diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_notify_module.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_notify_module.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_notify_module.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_notify_module.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,1728 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rtmp_cmd_module.h" +#include "ngx_rtmp_netcall_module.h" +#include "ngx_rtmp_record_module.h" +#include "ngx_rtmp_relay_module.h" + + +static ngx_rtmp_connect_pt next_connect; +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_done_pt next_record_done; + + +static char *ngx_rtmp_notify_on_srv_event(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +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 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, + void *parent, void *child); +static void *ngx_rtmp_notify_create_srv_conf(ngx_conf_t *cf); +static char *ngx_rtmp_notify_merge_srv_conf(ngx_conf_t *cf, void *parent, + void *child); +static ngx_int_t ngx_rtmp_notify_done(ngx_rtmp_session_t *s, char *cbname, + ngx_uint_t url_idx); + + +ngx_str_t ngx_rtmp_notify_urlencoded = + ngx_string("application/x-www-form-urlencoded"); + + +#define NGX_RTMP_NOTIFY_PUBLISHING 0x01 +#define NGX_RTMP_NOTIFY_PLAYING 0x02 + + +enum { + NGX_RTMP_NOTIFY_PLAY, + NGX_RTMP_NOTIFY_PUBLISH, + NGX_RTMP_NOTIFY_PLAY_DONE, + NGX_RTMP_NOTIFY_PUBLISH_DONE, + NGX_RTMP_NOTIFY_DONE, + NGX_RTMP_NOTIFY_RECORD_DONE, + NGX_RTMP_NOTIFY_UPDATE, + NGX_RTMP_NOTIFY_APP_MAX +}; + + +enum { + NGX_RTMP_NOTIFY_CONNECT, + NGX_RTMP_NOTIFY_DISCONNECT, + NGX_RTMP_NOTIFY_SRV_MAX +}; + + +typedef struct { + ngx_url_t *url[NGX_RTMP_NOTIFY_APP_MAX]; + ngx_flag_t active; + ngx_uint_t method; + ngx_msec_t update_timeout; + ngx_flag_t update_strict; + ngx_flag_t relay_redirect; +} ngx_rtmp_notify_app_conf_t; + + +typedef struct { + ngx_url_t *url[NGX_RTMP_NOTIFY_SRV_MAX]; + ngx_uint_t method; +} ngx_rtmp_notify_srv_conf_t; + + +typedef struct { + ngx_uint_t flags; + u_char name[NGX_RTMP_MAX_NAME]; + u_char args[NGX_RTMP_MAX_ARGS]; + ngx_event_t update_evt; + time_t start; +} ngx_rtmp_notify_ctx_t; + + +typedef struct { + u_char *cbname; + ngx_uint_t url_idx; +} ngx_rtmp_notify_done_t; + + +static ngx_command_t ngx_rtmp_notify_commands[] = { + + { ngx_string("on_connect"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_rtmp_notify_on_srv_event, + NGX_RTMP_SRV_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("on_disconnect"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_rtmp_notify_on_srv_event, + NGX_RTMP_SRV_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("on_publish"), + 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("on_play"), + 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("on_publish_done"), + 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("on_play_done"), + 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("on_done"), + 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("on_record_done"), + 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_update"), + 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, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("notify_update_timeout"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_notify_app_conf_t, update_timeout), + NULL }, + + { ngx_string("notify_update_strict"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_notify_app_conf_t, update_strict), + NULL }, + + { ngx_string("notify_relay_redirect"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_notify_app_conf_t, relay_redirect), + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_rtmp_notify_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_notify_postconfiguration, /* postconfiguration */ + NULL, /* create main configuration */ + NULL, /* init main configuration */ + ngx_rtmp_notify_create_srv_conf, /* create server configuration */ + ngx_rtmp_notify_merge_srv_conf, /* merge server configuration */ + ngx_rtmp_notify_create_app_conf, /* create app configuration */ + ngx_rtmp_notify_merge_app_conf /* merge app configuration */ +}; + + +ngx_module_t ngx_rtmp_notify_module = { + NGX_MODULE_V1, + &ngx_rtmp_notify_module_ctx, /* module context */ + ngx_rtmp_notify_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_rtmp_notify_create_app_conf(ngx_conf_t *cf) +{ + ngx_rtmp_notify_app_conf_t *nacf; + ngx_uint_t n; + + nacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_notify_app_conf_t)); + if (nacf == NULL) { + return NULL; + } + + for (n = 0; n < NGX_RTMP_NOTIFY_APP_MAX; ++n) { + nacf->url[n] = NGX_CONF_UNSET_PTR; + } + + nacf->method = NGX_CONF_UNSET_UINT; + nacf->update_timeout = NGX_CONF_UNSET_MSEC; + nacf->update_strict = NGX_CONF_UNSET; + nacf->relay_redirect = NGX_CONF_UNSET; + + return nacf; +} + + +static char * +ngx_rtmp_notify_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_notify_app_conf_t *prev = parent; + ngx_rtmp_notify_app_conf_t *conf = child; + ngx_uint_t n; + + for (n = 0; n < NGX_RTMP_NOTIFY_APP_MAX; ++n) { + ngx_conf_merge_ptr_value(conf->url[n], prev->url[n], NULL); + if (conf->url[n]) { + conf->active = 1; + } + } + + if (conf->active) { + prev->active = 1; + } + + ngx_conf_merge_uint_value(conf->method, prev->method, + NGX_RTMP_NETCALL_HTTP_POST); + ngx_conf_merge_msec_value(conf->update_timeout, prev->update_timeout, + 30000); + ngx_conf_merge_value(conf->update_strict, prev->update_strict, 0); + ngx_conf_merge_value(conf->relay_redirect, prev->relay_redirect, 0); + + return NGX_CONF_OK; +} + + +static void * +ngx_rtmp_notify_create_srv_conf(ngx_conf_t *cf) +{ + ngx_rtmp_notify_srv_conf_t *nscf; + ngx_uint_t n; + + nscf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_notify_srv_conf_t)); + if (nscf == NULL) { + return NULL; + } + + for (n = 0; n < NGX_RTMP_NOTIFY_SRV_MAX; ++n) { + nscf->url[n] = NGX_CONF_UNSET_PTR; + } + + nscf->method = NGX_CONF_UNSET_UINT; + + return nscf; +} + + +static char * +ngx_rtmp_notify_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_notify_srv_conf_t *prev = parent; + ngx_rtmp_notify_srv_conf_t *conf = child; + ngx_uint_t n; + + for (n = 0; n < NGX_RTMP_NOTIFY_SRV_MAX; ++n) { + ngx_conf_merge_ptr_value(conf->url[n], prev->url[n], NULL); + } + + ngx_conf_merge_uint_value(conf->method, prev->method, + NGX_RTMP_NETCALL_HTTP_POST); + + return NGX_CONF_OK; +} + + +static ngx_chain_t * +ngx_rtmp_notify_create_request(ngx_rtmp_session_t *s, ngx_pool_t *pool, + ngx_uint_t url_idx, ngx_chain_t *args) +{ + ngx_rtmp_notify_app_conf_t *nacf; + ngx_chain_t *al, *bl, *cl; + ngx_url_t *url; + + nacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module); + + url = nacf->url[url_idx]; + + al = ngx_rtmp_netcall_http_format_session(s, pool); + if (al == NULL) { + return NULL; + } + + al->next = args; + + bl = NULL; + + if (nacf->method == NGX_RTMP_NETCALL_HTTP_POST) { + cl = al; + al = bl; + bl = cl; + } + + 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_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_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; + } + + /* these values are still missing in session + * so we have to construct the request from + * connection struct */ + + app_len = ngx_strlen(v->app); + args_len = ngx_strlen(v->args); + flashver_len = ngx_strlen(v->flashver); + swf_url_len = ngx_strlen(v->swf_url); + 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("&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 + ); + + if (b == NULL) { + return NULL; + } + + al->buf = b; + al->next = NULL; + + 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); + + b->last = ngx_cpymem(b->last, (u_char*) "&flashver=", + sizeof("&flashver=") - 1); + b->last = (u_char*) ngx_escape_uri(b->last, v->flashver, 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, v->swf_url, 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, v->tc_url, 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, 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; + } + + 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_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_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); + if (b == NULL) { + return NULL; + } + + 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); + } + + url = nscf->url[NGX_RTMP_NOTIFY_DISCONNECT]; + + al = ngx_rtmp_netcall_http_format_session(s, pool); + if (al == NULL) { + return NULL; + } + + al->next = pl; + + bl = NULL; + + 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); +} + + +static ngx_chain_t * +ngx_rtmp_notify_publish_create(ngx_rtmp_session_t *s, void *arg, + ngx_pool_t *pool) +{ + ngx_rtmp_publish_t *v = arg; + + ngx_chain_t *pl; + ngx_buf_t *b; + size_t name_len, type_len, args_len; + + pl = ngx_alloc_chain_link(pool); + if (pl == NULL) { + return NULL; + } + + name_len = ngx_strlen(v->name); + type_len = ngx_strlen(v->type); + args_len = ngx_strlen(v->args); + + b = ngx_create_temp_buf(pool, + sizeof("&call=publish") + + sizeof("&name=") + name_len * 3 + + sizeof("&type=") + type_len * 3 + + 1 + args_len); + if (b == NULL) { + return NULL; + } + + pl->buf = b; + pl->next = NULL; + + 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, + NGX_ESCAPE_ARGS); + + b->last = ngx_cpymem(b->last, (u_char*) "&type=", sizeof("&type=") - 1); + 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); + } + + return ngx_rtmp_notify_create_request(s, pool, NGX_RTMP_NOTIFY_PUBLISH, pl); +} + + +static ngx_chain_t * +ngx_rtmp_notify_play_create(ngx_rtmp_session_t *s, void *arg, + ngx_pool_t *pool) +{ + ngx_rtmp_play_t *v = arg; + + ngx_chain_t *pl; + ngx_buf_t *b; + size_t name_len, args_len; + + pl = ngx_alloc_chain_link(pool); + if (pl == NULL) { + return NULL; + } + + name_len = ngx_strlen(v->name); + args_len = ngx_strlen(v->args); + + b = ngx_create_temp_buf(pool, + sizeof("&call=play") + + sizeof("&name=") + name_len * 3 + + sizeof("&start=&duration=&reset=") + + NGX_INT32_LEN * 3 + 1 + args_len); + if (b == NULL) { + return NULL; + } + + pl->buf = b; + pl->next = NULL; + + 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, + NGX_ESCAPE_ARGS); + + b->last = ngx_snprintf(b->last, b->end - b->last, + "&start=%uD&duration=%uD&reset=%d", + (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); + } + + return ngx_rtmp_notify_create_request(s, pool, NGX_RTMP_NOTIFY_PLAY, pl); +} + + +static ngx_chain_t * +ngx_rtmp_notify_done_create(ngx_rtmp_session_t *s, void *arg, + ngx_pool_t *pool) +{ + ngx_rtmp_notify_done_t *ds = arg; + + ngx_chain_t *pl; + ngx_buf_t *b; + size_t cbname_len, name_len, args_len; + ngx_rtmp_notify_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_notify_module); + + pl = ngx_alloc_chain_link(pool); + if (pl == NULL) { + return NULL; + } + + cbname_len = ngx_strlen(ds->cbname); + name_len = ctx ? ngx_strlen(ctx->name) : 0; + 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); + if (b == NULL) { + return NULL; + } + + pl->buf = b; + pl->next = NULL; + + 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) { + 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); + } + + if (args_len) { + *b->last++ = '&'; + b->last = (u_char *) ngx_cpymem(b->last, ctx->args, args_len); + } + + return ngx_rtmp_notify_create_request(s, pool, ds->url_idx, pl); +} + + +static ngx_chain_t * +ngx_rtmp_notify_update_create(ngx_rtmp_session_t *s, void *arg, + ngx_pool_t *pool) +{ + ngx_chain_t *pl; + ngx_buf_t *b; + size_t name_len, args_len; + ngx_rtmp_notify_ctx_t *ctx; + ngx_str_t sfx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_notify_module); + + pl = ngx_alloc_chain_link(pool); + if (pl == NULL) { + return NULL; + } + + if (ctx->flags & NGX_RTMP_NOTIFY_PUBLISHING) { + ngx_str_set(&sfx, "_publish"); + } else if (ctx->flags & NGX_RTMP_NOTIFY_PLAYING) { + ngx_str_set(&sfx, "_play"); + } else { + ngx_str_null(&sfx); + } + + name_len = ctx ? ngx_strlen(ctx->name) : 0; + args_len = ctx ? ngx_strlen(ctx->args) : 0; + + b = ngx_create_temp_buf(pool, + sizeof("&call=update") + sfx.len + + sizeof("&time=") + NGX_TIME_T_LEN + + sizeof("×tamp=") + NGX_INT32_LEN + + sizeof("&name=") + name_len * 3 + + 1 + args_len); + if (b == NULL) { + return NULL; + } + + pl->buf = b; + pl->next = NULL; + + 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=", + sizeof("&time=") - 1); + b->last = ngx_sprintf(b->last, "%T", ngx_cached_time->sec - ctx->start); + + b->last = ngx_cpymem(b->last, (u_char *) "×tamp=", + sizeof("×tamp=") - 1); + b->last = ngx_sprintf(b->last, "%D", s->current_time); + + if (name_len) { + 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); + } + + if (args_len) { + *b->last++ = '&'; + b->last = (u_char *) ngx_cpymem(b->last, ctx->args, args_len); + } + + return ngx_rtmp_notify_create_request(s, pool, NGX_RTMP_NOTIFY_UPDATE, pl); +} + + +static ngx_chain_t * +ngx_rtmp_notify_record_done_create(ngx_rtmp_session_t *s, void *arg, + ngx_pool_t *pool) +{ + ngx_rtmp_record_done_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_done") + + sizeof("&recorder=") + v->recorder.len + + sizeof("&name=") + name_len * 3 + + sizeof("&path=") + v->path.len * 3 + + 1 + args_len); + if (b == NULL) { + return NULL; + } + + pl->buf = b; + pl->next = NULL; + + 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); + 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); + + if (args_len) { + *b->last++ = '&'; + b->last = (u_char *) ngx_cpymem(b->last, ctx->args, args_len); + } + + return ngx_rtmp_notify_create_request(s, pool, NGX_RTMP_NOTIFY_RECORD_DONE, + pl); +} + + +static ngx_int_t +ngx_rtmp_notify_parse_http_retcode(ngx_rtmp_session_t *s, + ngx_chain_t *in) +{ + ngx_buf_t *b; + ngx_int_t n; + u_char c; + + /* find 10th character */ + + n = 9; + while (in) { + b = in->buf; + if (b->last - b->pos > n) { + c = b->pos[n]; + if (c >= (u_char)'0' && c <= (u_char)'9') { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "notify: HTTP retcode: %dxx", (int)(c - '0')); + switch (c) { + case (u_char) '2': + return NGX_OK; + case (u_char) '3': + return NGX_AGAIN; + default: + return NGX_ERROR; + } + } + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: invalid HTTP retcode: %d..", (int)c); + + return NGX_ERROR; + } + n -= (b->last - b->pos); + in = in->next; + } + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: empty or broken HTTP response"); + + /* + * not enough data; + * it can happen in case of empty or broken reply + */ + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_rtmp_notify_parse_http_header(ngx_rtmp_session_t *s, + ngx_chain_t *in, ngx_str_t *name, u_char *data, size_t len) +{ + ngx_buf_t *b; + ngx_int_t matched; + u_char *p, c; + ngx_uint_t n; + + enum { + parse_name, + parse_space, + parse_value, + parse_value_newline + } state = parse_name; + + n = 0; + matched = 0; + + while (in) { + b = in->buf; + + for (p = b->pos; p != b->last; ++p) { + c = *p; + + if (c == '\r') { + continue; + } + + switch (state) { + case parse_value_newline: + if (c == ' ' || c == '\t') { + state = parse_space; + break; + } + + if (matched) { + return n; + } + + if (c == '\n') { + return NGX_OK; + } + + n = 0; + state = parse_name; + + case parse_name: + switch (c) { + case ':': + matched = (n == name->len); + n = 0; + state = parse_space; + break; + case '\n': + n = 0; + break; + default: + if (n < name->len && + ngx_tolower(c) == ngx_tolower(name->data[n])) + { + ++n; + break; + } + n = name->len + 1; + } + break; + + case parse_space: + if (c == ' ' || c == '\t') { + break; + } + state = parse_value; + + case parse_value: + if (c == '\n') { + state = parse_value_newline; + break; + } + + if (matched && n + 1 < len) { + data[n++] = c; + } + + break; + } + } + + in = in->next; + } + + return NGX_OK; +} + + +static void +ngx_rtmp_notify_clear_flag(ngx_rtmp_session_t *s, ngx_uint_t flag) +{ + ngx_rtmp_notify_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_notify_module); + + ctx->flags &= ~flag; +} + + +static ngx_int_t +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; + u_char app[NGX_RTMP_MAX_NAME]; + + static ngx_str_t location = ngx_string("location"); + + rc = ngx_rtmp_notify_parse_http_retcode(s, in); + 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"); + + 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); + } + } + + return next_connect(s, v); +} + + +static void +ngx_rtmp_notify_set_name(u_char *dst, size_t dst_len, u_char *src, + size_t src_len) +{ + u_char result[16], *p; + ngx_md5_t md5; + + ngx_md5_init(&md5); + ngx_md5_update(&md5, src, src_len); + ngx_md5_final(result, &md5); + + p = ngx_hex_dump(dst, result, ngx_min((dst_len - 1) / 2, 16)); + *p = '\0'; +} + + +static ngx_int_t +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_str_t local_name; + ngx_rtmp_relay_target_t target; + ngx_url_t *u; + ngx_rtmp_notify_app_conf_t *nacf; + u_char name[NGX_RTMP_MAX_NAME]; + + static ngx_str_t location = ngx_string("location"); + + rc = ngx_rtmp_notify_parse_http_retcode(s, in); + if (rc == NGX_ERROR) { + ngx_rtmp_notify_clear_flag(s, NGX_RTMP_NOTIFY_PUBLISHING); + return NGX_ERROR; + } + + if (rc != NGX_AGAIN) { + goto next; + } + + /* HTTP 3xx */ + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "notify: publish redirect received"); + + rc = ngx_rtmp_notify_parse_http_header(s, in, &location, name, + sizeof(name) - 1); + if (rc <= 0) { + goto next; + } + + 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: publish redirect to '%s'", v->name); + goto next; + } + + /* push */ + + nacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module); + if (nacf->relay_redirect) { + 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, + "notify: push '%s' to '%*s'", v->name, rc, name); + + local_name.data = v->name; + local_name.len = ngx_strlen(v->name); + + ngx_memzero(&target, sizeof(target)); + + u = &target.url; + u->url = local_name; + u->url.data = name + 7; + u->url.len = rc - 7; + u->default_port = 1935; + u->uri_part = 1; + u->no_resolve = 1; /* want ip here */ + + if (ngx_parse_url(s->connection->pool, u) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: push failed '%V'", &local_name); + return NGX_ERROR; + } + + ngx_rtmp_relay_push(s, &local_name, &target); + +next: + + return next_publish(s, v); +} + + +static ngx_int_t +ngx_rtmp_notify_play_handle(ngx_rtmp_session_t *s, + void *arg, ngx_chain_t *in) +{ + ngx_rtmp_play_t *v = arg; + ngx_int_t rc; + ngx_str_t local_name; + ngx_rtmp_relay_target_t target; + ngx_url_t *u; + ngx_rtmp_notify_app_conf_t *nacf; + u_char name[NGX_RTMP_MAX_NAME]; + + static ngx_str_t location = ngx_string("location"); + + rc = ngx_rtmp_notify_parse_http_retcode(s, in); + if (rc == NGX_ERROR) { + ngx_rtmp_notify_clear_flag(s, NGX_RTMP_NOTIFY_PLAYING); + return NGX_ERROR; + } + + if (rc != NGX_AGAIN) { + goto next; + } + + /* HTTP 3xx */ + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "notify: play redirect received"); + + rc = ngx_rtmp_notify_parse_http_header(s, in, &location, name, + sizeof(name) - 1); + if (rc <= 0) { + goto next; + } + + 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); + goto next; + } + + /* pull */ + + nacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module); + if (nacf->relay_redirect) { + ngx_rtmp_notify_set_name(v->name, NGX_RTMP_MAX_NAME, name, (size_t) rc); + } + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: pull '%s' from '%*s'", v->name, rc, name); + + local_name.data = v->name; + local_name.len = ngx_strlen(v->name); + + ngx_memzero(&target, sizeof(target)); + + u = &target.url; + u->url = local_name; + u->url.data = name + 7; + u->url.len = rc - 7; + u->default_port = 1935; + u->uri_part = 1; + u->no_resolve = 1; /* want ip here */ + + if (ngx_parse_url(s->connection->pool, u) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: pull failed '%V'", &local_name); + return NGX_ERROR; + } + + ngx_rtmp_relay_pull(s, &local_name, &target); + +next: + + return next_play(s, v); +} + + +static ngx_int_t +ngx_rtmp_notify_update_handle(ngx_rtmp_session_t *s, + void *arg, ngx_chain_t *in) +{ + ngx_rtmp_notify_app_conf_t *nacf; + ngx_rtmp_notify_ctx_t *ctx; + ngx_int_t rc; + + nacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module); + + rc = ngx_rtmp_notify_parse_http_retcode(s, in); + + if ((!nacf->update_strict && rc == NGX_ERROR) || + (nacf->update_strict && rc != NGX_OK)) + { + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: update failed"); + + return NGX_ERROR; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_notify_module); + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "notify: schedule update %Mms", + nacf->update_timeout); + + ngx_add_timer(&ctx->update_evt, nacf->update_timeout); + + return NGX_OK; +} + + +static void +ngx_rtmp_notify_update(ngx_event_t *e) +{ + ngx_connection_t *c; + ngx_rtmp_session_t *s; + ngx_rtmp_notify_app_conf_t *nacf; + ngx_rtmp_netcall_init_t ci; + ngx_url_t *url; + + c = e->data; + s = c->data; + + nacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module); + + url = nacf->url[NGX_RTMP_NOTIFY_UPDATE]; + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: update '%V'", &url->url); + + ngx_memzero(&ci, sizeof(ci)); + + ci.url = url; + ci.create = ngx_rtmp_notify_update_create; + ci.handle = ngx_rtmp_notify_update_handle; + + if (ngx_rtmp_netcall_create(s, &ci) == NGX_OK) { + return; + } + + /* schedule next update on connection error */ + + ngx_rtmp_notify_update_handle(s, NULL, NULL); +} + + +static void +ngx_rtmp_notify_init(ngx_rtmp_session_t *s, + u_char name[NGX_RTMP_MAX_NAME], u_char args[NGX_RTMP_MAX_ARGS], + ngx_uint_t flags) +{ + ngx_rtmp_notify_ctx_t *ctx; + ngx_rtmp_notify_app_conf_t *nacf; + ngx_event_t *e; + + nacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module); + if (!nacf->active) { + return; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_notify_module); + + if (ctx == NULL) { + ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_rtmp_notify_ctx_t)); + if (ctx == NULL) { + return; + } + + ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_notify_module); + } + + ngx_memcpy(ctx->name, name, NGX_RTMP_MAX_NAME); + ngx_memcpy(ctx->args, args, NGX_RTMP_MAX_ARGS); + + ctx->flags |= flags; + + if (nacf->url[NGX_RTMP_NOTIFY_UPDATE] == NULL || + nacf->update_timeout == 0) + { + return; + } + + if (ctx->update_evt.timer_set) { + return; + } + + ctx->start = ngx_cached_time->sec; + + e = &ctx->update_evt; + + e->data = s->connection; + e->log = s->connection->log; + e->handler = ngx_rtmp_notify_update; + + ngx_add_timer(e, nacf->update_timeout); + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "notify: schedule initial update %Mms", + nacf->update_timeout); +} + + +static ngx_int_t +ngx_rtmp_notify_connect(ngx_rtmp_session_t *s, ngx_rtmp_connect_t *v) +{ + ngx_rtmp_notify_srv_conf_t *nscf; + ngx_rtmp_netcall_init_t ci; + ngx_url_t *url; + + if (s->auto_pushed || s->relay) { + goto next; + } + + nscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_notify_module); + + url = nscf->url[NGX_RTMP_NOTIFY_CONNECT]; + if (url == NULL) { + goto next; + } + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: connect '%V'", &url->url); + + ngx_memzero(&ci, sizeof(ci)); + + ci.url = url; + ci.create = ngx_rtmp_notify_connect_create; + ci.handle = ngx_rtmp_notify_connect_handle; + ci.arg = v; + ci.argsize = sizeof(*v); + + return ngx_rtmp_netcall_create(s, &ci); + +next: + return next_connect(s, v); +} + + +static ngx_int_t +ngx_rtmp_notify_disconnect(ngx_rtmp_session_t *s) +{ + ngx_rtmp_notify_srv_conf_t *nscf; + ngx_rtmp_netcall_init_t ci; + ngx_url_t *url; + + if (s->auto_pushed || s->relay) { + goto next; + } + + nscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_notify_module); + + url = nscf->url[NGX_RTMP_NOTIFY_DISCONNECT]; + if (url == NULL) { + goto next; + } + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: disconnect '%V'", &url->url); + + ngx_memzero(&ci, sizeof(ci)); + + ci.url = url; + ci.create = ngx_rtmp_notify_disconnect_create; + + ngx_rtmp_netcall_create(s, &ci); + +next: + return next_disconnect(s); +} + + +static ngx_int_t +ngx_rtmp_notify_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) +{ + ngx_rtmp_notify_app_conf_t *nacf; + ngx_rtmp_netcall_init_t ci; + ngx_url_t *url; + + if (s->auto_pushed) { + goto next; + } + + nacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module); + if (nacf == NULL) { + goto next; + } + + url = nacf->url[NGX_RTMP_NOTIFY_PUBLISH]; + + ngx_rtmp_notify_init(s, v->name, v->args, NGX_RTMP_NOTIFY_PUBLISHING); + + if (url == NULL) { + goto next; + } + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: publish '%V'", &url->url); + + ngx_memzero(&ci, sizeof(ci)); + + ci.url = url; + ci.create = ngx_rtmp_notify_publish_create; + ci.handle = ngx_rtmp_notify_publish_handle; + ci.arg = v; + ci.argsize = sizeof(*v); + + return ngx_rtmp_netcall_create(s, &ci); + +next: + return next_publish(s, v); +} + + +static ngx_int_t +ngx_rtmp_notify_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) +{ + ngx_rtmp_notify_app_conf_t *nacf; + ngx_rtmp_netcall_init_t ci; + ngx_url_t *url; + + if (s->auto_pushed) { + goto next; + } + + nacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module); + if (nacf == NULL) { + goto next; + } + + url = nacf->url[NGX_RTMP_NOTIFY_PLAY]; + + ngx_rtmp_notify_init(s, v->name, v->args, NGX_RTMP_NOTIFY_PLAYING); + + if (url == NULL) { + goto next; + } + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: play '%V'", &url->url); + + ngx_memzero(&ci, sizeof(ci)); + + ci.url = url; + ci.create = ngx_rtmp_notify_play_create; + ci.handle = ngx_rtmp_notify_play_handle; + ci.arg = v; + ci.argsize = sizeof(*v); + + return ngx_rtmp_netcall_create(s, &ci); + +next: + return next_play(s, v); +} + + +static ngx_int_t +ngx_rtmp_notify_close_stream(ngx_rtmp_session_t *s, + ngx_rtmp_close_stream_t *v) +{ + ngx_rtmp_notify_ctx_t *ctx; + ngx_rtmp_notify_app_conf_t *nacf; + + if (s->auto_pushed) { + goto next; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_notify_module); + + if (ctx == NULL) { + goto next; + } + + nacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module); + + if (nacf == NULL) { + goto next; + } + + if (ctx->flags & NGX_RTMP_NOTIFY_PUBLISHING) { + ngx_rtmp_notify_done(s, "publish_done", NGX_RTMP_NOTIFY_PUBLISH_DONE); + } + + if (ctx->flags & NGX_RTMP_NOTIFY_PLAYING) { + ngx_rtmp_notify_done(s, "play_done", NGX_RTMP_NOTIFY_PLAY_DONE); + } + + if (ctx->flags) { + ngx_rtmp_notify_done(s, "done", NGX_RTMP_NOTIFY_DONE); + } + + if (ctx->update_evt.timer_set) { + ngx_del_timer(&ctx->update_evt); + } + + ctx->flags = 0; + +next: + return next_close_stream(s, v); +} + + +static ngx_int_t +ngx_rtmp_notify_record_done(ngx_rtmp_session_t *s, ngx_rtmp_record_done_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_DONE] == NULL) { + goto next; + } + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: record_done recorder=%V path='%V' url='%V'", + &v->recorder, &v->path, + &nacf->url[NGX_RTMP_NOTIFY_RECORD_DONE]->url); + + ngx_memzero(&ci, sizeof(ci)); + + ci.url = nacf->url[NGX_RTMP_NOTIFY_RECORD_DONE]; + ci.create = ngx_rtmp_notify_record_done_create; + ci.arg = v; + + ngx_rtmp_netcall_create(s, &ci); + +next: + return next_record_done(s, v); +} + + +static ngx_int_t +ngx_rtmp_notify_done(ngx_rtmp_session_t *s, char *cbname, ngx_uint_t url_idx) +{ + ngx_rtmp_netcall_init_t ci; + ngx_rtmp_notify_done_t ds; + ngx_rtmp_notify_app_conf_t *nacf; + ngx_url_t *url; + + nacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module); + + url = nacf->url[url_idx]; + if (url == NULL) { + return NGX_OK; + } + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "notify: %s '%V'", cbname, &url->url); + + ds.cbname = (u_char *) cbname; + ds.url_idx = url_idx; + + ngx_memzero(&ci, sizeof(ci)); + + ci.url = url; + ci.arg = &ds; + ci.create = ngx_rtmp_notify_done_create; + + return ngx_rtmp_netcall_create(s, &ci); +} + + +static ngx_url_t * +ngx_rtmp_notify_parse_url(ngx_conf_t *cf, ngx_str_t *url) +{ + ngx_url_t *u; + size_t add; + + add = 0; + + u = ngx_pcalloc(cf->pool, sizeof(ngx_url_t)); + if (u == NULL) { + return NULL; + } + + if (ngx_strncasecmp(url->data, (u_char *) "http://", 7) == 0) { + add = 7; + } + + u->url.len = url->len - add; + u->url.data = url->data + add; + u->default_port = 80; + u->uri_part = 1; + + if (ngx_parse_url(cf->pool, u) != NGX_OK) { + if (u->err) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "%s in url \"%V\"", u->err, &u->url); + } + return NULL; + } + + return u; +} + + +static char * +ngx_rtmp_notify_on_srv_event(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_rtmp_notify_srv_conf_t *nscf = conf; + + ngx_str_t *name, *value; + ngx_url_t *u; + ngx_uint_t n; + + value = cf->args->elts; + + u = ngx_rtmp_notify_parse_url(cf, &value[1]); + if (u == NULL) { + return NGX_CONF_ERROR; + } + + name = &value[0]; + + n = 0; + + switch (name->len) { + case sizeof("on_connect") - 1: + n = NGX_RTMP_NOTIFY_CONNECT; + break; + + case sizeof("on_disconnect") - 1: + n = NGX_RTMP_NOTIFY_DISCONNECT; + break; + } + + nscf->url[n] = u; + + return NGX_CONF_OK; +} + + +static char * +ngx_rtmp_notify_on_app_event(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_rtmp_notify_app_conf_t *nacf = conf; + + ngx_str_t *name, *value; + ngx_url_t *u; + ngx_uint_t n; + + value = cf->args->elts; + + u = ngx_rtmp_notify_parse_url(cf, &value[1]); + if (u == NULL) { + return NGX_CONF_ERROR; + } + + name = &value[0]; + + n = 0; + + switch (name->len) { + case sizeof("on_done") - 1: /* and on_play */ + if (name->data[3] == 'd') { + n = NGX_RTMP_NOTIFY_DONE; + } else { + n = NGX_RTMP_NOTIFY_PLAY; + } + break; + + case sizeof("on_update") - 1: + n = NGX_RTMP_NOTIFY_UPDATE; + break; + + case sizeof("on_publish") - 1: + n = NGX_RTMP_NOTIFY_PUBLISH; + break; + + case sizeof("on_play_done") - 1: + n = NGX_RTMP_NOTIFY_PLAY_DONE; + break; + + case sizeof("on_record_done") - 1: + n = NGX_RTMP_NOTIFY_RECORD_DONE; + break; + + case sizeof("on_publish_done") - 1: + n = NGX_RTMP_NOTIFY_PUBLISH_DONE; + break; + } + + nacf->url[n] = u; + + return NGX_CONF_OK; +} + + +static char * +ngx_rtmp_notify_method(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("get") - 1 && + ngx_strncasecmp(value->data, (u_char *) "get", value->len) == 0) + { + nacf->method = NGX_RTMP_NETCALL_HTTP_GET; + + } else if (value->len == sizeof("post") - 1 && + ngx_strncasecmp(value->data, (u_char *) "post", value->len) == 0) + { + nacf->method = NGX_RTMP_NETCALL_HTTP_POST; + + } else { + return "got unexpected method"; + } + + nscf = ngx_rtmp_conf_get_module_srv_conf(cf, ngx_rtmp_notify_module); + nscf->method = nacf->method; + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_rtmp_notify_postconfiguration(ngx_conf_t *cf) +{ + next_connect = ngx_rtmp_connect; + ngx_rtmp_connect = ngx_rtmp_notify_connect; + + next_disconnect = ngx_rtmp_disconnect; + ngx_rtmp_disconnect = ngx_rtmp_notify_disconnect; + + next_publish = ngx_rtmp_publish; + ngx_rtmp_publish = ngx_rtmp_notify_publish; + + next_play = ngx_rtmp_play; + ngx_rtmp_play = ngx_rtmp_notify_play; + + next_close_stream = ngx_rtmp_close_stream; + ngx_rtmp_close_stream = ngx_rtmp_notify_close_stream; + + next_record_done = ngx_rtmp_record_done; + ngx_rtmp_record_done = ngx_rtmp_notify_record_done; + + return NGX_OK; +} diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_play_module.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_play_module.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_play_module.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_play_module.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,1278 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include +#include "ngx_rtmp_play_module.h" +#include "ngx_rtmp_cmd_module.h" +#include "ngx_rtmp_netcall_module.h" +#include "ngx_rtmp_streams.h" + + +static ngx_rtmp_play_pt next_play; +static ngx_rtmp_close_stream_pt next_close_stream; +static ngx_rtmp_seek_pt next_seek; +static ngx_rtmp_pause_pt next_pause; + + +static char *ngx_rtmp_play_url(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static void *ngx_rtmp_play_create_main_conf(ngx_conf_t *cf); +static ngx_int_t ngx_rtmp_play_postconfiguration(ngx_conf_t *cf); +static void * ngx_rtmp_play_create_app_conf(ngx_conf_t *cf); +static char * ngx_rtmp_play_merge_app_conf(ngx_conf_t *cf, + void *parent, void *child); + +static ngx_int_t ngx_rtmp_play_do_init(ngx_rtmp_session_t *s); +static ngx_int_t ngx_rtmp_play_do_done(ngx_rtmp_session_t *s); +static ngx_int_t ngx_rtmp_play_do_start(ngx_rtmp_session_t *s); +static ngx_int_t ngx_rtmp_play_do_stop(ngx_rtmp_session_t *s); +static ngx_int_t ngx_rtmp_play_do_seek(ngx_rtmp_session_t *s, + ngx_uint_t timestamp); + +static ngx_int_t ngx_rtmp_play_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v); +static ngx_int_t ngx_rtmp_play_seek(ngx_rtmp_session_t *s, ngx_rtmp_seek_t *v); +static ngx_int_t ngx_rtmp_play_pause(ngx_rtmp_session_t *s, + ngx_rtmp_pause_t *v); +static void ngx_rtmp_play_send(ngx_event_t *e); +static ngx_int_t ngx_rtmp_play_open(ngx_rtmp_session_t *s, double start); +static ngx_int_t ngx_rtmp_play_remote_handle(ngx_rtmp_session_t *s, + void *arg, ngx_chain_t *in); +static ngx_chain_t * ngx_rtmp_play_remote_create(ngx_rtmp_session_t *s, + void *arg, ngx_pool_t *pool); +static ngx_int_t ngx_rtmp_play_open_remote(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); +static ngx_rtmp_play_entry_t * ngx_rtmp_play_get_current_entry( + ngx_rtmp_session_t *s); +static void ngx_rtmp_play_cleanup_local_file(ngx_rtmp_session_t *s); +static void ngx_rtmp_play_copy_local_file(ngx_rtmp_session_t *s, u_char *name); +static u_char * ngx_rtmp_play_get_local_file_path(ngx_rtmp_session_t *s); + + +static ngx_command_t ngx_rtmp_play_commands[] = { + + { ngx_string("play"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_play_url, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("play_temp_path"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_play_app_conf_t, temp_path), + NULL }, + + { ngx_string("play_local_path"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_play_app_conf_t, local_path), + NULL }, + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_rtmp_play_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_play_postconfiguration, /* postconfiguration */ + ngx_rtmp_play_create_main_conf, /* create main configuration */ + NULL, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + ngx_rtmp_play_create_app_conf, /* create app configuration */ + ngx_rtmp_play_merge_app_conf /* merge app configuration */ +}; + + +ngx_module_t ngx_rtmp_play_module = { + NGX_MODULE_V1, + &ngx_rtmp_play_module_ctx, /* module context */ + ngx_rtmp_play_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +#define NGX_RTMP_PLAY_TMP_FILE "nginx-rtmp-vod." + + +static void * +ngx_rtmp_play_create_main_conf(ngx_conf_t *cf) +{ + ngx_rtmp_play_main_conf_t *pmcf; + + pmcf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_play_main_conf_t)); + if (pmcf == NULL) { + return NULL; + } + + if (ngx_array_init(&pmcf->fmts, cf->pool, 1, + sizeof(ngx_rtmp_play_fmt_t *)) + != NGX_OK) + { + return NULL; + } + + return pmcf; +} + + +static void * +ngx_rtmp_play_create_app_conf(ngx_conf_t *cf) +{ + ngx_rtmp_play_app_conf_t *pacf; + + pacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_play_app_conf_t)); + if (pacf == NULL) { + return NULL; + } + + pacf->nbuckets = 1024; + + return pacf; +} + + +static char * +ngx_rtmp_play_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_play_app_conf_t *prev = parent; + ngx_rtmp_play_app_conf_t *conf = child; + ngx_rtmp_play_entry_t **ppe; + + ngx_conf_merge_str_value(conf->temp_path, prev->temp_path, "/tmp"); + ngx_conf_merge_str_value(conf->local_path, prev->local_path, ""); + + if (prev->entries.nelts == 0) { + goto done; + } + + if (conf->entries.nelts == 0) { + conf->entries = prev->entries; + goto done; + } + + ppe = ngx_array_push_n(&conf->entries, prev->entries.nelts); + if (ppe == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memcpy(ppe, prev->entries.elts, prev->entries.nelts * sizeof(void *)); + +done: + + if (conf->entries.nelts == 0) { + return NGX_CONF_OK; + } + + conf->ctx = ngx_pcalloc(cf->pool, sizeof(void *) * conf->nbuckets); + if (conf->ctx == NULL) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_rtmp_play_join(ngx_rtmp_session_t *s) +{ + ngx_rtmp_play_ctx_t *ctx, **pctx; + ngx_rtmp_play_app_conf_t *pacf; + ngx_uint_t h; + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "play: join"); + + pacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_play_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); + if (ctx == NULL || ctx->joined) { + return NGX_ERROR; + } + + h = ngx_hash_key(ctx->name, ngx_strlen(ctx->name)); + pctx = &pacf->ctx[h % pacf->nbuckets]; + + while (*pctx) { + if (!ngx_strncmp((*pctx)->name, ctx->name, NGX_RTMP_MAX_NAME)) { + break; + } + pctx = &(*pctx)->next; + } + + ctx->next = *pctx; + *pctx = ctx; + ctx->joined = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_play_leave(ngx_rtmp_session_t *s) +{ + ngx_rtmp_play_ctx_t *ctx, **pctx; + ngx_rtmp_play_app_conf_t *pacf; + ngx_uint_t h; + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "play: leave"); + + pacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_play_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); + if (ctx == NULL || !ctx->joined) { + return NGX_ERROR; + } + + h = ngx_hash_key(ctx->name, ngx_strlen(ctx->name)); + pctx = &pacf->ctx[h % pacf->nbuckets]; + + while (*pctx && *pctx != ctx) { + pctx = &(*pctx)->next; + } + + if (*pctx == NULL) { + return NGX_ERROR; + } + + *pctx = (*pctx)->next; + ctx->joined = 0; + + return NGX_OK; +} + + +static void +ngx_rtmp_play_send(ngx_event_t *e) +{ + ngx_rtmp_session_t *s = e->data; + ngx_rtmp_play_ctx_t *ctx; + ngx_int_t rc; + ngx_uint_t ts; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); + + if (ctx == NULL || ctx->fmt == NULL || ctx->fmt->send == NULL) { + return; + } + + ts = 0; + + rc = ctx->fmt->send(s, &ctx->file, &ts); + + if (rc > 0) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "play: send schedule %i", rc); + + ngx_add_timer(e, rc); + return; + } + + if (rc == NGX_AGAIN) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "play: send buffer full"); + + ngx_post_event(e, &s->posted_dry_events); + return; + } + + if (rc == NGX_OK) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "play: send restart"); + + ngx_post_event(e, &ngx_posted_events); + return; + } + + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "play: send done"); + + ngx_rtmp_send_stream_eof(s, NGX_RTMP_MSID); + + ngx_rtmp_send_play_status(s, "NetStream.Play.Complete", "status", ts, 0); + + ngx_rtmp_send_status(s, "NetStream.Play.Stop", "status", "Stopped"); +} + + +static ngx_int_t +ngx_rtmp_play_do_init(ngx_rtmp_session_t *s) +{ + ngx_rtmp_play_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); + + if (ctx == NULL) { + return NGX_ERROR; + } + + if (ctx->fmt && ctx->fmt->init && + ctx->fmt->init(s, &ctx->file, ctx->aindex, ctx->vindex) != NGX_OK) + { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_play_do_done(ngx_rtmp_session_t *s) +{ + ngx_rtmp_play_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); + + if (ctx == NULL) { + return NGX_ERROR; + } + + if (ctx->fmt && ctx->fmt->done && + ctx->fmt->done(s, &ctx->file) != NGX_OK) + { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_play_do_start(ngx_rtmp_session_t *s) +{ + ngx_rtmp_play_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); + + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "play: start"); + + if (ctx->fmt && ctx->fmt->start && + ctx->fmt->start(s, &ctx->file) != NGX_OK) + { + return NGX_ERROR; + } + + ngx_post_event((&ctx->send_evt), &ngx_posted_events); + + ctx->playing = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_play_do_seek(ngx_rtmp_session_t *s, ngx_uint_t timestamp) +{ + ngx_rtmp_play_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); + + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "play: seek timestamp=%ui", timestamp); + + if (ctx->fmt && ctx->fmt->seek && + ctx->fmt->seek(s, &ctx->file, timestamp) != NGX_OK) + { + return NGX_ERROR; + } + + if (ctx->playing) { + ngx_post_event((&ctx->send_evt), &ngx_posted_events); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_play_do_stop(ngx_rtmp_session_t *s) +{ + ngx_rtmp_play_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); + + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "play: stop"); + + if (ctx->send_evt.timer_set) { + ngx_del_timer(&ctx->send_evt); + } + +#if (nginx_version >= 1007005) + if (ctx->send_evt.posted) +#else + if (ctx->send_evt.prev) +#endif + { + ngx_delete_posted_event((&ctx->send_evt)); + } + + if (ctx->fmt && ctx->fmt->stop && + ctx->fmt->stop(s, &ctx->file) != NGX_OK) + { + return NGX_ERROR; + } + + ctx->playing = 0; + + return NGX_OK; +} + + +/* This function returns pointer to a static buffer */ + +static u_char * +ngx_rtmp_play_get_local_file_path(ngx_rtmp_session_t *s) +{ + ngx_rtmp_play_app_conf_t *pacf; + ngx_rtmp_play_ctx_t *ctx; + u_char *p; + static u_char path[NGX_MAX_PATH + 1]; + + pacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_play_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); + + p = ngx_snprintf(path, NGX_MAX_PATH, "%V/" NGX_RTMP_PLAY_TMP_FILE "%ui", + &pacf->temp_path, ctx->file_id); + *p = 0; + + return path; +} + + +static void +ngx_rtmp_play_copy_local_file(ngx_rtmp_session_t *s, u_char *name) +{ + ngx_rtmp_play_app_conf_t *pacf; + ngx_rtmp_play_ctx_t *ctx; + u_char *path, *p; + static u_char dpath[NGX_MAX_PATH + 1]; + + pacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_play_module); + if (pacf == NULL) { + return; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); + if (ctx == NULL || ctx->file_id == 0) { + return; + } + + path = ngx_rtmp_play_get_local_file_path(s); + + p = ngx_snprintf(dpath, NGX_MAX_PATH, "%V/%s%V", &pacf->local_path, + name + ctx->pfx_size, &ctx->sfx); + *p = 0; + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "play: copy local file '%s' to '%s'", path, dpath); + + if (ngx_rename_file(path, dpath) == 0) { + ctx->file_id = 0; + return; + } + + ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, + "play: error copying local file '%s' to '%s'", + path, dpath); + + ngx_rtmp_play_cleanup_local_file(s); +} + + +static void +ngx_rtmp_play_cleanup_local_file(ngx_rtmp_session_t *s) +{ + ngx_rtmp_play_ctx_t *ctx; + u_char *path; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); + if (ctx == NULL || ctx->file_id == 0) { + return; + } + + path = ngx_rtmp_play_get_local_file_path(s); + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "play: deleting local file '%s'", path); + + ctx->file_id = 0; + + ngx_delete_file(path); +} + + +static ngx_int_t +ngx_rtmp_play_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v) +{ + ngx_rtmp_play_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); + if (ctx == NULL) { + goto next; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "play: close_stream"); + + ngx_rtmp_play_do_stop(s); + + ngx_rtmp_play_do_done(s); + + if (ctx->file.fd != NGX_INVALID_FILE) { + ngx_close_file(ctx->file.fd); + ctx->file.fd = NGX_INVALID_FILE; + + ngx_rtmp_send_stream_eof(s, NGX_RTMP_MSID); + + ngx_rtmp_send_status(s, "NetStream.Play.Stop", "status", + "Stop video on demand"); + } + + if (ctx->file_id) { + ngx_rtmp_play_cleanup_local_file(s); + } + + ngx_rtmp_play_leave(s); + +next: + return next_close_stream(s, v); +} + + +static ngx_int_t +ngx_rtmp_play_seek(ngx_rtmp_session_t *s, ngx_rtmp_seek_t *v) +{ + ngx_rtmp_play_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); + if (ctx == NULL || ctx->file.fd == NGX_INVALID_FILE) { + goto next; + } + + if (!ctx->opened) { + ctx->post_seek = (ngx_uint_t) v->offset; + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "play: post seek=%ui", ctx->post_seek); + goto next; + } + + if (ngx_rtmp_send_stream_eof(s, NGX_RTMP_MSID) != NGX_OK) { + return NGX_ERROR; + } + + ngx_rtmp_play_do_seek(s, (ngx_uint_t) v->offset); + + if (ngx_rtmp_send_status(s, "NetStream.Seek.Notify", "status", "Seeking") + != NGX_OK) + { + return NGX_ERROR; + } + + if (ngx_rtmp_send_stream_begin(s, NGX_RTMP_MSID) != NGX_OK) { + return NGX_ERROR; + } + +next: + return next_seek(s, v); +} + + +static ngx_int_t +ngx_rtmp_play_pause(ngx_rtmp_session_t *s, ngx_rtmp_pause_t *v) +{ + ngx_rtmp_play_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); + + if (ctx == NULL || ctx->file.fd == NGX_INVALID_FILE) { + goto next; + } + + if (!ctx->opened) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "play: pause ignored"); + goto next; + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "play: pause=%i timestamp=%f", + (ngx_int_t) v->pause, v->position); + + if (v->pause) { + if (ngx_rtmp_send_status(s, "NetStream.Pause.Notify", "status", + "Paused video on demand") + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_rtmp_play_do_stop(s); + + } else { + if (ngx_rtmp_send_status(s, "NetStream.Unpause.Notify", "status", + "Unpaused video on demand") + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_rtmp_play_do_start(s); /*TODO: v->position? */ + } + +next: + return next_pause(s, v); +} + + +static ngx_int_t +ngx_rtmp_play_parse_index(char type, u_char *args) +{ + u_char *p, c; + static u_char name[] = "xindex="; + + name[0] = (u_char) type; + + for ( ;; ) { + p = (u_char *) ngx_strstr(args, name); + if (p == NULL) { + return 0; + } + + if (p != args) { + c = *(p - 1); + if (c != '?' && c != '&') { + args = p + 1; + continue; + } + } + + return atoi((char *) p + (sizeof(name) - 1)); + } +} + + +static ngx_int_t +ngx_rtmp_play_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) +{ + ngx_rtmp_play_main_conf_t *pmcf; + ngx_rtmp_play_app_conf_t *pacf; + ngx_rtmp_play_ctx_t *ctx; + u_char *p; + ngx_rtmp_play_fmt_t *fmt, **pfmt; + ngx_str_t *pfx, *sfx; + ngx_str_t name; + ngx_uint_t n; + + pmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_play_module); + + pacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_play_module); + + if (pacf == NULL || pacf->entries.nelts == 0) { + goto next; + } + + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "play: play name='%s' timestamp=%i", + v->name, (ngx_int_t) v->start); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); + + if (ctx && ctx->file.fd != NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "play: already playing"); + goto next; + } + + /* check for double-dot in v->name; + * we should not move out of play directory */ + for (p = v->name; *p; ++p) { + if (ngx_path_separator(p[0]) && + p[1] == '.' && p[2] == '.' && + ngx_path_separator(p[3])) + { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "play: bad name '%s'", v->name); + return NGX_ERROR; + } + } + + if (ctx == NULL) { + ctx = ngx_palloc(s->connection->pool, sizeof(ngx_rtmp_play_ctx_t)); + ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_play_module); + } + + ngx_memzero(ctx, sizeof(*ctx)); + + ctx->session = s; + ctx->aindex = ngx_rtmp_play_parse_index('a', v->args); + ctx->vindex = ngx_rtmp_play_parse_index('v', v->args); + + ctx->file.log = s->connection->log; + + ngx_memcpy(ctx->name, v->name, NGX_RTMP_MAX_NAME); + + name.len = ngx_strlen(v->name); + name.data = v->name; + + pfmt = pmcf->fmts.elts; + + for (n = 0; n < pmcf->fmts.nelts; ++n, ++pfmt) { + fmt = *pfmt; + + pfx = &fmt->pfx; + sfx = &fmt->sfx; + + if (pfx->len == 0 && ctx->fmt == NULL) { + ctx->fmt = fmt; + } + + if (pfx->len && name.len >= pfx->len && + ngx_strncasecmp(pfx->data, name.data, pfx->len) == 0) + { + ctx->pfx_size = pfx->len; + ctx->fmt = fmt; + + break; + } + + if (name.len >= sfx->len && + ngx_strncasecmp(sfx->data, name.data + name.len - sfx->len, + sfx->len) == 0) + { + ctx->fmt = fmt; + } + } + + if (ctx->fmt == NULL) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "play: fmt not found"); + goto next; + } + + ctx->file.fd = NGX_INVALID_FILE; + ctx->nentry = NGX_CONF_UNSET_UINT; + ctx->post_seek = NGX_CONF_UNSET_UINT; + + sfx = &ctx->fmt->sfx; + + if (name.len < sfx->len || + ngx_strncasecmp(sfx->data, name.data + name.len - sfx->len, + sfx->len)) + { + ctx->sfx = *sfx; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "play: fmt=%V", &ctx->fmt->name); + + return ngx_rtmp_play_next_entry(s, v); + +next: + return next_play(s, v); +} + + +static ngx_int_t +ngx_rtmp_play_next_entry(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) +{ + ngx_rtmp_play_app_conf_t *pacf; + ngx_rtmp_play_ctx_t *ctx; + ngx_rtmp_play_entry_t *pe; + u_char *p; + static u_char path[NGX_MAX_PATH + 1]; + + pacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_play_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); + + for ( ;; ) { + + if (ctx->file.fd != NGX_INVALID_FILE) { + ngx_close_file(ctx->file.fd); + ctx->file.fd = NGX_INVALID_FILE; + } + + if (ctx->file_id) { + ngx_rtmp_play_cleanup_local_file(s); + } + + ctx->nentry = (ctx->nentry == NGX_CONF_UNSET_UINT ? + 0 : ctx->nentry + 1); + + if (ctx->nentry >= pacf->entries.nelts) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "play: all entries failed"); + + ngx_rtmp_send_status(s, "NetStream.Play.StreamNotFound", "error", + "Video on demand stream not found"); + break; + } + + pe = ngx_rtmp_play_get_current_entry(s); + + ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "play: trying %s entry %ui/%uz '%V'", + pe->url ? "remote" : "local", + ctx->nentry + 1, pacf->entries.nelts, + pe->url ? &pe->url->url : pe->root); + + /* open remote */ + + if (pe->url) { + return ngx_rtmp_play_open_remote(s, v); + } + + /* open local */ + + p = ngx_snprintf(path, NGX_MAX_PATH, "%V/%s%V", + pe->root, v->name + ctx->pfx_size, &ctx->sfx); + *p = 0; + + ctx->file.fd = ngx_open_file(path, NGX_FILE_RDONLY, NGX_FILE_OPEN, + NGX_FILE_DEFAULT_ACCESS); + + if (ctx->file.fd == NGX_INVALID_FILE) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, ngx_errno, + "play: error opening file '%s'", path); + continue; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "play: open local file '%s'", path); + + if (ngx_rtmp_play_open(s, v->start) != NGX_OK) { + return NGX_ERROR; + } + + break; + } + + return next_play(s, v); +} + + +static ngx_int_t +ngx_rtmp_play_open(ngx_rtmp_session_t *s, double start) +{ + ngx_rtmp_play_ctx_t *ctx; + ngx_event_t *e; + ngx_uint_t timestamp; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); + + if (ctx->file.fd == NGX_INVALID_FILE) { + return NGX_ERROR; + } + + if (ngx_rtmp_send_stream_begin(s, NGX_RTMP_MSID) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_rtmp_send_status(s, "NetStream.Play.Start", "status", + "Start video on demand") + != NGX_OK) + { + return NGX_ERROR; + } + + if (ngx_rtmp_play_join(s) != NGX_OK) { + return NGX_ERROR; + } + + e = &ctx->send_evt; + e->data = s; + e->handler = ngx_rtmp_play_send; + e->log = s->connection->log; + + ngx_rtmp_send_recorded(s, 1); + + if (ngx_rtmp_send_sample_access(s) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_rtmp_play_do_init(s) != NGX_OK) { + return NGX_ERROR; + } + + timestamp = ctx->post_seek != NGX_CONF_UNSET_UINT ? ctx->post_seek : + (start < 0 ? 0 : (ngx_uint_t) start); + + if (ngx_rtmp_play_do_seek(s, timestamp) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_rtmp_play_do_start(s) != NGX_OK) { + return NGX_ERROR; + } + + ctx->opened = 1; + + return NGX_OK; +} + + +static ngx_chain_t * +ngx_rtmp_play_remote_create(ngx_rtmp_session_t *s, void *arg, ngx_pool_t *pool) +{ + ngx_rtmp_play_t *v = arg; + + ngx_rtmp_play_ctx_t *ctx; + ngx_rtmp_play_entry_t *pe; + ngx_str_t *addr_text, uri; + u_char *p, *name; + size_t args_len, name_len, len; + static ngx_str_t text_plain = ngx_string("text/plain"); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); + + pe = ngx_rtmp_play_get_current_entry(s); + + name = v->name + ctx->pfx_size; + + name_len = ngx_strlen(name); + args_len = ngx_strlen(v->args); + addr_text = &s->connection->addr_text; + + len = pe->url->uri.len + 1 + + name_len + ctx->sfx.len + + sizeof("?addr=") + addr_text->len * 3 + + 1 + args_len; + + uri.data = ngx_palloc(pool, len); + if (uri.data == NULL) { + return NULL; + } + + p = uri.data; + + p = ngx_cpymem(p, pe->url->uri.data, pe->url->uri.len); + + if (p == uri.data || p[-1] != '/') { + *p++ = '/'; + } + + p = ngx_cpymem(p, name, name_len); + p = ngx_cpymem(p, ctx->sfx.data, ctx->sfx.len); + p = ngx_cpymem(p, (u_char*)"?addr=", sizeof("&addr=") -1); + p = (u_char*)ngx_escape_uri(p, addr_text->data, addr_text->len, + NGX_ESCAPE_ARGS); + if (args_len) { + *p++ = '&'; + p = (u_char *) ngx_cpymem(p, v->args, args_len); + } + + uri.len = p - uri.data; + + return ngx_rtmp_netcall_http_format_request(NGX_RTMP_NETCALL_HTTP_GET, + &pe->url->host, &uri, + NULL, NULL, pool, &text_plain); +} + + +static ngx_int_t +ngx_rtmp_play_remote_handle(ngx_rtmp_session_t *s, void *arg, ngx_chain_t *in) +{ + ngx_rtmp_play_t *v = arg; + + ngx_rtmp_play_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); + + if (ctx->nbody == 0) { + return ngx_rtmp_play_next_entry(s, v); + } + + if (ctx->file_id) { + ngx_rtmp_play_copy_local_file(s, v->name); + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "play: open remote file"); + + if (ngx_rtmp_play_open(s, v->start) != NGX_OK) { + return NGX_ERROR; + } + + return next_play(s, (ngx_rtmp_play_t *)arg); +} + + +static ngx_int_t +ngx_rtmp_play_remote_sink(ngx_rtmp_session_t *s, ngx_chain_t *in) +{ + ngx_rtmp_play_ctx_t *ctx; + ngx_buf_t *b; + ngx_int_t rc; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); + + /* skip HTTP header */ + while (in && ctx->ncrs != 2) { + b = in->buf; + + for (; b->pos != b->last && ctx->ncrs != 2; ++b->pos) { + switch (*b->pos) { + case '\n': + ++ctx->ncrs; + case '\r': + break; + default: + ctx->ncrs = 0; + } + /* 10th header byte is HTTP response header */ + if (++ctx->nheader == 10 && *b->pos != (u_char) '2') { + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "play: remote HTTP response code: %cxx", + *b->pos); + return NGX_ERROR; + } + } + + if (b->pos == b->last) { + in = in->next; + } + } + + /* write to temp file */ + for (; in; in = in->next) { + b = in->buf; + + if (b->pos == b->last) { + continue; + } + + rc = ngx_write_fd(ctx->file.fd, b->pos, b->last - b->pos); + + if (rc == NGX_ERROR) { + ngx_log_error(NGX_LOG_INFO, s->connection->log, ngx_errno, + "play: error writing to temp file"); + return NGX_ERROR; + } + + ctx->nbody += rc; + } + + return NGX_OK; +} + + +static ngx_rtmp_play_entry_t * +ngx_rtmp_play_get_current_entry(ngx_rtmp_session_t *s) +{ + ngx_rtmp_play_app_conf_t *pacf; + ngx_rtmp_play_ctx_t *ctx; + ngx_rtmp_play_entry_t **ppe; + + pacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_play_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); + + ppe = pacf->entries.elts; + + return ppe[ctx->nentry]; +} + + +static ngx_int_t +ngx_rtmp_play_open_remote(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) +{ + ngx_rtmp_play_app_conf_t *pacf; + ngx_rtmp_play_ctx_t *ctx; + ngx_rtmp_play_entry_t *pe; + ngx_rtmp_netcall_init_t ci; + u_char *path; + ngx_err_t err; + static ngx_uint_t file_id; + + pacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_play_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); + + ctx->ncrs = 0; + ctx->nheader = 0; + ctx->nbody = 0; + + for ( ;; ) { + ctx->file_id = ++file_id; + + /* no zero after overflow */ + if (ctx->file_id == 0) { + continue; + } + + path = ngx_rtmp_play_get_local_file_path(s); + + ctx->file.fd = ngx_open_tempfile(path, pacf->local_path.len, 0); + + if (pacf->local_path.len == 0) { + ctx->file_id = 0; + } + + if (ctx->file.fd != NGX_INVALID_FILE) { + break; + } + + err = ngx_errno; + + if (err != NGX_EEXIST) { + ctx->file_id = 0; + + ngx_log_error(NGX_LOG_INFO, s->connection->log, err, + "play: failed to create temp file"); + + return NGX_ERROR; + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "play: temp file '%s' file_id=%ui", + path, ctx->file_id); + + pe = ngx_rtmp_play_get_current_entry(s); + + ngx_memzero(&ci, sizeof(ci)); + + ci.url = pe->url; + ci.create = ngx_rtmp_play_remote_create; + ci.sink = ngx_rtmp_play_remote_sink; + ci.handle = ngx_rtmp_play_remote_handle; + ci.arg = v; + ci.argsize = sizeof(*v); + + return ngx_rtmp_netcall_create(s, &ci); +} + + +static char * +ngx_rtmp_play_url(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_rtmp_play_app_conf_t *pacf = conf; + + ngx_rtmp_play_entry_t *pe, **ppe; + ngx_str_t url; + ngx_url_t *u; + size_t add, n; + ngx_str_t *value; + + if (pacf->entries.nalloc == 0 && + ngx_array_init(&pacf->entries, cf->pool, 1, sizeof(void *)) != NGX_OK) + { + return NGX_CONF_ERROR; + } + + value = cf->args->elts; + + for (n = 1; n < cf->args->nelts; ++n) { + + ppe = ngx_array_push(&pacf->entries); + if (ppe == NULL) { + return NGX_CONF_ERROR; + } + + pe = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_play_entry_t)); + if (pe == NULL) { + return NGX_CONF_ERROR; + } + + *ppe = pe; + + if (ngx_strncasecmp(value[n].data, (u_char *) "http://", 7)) { + + /* local file */ + + pe->root = ngx_palloc(cf->pool, sizeof(ngx_str_t)); + if (pe->root == NULL) { + return NGX_CONF_ERROR; + } + + *pe->root = value[n]; + + continue; + } + + /* http case */ + + url = value[n]; + + add = sizeof("http://") - 1; + + url.data += add; + url.len -= add; + + u = ngx_pcalloc(cf->pool, sizeof(ngx_url_t)); + if (u == NULL) { + return NGX_CONF_ERROR; + } + + u->url.len = url.len; + u->url.data = url.data; + u->default_port = 80; + u->uri_part = 1; + + if (ngx_parse_url(cf->pool, u) != NGX_OK) { + if (u->err) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "%s in url \"%V\"", u->err, &u->url); + } + return NGX_CONF_ERROR; + } + + pe->url = u; + } + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_rtmp_play_postconfiguration(ngx_conf_t *cf) +{ + next_play = ngx_rtmp_play; + ngx_rtmp_play = ngx_rtmp_play_play; + + next_close_stream = ngx_rtmp_close_stream; + ngx_rtmp_close_stream = ngx_rtmp_play_close_stream; + + next_seek = ngx_rtmp_seek; + ngx_rtmp_seek = ngx_rtmp_play_seek; + + next_pause = ngx_rtmp_pause; + ngx_rtmp_pause = ngx_rtmp_play_pause; + + return NGX_OK; +} diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_play_module.h nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_play_module.h --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_play_module.h 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_play_module.h 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,93 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_PLAY_H_INCLUDED_ +#define _NGX_RTMP_PLAY_H_INCLUDED_ + + +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rtmp_cmd_module.h" + + +typedef ngx_int_t (*ngx_rtmp_play_init_pt) (ngx_rtmp_session_t *s, + ngx_file_t *f, ngx_int_t aindex, ngx_int_t vindex); +typedef ngx_int_t (*ngx_rtmp_play_done_pt) (ngx_rtmp_session_t *s, + ngx_file_t *f); +typedef ngx_int_t (*ngx_rtmp_play_start_pt) (ngx_rtmp_session_t *s, + ngx_file_t *f); +typedef ngx_int_t (*ngx_rtmp_play_seek_pt) (ngx_rtmp_session_t *s, + ngx_file_t *f, ngx_uint_t offs); +typedef ngx_int_t (*ngx_rtmp_play_stop_pt) (ngx_rtmp_session_t *s, + ngx_file_t *f); +typedef ngx_int_t (*ngx_rtmp_play_send_pt) (ngx_rtmp_session_t *s, + ngx_file_t *f, ngx_uint_t *ts); + + +typedef struct { + ngx_str_t name; + ngx_str_t pfx; + ngx_str_t sfx; + + ngx_rtmp_play_init_pt init; + ngx_rtmp_play_done_pt done; + ngx_rtmp_play_start_pt start; + ngx_rtmp_play_seek_pt seek; + ngx_rtmp_play_stop_pt stop; + ngx_rtmp_play_send_pt send; +} ngx_rtmp_play_fmt_t; + + +typedef struct ngx_rtmp_play_ctx_s ngx_rtmp_play_ctx_t; + + +struct ngx_rtmp_play_ctx_s { + ngx_rtmp_session_t *session; + ngx_file_t file; + ngx_rtmp_play_fmt_t *fmt; + ngx_event_t send_evt; + unsigned playing:1; + unsigned opened:1; + unsigned joined:1; + ngx_uint_t ncrs; + ngx_uint_t nheader; + ngx_uint_t nbody; + size_t pfx_size; + ngx_str_t sfx; + ngx_uint_t file_id; + ngx_int_t aindex, vindex; + ngx_uint_t nentry; + ngx_uint_t post_seek; + u_char name[NGX_RTMP_MAX_NAME]; + ngx_rtmp_play_ctx_t *next; +}; + + +typedef struct { + ngx_str_t *root; + ngx_url_t *url; +} ngx_rtmp_play_entry_t; + + +typedef struct { + ngx_str_t temp_path; + ngx_str_t local_path; + ngx_array_t entries; /* ngx_rtmp_play_entry_t * */ + ngx_uint_t nbuckets; + ngx_rtmp_play_ctx_t **ctx; +} ngx_rtmp_play_app_conf_t; + + +typedef struct { + ngx_array_t fmts; /* ngx_rtmp_play_fmt_t * */ +} ngx_rtmp_play_main_conf_t; + + +extern ngx_module_t ngx_rtmp_play_module; + + +#endif /* _NGX_RTMP_PLAY_H_INCLUDED_ */ diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_proxy_protocol.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_proxy_protocol.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_proxy_protocol.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_proxy_protocol.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,197 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include +#include "ngx_rtmp_proxy_protocol.h" + + +static void ngx_rtmp_proxy_protocol_recv(ngx_event_t *rev); + + +void +ngx_rtmp_proxy_protocol(ngx_rtmp_session_t *s) +{ + ngx_event_t *rev; + ngx_connection_t *c; + + c = s->connection; + rev = c->read; + rev->handler = ngx_rtmp_proxy_protocol_recv; + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "proxy_protocol: start"); + + if (rev->ready) { + /* the deferred accept(), rtsig, aio, iocp */ + + if (ngx_use_accept_mutex) { + ngx_post_event(rev, &ngx_posted_events); + return; + } + + rev->handler(rev); + return; + } + + ngx_add_timer(rev, s->timeout); + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_rtmp_finalize_session(s); + return; + } +} + + +static void +ngx_rtmp_proxy_protocol_recv(ngx_event_t *rev) +{ + u_char buf[107], *p, *pp, *text; + size_t len; + ssize_t n; + ngx_err_t err; + ngx_int_t i; + ngx_addr_t addr; + ngx_connection_t *c; + ngx_rtmp_session_t *s; + + c = rev->data; + s = c->data; + + if (c->destroyed) { + return; + } + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, + "proxy_protocol: recv: client timed out"); + c->timedout = 1; + ngx_rtmp_finalize_session(s); + return; + } + + if (rev->timer_set) { + ngx_del_timer(rev); + } + + n = recv(c->fd, (char *) buf, sizeof(buf), MSG_PEEK); + + err = ngx_socket_errno; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, c->log, 0, "recv(): %d", n); + + if (n == -1) { + + if (err == NGX_EAGAIN) { + ngx_add_timer(rev, s->timeout); + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + ngx_rtmp_finalize_session(s); + } + + return; + } + + ngx_rtmp_finalize_session(s); + + return; + } + + p = buf; + + if (n <= 8 && ngx_strncmp(p, "PROXY ", 6) != 0) { + goto bad_header; + } + + n -= 6; + p += 6; + + ngx_memzero(&addr, sizeof(ngx_addr_t)); + + if (n >= 7 && ngx_strncmp(p, "UNKNOWN", 7) == 0) { + n -= 7; + p += 7; + goto skip; + } + + if (n < 5 || ngx_strncmp(p, "TCP", 3) != 0 + || (p[3] != '4' && p[3] != '6') || p[4] != ' ') + { + goto bad_header; + } + + n -= 5; + p += 5; + + pp = ngx_strlchr(p, p + n, ' '); + + if (pp == NULL) { + goto bad_header; + } + + if (ngx_parse_addr(s->connection->pool, &addr, p, pp - p) != NGX_OK) { + goto bad_header; + } + + n -= pp - p; + p = pp; + +skip: + + for (i = 0; i + 1 < n; i++) { + if (p[i] == CR && p[i + 1] == LF) { + break; + } + } + + if (i + 1 >= n) { + goto bad_header; + } + + n = p - buf + i + 2; + + if (c->recv(c, buf, n) != n) { + goto failed; + } + + if (addr.socklen) { + text = ngx_palloc(s->connection->pool, NGX_SOCKADDR_STRLEN); + + if (text == NULL) { + goto failed; + } + + len = ngx_sock_ntop(addr.sockaddr, +#if (nginx_version >= 1005003) + addr.socklen, +#endif + text, NGX_SOCKADDR_STRLEN, 0); + if (len == 0) { + goto failed; + } + + c->sockaddr = addr.sockaddr; + c->socklen = addr.socklen; + c->addr_text.data = text; + c->addr_text.len = len; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, c->log, 0, + "proxy_protocol: remote_addr:'%V'", &c->addr_text); + } + + ngx_rtmp_handshake(s); + + return; + +bad_header: + + ngx_log_error(NGX_LOG_INFO, c->log, 0, "proxy_protocol: bad header"); + +failed: + + ngx_rtmp_finalize_session(s); +} diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_proxy_protocol.h nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_proxy_protocol.h --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_proxy_protocol.h 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_proxy_protocol.h 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,19 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_PROXY_PROTOCOL_H_INCLUDED_ +#define _NGX_RTMP_PROXY_PROTOCOL_H_INCLUDED_ + + +#include +#include +#include "ngx_rtmp.h" + + +void ngx_rtmp_proxy_protocol(ngx_rtmp_session_t *c); + + +#endif /* _NGX_RTMP_PROXY_PROTOCOL_H_INCLUDED_ */ diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_receive.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_receive.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_receive.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_receive.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,464 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rtmp_amf.h" +#include "ngx_rtmp_cmd_module.h" +#include + + +ngx_int_t +ngx_rtmp_protocol_message_handler(ngx_rtmp_session_t *s, + ngx_rtmp_header_t *h, ngx_chain_t *in) +{ + ngx_buf_t *b; + u_char *p; + uint32_t val; + uint8_t limit; + + b = in->buf; + + if (b->last - b->pos < 4) { + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "too small buffer for %d message: %d", + (int)h->type, b->last - b->pos); + return NGX_OK; + } + + p = (u_char*)&val; + p[0] = b->pos[3]; + p[1] = b->pos[2]; + p[2] = b->pos[1]; + p[3] = b->pos[0]; + + switch(h->type) { + case NGX_RTMP_MSG_CHUNK_SIZE: + /* set chunk size =val */ + ngx_rtmp_set_chunk_size(s, val); + break; + + case NGX_RTMP_MSG_ABORT: + /* abort chunk stream =val */ + break; + + case NGX_RTMP_MSG_ACK: + /* receive ack with sequence number =val */ + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "receive ack seq=%uD", val); + break; + + case NGX_RTMP_MSG_ACK_SIZE: + /* receive window size =val */ + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "receive ack_size=%uD", val); + s->ack_size = val; + break; + + case NGX_RTMP_MSG_BANDWIDTH: + if (b->last - b->pos >= 5) { + limit = *(uint8_t*)&b->pos[4]; + + (void)val; + (void)limit; + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "receive bandwidth=%uD limit=%d", + val, (int)limit); + + /* receive window size =val + * && limit */ + } + break; + + default: + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_user_message_handler(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_buf_t *b; + u_char *p; + uint16_t evt; + uint32_t val; + + b = in->buf; + + if (b->last - b->pos < 6) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "too small buffer for user message: %d", + b->last - b->pos); + return NGX_OK; + } + + p = (u_char*)&evt; + + p[0] = b->pos[1]; + p[1] = b->pos[0]; + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "RTMP recv user evt %s (%i)", + ngx_rtmp_user_message_type(evt), (ngx_int_t) evt); + + p = (u_char *) &val; + + p[0] = b->pos[5]; + p[1] = b->pos[4]; + p[2] = b->pos[3]; + p[3] = b->pos[2]; + + switch(evt) { + case NGX_RTMP_USER_STREAM_BEGIN: + { + ngx_rtmp_stream_begin_t v; + + v.msid = val; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "receive: stream_begin msid=%uD", v.msid); + + return ngx_rtmp_stream_begin(s, &v); + } + + case NGX_RTMP_USER_STREAM_EOF: + { + ngx_rtmp_stream_eof_t v; + + v.msid = val; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "receive: stream_eof msid=%uD", v.msid); + + return ngx_rtmp_stream_eof(s, &v); + } + + case NGX_RTMP_USER_STREAM_DRY: + { + ngx_rtmp_stream_dry_t v; + + v.msid = val; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "receive: stream_dry msid=%uD", v.msid); + + return ngx_rtmp_stream_dry(s, &v); + } + + case NGX_RTMP_USER_SET_BUFLEN: + { + ngx_rtmp_set_buflen_t v; + + v.msid = val; + + if (b->last - b->pos < 10) { + return NGX_OK; + } + + p = (u_char *) &v.buflen; + + p[0] = b->pos[9]; + p[1] = b->pos[8]; + p[2] = b->pos[7]; + p[3] = b->pos[6]; + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "receive: set_buflen msid=%uD buflen=%uD", + v.msid, v.buflen); + + /*TODO: move this to play module */ + s->buflen = v.buflen; + + return ngx_rtmp_set_buflen(s, &v); + } + + case NGX_RTMP_USER_RECORDED: + { + ngx_rtmp_recorded_t v; + + v.msid = val; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "receive: recorded msid=%uD", v.msid); + + return ngx_rtmp_recorded(s, &v); + } + + case NGX_RTMP_USER_PING_REQUEST: + return ngx_rtmp_send_ping_response(s, val); + + case NGX_RTMP_USER_PING_RESPONSE: + + /* val = incoming timestamp */ + + ngx_rtmp_reset_ping(s); + + return NGX_OK; + + default: + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "unexpected user event: %i", (ngx_int_t) evt); + + return NGX_OK; + } +} + + +static ngx_int_t +ngx_rtmp_fetch(ngx_chain_t **in, u_char *ret) +{ + while (*in && (*in)->buf->pos >= (*in)->buf->last) { + *in = (*in)->next; + } + + if (*in == NULL) { + return NGX_DONE; + } + + *ret = *(*in)->buf->pos++; + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_fetch_uint8(ngx_chain_t **in, uint8_t *ret) +{ + return ngx_rtmp_fetch(in, (u_char *) ret); +} + + +static ngx_int_t +ngx_rtmp_fetch_uint32(ngx_chain_t **in, uint32_t *ret, ngx_int_t n) +{ + u_char *r = (u_char *) ret; + ngx_int_t rc; + + *ret = 0; + + while (--n >= 0) { + rc = ngx_rtmp_fetch(in, &r[n]); + if (rc != NGX_OK) { + return rc; + } + } + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_aggregate_message_handler(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + uint32_t base_time, timestamp, prev_size; + size_t len; + ngx_int_t first; + u_char *last; + ngx_int_t rc; + ngx_buf_t *b; + ngx_chain_t *cl, *next; + ngx_rtmp_header_t ch; + + ch = *h; + + first = 1; + base_time = 0; + + while (in) { + if (ngx_rtmp_fetch_uint8(&in, &ch.type) != NGX_OK) { + return NGX_OK; + } + + if (ngx_rtmp_fetch_uint32(&in, &ch.mlen, 3) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_rtmp_fetch_uint32(&in, ×tamp, 3) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_rtmp_fetch_uint8(&in, (uint8_t *) ×tamp + 3) != NGX_OK) + { + return NGX_ERROR; + } + + if (ngx_rtmp_fetch_uint32(&in, &ch.msid, 3) != NGX_OK) + { + return NGX_ERROR; + } + + if (first) { + base_time = timestamp; + first = 0; + } + + ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "RTMP aggregate %s (%d) len=%uD time=%uD (+%D) msid=%uD", + ngx_rtmp_message_type(ch.type), + (ngx_int_t) ch.type, ch.mlen, ch.timestamp, + timestamp - base_time, ch.msid); + + /* limit chain */ + + len = 0; + cl = in; + while (cl) { + b = cl->buf; + len += (b->last - b->pos); + if (len > ch.mlen) { + break; + } + cl = cl->next; + } + + if (cl == NULL) { + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "RTMP error parsing aggregate"); + return NGX_ERROR; + } + + next = cl->next; + cl->next = NULL; + b = cl->buf; + last = b->last; + b->last -= (len - ch.mlen); + + /* handle aggregated message */ + + ch.timestamp = h->timestamp + timestamp - base_time; + + rc = ngx_rtmp_receive_message(s, &ch, in); + + /* restore chain before checking the result */ + + in = cl; + in->next = next; + b->pos = b->last; + b->last = last; + + if (rc != NGX_OK) { + return rc; + } + + /* read 32-bit previous tag size */ + + if (ngx_rtmp_fetch_uint32(&in, &prev_size, 4) != NGX_OK) { + return NGX_OK; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "RTMP aggregate prev_size=%uD", prev_size); + } + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_amf_message_handler(ngx_rtmp_session_t *s, + ngx_rtmp_header_t *h, ngx_chain_t *in) +{ + ngx_rtmp_amf_ctx_t act; + ngx_rtmp_core_main_conf_t *cmcf; + ngx_array_t *ch; + ngx_rtmp_handler_pt *ph; + size_t len, n; + + static u_char func[128]; + + static ngx_rtmp_amf_elt_t elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + func, sizeof(func) }, + }; + + /* AMF command names come with string type, but shared object names + * come without type */ + if (h->type == NGX_RTMP_MSG_AMF_SHARED || + h->type == NGX_RTMP_MSG_AMF3_SHARED) + { + elts[0].type |= NGX_RTMP_AMF_TYPELESS; + } else { + elts[0].type &= ~NGX_RTMP_AMF_TYPELESS; + } + + if ((h->type == NGX_RTMP_MSG_AMF3_SHARED || + h->type == NGX_RTMP_MSG_AMF3_META || + h->type == NGX_RTMP_MSG_AMF3_CMD) + && in->buf->last > in->buf->pos) + { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "AMF3 prefix: %ui", (ngx_int_t)*in->buf->pos); + ++in->buf->pos; + } + + cmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_core_module); + + /* read AMF func name & transaction id */ + ngx_memzero(&act, sizeof(act)); + act.link = in; + act.log = s->connection->log; + memset(func, 0, sizeof(func)); + + if (ngx_rtmp_amf_read(&act, elts, + sizeof(elts) / sizeof(elts[0])) != NGX_OK) + { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "AMF cmd failed"); + return NGX_ERROR; + } + + /* skip name */ + in = act.link; + in->buf->pos += act.offset; + + len = ngx_strlen(func); + + ch = ngx_hash_find(&cmcf->amf_hash, + ngx_hash_strlow(func, func, len), func, len); + + if (ch && ch->nelts) { + ph = ch->elts; + for (n = 0; n < ch->nelts; ++n, ++ph) { + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "AMF func '%s' passed to handler %d/%d", + func, n, ch->nelts); + switch ((*ph)(s, h, in)) { + case NGX_ERROR: + return NGX_ERROR; + case NGX_DONE: + return NGX_OK; + } + } + } else { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "AMF cmd '%s' no handler", func); + } + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_receive_amf(ngx_rtmp_session_t *s, ngx_chain_t *in, + ngx_rtmp_amf_elt_t *elts, size_t nelts) +{ + ngx_rtmp_amf_ctx_t act; + + ngx_memzero(&act, sizeof(act)); + act.link = in; + act.log = s->connection->log; + + return ngx_rtmp_amf_read(&act, elts, nelts); +} diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_record_module.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_record_module.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_record_module.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_record_module.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,1301 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rtmp_cmd_module.h" +#include "ngx_rtmp_netcall_module.h" +#include "ngx_rtmp_codec_module.h" +#include "ngx_rtmp_record_module.h" + + +ngx_rtmp_record_done_pt ngx_rtmp_record_done; + + +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 char *ngx_rtmp_record_recorder(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static ngx_int_t ngx_rtmp_record_postconfiguration(ngx_conf_t *cf); +static void * ngx_rtmp_record_create_app_conf(ngx_conf_t *cf); +static char * ngx_rtmp_record_merge_app_conf(ngx_conf_t *cf, + void *parent, void *child); +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, + ngx_rtmp_header_t *h, ngx_chain_t *in); +static ngx_int_t ngx_rtmp_record_node_av(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); +static ngx_int_t ngx_rtmp_record_node_close(ngx_rtmp_session_t *s, + ngx_rtmp_record_rec_ctx_t *rctx); +static void ngx_rtmp_record_make_path(ngx_rtmp_session_t *s, + ngx_rtmp_record_rec_ctx_t *rctx, ngx_str_t *path); +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_string("audio"), NGX_RTMP_RECORD_AUDIO }, + { ngx_string("video"), NGX_RTMP_RECORD_VIDEO }, + { ngx_string("keyframes"), NGX_RTMP_RECORD_KEYFRAMES }, + { ngx_string("manual"), NGX_RTMP_RECORD_MANUAL }, + { ngx_null_string, 0 } +}; + + +static ngx_command_t ngx_rtmp_record_commands[] = { + + { ngx_string("record"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF| + NGX_RTMP_REC_CONF|NGX_CONF_1MORE, + ngx_conf_set_bitmask_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_record_app_conf_t, flags), + ngx_rtmp_record_mask }, + + { ngx_string("record_path"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF| + NGX_RTMP_REC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_record_app_conf_t, path), + NULL }, + + { ngx_string("record_suffix"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF| + NGX_RTMP_REC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_record_app_conf_t, suffix), + NULL }, + + { ngx_string("record_unique"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF| + NGX_RTMP_REC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_record_app_conf_t, unique), + NULL }, + + { ngx_string("record_append"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF| + NGX_RTMP_REC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_record_app_conf_t, append), + NULL }, + + { ngx_string("record_lock"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF| + NGX_RTMP_REC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_record_app_conf_t, lock_file), + 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, + ngx_conf_set_size_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_record_app_conf_t, max_size), + NULL }, + + { ngx_string("record_max_frames"), + 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, max_frames), + NULL }, + + { ngx_string("record_interval"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF| + NGX_RTMP_REC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_record_app_conf_t, interval), + NULL }, + + { ngx_string("record_notify"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF| + NGX_RTMP_REC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_record_app_conf_t, notify), + NULL }, + + { ngx_string("recorder"), + NGX_RTMP_APP_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE1, + ngx_rtmp_record_recorder, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_rtmp_record_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_record_postconfiguration, /* postconfiguration */ + NULL, /* create main configuration */ + NULL, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + ngx_rtmp_record_create_app_conf, /* create app configuration */ + ngx_rtmp_record_merge_app_conf /* merge app configuration */ +}; + + +ngx_module_t ngx_rtmp_record_module = { + NGX_MODULE_V1, + &ngx_rtmp_record_module_ctx, /* module context */ + ngx_rtmp_record_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_rtmp_record_create_app_conf(ngx_conf_t *cf) +{ + ngx_rtmp_record_app_conf_t *racf; + + racf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_record_app_conf_t)); + + if (racf == NULL) { + return NULL; + } + + racf->max_size = NGX_CONF_UNSET_SIZE; + racf->max_frames = NGX_CONF_UNSET_SIZE; + racf->interval = NGX_CONF_UNSET_MSEC; + racf->unique = NGX_CONF_UNSET; + racf->append = NGX_CONF_UNSET; + racf->lock_file = NGX_CONF_UNSET; + racf->notify = NGX_CONF_UNSET; + racf->url = NGX_CONF_UNSET_PTR; + + if (ngx_array_init(&racf->rec, cf->pool, 1, sizeof(void *)) != NGX_OK) { + return NULL; + } + + return racf; +} + + +static char * +ngx_rtmp_record_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_record_app_conf_t *prev = parent; + ngx_rtmp_record_app_conf_t *conf = child; + ngx_rtmp_record_app_conf_t **rracf; + + 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->max_frames, prev->max_frames, 0); + ngx_conf_merge_value(conf->unique, prev->unique, 0); + ngx_conf_merge_value(conf->append, prev->append, 0); + ngx_conf_merge_value(conf->lock_file, prev->lock_file, 0); + ngx_conf_merge_value(conf->notify, prev->notify, 0); + ngx_conf_merge_msec_value(conf->interval, prev->interval, + (ngx_msec_t) NGX_CONF_UNSET); + ngx_conf_merge_bitmask_value(conf->flags, prev->flags, 0); + ngx_conf_merge_ptr_value(conf->url, prev->url, NULL); + + if (conf->flags) { + rracf = ngx_array_push(&conf->rec); + if (rracf == NULL) { + return NGX_CONF_ERROR; + } + + *rracf = conf; + } + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_rtmp_record_write_header(ngx_file_t *file) +{ + static u_char flv_header[] = { + 0x46, /* 'F' */ + 0x4c, /* 'L' */ + 0x56, /* 'V' */ + 0x01, /* version = 1 */ + 0x05, /* 00000 1 0 1 = has audio & video */ + 0x00, + 0x00, + 0x00, + 0x09, /* header size */ + 0x00, + 0x00, + 0x00, + 0x00 /* PreviousTagSize0 (not actually a header) */ + }; + + return ngx_write_file(file, flv_header, sizeof(flv_header), 0) == NGX_ERROR + ? NGX_ERROR + : NGX_OK; +} + + +static ngx_rtmp_record_rec_ctx_t * +ngx_rtmp_record_get_node_ctx(ngx_rtmp_session_t *s, ngx_uint_t n) +{ + ngx_rtmp_record_ctx_t *ctx; + ngx_rtmp_record_rec_ctx_t *rctx; + + if (ngx_rtmp_record_init(s) != NGX_OK) { + return NULL; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_record_module); + + if (n >= ctx->rec.nelts) { + return NULL; + } + + rctx = ctx->rec.elts; + + return &rctx[n]; +} + + +ngx_int_t +ngx_rtmp_record_open(ngx_rtmp_session_t *s, ngx_uint_t n, ngx_str_t *path) +{ + ngx_rtmp_record_rec_ctx_t *rctx; + ngx_int_t rc; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "record: #%ui manual open", n); + + rctx = ngx_rtmp_record_get_node_ctx(s, n); + + if (rctx == NULL) { + return NGX_ERROR; + } + + rc = ngx_rtmp_record_node_open(s, rctx); + if (rc != NGX_OK) { + return rc; + } + + if (path) { + ngx_rtmp_record_make_path(s, rctx, path); + } + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_record_close(ngx_rtmp_session_t *s, ngx_uint_t n, ngx_str_t *path) +{ + ngx_rtmp_record_rec_ctx_t *rctx; + ngx_int_t rc; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "record: #%ui manual close", n); + + rctx = ngx_rtmp_record_get_node_ctx(s, n); + + if (rctx == NULL) { + return NGX_ERROR; + } + + rc = ngx_rtmp_record_node_close(s, rctx); + if (rc != NGX_OK) { + return rc; + } + + if (path) { + ngx_rtmp_record_make_path(s, rctx, path); + } + + return NGX_OK; +} + + +ngx_uint_t +ngx_rtmp_record_find(ngx_rtmp_record_app_conf_t *racf, ngx_str_t *id) +{ + ngx_rtmp_record_app_conf_t **pracf, *rracf; + ngx_uint_t n; + + pracf = racf->rec.elts; + + for (n = 0; n < racf->rec.nelts; ++n, ++pracf) { + rracf = *pracf; + + if (rracf->id.len == id->len && + ngx_strncmp(rracf->id.data, id->data, id->len) == 0) + { + return n; + } + } + + return NGX_CONF_UNSET_UINT; +} + + +/* This funcion returns pointer to a static buffer */ +static void +ngx_rtmp_record_make_path(ngx_rtmp_session_t *s, + ngx_rtmp_record_rec_ctx_t *rctx, ngx_str_t *path) +{ + ngx_rtmp_record_ctx_t *ctx; + ngx_rtmp_record_app_conf_t *rracf; + u_char *p, *l; + struct tm tm; + + static u_char buf[NGX_TIME_T_LEN + 1]; + static u_char pbuf[NGX_MAX_PATH + 1]; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_record_module); + + rracf = rctx->conf; + + /* create file path */ + p = pbuf; + l = pbuf + sizeof(pbuf) - 1; + + p = ngx_cpymem(p, rracf->path.data, + ngx_min(rracf->path.len, (size_t)(l - p - 1))); + *p++ = '/'; + p = (u_char *)ngx_escape_uri(p, ctx->name, ngx_min(ngx_strlen(ctx->name), + (size_t)(l - p)), NGX_ESCAPE_URI_COMPONENT); + + /* append timestamp */ + if (rracf->unique) { + p = ngx_cpymem(p, buf, ngx_min(ngx_sprintf(buf, "-%T", + rctx->timestamp) - buf, l - p)); + } + + if (ngx_strchr(rracf->suffix.data, '%')) { + ngx_libc_localtime(rctx->timestamp, &tm); + p += strftime((char *) p, l - p, (char *) rracf->suffix.data, &tm); + } else { + p = ngx_cpymem(p, rracf->suffix.data, + ngx_min(rracf->suffix.len, (size_t)(l - p))); + } + + *p = 0; + path->data = pbuf; + path->len = p - pbuf; + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "record: %V path: '%V'", &rracf->id, path); +} + + +static void +ngx_rtmp_record_notify_error(ngx_rtmp_session_t *s, + ngx_rtmp_record_rec_ctx_t *rctx) +{ + ngx_rtmp_record_app_conf_t *rracf = rctx->conf; + + rctx->failed = 1; + + if (!rracf->notify) { + return; + } + + ngx_rtmp_send_status(s, "NetStream.Record.Failed", "error", + rracf->id.data ? (char *) rracf->id.data : ""); +} + + +static ngx_int_t +ngx_rtmp_record_node_open(ngx_rtmp_session_t *s, + ngx_rtmp_record_rec_ctx_t *rctx) +{ + ngx_rtmp_record_app_conf_t *rracf; + ngx_err_t err; + ngx_str_t path; + ngx_int_t mode, create_mode; + u_char buf[8], *p; + off_t file_size; + uint32_t tag_size, mlen, timestamp; + + rracf = rctx->conf; + tag_size = 0; + + if (rctx->file.fd != NGX_INVALID_FILE) { + return NGX_AGAIN; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "record: %V opening", &rracf->id); + + ngx_memzero(rctx, sizeof(*rctx)); + rctx->conf = rracf; + rctx->last = *ngx_cached_time; + rctx->timestamp = ngx_cached_time->sec; + + ngx_rtmp_record_make_path(s, rctx, &path); + + mode = rracf->append ? NGX_FILE_RDWR : NGX_FILE_WRONLY; + create_mode = rracf->append ? NGX_FILE_CREATE_OR_OPEN : NGX_FILE_TRUNCATE; + + ngx_memzero(&rctx->file, sizeof(rctx->file)); + rctx->file.offset = 0; + rctx->file.log = s->connection->log; + rctx->file.fd = ngx_open_file(path.data, mode, create_mode, + NGX_FILE_DEFAULT_ACCESS); + ngx_str_set(&rctx->file.name, "recorded"); + + if (rctx->file.fd == NGX_INVALID_FILE) { + err = ngx_errno; + + if (err != NGX_ENOENT) { + ngx_log_error(NGX_LOG_CRIT, s->connection->log, err, + "record: %V failed to open file '%V'", + &rracf->id, &path); + } + + ngx_rtmp_record_notify_error(s, rctx); + + return NGX_OK; + } + +#if !(NGX_WIN32) + if (rracf->lock_file) { + err = ngx_lock_fd(rctx->file.fd); + if (err) { + ngx_log_error(NGX_LOG_CRIT, s->connection->log, err, + "record: %V lock failed", &rracf->id); + } + } +#endif + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "record: %V opened '%V'", &rracf->id, &path); + + if (rracf->notify) { + ngx_rtmp_send_status(s, "NetStream.Record.Start", "status", + rracf->id.data ? (char *) rracf->id.data : ""); + } + + if (rracf->append) { + + file_size = 0; + timestamp = 0; + +#if (NGX_WIN32) + { + LONG lo, hi; + + lo = 0; + hi = 0; + lo = SetFilePointer(rctx->file.fd, lo, &hi, FILE_END); + file_size = (lo == INVALID_SET_FILE_POINTER ? + (off_t) -1 : (off_t) hi << 32 | (off_t) lo); + } +#else + file_size = lseek(rctx->file.fd, 0, SEEK_END); +#endif + if (file_size == (off_t) -1) { + ngx_log_error(NGX_LOG_CRIT, s->connection->log, ngx_errno, + "record: %V seek failed", &rracf->id); + goto done; + } + + if (file_size < 4) { + goto done; + } + + if (ngx_read_file(&rctx->file, buf, 4, file_size - 4) != 4) { + ngx_log_error(NGX_LOG_CRIT, s->connection->log, ngx_errno, + "record: %V tag size read failed", &rracf->id); + goto done; + } + + p = (u_char *) &tag_size; + p[0] = buf[3]; + p[1] = buf[2]; + p[2] = buf[1]; + p[3] = buf[0]; + + if (tag_size == 0 || tag_size + 4 > file_size) { + file_size = 0; + goto done; + } + + if (ngx_read_file(&rctx->file, buf, 8, file_size - tag_size - 4) != 8) + { + ngx_log_error(NGX_LOG_CRIT, s->connection->log, ngx_errno, + "record: %V tag read failed", &rracf->id); + goto done; + } + + p = (u_char *) &mlen; + p[0] = buf[3]; + p[1] = buf[2]; + p[2] = buf[1]; + p[3] = 0; + + if (tag_size != mlen + 11) { + ngx_log_error(NGX_LOG_CRIT, s->connection->log, ngx_errno, + "record: %V tag size mismatch: " + "tag_size=%uD, mlen=%uD", &rracf->id, tag_size, mlen); + goto done; + } + + p = (u_char *) ×tamp; + p[3] = buf[7]; + p[0] = buf[6]; + p[1] = buf[5]; + p[2] = buf[4]; + +done: + rctx->file.offset = file_size; + rctx->time_shift = timestamp; + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "record: append offset=%O, time=%uD, tag_size=%uD", + file_size, timestamp, tag_size); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_record_init(ngx_rtmp_session_t *s) +{ + ngx_rtmp_record_app_conf_t *racf, **rracf; + ngx_rtmp_record_rec_ctx_t *rctx; + ngx_rtmp_record_ctx_t *ctx; + ngx_uint_t n; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_record_module); + + if (ctx) { + return NGX_OK; + } + + racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_record_module); + + if (racf == NULL || racf->rec.nelts == 0) { + return NGX_OK; + } + + ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_rtmp_record_ctx_t)); + + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_record_module); + + if (ngx_array_init(&ctx->rec, s->connection->pool, racf->rec.nelts, + sizeof(ngx_rtmp_record_rec_ctx_t)) + != NGX_OK) + { + return NGX_ERROR; + } + + rracf = racf->rec.elts; + + rctx = ngx_array_push_n(&ctx->rec, racf->rec.nelts); + + if (rctx == NULL) { + return NGX_ERROR; + } + + for (n = 0; n < racf->rec.nelts; ++n, ++rracf, ++rctx) { + ngx_memzero(rctx, sizeof(*rctx)); + + rctx->conf = *rracf; + rctx->file.fd = NGX_INVALID_FILE; + } + + return NGX_OK; +} + + +static void +ngx_rtmp_record_start(ngx_rtmp_session_t *s) +{ + ngx_rtmp_record_app_conf_t *racf; + ngx_rtmp_record_rec_ctx_t *rctx; + ngx_rtmp_record_ctx_t *ctx; + ngx_uint_t n; + + racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_record_module); + if (racf == NULL || racf->rec.nelts == 0) { + return; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_record_module); + if (ctx == NULL) { + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "record: start"); + + rctx = ctx->rec.elts; + for (n = 0; n < ctx->rec.nelts; ++n, ++rctx) { + if (rctx->conf->flags & (NGX_RTMP_RECORD_OFF|NGX_RTMP_RECORD_MANUAL)) { + continue; + } + ngx_rtmp_record_node_open(s, rctx); + } +} + + +static void +ngx_rtmp_record_stop(ngx_rtmp_session_t *s) +{ + ngx_rtmp_record_app_conf_t *racf; + ngx_rtmp_record_rec_ctx_t *rctx; + ngx_rtmp_record_ctx_t *ctx; + ngx_uint_t n; + + racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_record_module); + if (racf == NULL || racf->rec.nelts == 0) { + return; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_record_module); + if (ctx == NULL) { + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "record: stop"); + + rctx = ctx->rec.elts; + for (n = 0; n < ctx->rec.nelts; ++n, ++rctx) { + ngx_rtmp_record_node_close(s, rctx); + } +} + + +static ngx_int_t +ngx_rtmp_record_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) +{ + ngx_rtmp_record_app_conf_t *racf; + ngx_rtmp_record_ctx_t *ctx; + u_char *p; + + if (s->auto_pushed) { + goto next; + } + + racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_record_module); + + if (racf == NULL || racf->rec.nelts == 0) { + goto next; + } + + if (ngx_rtmp_record_init(s) != NGX_OK) { + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "record: publish %ui nodes", + racf->rec.nelts); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_record_module); + + ngx_memcpy(ctx->name, v->name, sizeof(ctx->name)); + ngx_memcpy(ctx->args, v->args, sizeof(ctx->args)); + + /* terminate name on /../ */ + for (p = ctx->name; *p; ++p) { + if (ngx_path_separator(p[0]) && + p[1] == '.' && p[2] == '.' && + ngx_path_separator(p[3])) + { + *p = 0; + break; + } + } + + ngx_rtmp_record_start(s); + +next: + return next_publish(s, v); +} + + +static ngx_int_t +ngx_rtmp_record_stream_begin(ngx_rtmp_session_t *s, ngx_rtmp_stream_begin_t *v) +{ + if (s->auto_pushed) { + goto next; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "record: stream_begin"); + + ngx_rtmp_record_start(s); + +next: + return next_stream_begin(s, v); +} + + +static ngx_int_t +ngx_rtmp_record_stream_eof(ngx_rtmp_session_t *s, ngx_rtmp_stream_begin_t *v) +{ + if (s->auto_pushed) { + goto next; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "record: stream_eof"); + + ngx_rtmp_record_stop(s); + +next: + return next_stream_eof(s, v); +} + + +static ngx_int_t +ngx_rtmp_record_node_close(ngx_rtmp_session_t *s, + ngx_rtmp_record_rec_ctx_t *rctx) +{ + ngx_rtmp_record_app_conf_t *rracf; + ngx_err_t err; + void **app_conf; + ngx_int_t rc; + ngx_rtmp_record_done_t v; + u_char av; + + rracf = rctx->conf; + + if (rctx->file.fd == NGX_INVALID_FILE) { + return NGX_AGAIN; + } + + if (rctx->initialized) { + av = 0; + + if (rctx->video) { + av |= 0x01; + } + + if (rctx->audio) { + av |= 0x04; + } + + if (ngx_write_file(&rctx->file, &av, 1, 4) == NGX_ERROR) { + ngx_log_error(NGX_LOG_CRIT, s->connection->log, ngx_errno, + "record: %V error writing av mask", &rracf->id); + } + } + + if (ngx_close_file(rctx->file.fd) == NGX_FILE_ERROR) { + err = ngx_errno; + ngx_log_error(NGX_LOG_CRIT, s->connection->log, err, + "record: %V error closing file", &rracf->id); + + ngx_rtmp_record_notify_error(s, rctx); + } + + rctx->file.fd = NGX_INVALID_FILE; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "record: %V closed", &rracf->id); + + if (rracf->notify) { + ngx_rtmp_send_status(s, "NetStream.Record.Stop", "status", + rracf->id.data ? (char *) rracf->id.data : ""); + } + + app_conf = s->app_conf; + + if (rracf->rec_conf) { + s->app_conf = rracf->rec_conf; + } + + v.recorder = rracf->id; + ngx_rtmp_record_make_path(s, rctx, &v.path); + + rc = ngx_rtmp_record_done(s, &v); + + s->app_conf = app_conf; + + return rc; +} + + +static ngx_int_t +ngx_rtmp_record_close_stream(ngx_rtmp_session_t *s, + ngx_rtmp_close_stream_t *v) +{ + if (s->auto_pushed) { + goto next; + } + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "record: close_stream"); + + ngx_rtmp_record_stop(s); + +next: + return next_close_stream(s, v); +} + + +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) +{ + u_char hdr[11], *p, *ph; + uint32_t timestamp, tag_size; + ngx_rtmp_record_app_conf_t *rracf; + + rracf = rctx->conf; + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "record: %V frame: mlen=%uD", + &rracf->id, h->mlen); + + if (h->type == NGX_RTMP_MSG_VIDEO) { + rctx->video = 1; + } else { + rctx->audio = 1; + } + + timestamp = h->timestamp - rctx->epoch; + + if ((int32_t) timestamp < 0) { + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "record: %V cut timestamp=%D", &rracf->id, timestamp); + + timestamp = 0; + } + + /* write tag header */ + ph = hdr; + + *ph++ = (u_char)h->type; + + p = (u_char*)&h->mlen; + *ph++ = p[2]; + *ph++ = p[1]; + *ph++ = p[0]; + + p = (u_char*)×tamp; + *ph++ = p[2]; + *ph++ = p[1]; + *ph++ = p[0]; + *ph++ = p[3]; + + *ph++ = 0; + *ph++ = 0; + *ph++ = 0; + + tag_size = (ph - hdr) + h->mlen; + + if (ngx_write_file(&rctx->file, hdr, ph - hdr, rctx->file.offset) + == NGX_ERROR) + { + ngx_rtmp_record_notify_error(s, rctx); + + ngx_close_file(rctx->file.fd); + + return NGX_ERROR; + } + + /* write tag body + * FIXME: NGINX + * ngx_write_chain seems to fit best + * but it suffers from uncontrollable + * allocations. + * we're left with plain writing */ + for(; in; in = in->next) { + if (in->buf->pos == in->buf->last) { + continue; + } + + if (ngx_write_file(&rctx->file, in->buf->pos, in->buf->last + - in->buf->pos, rctx->file.offset) + == NGX_ERROR) + { + return NGX_ERROR; + } + } + + /* write tag size */ + ph = hdr; + p = (u_char*)&tag_size; + + *ph++ = p[3]; + *ph++ = p[2]; + *ph++ = p[1]; + *ph++ = p[0]; + + if (ngx_write_file(&rctx->file, hdr, ph - hdr, + rctx->file.offset) + == NGX_ERROR) + { + return NGX_ERROR; + } + + rctx->nframes += inc_nframes; + + /* watch max size */ + if ((rracf->max_size && rctx->file.offset >= (ngx_int_t) rracf->max_size) || + (rracf->max_frames && rctx->nframes >= rracf->max_frames)) + { + ngx_rtmp_record_node_close(s, rctx); + } + + return NGX_OK; +} + + +static size_t +ngx_rtmp_record_get_chain_mlen(ngx_chain_t *in) +{ + size_t ret; + + for (ret = 0; in; in = in->next) { + ret += (in->buf->last - in->buf->pos); + } + + return ret; +} + + +static ngx_int_t +ngx_rtmp_record_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_rtmp_record_ctx_t *ctx; + ngx_rtmp_record_rec_ctx_t *rctx; + ngx_uint_t n; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_record_module); + + if (ctx == NULL) { + return NGX_OK; + } + + rctx = ctx->rec.elts; + + for (n = 0; n < ctx->rec.nelts; ++n, ++rctx) { + ngx_rtmp_record_node_av(s, rctx, h, in); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_record_node_av(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; + ngx_rtmp_header_t ch; + ngx_rtmp_codec_ctx_t *codec_ctx; + ngx_int_t keyframe, brkframe; + ngx_rtmp_record_app_conf_t *rracf; + + rracf = rctx->conf; + + if (rracf->flags & NGX_RTMP_RECORD_OFF) { + ngx_rtmp_record_node_close(s, rctx); + return NGX_OK; + } + + keyframe = (h->type == NGX_RTMP_MSG_VIDEO) + ? (ngx_rtmp_get_video_frame_type(in) == NGX_RTMP_VIDEO_KEY_FRAME) + : 0; + + brkframe = (h->type == NGX_RTMP_MSG_VIDEO) + ? 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) { + return NGX_OK; + } + + if (h->type == NGX_RTMP_MSG_AUDIO && + (rracf->flags & NGX_RTMP_RECORD_AUDIO) == 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)) + { + return NGX_OK; + } + + if (!rctx->initialized) { + + rctx->initialized = 1; + rctx->epoch = h->timestamp - rctx->time_shift; + + if (rctx->file.offset == 0 && + ngx_rtmp_record_write_header(&rctx->file) != NGX_OK) + { + ngx_rtmp_record_node_close(s, rctx); + return NGX_OK; + } + } + + codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + if (codec_ctx) { + ch = *h; + + /* AAC header */ + if (!rctx->aac_header_sent && codec_ctx->aac_header && + (rracf->flags & NGX_RTMP_RECORD_AUDIO)) + { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "record: %V writing AAC header", &rracf->id); + + ch.type = NGX_RTMP_MSG_AUDIO; + ch.mlen = ngx_rtmp_record_get_chain_mlen(codec_ctx->aac_header); + + if (ngx_rtmp_record_write_frame(s, rctx, &ch, + codec_ctx->aac_header, 0) + != NGX_OK) + { + return NGX_OK; + } + + rctx->aac_header_sent = 1; + } + + /* AVC header */ + if (!rctx->avc_header_sent && codec_ctx->avc_header && + (rracf->flags & (NGX_RTMP_RECORD_VIDEO| + NGX_RTMP_RECORD_KEYFRAMES))) + { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "record: %V writing AVC header", &rracf->id); + + ch.type = NGX_RTMP_MSG_VIDEO; + ch.mlen = ngx_rtmp_record_get_chain_mlen(codec_ctx->avc_header); + + if (ngx_rtmp_record_write_frame(s, rctx, &ch, + codec_ctx->avc_header, 0) + != NGX_OK) + { + return NGX_OK; + } + + rctx->avc_header_sent = 1; + } + } + + if (h->type == NGX_RTMP_MSG_VIDEO) { + if (codec_ctx && codec_ctx->video_codec_id == NGX_RTMP_VIDEO_H264 && + !rctx->avc_header_sent) + { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "record: %V skipping until H264 header", &rracf->id); + return NGX_OK; + } + + if (ngx_rtmp_get_video_frame_type(in) == NGX_RTMP_VIDEO_KEY_FRAME && + ((codec_ctx && codec_ctx->video_codec_id != NGX_RTMP_VIDEO_H264) || + !ngx_rtmp_is_codec_header(in))) + { + rctx->video_key_sent = 1; + } + + if (!rctx->video_key_sent) { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "record: %V skipping until keyframe", &rracf->id); + return NGX_OK; + } + + } else { + if (codec_ctx && codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC && + !rctx->aac_header_sent) + { + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "record: %V skipping until AAC header", &rracf->id); + return NGX_OK; + } + } + + return ngx_rtmp_record_write_frame(s, rctx, h, in, 1); +} + + +static ngx_int_t +ngx_rtmp_record_done_init(ngx_rtmp_session_t *s, ngx_rtmp_record_done_t *v) +{ + return NGX_OK; +} + + +static char * +ngx_rtmp_record_recorder(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + char *rv; + ngx_int_t i; + ngx_str_t *value; + ngx_conf_t save; + ngx_rtmp_module_t *module; + ngx_rtmp_core_app_conf_t *cacf, **pcacf, *rcacf; + ngx_rtmp_record_app_conf_t *racf, **pracf, *rracf; + ngx_rtmp_conf_ctx_t *ctx, *pctx; + + value = cf->args->elts; + + cacf = ngx_rtmp_conf_get_module_app_conf(cf, ngx_rtmp_core_module); + + racf = ngx_rtmp_conf_get_module_app_conf(cf, ngx_rtmp_record_module); + + ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_conf_ctx_t)); + if (ctx == NULL) { + return NGX_CONF_ERROR; + } + + pctx = cf->ctx; + + ctx->main_conf = pctx->main_conf; + ctx->srv_conf = pctx->srv_conf; + + ctx->app_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module); + if (ctx->app_conf == NULL) { + return NGX_CONF_ERROR; + } + + for (i = 0; ngx_modules[i]; i++) { + if (ngx_modules[i]->type != NGX_RTMP_MODULE) { + continue; + } + + module = ngx_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) { + return NGX_CONF_ERROR; + } + } + } + + /* add to sub-applications */ + rcacf = ctx->app_conf[ngx_rtmp_core_module.ctx_index]; + rcacf->app_conf = ctx->app_conf; + pcacf = ngx_array_push(&cacf->applications); + if (pcacf == NULL) { + return NGX_CONF_ERROR; + } + *pcacf = rcacf; + + /* add to recorders */ + rracf = ctx->app_conf[ngx_rtmp_record_module.ctx_index]; + rracf->rec_conf = ctx->app_conf; + pracf = ngx_array_push(&racf->rec); + if (pracf == NULL) { + return NGX_CONF_ERROR; + } + *pracf = rracf; + + rracf->id = value[1]; + + + save = *cf; + cf->ctx = ctx; + cf->cmd_type = NGX_RTMP_REC_CONF; + + rv = ngx_conf_parse(cf, NULL); + *cf= save; + + return rv; +} + + +static ngx_int_t +ngx_rtmp_record_postconfiguration(ngx_conf_t *cf) +{ + ngx_rtmp_core_main_conf_t *cmcf; + ngx_rtmp_handler_pt *h; + + 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_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]); + *h = ngx_rtmp_record_av; + + next_publish = ngx_rtmp_publish; + ngx_rtmp_publish = ngx_rtmp_record_publish; + + next_close_stream = ngx_rtmp_close_stream; + ngx_rtmp_close_stream = ngx_rtmp_record_close_stream; + + next_stream_begin = ngx_rtmp_stream_begin; + ngx_rtmp_stream_begin = ngx_rtmp_record_stream_begin; + + next_stream_eof = ngx_rtmp_stream_eof; + ngx_rtmp_stream_eof = ngx_rtmp_record_stream_eof; + + return NGX_OK; +} diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_record_module.h nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_record_module.h --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_record_module.h 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_record_module.h 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,96 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_RECORD_H_INCLUDED_ +#define _NGX_RTMP_RECORD_H_INCLUDED_ + + +#include +#include +#include "ngx_rtmp.h" + + +#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 + + +typedef struct { + ngx_str_t id; + ngx_uint_t flags; + ngx_str_t path; + size_t max_size; + size_t max_frames; + ngx_msec_t interval; + ngx_str_t suffix; + ngx_flag_t unique; + ngx_flag_t append; + ngx_flag_t lock_file; + ngx_flag_t notify; + ngx_url_t *url; + + void **rec_conf; + ngx_array_t rec; /* ngx_rtmp_record_app_conf_t * */ +} ngx_rtmp_record_app_conf_t; + + +typedef struct { + ngx_rtmp_record_app_conf_t *conf; + ngx_file_t file; + ngx_uint_t nframes; + uint32_t epoch, time_shift; + ngx_time_t last; + time_t timestamp; + unsigned failed:1; + unsigned initialized:1; + unsigned aac_header_sent:1; + unsigned avc_header_sent:1; + unsigned video_key_sent:1; + unsigned audio:1; + unsigned video:1; +} ngx_rtmp_record_rec_ctx_t; + + +typedef struct { + ngx_array_t rec; /* ngx_rtmp_record_rec_ctx_t */ + u_char name[NGX_RTMP_MAX_NAME]; + u_char args[NGX_RTMP_MAX_ARGS]; +} ngx_rtmp_record_ctx_t; + + +ngx_uint_t ngx_rtmp_record_find(ngx_rtmp_record_app_conf_t *racf, + ngx_str_t *id); + + +/* Manual recording control, + * 'n' is record node index in config array. + * Note: these functions allocate path in static buffer */ + +ngx_int_t ngx_rtmp_record_open(ngx_rtmp_session_t *s, ngx_uint_t n, + ngx_str_t *path); +ngx_int_t ngx_rtmp_record_close(ngx_rtmp_session_t *s, ngx_uint_t n, + ngx_str_t *path); + + +typedef struct { + ngx_str_t recorder; + ngx_str_t path; +} ngx_rtmp_record_done_t; + + +typedef ngx_int_t (*ngx_rtmp_record_done_pt)(ngx_rtmp_session_t *s, + ngx_rtmp_record_done_t *v); + + +extern ngx_rtmp_record_done_pt ngx_rtmp_record_done; + + +extern ngx_module_t ngx_rtmp_record_module; + + +#endif /* _NGX_RTMP_RECORD_H_INCLUDED_ */ diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_relay_module.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_relay_module.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_relay_module.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_relay_module.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,1690 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp_relay_module.h" +#include "ngx_rtmp_cmd_module.h" + + +static ngx_rtmp_publish_pt next_publish; +static ngx_rtmp_play_pt next_play; +static ngx_rtmp_delete_stream_pt next_delete_stream; +static ngx_rtmp_close_stream_pt next_close_stream; + + +static ngx_int_t ngx_rtmp_relay_init_process(ngx_cycle_t *cycle); +static ngx_int_t ngx_rtmp_relay_postconfiguration(ngx_conf_t *cf); +static void * ngx_rtmp_relay_create_app_conf(ngx_conf_t *cf); +static char * ngx_rtmp_relay_merge_app_conf(ngx_conf_t *cf, + void *parent, void *child); +static char * ngx_rtmp_relay_push_pull(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static ngx_int_t ngx_rtmp_relay_publish(ngx_rtmp_session_t *s, + ngx_rtmp_publish_t *v); +static ngx_rtmp_relay_ctx_t * ngx_rtmp_relay_create_connection( + ngx_rtmp_conf_ctx_t *cctx, ngx_str_t* name, + ngx_rtmp_relay_target_t *target); + + +/* _____ + * =push= | |---publish---> + * ---publish--->| |---publish---> + * (src) | |---publish---> + * ----- (next,relay) + * need reconnect + * =pull= _____ + * -----play---->| | + * -----play---->| |----play-----> + * -----play---->| | (src,relay) + * (next) ----- + */ + + +typedef struct { + ngx_array_t pulls; /* ngx_rtmp_relay_target_t * */ + ngx_array_t pushes; /* ngx_rtmp_relay_target_t * */ + ngx_array_t static_pulls; /* ngx_rtmp_relay_target_t * */ + ngx_array_t static_events; /* ngx_event_t * */ + ngx_log_t *log; + ngx_uint_t nbuckets; + ngx_msec_t buflen; + ngx_flag_t session_relay; + ngx_msec_t push_reconnect; + ngx_msec_t pull_reconnect; + ngx_rtmp_relay_ctx_t **ctx; +} ngx_rtmp_relay_app_conf_t; + + +typedef struct { + ngx_rtmp_conf_ctx_t cctx; + ngx_rtmp_relay_target_t *target; +} ngx_rtmp_relay_static_t; + + +#define NGX_RTMP_RELAY_CONNECT_TRANS 1 +#define NGX_RTMP_RELAY_CREATE_STREAM_TRANS 2 + + +#define NGX_RTMP_RELAY_CSID_AMF_INI 3 +#define NGX_RTMP_RELAY_CSID_AMF 5 +#define NGX_RTMP_RELAY_MSID 1 + + +/* default flashVer */ +#define NGX_RTMP_RELAY_FLASHVER "LNX.11,1,102,55" + + +static ngx_command_t ngx_rtmp_relay_commands[] = { + + { ngx_string("push"), + NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_relay_push_pull, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("pull"), + NGX_RTMP_APP_CONF|NGX_CONF_1MORE, + ngx_rtmp_relay_push_pull, + NGX_RTMP_APP_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("relay_buffer"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_relay_app_conf_t, buflen), + NULL }, + + { ngx_string("push_reconnect"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_relay_app_conf_t, push_reconnect), + NULL }, + + { ngx_string("pull_reconnect"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_relay_app_conf_t, pull_reconnect), + NULL }, + + { ngx_string("session_relay"), + NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_RTMP_APP_CONF_OFFSET, + offsetof(ngx_rtmp_relay_app_conf_t, session_relay), + NULL }, + + + ngx_null_command +}; + + +static ngx_rtmp_module_t ngx_rtmp_relay_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_relay_postconfiguration, /* postconfiguration */ + NULL, /* create main configuration */ + NULL, /* init main configuration */ + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + ngx_rtmp_relay_create_app_conf, /* create app configuration */ + ngx_rtmp_relay_merge_app_conf /* merge app configuration */ +}; + + +ngx_module_t ngx_rtmp_relay_module = { + NGX_MODULE_V1, + &ngx_rtmp_relay_module_ctx, /* module context */ + ngx_rtmp_relay_commands, /* module directives */ + NGX_RTMP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + ngx_rtmp_relay_init_process, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_rtmp_relay_create_app_conf(ngx_conf_t *cf) +{ + ngx_rtmp_relay_app_conf_t *racf; + + racf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_relay_app_conf_t)); + if (racf == NULL) { + return NULL; + } + + if (ngx_array_init(&racf->pushes, cf->pool, 1, sizeof(void *)) != NGX_OK) { + return NULL; + } + + if (ngx_array_init(&racf->pulls, cf->pool, 1, sizeof(void *)) != NGX_OK) { + return NULL; + } + + if (ngx_array_init(&racf->static_pulls, cf->pool, 1, sizeof(void *)) + != NGX_OK) + { + return NULL; + } + + if (ngx_array_init(&racf->static_events, cf->pool, 1, sizeof(void *)) + != NGX_OK) + { + return NULL; + } + + racf->nbuckets = 1024; + racf->log = &cf->cycle->new_log; + racf->buflen = NGX_CONF_UNSET_MSEC; + racf->session_relay = NGX_CONF_UNSET; + racf->push_reconnect = NGX_CONF_UNSET_MSEC; + racf->pull_reconnect = NGX_CONF_UNSET_MSEC; + + return racf; +} + + +static char * +ngx_rtmp_relay_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_relay_app_conf_t *prev = parent; + ngx_rtmp_relay_app_conf_t *conf = child; + + conf->ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_relay_ctx_t *) + * conf->nbuckets); + + ngx_conf_merge_value(conf->session_relay, prev->session_relay, 0); + ngx_conf_merge_msec_value(conf->buflen, prev->buflen, 5000); + ngx_conf_merge_msec_value(conf->push_reconnect, prev->push_reconnect, + 3000); + ngx_conf_merge_msec_value(conf->pull_reconnect, prev->pull_reconnect, + 3000); + + return NGX_CONF_OK; +} + + +static void +ngx_rtmp_relay_static_pull_reconnect(ngx_event_t *ev) +{ + ngx_rtmp_relay_static_t *rs = ev->data; + + ngx_rtmp_relay_ctx_t *ctx; + ngx_rtmp_relay_app_conf_t *racf; + + racf = ngx_rtmp_get_module_app_conf(&rs->cctx, ngx_rtmp_relay_module); + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, racf->log, 0, + "relay: reconnecting static pull"); + + ctx = ngx_rtmp_relay_create_connection(&rs->cctx, &rs->target->name, + rs->target); + if (ctx) { + ctx->session->static_relay = 1; + ctx->static_evt = ev; + return; + } + + ngx_add_timer(ev, racf->pull_reconnect); +} + + +static void +ngx_rtmp_relay_push_reconnect(ngx_event_t *ev) +{ + ngx_rtmp_session_t *s = ev->data; + + ngx_rtmp_relay_app_conf_t *racf; + ngx_rtmp_relay_ctx_t *ctx, *pctx; + ngx_uint_t n; + ngx_rtmp_relay_target_t *target, **t; + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "relay: push reconnect"); + + racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_relay_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module); + if (ctx == NULL) { + return; + } + + t = racf->pushes.elts; + for (n = 0; n < racf->pushes.nelts; ++n, ++t) { + target = *t; + + if (target->name.len && (ctx->name.len != target->name.len || + ngx_memcmp(ctx->name.data, target->name.data, ctx->name.len))) + { + continue; + } + + for (pctx = ctx->play; pctx; pctx = pctx->next) { + if (pctx->tag == &ngx_rtmp_relay_module && + pctx->data == target) + { + break; + } + } + + if (pctx) { + continue; + } + + if (ngx_rtmp_relay_push(s, &ctx->name, target) == NGX_OK) { + continue; + } + + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "relay: push reconnect failed name='%V' app='%V' " + "playpath='%V' url='%V'", + &ctx->name, &target->app, &target->play_path, + &target->url.url); + + if (!ctx->push_evt.timer_set) { + ngx_add_timer(&ctx->push_evt, racf->push_reconnect); + } + } +} + + +static ngx_int_t +ngx_rtmp_relay_get_peer(ngx_peer_connection_t *pc, void *data) +{ + return NGX_OK; +} + + +static void +ngx_rtmp_relay_free_peer(ngx_peer_connection_t *pc, void *data, + ngx_uint_t state) +{ +} + + +typedef ngx_rtmp_relay_ctx_t * (* ngx_rtmp_relay_create_ctx_pt) + (ngx_rtmp_session_t *s, ngx_str_t *name, ngx_rtmp_relay_target_t *target); + + +static ngx_int_t +ngx_rtmp_relay_copy_str(ngx_pool_t *pool, ngx_str_t *dst, ngx_str_t *src) +{ + if (src->len == 0) { + return NGX_OK; + } + dst->len = src->len; + dst->data = ngx_palloc(pool, src->len); + if (dst->data == NULL) { + return NGX_ERROR; + } + ngx_memcpy(dst->data, src->data, src->len); + return NGX_OK; +} + + +static ngx_rtmp_relay_ctx_t * +ngx_rtmp_relay_create_connection(ngx_rtmp_conf_ctx_t *cctx, ngx_str_t* name, + ngx_rtmp_relay_target_t *target) +{ + ngx_rtmp_relay_app_conf_t *racf; + ngx_rtmp_relay_ctx_t *rctx; + ngx_rtmp_addr_conf_t *addr_conf; + ngx_rtmp_conf_ctx_t *addr_ctx; + ngx_rtmp_session_t *rs; + ngx_peer_connection_t *pc; + ngx_connection_t *c; + ngx_addr_t *addr; + ngx_pool_t *pool; + ngx_int_t rc; + ngx_str_t v, *uri; + u_char *first, *last, *p; + + racf = ngx_rtmp_get_module_app_conf(cctx, ngx_rtmp_relay_module); + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, racf->log, 0, + "relay: create remote context"); + + pool = NULL; + pool = ngx_create_pool(4096, racf->log); + if (pool == NULL) { + return NULL; + } + + rctx = ngx_pcalloc(pool, sizeof(ngx_rtmp_relay_ctx_t)); + if (rctx == NULL) { + goto clear; + } + + if (name && ngx_rtmp_relay_copy_str(pool, &rctx->name, name) != NGX_OK) { + goto clear; + } + + if (ngx_rtmp_relay_copy_str(pool, &rctx->url, &target->url.url) != NGX_OK) { + goto clear; + } + + rctx->tag = target->tag; + rctx->data = target->data; + +#define NGX_RTMP_RELAY_STR_COPY(to, from) \ + if (ngx_rtmp_relay_copy_str(pool, &rctx->to, &target->from) != NGX_OK) { \ + goto clear; \ + } + + NGX_RTMP_RELAY_STR_COPY(app, app); + NGX_RTMP_RELAY_STR_COPY(tc_url, tc_url); + NGX_RTMP_RELAY_STR_COPY(page_url, page_url); + NGX_RTMP_RELAY_STR_COPY(swf_url, swf_url); + NGX_RTMP_RELAY_STR_COPY(flash_ver, flash_ver); + NGX_RTMP_RELAY_STR_COPY(play_path, play_path); + + rctx->live = target->live; + rctx->start = target->start; + rctx->stop = target->stop; + +#undef NGX_RTMP_RELAY_STR_COPY + + if (rctx->app.len == 0 || rctx->play_path.len == 0) { + /* parse uri */ + uri = &target->url.uri; + first = uri->data; + last = uri->data + uri->len; + if (first != last && *first == '/') { + ++first; + } + + if (first != last) { + + /* deduce app */ + p = ngx_strlchr(first, last, '/'); + if (p == NULL) { + p = last; + } + + if (rctx->app.len == 0 && first != p) { + v.data = first; + v.len = p - first; + if (ngx_rtmp_relay_copy_str(pool, &rctx->app, &v) != NGX_OK) { + goto clear; + } + } + + /* deduce play_path */ + if (p != last) { + ++p; + } + + if (rctx->play_path.len == 0 && p != last) { + v.data = p; + v.len = last - p; + if (ngx_rtmp_relay_copy_str(pool, &rctx->play_path, &v) + != NGX_OK) + { + goto clear; + } + } + } + } + + pc = ngx_pcalloc(pool, sizeof(ngx_peer_connection_t)); + if (pc == NULL) { + goto clear; + } + + if (target->url.naddrs == 0) { + ngx_log_error(NGX_LOG_ERR, racf->log, 0, + "relay: no address"); + goto clear; + } + + /* get address */ + addr = &target->url.addrs[target->counter % target->url.naddrs]; + target->counter++; + + /* copy log to keep shared log unchanged */ + rctx->log = *racf->log; + pc->log = &rctx->log; + pc->get = ngx_rtmp_relay_get_peer; + pc->free = ngx_rtmp_relay_free_peer; + pc->name = &addr->name; + pc->socklen = addr->socklen; + pc->sockaddr = (struct sockaddr *)ngx_palloc(pool, pc->socklen); + if (pc->sockaddr == NULL) { + goto clear; + } + ngx_memcpy(pc->sockaddr, addr->sockaddr, pc->socklen); + + rc = ngx_event_connect_peer(pc); + if (rc != NGX_OK && rc != NGX_AGAIN ) { + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, racf->log, 0, + "relay: connection failed"); + goto clear; + } + c = pc->connection; + c->pool = pool; + c->addr_text = rctx->url; + + addr_conf = ngx_pcalloc(pool, sizeof(ngx_rtmp_addr_conf_t)); + if (addr_conf == NULL) { + goto clear; + } + addr_ctx = ngx_pcalloc(pool, sizeof(ngx_rtmp_conf_ctx_t)); + if (addr_ctx == NULL) { + goto clear; + } + addr_conf->ctx = addr_ctx; + addr_ctx->main_conf = cctx->main_conf; + addr_ctx->srv_conf = cctx->srv_conf; + ngx_str_set(&addr_conf->addr_text, "ngx-relay"); + + rs = ngx_rtmp_init_session(c, addr_conf); + if (rs == NULL) { + /* no need to destroy pool */ + return NULL; + } + rs->app_conf = cctx->app_conf; + rs->relay = 1; + rctx->session = rs; + ngx_rtmp_set_ctx(rs, rctx, ngx_rtmp_relay_module); + ngx_str_set(&rs->flashver, "ngx-local-relay"); + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, 1); +#endif + + ngx_rtmp_client_handshake(rs, 1); + return rctx; + +clear: + if (pool) { + ngx_destroy_pool(pool); + } + return NULL; +} + + +static ngx_rtmp_relay_ctx_t * +ngx_rtmp_relay_create_remote_ctx(ngx_rtmp_session_t *s, ngx_str_t* name, + ngx_rtmp_relay_target_t *target) +{ + ngx_rtmp_conf_ctx_t cctx; + + cctx.app_conf = s->app_conf; + cctx.srv_conf = s->srv_conf; + cctx.main_conf = s->main_conf; + + return ngx_rtmp_relay_create_connection(&cctx, name, target); +} + + +static ngx_rtmp_relay_ctx_t * +ngx_rtmp_relay_create_local_ctx(ngx_rtmp_session_t *s, ngx_str_t *name, + ngx_rtmp_relay_target_t *target) +{ + ngx_rtmp_relay_ctx_t *ctx; + + ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "relay: create local context"); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module); + if (ctx == NULL) { + ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_rtmp_relay_ctx_t)); + if (ctx == NULL) { + return NULL; + } + ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_relay_module); + } + ctx->session = s; + + ctx->push_evt.data = s; + ctx->push_evt.log = s->connection->log; + ctx->push_evt.handler = ngx_rtmp_relay_push_reconnect; + + if (ctx->publish) { + return NULL; + } + + if (ngx_rtmp_relay_copy_str(s->connection->pool, &ctx->name, name) + != NGX_OK) + { + return NULL; + } + + return ctx; +} + + +static ngx_int_t +ngx_rtmp_relay_create(ngx_rtmp_session_t *s, ngx_str_t *name, + ngx_rtmp_relay_target_t *target, + ngx_rtmp_relay_create_ctx_pt create_publish_ctx, + ngx_rtmp_relay_create_ctx_pt create_play_ctx) +{ + ngx_rtmp_relay_app_conf_t *racf; + ngx_rtmp_relay_ctx_t *publish_ctx, *play_ctx, **cctx; + ngx_uint_t hash; + + + racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_relay_module); + if (racf == NULL) { + return NGX_ERROR; + } + + play_ctx = create_play_ctx(s, name, target); + if (play_ctx == NULL) { + return NGX_ERROR; + } + + hash = ngx_hash_key(name->data, name->len); + cctx = &racf->ctx[hash % racf->nbuckets]; + for (; *cctx; cctx = &(*cctx)->next) { + if ((*cctx)->name.len == name->len + && !ngx_memcmp(name->data, (*cctx)->name.data, + name->len)) + { + break; + } + } + + if (*cctx) { + play_ctx->publish = (*cctx)->publish; + play_ctx->next = (*cctx)->play; + (*cctx)->play = play_ctx; + return NGX_OK; + } + + publish_ctx = create_publish_ctx(s, name, target); + if (publish_ctx == NULL) { + ngx_rtmp_finalize_session(play_ctx->session); + return NGX_ERROR; + } + + publish_ctx->publish = publish_ctx; + publish_ctx->play = play_ctx; + play_ctx->publish = publish_ctx; + *cctx = publish_ctx; + + return NGX_OK; +} + + +ngx_int_t +ngx_rtmp_relay_pull(ngx_rtmp_session_t *s, ngx_str_t *name, + ngx_rtmp_relay_target_t *target) +{ + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "relay: create pull name='%V' app='%V' playpath='%V' url='%V'", + name, &target->app, &target->play_path, &target->url.url); + + return ngx_rtmp_relay_create(s, name, target, + ngx_rtmp_relay_create_remote_ctx, + ngx_rtmp_relay_create_local_ctx); +} + + +ngx_int_t +ngx_rtmp_relay_push(ngx_rtmp_session_t *s, ngx_str_t *name, + ngx_rtmp_relay_target_t *target) +{ + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "relay: create push name='%V' app='%V' playpath='%V' url='%V'", + name, &target->app, &target->play_path, &target->url.url); + + return ngx_rtmp_relay_create(s, name, target, + ngx_rtmp_relay_create_local_ctx, + ngx_rtmp_relay_create_remote_ctx); +} + + +static ngx_int_t +ngx_rtmp_relay_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) +{ + ngx_rtmp_relay_app_conf_t *racf; + ngx_rtmp_relay_target_t *target, **t; + ngx_str_t name; + size_t n; + ngx_rtmp_relay_ctx_t *ctx; + + if (s->auto_pushed) { + goto next; + } + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module); + if (ctx && s->relay) { + goto next; + } + + racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_relay_module); + if (racf == NULL || racf->pushes.nelts == 0) { + goto next; + } + + name.len = ngx_strlen(v->name); + name.data = v->name; + + t = racf->pushes.elts; + for (n = 0; n < racf->pushes.nelts; ++n, ++t) { + target = *t; + + if (target->name.len && (name.len != target->name.len || + ngx_memcmp(name.data, target->name.data, name.len))) + { + continue; + } + + if (ngx_rtmp_relay_push(s, &name, target) == NGX_OK) { + continue; + } + + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "relay: push failed name='%V' app='%V' " + "playpath='%V' url='%V'", + &name, &target->app, &target->play_path, + &target->url.url); + + if (!ctx->push_evt.timer_set) { + ngx_add_timer(&ctx->push_evt, racf->push_reconnect); + } + } + +next: + return next_publish(s, v); +} + + +static ngx_int_t +ngx_rtmp_relay_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) +{ + ngx_rtmp_relay_app_conf_t *racf; + ngx_rtmp_relay_target_t *target, **t; + ngx_str_t name; + size_t n; + ngx_rtmp_relay_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module); + if (ctx && s->relay) { + goto next; + } + + racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_relay_module); + if (racf == NULL || racf->pulls.nelts == 0) { + goto next; + } + + name.len = ngx_strlen(v->name); + name.data = v->name; + + t = racf->pulls.elts; + for (n = 0; n < racf->pulls.nelts; ++n, ++t) { + target = *t; + + if (target->name.len && (name.len != target->name.len || + ngx_memcmp(name.data, target->name.data, name.len))) + { + continue; + } + + if (ngx_rtmp_relay_pull(s, &name, target) == NGX_OK) { + continue; + } + + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "relay: pull failed name='%V' app='%V' " + "playpath='%V' url='%V'", + &name, &target->app, &target->play_path, + &target->url.url); + } + +next: + return next_play(s, v); +} + + +static ngx_int_t +ngx_rtmp_relay_play_local(ngx_rtmp_session_t *s) +{ + ngx_rtmp_play_t v; + ngx_rtmp_relay_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module); + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_memzero(&v, sizeof(ngx_rtmp_play_t)); + v.silent = 1; + *(ngx_cpymem(v.name, ctx->name.data, + ngx_min(sizeof(v.name) - 1, ctx->name.len))) = 0; + + return ngx_rtmp_play(s, &v); +} + + +static ngx_int_t +ngx_rtmp_relay_publish_local(ngx_rtmp_session_t *s) +{ + ngx_rtmp_publish_t v; + ngx_rtmp_relay_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module); + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_memzero(&v, sizeof(ngx_rtmp_publish_t)); + v.silent = 1; + *(ngx_cpymem(v.name, ctx->name.data, + ngx_min(sizeof(v.name) - 1, ctx->name.len))) = 0; + + return ngx_rtmp_publish(s, &v); +} + + +static ngx_int_t +ngx_rtmp_relay_send_connect(ngx_rtmp_session_t *s) +{ + static double trans = NGX_RTMP_RELAY_CONNECT_TRANS; + static double acodecs = 3575; + static double vcodecs = 252; + + static ngx_rtmp_amf_elt_t out_cmd[] = { + + { NGX_RTMP_AMF_STRING, + ngx_string("app"), + NULL, 0 }, /* <-- fill */ + + { NGX_RTMP_AMF_STRING, + ngx_string("tcUrl"), + NULL, 0 }, /* <-- fill */ + + { NGX_RTMP_AMF_STRING, + ngx_string("pageUrl"), + NULL, 0 }, /* <-- fill */ + + { NGX_RTMP_AMF_STRING, + ngx_string("swfUrl"), + NULL, 0 }, /* <-- fill */ + + { NGX_RTMP_AMF_STRING, + ngx_string("flashVer"), + NULL, 0 }, /* <-- fill */ + + { NGX_RTMP_AMF_NUMBER, + ngx_string("audioCodecs"), + &acodecs, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("videoCodecs"), + &vcodecs, 0 } + }; + + static ngx_rtmp_amf_elt_t out_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + "connect", 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &trans, 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + out_cmd, sizeof(out_cmd) } + }; + + ngx_rtmp_core_app_conf_t *cacf; + ngx_rtmp_core_srv_conf_t *cscf; + ngx_rtmp_relay_ctx_t *ctx; + ngx_rtmp_header_t h; + size_t len, url_len; + u_char *p, *url_end; + + + cacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_core_module); + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module); + if (cacf == NULL || ctx == NULL) { + return NGX_ERROR; + } + + /* app */ + if (ctx->app.len) { + out_cmd[0].data = ctx->app.data; + out_cmd[0].len = ctx->app.len; + } else { + out_cmd[0].data = cacf->name.data; + out_cmd[0].len = cacf->name.len; + } + + /* tcUrl */ + if (ctx->tc_url.len) { + out_cmd[1].data = ctx->tc_url.data; + out_cmd[1].len = ctx->tc_url.len; + } else { + len = sizeof("rtmp://") - 1 + ctx->url.len + + sizeof("/") - 1 + ctx->app.len; + p = ngx_palloc(s->connection->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + out_cmd[1].data = p; + p = ngx_cpymem(p, "rtmp://", sizeof("rtmp://") - 1); + + url_len = ctx->url.len; + url_end = ngx_strlchr(ctx->url.data, ctx->url.data + ctx->url.len, '/'); + if (url_end) { + url_len = (size_t) (url_end - ctx->url.data); + } + + p = ngx_cpymem(p, ctx->url.data, url_len); + *p++ = '/'; + p = ngx_cpymem(p, ctx->app.data, ctx->app.len); + out_cmd[1].len = p - (u_char *)out_cmd[1].data; + } + + /* pageUrl */ + out_cmd[2].data = ctx->page_url.data; + out_cmd[2].len = ctx->page_url.len; + + /* swfUrl */ + out_cmd[3].data = ctx->swf_url.data; + out_cmd[3].len = ctx->swf_url.len; + + /* flashVer */ + if (ctx->flash_ver.len) { + out_cmd[4].data = ctx->flash_ver.data; + out_cmd[4].len = ctx->flash_ver.len; + } else { + out_cmd[4].data = NGX_RTMP_RELAY_FLASHVER; + out_cmd[4].len = sizeof(NGX_RTMP_RELAY_FLASHVER) - 1; + } + + ngx_memzero(&h, sizeof(h)); + h.csid = NGX_RTMP_RELAY_CSID_AMF_INI; + h.type = NGX_RTMP_MSG_AMF_CMD; + + return ngx_rtmp_send_chunk_size(s, cscf->chunk_size) != NGX_OK + || ngx_rtmp_send_ack_size(s, cscf->ack_window) != NGX_OK + || ngx_rtmp_send_amf(s, &h, out_elts, + sizeof(out_elts) / sizeof(out_elts[0])) != NGX_OK + ? NGX_ERROR + : NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_relay_send_create_stream(ngx_rtmp_session_t *s) +{ + static double trans = NGX_RTMP_RELAY_CREATE_STREAM_TRANS; + + static ngx_rtmp_amf_elt_t out_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + "createStream", 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &trans, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 } + }; + + ngx_rtmp_header_t h; + + + ngx_memzero(&h, sizeof(h)); + h.csid = NGX_RTMP_RELAY_CSID_AMF_INI; + h.type = NGX_RTMP_MSG_AMF_CMD; + + return ngx_rtmp_send_amf(s, &h, out_elts, + sizeof(out_elts) / sizeof(out_elts[0])); +} + + +static ngx_int_t +ngx_rtmp_relay_send_publish(ngx_rtmp_session_t *s) +{ + static double trans; + + static ngx_rtmp_amf_elt_t out_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + "publish", 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &trans, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + NULL, 0 }, /* <- to fill */ + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + "live", 0 } + }; + + ngx_rtmp_header_t h; + ngx_rtmp_relay_ctx_t *ctx; + + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module); + if (ctx == NULL) { + return NGX_ERROR; + } + + if (ctx->play_path.len) { + out_elts[3].data = ctx->play_path.data; + out_elts[3].len = ctx->play_path.len; + } else { + out_elts[3].data = ctx->name.data; + out_elts[3].len = ctx->name.len; + } + + ngx_memzero(&h, sizeof(h)); + h.csid = NGX_RTMP_RELAY_CSID_AMF; + h.msid = NGX_RTMP_RELAY_MSID; + h.type = NGX_RTMP_MSG_AMF_CMD; + + return ngx_rtmp_send_amf(s, &h, out_elts, + sizeof(out_elts) / sizeof(out_elts[0])); +} + + +static ngx_int_t +ngx_rtmp_relay_send_play(ngx_rtmp_session_t *s) +{ + static double trans; + static double start, duration; + + static ngx_rtmp_amf_elt_t out_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + "play", 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &trans, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + NULL, 0 }, /* <- fill */ + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &start, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &duration, 0 }, + }; + + ngx_rtmp_header_t h; + ngx_rtmp_relay_ctx_t *ctx; + ngx_rtmp_relay_app_conf_t *racf; + + + racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_relay_module); + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module); + if (racf == NULL || ctx == NULL) { + return NGX_ERROR; + } + + if (ctx->play_path.len) { + out_elts[3].data = ctx->play_path.data; + out_elts[3].len = ctx->play_path.len; + } else { + out_elts[3].data = ctx->name.data; + out_elts[3].len = ctx->name.len; + } + + if (ctx->live) { + start = -1000; + duration = -1000; + } else { + start = (ctx->start ? ctx->start : -2000); + duration = (ctx->stop ? ctx->stop - ctx->start : -1000); + } + + ngx_memzero(&h, sizeof(h)); + h.csid = NGX_RTMP_RELAY_CSID_AMF; + h.msid = NGX_RTMP_RELAY_MSID; + h.type = NGX_RTMP_MSG_AMF_CMD; + + return ngx_rtmp_send_amf(s, &h, out_elts, + sizeof(out_elts) / sizeof(out_elts[0])) != NGX_OK + || ngx_rtmp_send_set_buflen(s, NGX_RTMP_RELAY_MSID, + racf->buflen) != NGX_OK + ? NGX_ERROR + : NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_relay_on_result(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]; + u_char code[128]; + u_char desc[1024]; + } v; + + static ngx_rtmp_amf_elt_t in_inf[] = { + + { NGX_RTMP_AMF_STRING, + ngx_string("level"), + &v.level, sizeof(v.level) }, + + { NGX_RTMP_AMF_STRING, + ngx_string("code"), + &v.code, sizeof(v.code) }, + + { NGX_RTMP_AMF_STRING, + ngx_string("description"), + &v.desc, sizeof(v.desc) }, + }; + + static ngx_rtmp_amf_elt_t in_elts[] = { + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &v.trans, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + in_inf, sizeof(in_inf) }, + }; + + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module); + if (ctx == NULL || !s->relay) { + return NGX_OK; + } + + ngx_memzero(&v, sizeof(v)); + if (ngx_rtmp_receive_amf(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0]))) + { + return NGX_ERROR; + } + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "relay: _result: level='%s' code='%s' description='%s'", + v.level, v.code, v.desc); + + switch ((ngx_int_t)v.trans) { + case NGX_RTMP_RELAY_CONNECT_TRANS: + return ngx_rtmp_relay_send_create_stream(s); + + case NGX_RTMP_RELAY_CREATE_STREAM_TRANS: + if (ctx->publish != ctx && !s->static_relay) { + if (ngx_rtmp_relay_send_publish(s) != NGX_OK) { + return NGX_ERROR; + } + return ngx_rtmp_relay_play_local(s); + + } else { + if (ngx_rtmp_relay_send_play(s) != NGX_OK) { + return NGX_ERROR; + } + return ngx_rtmp_relay_publish_local(s); + } + + default: + return NGX_OK; + } +} + + +static ngx_int_t +ngx_rtmp_relay_on_error(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]; + u_char code[128]; + u_char desc[1024]; + } v; + + static ngx_rtmp_amf_elt_t in_inf[] = { + + { NGX_RTMP_AMF_STRING, + ngx_string("level"), + &v.level, sizeof(v.level) }, + + { NGX_RTMP_AMF_STRING, + ngx_string("code"), + &v.code, sizeof(v.code) }, + + { NGX_RTMP_AMF_STRING, + ngx_string("description"), + &v.desc, sizeof(v.desc) }, + }; + + static ngx_rtmp_amf_elt_t in_elts[] = { + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &v.trans, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + in_inf, sizeof(in_inf) }, + }; + + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module); + if (ctx == NULL || !s->relay) { + return NGX_OK; + } + + ngx_memzero(&v, sizeof(v)); + if (ngx_rtmp_receive_amf(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0]))) + { + return NGX_ERROR; + } + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "relay: _error: level='%s' code='%s' description='%s'", + v.level, v.code, v.desc); + + 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]; + u_char code[128]; + u_char desc[1024]; + } v; + + static ngx_rtmp_amf_elt_t in_inf[] = { + + { NGX_RTMP_AMF_STRING, + ngx_string("level"), + &v.level, sizeof(v.level) }, + + { NGX_RTMP_AMF_STRING, + ngx_string("code"), + &v.code, sizeof(v.code) }, + + { NGX_RTMP_AMF_STRING, + ngx_string("description"), + &v.desc, sizeof(v.desc) }, + }; + + static ngx_rtmp_amf_elt_t in_elts[] = { + + { NGX_RTMP_AMF_NUMBER, + ngx_null_string, + &v.trans, 0 }, + + { NGX_RTMP_AMF_NULL, + ngx_null_string, + NULL, 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + in_inf, sizeof(in_inf) }, + }; + + static ngx_rtmp_amf_elt_t in_elts_meta[] = { + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + in_inf, sizeof(in_inf) }, + }; + + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module); + if (ctx == NULL || !s->relay) { + return NGX_OK; + } + + ngx_memzero(&v, sizeof(v)); + if (h->type == NGX_RTMP_MSG_AMF_META) { + ngx_rtmp_receive_amf(s, in, in_elts_meta, + sizeof(in_elts_meta) / sizeof(in_elts_meta[0])); + } else { + ngx_rtmp_receive_amf(s, in, in_elts, + sizeof(in_elts) / sizeof(in_elts[0])); + } + + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "relay: onStatus: level='%s' code='%s' description='%s'", + v.level, v.code, v.desc); + + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_relay_handshake_done(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_chain_t *in) +{ + ngx_rtmp_relay_ctx_t *ctx; + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module); + if (ctx == NULL || !s->relay) { + return NGX_OK; + } + + return ngx_rtmp_relay_send_connect(s); +} + + +static void +ngx_rtmp_relay_close(ngx_rtmp_session_t *s) +{ + ngx_rtmp_relay_app_conf_t *racf; + ngx_rtmp_relay_ctx_t *ctx, **cctx; + ngx_uint_t hash; + + racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_relay_module); + + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module); + if (ctx == NULL) { + return; + } + + if (s->static_relay) { + ngx_add_timer(ctx->static_evt, racf->pull_reconnect); + } + + if (ctx->publish == NULL) { + return; + } + + /* play end disconnect? */ + if (ctx->publish != ctx) { + for (cctx = &ctx->publish->play; *cctx; cctx = &(*cctx)->next) { + if (*cctx == ctx) { + *cctx = ctx->next; + break; + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ctx->session->connection->log, 0, + "relay: play disconnect app='%V' name='%V'", + &ctx->app, &ctx->name); + + /* push reconnect */ + if (s->relay && ctx->tag == &ngx_rtmp_relay_module && + !ctx->publish->push_evt.timer_set) + { + ngx_add_timer(&ctx->publish->push_evt, racf->push_reconnect); + } + +#ifdef NGX_DEBUG + { + ngx_uint_t n = 0; + for (cctx = &ctx->publish->play; *cctx; cctx = &(*cctx)->next, ++n); + ngx_log_debug3(NGX_LOG_DEBUG_RTMP, ctx->session->connection->log, 0, + "relay: play left after disconnect app='%V' name='%V': %ui", + &ctx->app, &ctx->name, n); + } +#endif + + if (ctx->publish->play == NULL && ctx->publish->session->relay) { + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, + ctx->publish->session->connection->log, 0, + "relay: publish disconnect empty app='%V' name='%V'", + &ctx->app, &ctx->name); + ngx_rtmp_finalize_session(ctx->publish->session); + } + + ctx->publish = NULL; + + return; + } + + /* publish end disconnect */ + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ctx->session->connection->log, 0, + "relay: publish disconnect app='%V' name='%V'", + &ctx->app, &ctx->name); + + if (ctx->push_evt.timer_set) { + ngx_del_timer(&ctx->push_evt); + } + + for (cctx = &ctx->play; *cctx; cctx = &(*cctx)->next) { + (*cctx)->publish = NULL; + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, (*cctx)->session->connection->log, + 0, "relay: play disconnect orphan app='%V' name='%V'", + &(*cctx)->app, &(*cctx)->name); + ngx_rtmp_finalize_session((*cctx)->session); + } + ctx->publish = NULL; + + hash = ngx_hash_key(ctx->name.data, ctx->name.len); + cctx = &racf->ctx[hash % racf->nbuckets]; + for (; *cctx && *cctx != ctx; cctx = &(*cctx)->next); + if (*cctx) { + *cctx = ctx->next; + } +} + + +static ngx_int_t +ngx_rtmp_relay_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v) +{ + ngx_rtmp_relay_app_conf_t *racf; + + racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_relay_module); + if (racf && !racf->session_relay) { + ngx_rtmp_relay_close(s); + } + + return next_close_stream(s, v); +} + + +static ngx_int_t +ngx_rtmp_relay_delete_stream(ngx_rtmp_session_t *s, ngx_rtmp_delete_stream_t *v) +{ + ngx_rtmp_relay_close(s); + + return next_delete_stream(s, v); +} + + +static char * +ngx_rtmp_relay_push_pull(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_str_t *value, v, n; + ngx_rtmp_relay_app_conf_t *racf; + ngx_rtmp_relay_target_t *target, **t; + ngx_url_t *u; + ngx_uint_t i; + ngx_int_t is_pull, is_static; + ngx_event_t **ee, *e; + ngx_rtmp_relay_static_t *rs; + u_char *p; + + value = cf->args->elts; + + racf = ngx_rtmp_conf_get_module_app_conf(cf, ngx_rtmp_relay_module); + + is_pull = (value[0].data[3] == 'l'); + is_static = 0; + + target = ngx_pcalloc(cf->pool, sizeof(*target)); + if (target == NULL) { + return NGX_CONF_ERROR; + } + + target->tag = &ngx_rtmp_relay_module; + target->data = target; + + u = &target->url; + u->default_port = 1935; + u->uri_part = 1; + u->url = value[1]; + + if (ngx_strncasecmp(u->url.data, (u_char *) "rtmp://", 7) == 0) { + u->url.data += 7; + u->url.len -= 7; + } + + if (ngx_parse_url(cf->pool, u) != NGX_OK) { + if (u->err) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "%s in url \"%V\"", u->err, &u->url); + } + return NGX_CONF_ERROR; + } + + value += 2; + for (i = 2; i < cf->args->nelts; ++i, ++value) { + p = ngx_strlchr(value->data, value->data + value->len, '='); + + if (p == NULL) { + n = *value; + ngx_str_set(&v, "1"); + + } else { + n.data = value->data; + n.len = p - value->data; + + v.data = p + 1; + v.len = value->data + value->len - p - 1; + } + +#define NGX_RTMP_RELAY_STR_PAR(name, var) \ + if (n.len == sizeof(name) - 1 \ + && ngx_strncasecmp(n.data, (u_char *) name, n.len) == 0) \ + { \ + target->var = v; \ + continue; \ + } + +#define NGX_RTMP_RELAY_NUM_PAR(name, var) \ + if (n.len == sizeof(name) - 1 \ + && ngx_strncasecmp(n.data, (u_char *) name, n.len) == 0) \ + { \ + target->var = ngx_atoi(v.data, v.len); \ + continue; \ + } + + NGX_RTMP_RELAY_STR_PAR("app", app); + NGX_RTMP_RELAY_STR_PAR("name", name); + NGX_RTMP_RELAY_STR_PAR("tcUrl", tc_url); + NGX_RTMP_RELAY_STR_PAR("pageUrl", page_url); + NGX_RTMP_RELAY_STR_PAR("swfUrl", swf_url); + NGX_RTMP_RELAY_STR_PAR("flashVer", flash_ver); + NGX_RTMP_RELAY_STR_PAR("playPath", play_path); + NGX_RTMP_RELAY_NUM_PAR("live", live); + NGX_RTMP_RELAY_NUM_PAR("start", start); + NGX_RTMP_RELAY_NUM_PAR("stop", stop); + +#undef NGX_RTMP_RELAY_STR_PAR +#undef NGX_RTMP_RELAY_NUM_PAR + + if (n.len == sizeof("static") - 1 && + ngx_strncasecmp(n.data, (u_char *) "static", n.len) == 0 && + ngx_atoi(v.data, v.len)) + { + is_static = 1; + continue; + } + + return "unsuppored parameter"; + } + + if (is_static) { + + if (!is_pull) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "static push is not allowed"); + return NGX_CONF_ERROR; + } + + if (target->name.len == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "stream name missing in static pull " + "declaration"); + return NGX_CONF_ERROR; + } + + ee = ngx_array_push(&racf->static_events); + if (ee == NULL) { + return NGX_CONF_ERROR; + } + + e = ngx_pcalloc(cf->pool, sizeof(ngx_event_t)); + if (e == NULL) { + return NGX_CONF_ERROR; + } + + *ee = e; + + rs = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_relay_static_t)); + if (rs == NULL) { + return NGX_CONF_ERROR; + } + + rs->target = target; + + e->data = rs; + e->log = &cf->cycle->new_log; + e->handler = ngx_rtmp_relay_static_pull_reconnect; + + t = ngx_array_push(&racf->static_pulls); + + } else if (is_pull) { + t = ngx_array_push(&racf->pulls); + + } else { + t = ngx_array_push(&racf->pushes); + } + + if (t == NULL) { + return NGX_CONF_ERROR; + } + + *t = target; + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_rtmp_relay_init_process(ngx_cycle_t *cycle) +{ +#if !(NGX_WIN32) + ngx_rtmp_core_main_conf_t *cmcf = ngx_rtmp_core_main_conf; + ngx_rtmp_core_srv_conf_t **pcscf, *cscf; + ngx_rtmp_core_app_conf_t **pcacf, *cacf; + ngx_rtmp_relay_app_conf_t *racf; + ngx_uint_t n, m, k; + ngx_rtmp_relay_static_t *rs; + ngx_rtmp_listen_t *lst; + ngx_event_t **pevent, *event; + + if (cmcf == NULL || cmcf->listen.nelts == 0) { + return NGX_OK; + } + + /* only first worker does static pulling */ + + if (ngx_process_slot) { + return NGX_OK; + } + + lst = cmcf->listen.elts; + + pcscf = cmcf->servers.elts; + for (n = 0; n < cmcf->servers.nelts; ++n, ++pcscf) { + + cscf = *pcscf; + pcacf = cscf->applications.elts; + + for (m = 0; m < cscf->applications.nelts; ++m, ++pcacf) { + + cacf = *pcacf; + racf = cacf->app_conf[ngx_rtmp_relay_module.ctx_index]; + pevent = racf->static_events.elts; + + for (k = 0; k < racf->static_events.nelts; ++k, ++pevent) { + event = *pevent; + + rs = event->data; + rs->cctx = *lst->ctx; + rs->cctx.app_conf = cacf->app_conf; + + ngx_post_event(event, &ngx_rtmp_init_queue); + } + } + } +#endif + return NGX_OK; +} + + +static ngx_int_t +ngx_rtmp_relay_postconfiguration(ngx_conf_t *cf) +{ + ngx_rtmp_core_main_conf_t *cmcf; + ngx_rtmp_handler_pt *h; + ngx_rtmp_amf_handler_t *ch; + + cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); + + + h = ngx_array_push(&cmcf->events[NGX_RTMP_HANDSHAKE_DONE]); + *h = ngx_rtmp_relay_handshake_done; + + + next_publish = ngx_rtmp_publish; + ngx_rtmp_publish = ngx_rtmp_relay_publish; + + next_play = ngx_rtmp_play; + ngx_rtmp_play = ngx_rtmp_relay_play; + + next_delete_stream = ngx_rtmp_delete_stream; + ngx_rtmp_delete_stream = ngx_rtmp_relay_delete_stream; + + next_close_stream = ngx_rtmp_close_stream; + ngx_rtmp_close_stream = ngx_rtmp_relay_close_stream; + + + ch = ngx_array_push(&cmcf->amf); + ngx_str_set(&ch->name, "_result"); + ch->handler = ngx_rtmp_relay_on_result; + + ch = ngx_array_push(&cmcf->amf); + ngx_str_set(&ch->name, "_error"); + ch->handler = ngx_rtmp_relay_on_error; + + ch = ngx_array_push(&cmcf->amf); + ngx_str_set(&ch->name, "onStatus"); + ch->handler = ngx_rtmp_relay_on_status; + + return NGX_OK; +} diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_relay_module.h nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_relay_module.h --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_relay_module.h 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_relay_module.h 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,72 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_RELAY_H_INCLUDED_ +#define _NGX_RTMP_RELAY_H_INCLUDED_ + + +#include +#include +#include "ngx_rtmp.h" + + +typedef struct { + ngx_url_t url; + ngx_str_t app; + ngx_str_t name; + ngx_str_t tc_url; + ngx_str_t page_url; + ngx_str_t swf_url; + ngx_str_t flash_ver; + ngx_str_t play_path; + ngx_int_t live; + ngx_int_t start; + ngx_int_t stop; + + void *tag; /* usually module reference */ + void *data; /* module-specific data */ + ngx_uint_t counter; /* mutable connection counter */ +} ngx_rtmp_relay_target_t; + + +typedef struct ngx_rtmp_relay_ctx_s ngx_rtmp_relay_ctx_t; + +struct ngx_rtmp_relay_ctx_s { + ngx_str_t name; + ngx_str_t url; + ngx_log_t log; + ngx_rtmp_session_t *session; + ngx_rtmp_relay_ctx_t *publish; + ngx_rtmp_relay_ctx_t *play; + ngx_rtmp_relay_ctx_t *next; + + ngx_str_t app; + ngx_str_t tc_url; + ngx_str_t page_url; + ngx_str_t swf_url; + ngx_str_t flash_ver; + ngx_str_t play_path; + ngx_int_t live; + ngx_int_t start; + ngx_int_t stop; + + ngx_event_t push_evt; + ngx_event_t *static_evt; + void *tag; + void *data; +}; + + +extern ngx_module_t ngx_rtmp_relay_module; + + +ngx_int_t ngx_rtmp_relay_pull(ngx_rtmp_session_t *s, ngx_str_t *name, + ngx_rtmp_relay_target_t *target); +ngx_int_t ngx_rtmp_relay_push(ngx_rtmp_session_t *s, ngx_str_t *name, + ngx_rtmp_relay_target_t *target); + + +#endif /* _NGX_RTMP_RELAY_H_INCLUDED_ */ diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_send.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_send.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_send.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_send.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,635 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rtmp_amf.h" +#include "ngx_rtmp_streams.h" + + +#define NGX_RTMP_USER_START(s, tp) \ + ngx_rtmp_header_t __h; \ + ngx_chain_t *__l; \ + ngx_buf_t *__b; \ + ngx_rtmp_core_srv_conf_t *__cscf; \ + \ + __cscf = ngx_rtmp_get_module_srv_conf( \ + s, ngx_rtmp_core_module); \ + memset(&__h, 0, sizeof(__h)); \ + __h.type = tp; \ + __h.csid = 2; \ + __l = ngx_rtmp_alloc_shared_buf(__cscf); \ + if (__l == NULL) { \ + return NULL; \ + } \ + __b = __l->buf; + +#define NGX_RTMP_UCTL_START(s, type, utype) \ + NGX_RTMP_USER_START(s, type); \ + *(__b->last++) = (u_char)((utype) >> 8); \ + *(__b->last++) = (u_char)(utype); + +#define NGX_RTMP_USER_OUT1(v) \ + *(__b->last++) = ((u_char*)&v)[0]; + +#define NGX_RTMP_USER_OUT4(v) \ + *(__b->last++) = ((u_char*)&v)[3]; \ + *(__b->last++) = ((u_char*)&v)[2]; \ + *(__b->last++) = ((u_char*)&v)[1]; \ + *(__b->last++) = ((u_char*)&v)[0]; + +#define NGX_RTMP_USER_END(s) \ + ngx_rtmp_prepare_message(s, &__h, NULL, __l); \ + return __l; + + +static ngx_int_t +ngx_rtmp_send_shared_packet(ngx_rtmp_session_t *s, ngx_chain_t *cl) +{ + ngx_rtmp_core_srv_conf_t *cscf; + ngx_int_t rc; + + if (cl == NULL) { + return NGX_ERROR; + } + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + rc = ngx_rtmp_send_message(s, cl, 0); + + ngx_rtmp_free_shared_chain(cscf, cl); + + return rc; +} + + +/* Protocol control messages */ + +ngx_chain_t * +ngx_rtmp_create_chunk_size(ngx_rtmp_session_t *s, uint32_t chunk_size) +{ + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "chunk_size=%uD", chunk_size); + + { + NGX_RTMP_USER_START(s, NGX_RTMP_MSG_CHUNK_SIZE); + + NGX_RTMP_USER_OUT4(chunk_size); + + NGX_RTMP_USER_END(s); + } +} + + +ngx_int_t +ngx_rtmp_send_chunk_size(ngx_rtmp_session_t *s, uint32_t chunk_size) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_chunk_size(s, chunk_size)); +} + + +ngx_chain_t * +ngx_rtmp_create_abort(ngx_rtmp_session_t *s, uint32_t csid) +{ + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "create: abort csid=%uD", csid); + + { + NGX_RTMP_USER_START(s, NGX_RTMP_MSG_CHUNK_SIZE); + + NGX_RTMP_USER_OUT4(csid); + + NGX_RTMP_USER_END(s); + } +} + + +ngx_int_t +ngx_rtmp_send_abort(ngx_rtmp_session_t *s, uint32_t csid) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_abort(s, csid)); +} + + +ngx_chain_t * +ngx_rtmp_create_ack(ngx_rtmp_session_t *s, uint32_t seq) +{ + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "create: ack seq=%uD", seq); + + { + NGX_RTMP_USER_START(s, NGX_RTMP_MSG_ACK); + + NGX_RTMP_USER_OUT4(seq); + + NGX_RTMP_USER_END(s); + } +} + + +ngx_int_t +ngx_rtmp_send_ack(ngx_rtmp_session_t *s, uint32_t seq) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_ack(s, seq)); +} + + +ngx_chain_t * +ngx_rtmp_create_ack_size(ngx_rtmp_session_t *s, uint32_t ack_size) +{ + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "create: ack_size=%uD", ack_size); + + { + NGX_RTMP_USER_START(s, NGX_RTMP_MSG_ACK_SIZE); + + NGX_RTMP_USER_OUT4(ack_size); + + NGX_RTMP_USER_END(s); + } +} + + +ngx_int_t +ngx_rtmp_send_ack_size(ngx_rtmp_session_t *s, uint32_t ack_size) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_ack_size(s, ack_size)); +} + + +ngx_chain_t * +ngx_rtmp_create_bandwidth(ngx_rtmp_session_t *s, uint32_t ack_size, + uint8_t limit_type) +{ + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "create: bandwidth ack_size=%uD limit=%d", + ack_size, (int)limit_type); + + { + NGX_RTMP_USER_START(s, NGX_RTMP_MSG_BANDWIDTH); + + NGX_RTMP_USER_OUT4(ack_size); + NGX_RTMP_USER_OUT1(limit_type); + + NGX_RTMP_USER_END(s); + } +} + + +ngx_int_t +ngx_rtmp_send_bandwidth(ngx_rtmp_session_t *s, uint32_t ack_size, + uint8_t limit_type) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_bandwidth(s, ack_size, limit_type)); +} + + +/* User control messages */ + +ngx_chain_t * +ngx_rtmp_create_stream_begin(ngx_rtmp_session_t *s, uint32_t msid) +{ + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "create: stream_begin msid=%uD", msid); + + { + NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_STREAM_BEGIN); + + NGX_RTMP_USER_OUT4(msid); + + NGX_RTMP_USER_END(s); + } +} + + +ngx_int_t +ngx_rtmp_send_stream_begin(ngx_rtmp_session_t *s, uint32_t msid) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_stream_begin(s, msid)); +} + + +ngx_chain_t * +ngx_rtmp_create_stream_eof(ngx_rtmp_session_t *s, uint32_t msid) +{ + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "create: stream_end msid=%uD", msid); + + { + NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_STREAM_EOF); + + NGX_RTMP_USER_OUT4(msid); + + NGX_RTMP_USER_END(s); + } +} + + +ngx_int_t +ngx_rtmp_send_stream_eof(ngx_rtmp_session_t *s, uint32_t msid) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_stream_eof(s, msid)); +} + + +ngx_chain_t * +ngx_rtmp_create_stream_dry(ngx_rtmp_session_t *s, uint32_t msid) +{ + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "create: stream_dry msid=%uD", msid); + + { + NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_STREAM_DRY); + + NGX_RTMP_USER_OUT4(msid); + + NGX_RTMP_USER_END(s); + } +} + + +ngx_int_t +ngx_rtmp_send_stream_dry(ngx_rtmp_session_t *s, uint32_t msid) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_stream_dry(s, msid)); +} + + +ngx_chain_t * +ngx_rtmp_create_set_buflen(ngx_rtmp_session_t *s, uint32_t msid, + uint32_t buflen_msec) +{ + ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "create: set_buflen msid=%uD buflen=%uD", + msid, buflen_msec); + + { + NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_SET_BUFLEN); + + NGX_RTMP_USER_OUT4(msid); + NGX_RTMP_USER_OUT4(buflen_msec); + + NGX_RTMP_USER_END(s); + } +} + + +ngx_int_t +ngx_rtmp_send_set_buflen(ngx_rtmp_session_t *s, uint32_t msid, + uint32_t buflen_msec) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_set_buflen(s, msid, buflen_msec)); +} + + +ngx_chain_t * +ngx_rtmp_create_recorded(ngx_rtmp_session_t *s, uint32_t msid) +{ + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "create: recorded msid=%uD", msid); + + { + NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_RECORDED); + + NGX_RTMP_USER_OUT4(msid); + + NGX_RTMP_USER_END(s); + } +} + + +ngx_int_t +ngx_rtmp_send_recorded(ngx_rtmp_session_t *s, uint32_t msid) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_recorded(s, msid)); +} + + +ngx_chain_t * +ngx_rtmp_create_ping_request(ngx_rtmp_session_t *s, uint32_t timestamp) +{ + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "create: ping_request timestamp=%uD", timestamp); + + { + NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_PING_REQUEST); + + NGX_RTMP_USER_OUT4(timestamp); + + NGX_RTMP_USER_END(s); + } +} + + +ngx_int_t +ngx_rtmp_send_ping_request(ngx_rtmp_session_t *s, uint32_t timestamp) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_ping_request(s, timestamp)); +} + + +ngx_chain_t * +ngx_rtmp_create_ping_response(ngx_rtmp_session_t *s, uint32_t timestamp) +{ + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "create: ping_response timestamp=%uD", timestamp); + + { + NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_PING_RESPONSE); + + NGX_RTMP_USER_OUT4(timestamp); + + NGX_RTMP_USER_END(s); + } +} + + +ngx_int_t +ngx_rtmp_send_ping_response(ngx_rtmp_session_t *s, uint32_t timestamp) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_ping_response(s, timestamp)); +} + + +static ngx_chain_t * +ngx_rtmp_alloc_amf_buf(void *arg) +{ + return ngx_rtmp_alloc_shared_buf((ngx_rtmp_core_srv_conf_t *)arg); +} + + +/* AMF sender */ + +/* NOTE: this function does not free shared bufs on error */ +ngx_int_t +ngx_rtmp_append_amf(ngx_rtmp_session_t *s, + ngx_chain_t **first, ngx_chain_t **last, + ngx_rtmp_amf_elt_t *elts, size_t nelts) +{ + ngx_rtmp_amf_ctx_t act; + ngx_rtmp_core_srv_conf_t *cscf; + ngx_int_t rc; + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + memset(&act, 0, sizeof(act)); + act.arg = cscf; + act.alloc = ngx_rtmp_alloc_amf_buf; + act.log = s->connection->log; + + if (first) { + act.first = *first; + } + + if (last) { + act.link = *last; + } + + rc = ngx_rtmp_amf_write(&act, elts, nelts); + + if (first) { + *first = act.first; + } + + if (last) { + *last = act.link; + } + + return rc; +} + + +ngx_chain_t * +ngx_rtmp_create_amf(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_rtmp_amf_elt_t *elts, size_t nelts) +{ + ngx_chain_t *first; + ngx_int_t rc; + ngx_rtmp_core_srv_conf_t *cscf; + + ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "create: amf nelts=%ui", nelts); + + cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); + + first = NULL; + + rc = ngx_rtmp_append_amf(s, &first, NULL, elts, nelts); + + if (rc != NGX_OK && first) { + ngx_rtmp_free_shared_chain(cscf, first); + first = NULL; + } + + if (first) { + ngx_rtmp_prepare_message(s, h, NULL, first); + } + + return first; +} + + +ngx_int_t +ngx_rtmp_send_amf(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, + ngx_rtmp_amf_elt_t *elts, size_t nelts) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_amf(s, h, elts, nelts)); +} + + +ngx_chain_t * +ngx_rtmp_create_status(ngx_rtmp_session_t *s, char *code, char* level, + 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"), + NULL, 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_string("code"), + NULL, 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, + "onStatus", 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_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "create: status code='%s' level='%s' desc='%s'", + code, level, desc); + + out_inf[0].data = level; + out_inf[1].data = code; + out_inf[2].data = desc; + + 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_status(ngx_rtmp_session_t *s, char *code, char* level, char *desc) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_status(s, code, level, desc)); +} + + +ngx_chain_t * +ngx_rtmp_create_play_status(ngx_rtmp_session_t *s, char *code, char* level, + ngx_uint_t duration, ngx_uint_t bytes) +{ + ngx_rtmp_header_t h; + static double dduration; + static double dbytes; + + static ngx_rtmp_amf_elt_t out_inf[] = { + + { NGX_RTMP_AMF_STRING, + ngx_string("code"), + NULL, 0 }, + + { NGX_RTMP_AMF_STRING, + ngx_string("level"), + NULL, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("duration"), + &dduration, 0 }, + + { NGX_RTMP_AMF_NUMBER, + ngx_string("bytes"), + &dbytes, 0 }, + }; + + static ngx_rtmp_amf_elt_t out_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + "onPlayStatus", 0 }, + + { NGX_RTMP_AMF_OBJECT, + ngx_null_string, + out_inf, + sizeof(out_inf) }, + }; + + ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, + "create: play_status code='%s' level='%s' " + "duration=%ui bytes=%ui", + code, level, duration, bytes); + + out_inf[0].data = code; + out_inf[1].data = level; + + dduration = duration; + dbytes = bytes; + + memset(&h, 0, sizeof(h)); + + h.type = NGX_RTMP_MSG_AMF_META; + h.csid = NGX_RTMP_CSID_AMF; + h.msid = NGX_RTMP_MSID; + h.timestamp = duration; + + return ngx_rtmp_create_amf(s, &h, out_elts, + sizeof(out_elts) / sizeof(out_elts[0])); +} + + +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) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_play_status(s, code, level, duration, bytes)); +} + + +ngx_chain_t * +ngx_rtmp_create_sample_access(ngx_rtmp_session_t *s) +{ + ngx_rtmp_header_t h; + + static int access = 1; + + static ngx_rtmp_amf_elt_t access_elts[] = { + + { NGX_RTMP_AMF_STRING, + ngx_null_string, + "|RtmpSampleAccess", 0 }, + + { NGX_RTMP_AMF_BOOLEAN, + ngx_null_string, + &access, 0 }, + + { NGX_RTMP_AMF_BOOLEAN, + ngx_null_string, + &access, 0 }, + }; + + memset(&h, 0, sizeof(h)); + + h.type = NGX_RTMP_MSG_AMF_META; + h.csid = NGX_RTMP_CSID_AMF; + h.msid = NGX_RTMP_MSID; + + return ngx_rtmp_create_amf(s, &h, access_elts, + sizeof(access_elts) / sizeof(access_elts[0])); +} + + +ngx_int_t +ngx_rtmp_send_sample_access(ngx_rtmp_session_t *s) +{ + return ngx_rtmp_send_shared_packet(s, + ngx_rtmp_create_sample_access(s)); +} diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_shared.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_shared.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_shared.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_shared.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,126 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include "ngx_rtmp.h" + + +ngx_chain_t * +ngx_rtmp_alloc_shared_buf(ngx_rtmp_core_srv_conf_t *cscf) +{ + u_char *p; + ngx_chain_t *out; + ngx_buf_t *b; + size_t size; + + if (cscf->free) { + out = cscf->free; + cscf->free = out->next; + + } else { + + size = cscf->chunk_size + NGX_RTMP_MAX_CHUNK_HEADER; + + p = ngx_pcalloc(cscf->pool, NGX_RTMP_REFCOUNT_BYTES + + sizeof(ngx_chain_t) + + sizeof(ngx_buf_t) + + size); + if (p == NULL) { + return NULL; + } + + p += NGX_RTMP_REFCOUNT_BYTES; + out = (ngx_chain_t *)p; + + p += sizeof(ngx_chain_t); + out->buf = (ngx_buf_t *)p; + + p += sizeof(ngx_buf_t); + out->buf->start = p; + out->buf->end = p + size; + } + + out->next = NULL; + b = out->buf; + b->pos = b->last = b->start + NGX_RTMP_MAX_CHUNK_HEADER; + b->memory = 1; + + /* buffer has refcount =1 when created! */ + ngx_rtmp_ref_set(out, 1); + + return out; +} + + +void +ngx_rtmp_free_shared_chain(ngx_rtmp_core_srv_conf_t *cscf, ngx_chain_t *in) +{ + ngx_chain_t *cl; + + if (ngx_rtmp_ref_put(in)) { + return; + } + + for (cl = in; ; cl = cl->next) { + if (cl->next == NULL) { + cl->next = cscf->free; + cscf->free = in; + return; + } + } +} + + +ngx_chain_t * +ngx_rtmp_append_shared_bufs(ngx_rtmp_core_srv_conf_t *cscf, + ngx_chain_t *head, ngx_chain_t *in) +{ + ngx_chain_t *l, **ll; + u_char *p; + size_t size; + + ll = &head; + p = in->buf->pos; + l = head; + + if (l) { + for(; l->next; l = l->next); + ll = &l->next; + } + + for ( ;; ) { + + if (l == NULL || l->buf->last == l->buf->end) { + l = ngx_rtmp_alloc_shared_buf(cscf); + if (l == NULL || l->buf == NULL) { + break; + } + + *ll = l; + ll = &l->next; + } + + while (l->buf->end - l->buf->last >= in->buf->last - p) { + l->buf->last = ngx_cpymem(l->buf->last, p, + in->buf->last - p); + in = in->next; + if (in == NULL) { + goto done; + } + p = in->buf->pos; + } + + size = l->buf->end - l->buf->last; + l->buf->last = ngx_cpymem(l->buf->last, p, size); + p += size; + } + +done: + *ll = NULL; + + return head; +} diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_stat_module.c nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_stat_module.c --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_stat_module.c 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_stat_module.c 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,863 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include +#include +#include "ngx_rtmp.h" +#include "ngx_rtmp_version.h" +#include "ngx_rtmp_live_module.h" +#include "ngx_rtmp_play_module.h" +#include "ngx_rtmp_codec_module.h" + + +static ngx_int_t ngx_rtmp_stat_init_process(ngx_cycle_t *cycle); +static char *ngx_rtmp_stat(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static ngx_int_t ngx_rtmp_stat_postconfiguration(ngx_conf_t *cf); +static void * ngx_rtmp_stat_create_loc_conf(ngx_conf_t *cf); +static char * ngx_rtmp_stat_merge_loc_conf(ngx_conf_t *cf, + void *parent, void *child); + + +static time_t start_time; + + +#define NGX_RTMP_STAT_ALL 0xff +#define NGX_RTMP_STAT_GLOBAL 0x01 +#define NGX_RTMP_STAT_LIVE 0x02 +#define NGX_RTMP_STAT_CLIENTS 0x04 +#define NGX_RTMP_STAT_PLAY 0x08 + +/* + * global: stat-{bufs-{total,free,used}, total bytes in/out, bw in/out} - cscf +*/ + + +typedef struct { + ngx_uint_t stat; + ngx_str_t stylesheet; +} ngx_rtmp_stat_loc_conf_t; + + +static ngx_conf_bitmask_t ngx_rtmp_stat_masks[] = { + { ngx_string("all"), NGX_RTMP_STAT_ALL }, + { ngx_string("global"), NGX_RTMP_STAT_GLOBAL }, + { ngx_string("live"), NGX_RTMP_STAT_LIVE }, + { ngx_string("clients"), NGX_RTMP_STAT_CLIENTS }, + { ngx_null_string, 0 } +}; + + +static ngx_command_t ngx_rtmp_stat_commands[] = { + + { ngx_string("rtmp_stat"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, + ngx_rtmp_stat, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_rtmp_stat_loc_conf_t, stat), + ngx_rtmp_stat_masks }, + + { ngx_string("rtmp_stat_stylesheet"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_rtmp_stat_loc_conf_t, stylesheet), + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_rtmp_stat_module_ctx = { + NULL, /* preconfiguration */ + ngx_rtmp_stat_postconfiguration, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + ngx_rtmp_stat_create_loc_conf, /* create location configuration */ + ngx_rtmp_stat_merge_loc_conf, /* merge location configuration */ +}; + + +ngx_module_t ngx_rtmp_stat_module = { + NGX_MODULE_V1, + &ngx_rtmp_stat_module_ctx, /* module context */ + ngx_rtmp_stat_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + ngx_rtmp_stat_init_process, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +#define NGX_RTMP_STAT_BUFSIZE 256 + + +static ngx_int_t +ngx_rtmp_stat_init_process(ngx_cycle_t *cycle) +{ + /* + * HTTP process initializer is called + * after event module initializer + * so we can run posted events here + */ + + ngx_event_process_posted(cycle, &ngx_rtmp_init_queue); + + return NGX_OK; +} + + +/* ngx_escape_html does not escape characters out of ASCII range + * which are bad for xslt */ + +static void * +ngx_rtmp_stat_escape(ngx_http_request_t *r, void *data, size_t len) +{ + u_char *p, *np; + void *new_data; + size_t n; + + p = data; + + for (n = 0; n < len; ++n, ++p) { + if (*p < 0x20 || *p >= 0x7f) { + break; + } + } + + if (n == len) { + return data; + } + + new_data = ngx_palloc(r->pool, len); + if (new_data == NULL) { + return NULL; + } + + p = data; + np = new_data; + + for (n = 0; n < len; ++n, ++p, ++np) { + *np = (*p < 0x20 || *p >= 0x7f) ? (u_char) ' ' : *p; + } + + return new_data; +} + +#if (NGX_WIN32) +/* + * Fix broken MSVC memcpy optimization for 4-byte data + * when this function is inlined + */ +__declspec(noinline) +#endif + +static void +ngx_rtmp_stat_output(ngx_http_request_t *r, ngx_chain_t ***lll, + void *data, size_t len, ngx_uint_t escape) +{ + ngx_chain_t *cl; + ngx_buf_t *b; + size_t real_len; + + if (len == 0) { + return; + } + + if (escape) { + data = ngx_rtmp_stat_escape(r, data, len); + if (data == NULL) { + return; + } + } + + real_len = escape + ? len + ngx_escape_html(NULL, data, len) + : len; + + cl = **lll; + if (cl && cl->buf->last + real_len > cl->buf->end) { + *lll = &cl->next; + } + + if (**lll == NULL) { + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return; + } + b = ngx_create_temp_buf(r->pool, + ngx_max(NGX_RTMP_STAT_BUFSIZE, real_len)); + if (b == NULL || b->pos == NULL) { + return; + } + cl->next = NULL; + cl->buf = b; + **lll = cl; + } + + b = (**lll)->buf; + + if (escape) { + b->last = (u_char *)ngx_escape_html(b->last, data, len); + } else { + b->last = ngx_cpymem(b->last, data, len); + } +} + + +/* These shortcuts assume 2 variables exist in current context: + * ngx_http_request_t *r + * ngx_chain_t ***lll */ + +/* plain data */ +#define NGX_RTMP_STAT(data, len) ngx_rtmp_stat_output(r, lll, data, len, 0) + +/* escaped data */ +#define NGX_RTMP_STAT_E(data, len) ngx_rtmp_stat_output(r, lll, data, len, 1) + +/* literal */ +#define NGX_RTMP_STAT_L(s) NGX_RTMP_STAT((s), sizeof(s) - 1) + +/* ngx_str_t */ +#define NGX_RTMP_STAT_S(s) NGX_RTMP_STAT((s)->data, (s)->len) + +/* escaped ngx_str_t */ +#define NGX_RTMP_STAT_ES(s) NGX_RTMP_STAT_E((s)->data, (s)->len) + +/* C string */ +#define NGX_RTMP_STAT_CS(s) NGX_RTMP_STAT((s), ngx_strlen(s)) + +/* escaped C string */ +#define NGX_RTMP_STAT_ECS(s) NGX_RTMP_STAT_E((s), ngx_strlen(s)) + + +#define NGX_RTMP_STAT_BW 0x01 +#define NGX_RTMP_STAT_BYTES 0x02 +#define NGX_RTMP_STAT_BW_BYTES 0x03 + + +static void +ngx_rtmp_stat_bw(ngx_http_request_t *r, ngx_chain_t ***lll, + ngx_rtmp_bandwidth_t *bw, char *name, + ngx_uint_t flags) +{ + u_char buf[NGX_INT64_LEN + 9]; + + ngx_rtmp_update_bandwidth(bw, 0); + + if (flags & NGX_RTMP_STAT_BW) { + NGX_RTMP_STAT_L("%uLbandwidth * 8) + - buf); + NGX_RTMP_STAT_CS(name); + NGX_RTMP_STAT_L(">\r\n"); + } + + if (flags & NGX_RTMP_STAT_BYTES) { + NGX_RTMP_STAT_L("%uLbytes) + - buf); + NGX_RTMP_STAT_CS(name); + NGX_RTMP_STAT_L(">\r\n"); + } +} + + +#ifdef NGX_RTMP_POOL_DEBUG +static void +ngx_rtmp_stat_get_pool_size(ngx_pool_t *pool, ngx_uint_t *nlarge, + ngx_uint_t *size) +{ + ngx_pool_large_t *l; + ngx_pool_t *p, *n; + + *nlarge = 0; + for (l = pool->large; l; l = l->next) { + ++*nlarge; + } + + *size = 0; + for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) { + *size += (p->d.last - (u_char *)p); + if (n == NULL) { + break; + } + } +} + + +static void +ngx_rtmp_stat_dump_pool(ngx_http_request_t *r, ngx_chain_t ***lll, + ngx_pool_t *pool) +{ + ngx_uint_t nlarge, size; + u_char buf[NGX_INT_T_LEN]; + + size = 0; + nlarge = 0; + ngx_rtmp_stat_get_pool_size(pool, &nlarge, &size); + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), "%ui", nlarge) - buf); + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), "%ui", size) - buf); + NGX_RTMP_STAT_L("\r\n"); +} +#endif + + + +static void +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]; + +#ifdef NGX_RTMP_POOL_DEBUG + ngx_rtmp_stat_dump_pool(r, lll, s->connection->pool); +#endif + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), "%ui", + (ngx_uint_t) s->connection->number) - buf); + NGX_RTMP_STAT_L(""); + + NGX_RTMP_STAT_L("
"); + NGX_RTMP_STAT_ES(&s->connection->addr_text); + NGX_RTMP_STAT_L("
"); + + NGX_RTMP_STAT_L(""); + + if (s->flashver.len) { + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT_ES(&s->flashver); + NGX_RTMP_STAT_L(""); + } + + if (s->page_url.len) { + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT_ES(&s->page_url); + NGX_RTMP_STAT_L(""); + } + + if (s->swf_url.len) { + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT_ES(&s->swf_url); + NGX_RTMP_STAT_L(""); + } +} + + +static char * +ngx_rtmp_stat_get_aac_profile(ngx_uint_t p, ngx_uint_t sbr, ngx_uint_t ps) { + switch (p) { + case 1: + return "Main"; + case 2: + if (ps) { + return "HEv2"; + } + if (sbr) { + return "HE"; + } + return "LC"; + case 3: + return "SSR"; + case 4: + return "LTP"; + case 5: + return "SBR"; + default: + return ""; + } +} + + +static char * +ngx_rtmp_stat_get_avc_profile(ngx_uint_t p) { + switch (p) { + case 66: + return "Baseline"; + case 77: + return "Main"; + case 100: + return "High"; + default: + return ""; + } +} + + +static void +ngx_rtmp_stat_live(ngx_http_request_t *r, ngx_chain_t ***lll, + ngx_rtmp_live_app_conf_t *lacf) +{ + ngx_rtmp_live_stream_t *stream; + ngx_rtmp_codec_ctx_t *codec; + ngx_rtmp_live_ctx_t *ctx; + ngx_rtmp_session_t *s; + ngx_int_t n; + ngx_uint_t nclients, total_nclients; + u_char buf[NGX_INT_T_LEN]; + u_char bbuf[NGX_INT32_LEN]; + ngx_rtmp_stat_loc_conf_t *slcf; + u_char *cname; + + if (!lacf->live) { + return; + } + + slcf = ngx_http_get_module_loc_conf(r, ngx_rtmp_stat_module); + + NGX_RTMP_STAT_L("\r\n"); + + total_nclients = 0; + for (n = 0; n < lacf->nbuckets; ++n) { + for (stream = lacf->streams[n]; stream; stream = stream->next) { + NGX_RTMP_STAT_L("\r\n"); + + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT_ECS(stream->name); + NGX_RTMP_STAT_L("\r\n"); + + NGX_RTMP_STAT_L(""); + + ngx_rtmp_stat_bw(r, lll, &stream->bw_in, "in", + NGX_RTMP_STAT_BW_BYTES); + ngx_rtmp_stat_bw(r, lll, &stream->bw_out, "out", + NGX_RTMP_STAT_BW_BYTES); + ngx_rtmp_stat_bw(r, lll, &stream->bw_in_audio, "audio", + NGX_RTMP_STAT_BW); + ngx_rtmp_stat_bw(r, lll, &stream->bw_in_video, "video", + NGX_RTMP_STAT_BW); + + nclients = 0; + codec = NULL; + for (ctx = stream->ctx; ctx; ctx = ctx->next, ++nclients) { + s = ctx->session; + if (slcf->stat & NGX_RTMP_STAT_CLIENTS) { + NGX_RTMP_STAT_L(""); + + ngx_rtmp_stat_client(r, lll, s); + + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), + "%ui", ctx->ndropped) - buf); + NGX_RTMP_STAT_L(""); + + NGX_RTMP_STAT_L(""); + if (!lacf->interleave) { + NGX_RTMP_STAT(bbuf, ngx_snprintf(bbuf, sizeof(bbuf), + "%D", ctx->cs[1].timestamp - + ctx->cs[0].timestamp) - bbuf); + } + NGX_RTMP_STAT_L(""); + + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT(bbuf, ngx_snprintf(bbuf, sizeof(bbuf), + "%D", s->current_time) - bbuf); + NGX_RTMP_STAT_L(""); + + if (ctx->publishing) { + NGX_RTMP_STAT_L(""); + } + + if (ctx->active) { + NGX_RTMP_STAT_L(""); + } + + NGX_RTMP_STAT_L("\r\n"); + } + if (ctx->publishing) { + codec = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); + } + } + total_nclients += nclients; + + if (codec) { + NGX_RTMP_STAT_L(""); + + NGX_RTMP_STAT_L(""); + + NGX_RTMP_STAT_L(""); + + NGX_RTMP_STAT_L("\r\n"); + } + + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), + "%ui", nclients) - buf); + NGX_RTMP_STAT_L("\r\n"); + + if (stream->publishing) { + NGX_RTMP_STAT_L("\r\n"); + } + + if (stream->active) { + NGX_RTMP_STAT_L("\r\n"); + } + + NGX_RTMP_STAT_L("\r\n"); + } + } + + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), + "%ui", total_nclients) - buf); + NGX_RTMP_STAT_L("\r\n"); + + NGX_RTMP_STAT_L("\r\n"); +} + + +static void +ngx_rtmp_stat_play(ngx_http_request_t *r, ngx_chain_t ***lll, + ngx_rtmp_play_app_conf_t *pacf) +{ + ngx_rtmp_play_ctx_t *ctx, *sctx; + ngx_rtmp_session_t *s; + ngx_uint_t n, nclients, total_nclients; + u_char buf[NGX_INT_T_LEN]; + u_char bbuf[NGX_INT32_LEN]; + ngx_rtmp_stat_loc_conf_t *slcf; + + if (pacf->entries.nelts == 0) { + return; + } + + slcf = ngx_http_get_module_loc_conf(r, ngx_rtmp_stat_module); + + NGX_RTMP_STAT_L("\r\n"); + + total_nclients = 0; + for (n = 0; n < pacf->nbuckets; ++n) { + for (ctx = pacf->ctx[n]; ctx; ) { + NGX_RTMP_STAT_L("\r\n"); + + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT_ECS(ctx->name); + NGX_RTMP_STAT_L("\r\n"); + + nclients = 0; + sctx = ctx; + for (; ctx; ctx = ctx->next) { + if (ngx_strcmp(ctx->name, sctx->name)) { + break; + } + + nclients++; + + s = ctx->session; + if (slcf->stat & NGX_RTMP_STAT_CLIENTS) { + NGX_RTMP_STAT_L(""); + + ngx_rtmp_stat_client(r, lll, s); + + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT(bbuf, ngx_snprintf(bbuf, sizeof(bbuf), + "%D", s->current_time) - bbuf); + NGX_RTMP_STAT_L(""); + + NGX_RTMP_STAT_L("\r\n"); + } + } + total_nclients += nclients; + + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), + "%ui", nclients) - buf); + NGX_RTMP_STAT_L("\r\n"); + + NGX_RTMP_STAT_L("\r\n"); + } + } + + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), + "%ui", total_nclients) - buf); + NGX_RTMP_STAT_L("\r\n"); + + NGX_RTMP_STAT_L("\r\n"); +} + + +static void +ngx_rtmp_stat_application(ngx_http_request_t *r, ngx_chain_t ***lll, + ngx_rtmp_core_app_conf_t *cacf) +{ + ngx_rtmp_stat_loc_conf_t *slcf; + + NGX_RTMP_STAT_L("\r\n"); + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT_ES(&cacf->name); + NGX_RTMP_STAT_L("\r\n"); + + slcf = ngx_http_get_module_loc_conf(r, ngx_rtmp_stat_module); + + if (slcf->stat & NGX_RTMP_STAT_LIVE) { + ngx_rtmp_stat_live(r, lll, + cacf->app_conf[ngx_rtmp_live_module.ctx_index]); + } + + if (slcf->stat & NGX_RTMP_STAT_PLAY) { + ngx_rtmp_stat_play(r, lll, + cacf->app_conf[ngx_rtmp_play_module.ctx_index]); + } + + NGX_RTMP_STAT_L("\r\n"); +} + + +static void +ngx_rtmp_stat_server(ngx_http_request_t *r, ngx_chain_t ***lll, + ngx_rtmp_core_srv_conf_t *cscf) +{ + ngx_rtmp_core_app_conf_t **cacf; + size_t n; + + NGX_RTMP_STAT_L("\r\n"); + +#ifdef NGX_RTMP_POOL_DEBUG + ngx_rtmp_stat_dump_pool(r, lll, cscf->pool); +#endif + + cacf = cscf->applications.elts; + for (n = 0; n < cscf->applications.nelts; ++n, ++cacf) { + ngx_rtmp_stat_application(r, lll, *cacf); + } + + NGX_RTMP_STAT_L("\r\n"); +} + + +static ngx_int_t +ngx_rtmp_stat_handler(ngx_http_request_t *r) +{ + ngx_rtmp_stat_loc_conf_t *slcf; + ngx_rtmp_core_main_conf_t *cmcf; + ngx_rtmp_core_srv_conf_t **cscf; + ngx_chain_t *cl, *l, **ll, ***lll; + size_t n; + off_t len; + static u_char tbuf[NGX_TIME_T_LEN]; + static u_char nbuf[NGX_INT_T_LEN]; + + slcf = ngx_http_get_module_loc_conf(r, ngx_rtmp_stat_module); + if (slcf->stat == 0) { + return NGX_DECLINED; + } + + cmcf = ngx_rtmp_core_main_conf; + if (cmcf == NULL) { + goto error; + } + + cl = NULL; + ll = &cl; + lll = ≪ + + NGX_RTMP_STAT_L("\r\n"); + if (slcf->stylesheet.len) { + NGX_RTMP_STAT_L("stylesheet); + NGX_RTMP_STAT_L("\" ?>\r\n"); + } + + NGX_RTMP_STAT_L("\r\n"); + +#ifdef NGINX_VERSION + NGX_RTMP_STAT_L("" NGINX_VERSION "\r\n"); +#endif + +#ifdef NGINX_RTMP_VERSION + NGX_RTMP_STAT_L("" NGINX_RTMP_VERSION "\r\n"); +#endif + +#ifdef NGX_COMPILER + NGX_RTMP_STAT_L("" NGX_COMPILER "\r\n"); +#endif + NGX_RTMP_STAT_L("" __DATE__ " " __TIME__ "\r\n"); + + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT(nbuf, ngx_snprintf(nbuf, sizeof(nbuf), + "%ui", (ngx_uint_t) ngx_getpid()) - nbuf); + NGX_RTMP_STAT_L("\r\n"); + + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT(tbuf, ngx_snprintf(tbuf, sizeof(tbuf), + "%T", ngx_cached_time->sec - start_time) - tbuf); + NGX_RTMP_STAT_L("\r\n"); + + NGX_RTMP_STAT_L(""); + NGX_RTMP_STAT(nbuf, ngx_snprintf(nbuf, sizeof(nbuf), + "%ui", ngx_rtmp_naccepted) - nbuf); + NGX_RTMP_STAT_L("\r\n"); + + ngx_rtmp_stat_bw(r, lll, &ngx_rtmp_bw_in, "in", NGX_RTMP_STAT_BW_BYTES); + ngx_rtmp_stat_bw(r, lll, &ngx_rtmp_bw_out, "out", NGX_RTMP_STAT_BW_BYTES); + + cscf = cmcf->servers.elts; + for (n = 0; n < cmcf->servers.nelts; ++n, ++cscf) { + ngx_rtmp_stat_server(r, lll, *cscf); + } + + NGX_RTMP_STAT_L("\r\n"); + + len = 0; + for (l = cl; l; l = l->next) { + len += (l->buf->last - l->buf->pos); + } + ngx_str_set(&r->headers_out.content_type, "text/xml"); + r->headers_out.content_length_n = len; + r->headers_out.status = NGX_HTTP_OK; + ngx_http_send_header(r); + (*ll)->buf->last_buf = 1; + return ngx_http_output_filter(r, cl); + +error: + r->headers_out.status = NGX_HTTP_INTERNAL_SERVER_ERROR; + r->headers_out.content_length_n = 0; + return ngx_http_send_header(r); +} + + +static void * +ngx_rtmp_stat_create_loc_conf(ngx_conf_t *cf) +{ + ngx_rtmp_stat_loc_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_stat_loc_conf_t)); + if (conf == NULL) { + return NULL; + } + + conf->stat = 0; + + return conf; +} + + +static char * +ngx_rtmp_stat_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_rtmp_stat_loc_conf_t *prev = parent; + ngx_rtmp_stat_loc_conf_t *conf = child; + + ngx_conf_merge_bitmask_value(conf->stat, prev->stat, 0); + ngx_conf_merge_str_value(conf->stylesheet, prev->stylesheet, ""); + + return NGX_CONF_OK; +} + + +static char * +ngx_rtmp_stat(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_core_loc_conf_t *clcf; + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_rtmp_stat_handler; + + return ngx_conf_set_bitmask_slot(cf, cmd, conf); +} + + +static ngx_int_t +ngx_rtmp_stat_postconfiguration(ngx_conf_t *cf) +{ + start_time = ngx_cached_time->sec; + + return NGX_OK; +} diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_streams.h nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_streams.h --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_streams.h 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_streams.h 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,19 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_STREAMS_H_INCLUDED_ +#define _NGX_RTMP_STREAMS_H_INCLUDED_ + + +#define NGX_RTMP_MSID 1 + +#define NGX_RTMP_CSID_AMF_INI 3 +#define NGX_RTMP_CSID_AMF 5 +#define NGX_RTMP_CSID_AUDIO 6 +#define NGX_RTMP_CSID_VIDEO 7 + + +#endif /* _NGX_RTMP_STREAMS_H_INCLUDED_ */ diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_version.h nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_version.h --- nginx-1.9.15/debian/modules/nginx-rtmp-module/ngx_rtmp_version.h 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/ngx_rtmp_version.h 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,15 @@ + +/* + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_RTMP_VERSION_H_INCLUDED_ +#define _NGX_RTMP_VERSION_H_INCLUDED_ + + +#define nginx_rtmp_version 1001004 +#define NGINX_RTMP_VERSION "1.1.4" + + +#endif /* _NGX_RTMP_VERSION_H_INCLUDED_ */ diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/README.md nginx-1.10.0/debian/modules/nginx-rtmp-module/README.md --- nginx-1.9.15/debian/modules/nginx-rtmp-module/README.md 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/README.md 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,332 @@ +# NGINX-based Media Streaming Server +## nginx-rtmp-module + + +### Project blog + + http://nginx-rtmp.blogspot.com + +### Wiki manual + + https://github.com/arut/nginx-rtmp-module/wiki/Directives + +### Google group + + https://groups.google.com/group/nginx-rtmp + + https://groups.google.com/group/nginx-rtmp-ru (Russian) + +### Donation page (Paypal etc) + + http://arut.github.com/nginx-rtmp-module/ + +### Features + +* RTMP/HLS/MPEG-DASH live streaming + +* RTMP Video on demand FLV/MP4, + playing from local filesystem or HTTP + +* Stream relay support for distributed + streaming: push & pull models + +* Recording streams in multiple FLVs + +* H264/AAC support + +* Online transcoding with FFmpeg + +* HTTP callbacks (publish/play/record/update etc) + +* Running external programs on certain events (exec) + +* HTTP control module for recording audio/video and dropping clients + +* Advanced buffering techniques + to keep memory allocations at a minimum + level for faster streaming and low + memory footprint + +* Proved to work with Wirecast, FMS, Wowza, + JWPlayer, FlowPlayer, StrobeMediaPlayback, + ffmpeg, avconv, rtmpdump, flvstreamer + and many more + +* Statistics in XML/XSL in machine- & human- + readable form + +* Linux/FreeBSD/MacOS/Windows + +### Build + +cd to NGINX source directory & run this: + + ./configure --add-module=/path/to/nginx-rtmp-module + make + make install + +Several versions of nginx (1.3.14 - 1.5.0) require http_ssl_module to be +added as well: + + ./configure --add-module=/path/to/nginx-rtmp-module --with-http_ssl_module + +For building debug version of nginx add `--with-debug` + + ./configure --add-module=/path/to-nginx/rtmp-module --with-debug + +[Read more about debug log](https://github.com/arut/nginx-rtmp-module/wiki/Debug-log) + +### Windows limitations + +Windows support is limited. These features are not supported + +* execs +* static pulls +* auto_push + +### RTMP URL format + + rtmp://rtmp.example.com/app[/name] + +app - should match one of application {} + blocks in config + +name - interpreted by each application + can be empty + + +### 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. + + +### Example nginx.conf + + rtmp { + + server { + + listen 1935; + + chunk_size 4000; + + # TV mode: one publisher, many subscribers + application mytv { + + # enable live streaming + live on; + + # record first 1K of stream + record all; + record_path /tmp/av; + record_max_size 1K; + + # append current timestamp to each flv + record_unique on; + + # publish only from localhost + allow publish 127.0.0.1; + deny publish all; + + #allow play all; + } + + # Transcoding (ffmpeg needed) + application big { + live on; + + # On every pusblished stream run this command (ffmpeg) + # with substitutions: $app/${app}, $name/${name} for application & stream name. + # + # This ffmpeg call receives stream from this application & + # reduces the resolution down to 32x32. The stream is the published to + # 'small' application (see below) under the same name. + # + # ffmpeg can do anything with the stream like video/audio + # transcoding, resizing, altering container/codec params etc + # + # Multiple exec lines can be specified. + + exec 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 with reduced resolution comes here from ffmpeg + } + + application webcam { + live on; + + # Stream from local webcam + exec_static ffmpeg -f video4linux2 -i /dev/video0 -c:v libx264 -an + -f flv rtmp://localhost:1935/webcam/mystream; + } + + application mypush { + live on; + + # Every stream published here + # is automatically pushed to + # these two machines + push rtmp1.example.com; + push rtmp2.example.com:1934; + } + + application mypull { + live on; + + # Pull all streams from remote machine + # and play locally + pull rtmp://rtmp3.example.com pageUrl=www.example.com/index.html; + } + + application mystaticpull { + live on; + + # Static pull is started at nginx start + pull rtmp://rtmp4.example.com pageUrl=www.example.com/index.html name=mystream static; + } + + # video on demand + application vod { + play /var/flvs; + } + + application vod2 { + play /var/mp4s; + } + + # Many publishers, many subscribers + # no checks, no recording + application videochat { + + live on; + + # The following notifications receive all + # the session variables as well as + # particular call arguments in HTTP POST + # request + + # Make HTTP request & use HTTP retcode + # to decide whether to allow publishing + # from this connection or not + on_publish http://localhost:8080/publish; + + # Same with playing + on_play http://localhost:8080/play; + + # Publish/play end (repeats on disconnect) + on_done http://localhost:8080/done; + + # All above mentioned notifications receive + # standard connect() arguments as well as + # play/publish ones. If any arguments are sent + # with GET-style syntax to play & publish + # these are also included. + # Example URL: + # rtmp://localhost/myapp/mystream?a=b&c=d + + # record 10 video keyframes (no audio) every 2 minutes + record keyframes; + record_path /tmp/vc; + record_max_frames 10; + record_interval 2m; + + # Async notify about an flv recorded + on_record_done http://localhost:8080/record_done; + + } + + + # HLS + + # For HLS to work please create a directory in tmpfs (/tmp/hls here) + # for the fragments. The directory contents is served via HTTP (see + # http{} section in config) + # + # Incoming stream must be in H264/AAC. For iPhones use baseline H264 + # profile (see ffmpeg example). + # This example creates RTMP stream from movie ready for HLS: + # + # ffmpeg -loglevel verbose -re -i movie.avi -vcodec libx264 + # -vprofile baseline -acodec libmp3lame -ar 44100 -ac 1 + # -f flv rtmp://localhost:1935/hls/movie + # + # If you need to transcode live stream use 'exec' feature. + # + application hls { + live on; + hls on; + hls_path /tmp/hls; + } + + # MPEG-DASH is similar to HLS + + application dash { + live on; + dash on; + dash_path /tmp/dash; + } + } + } + + # HTTP can be used for accessing RTMP stats + http { + + server { + + listen 8080; + + # This URL provides RTMP statistics in XML + location /stat { + rtmp_stat all; + + # Use this stylesheet to view XML as web page + # in browser + rtmp_stat_stylesheet stat.xsl; + } + + location /stat.xsl { + # XML stylesheet to view RTMP stats. + # Copy stat.xsl wherever you want + # and put the full directory path here + root /path/to/stat.xsl/; + } + + location /hls { + # Serve HLS fragments + types { + application/vnd.apple.mpegurl m3u8; + video/mp2t ts; + } + root /tmp; + add_header Cache-Control no-cache; + } + + location /dash { + # Serve DASH fragments + root /tmp; + add_header Cache-Control no-cache; + } + } + } + + +### Multi-worker streaming example + + rtmp_auto_push on; + + rtmp { + server { + listen 1935; + + application mytv { + live on; + } + } + } diff -Nru nginx-1.9.15/debian/modules/nginx-rtmp-module/stat.xsl nginx-1.10.0/debian/modules/nginx-rtmp-module/stat.xsl --- nginx-1.9.15/debian/modules/nginx-rtmp-module/stat.xsl 1970-01-01 00:00:00.000000000 +0000 +++ nginx-1.10.0/debian/modules/nginx-rtmp-module/stat.xsl 2016-05-31 10:00:53.000000000 +0000 @@ -0,0 +1,355 @@ + + + + + + + + + + + + + RTMP statistics + + + +
+ Generated by + nginx-rtmp-module , + nginx , + pid , + built   + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RTMP#clientsVideoAudioIn bytesOut bytesIn bits/sOut bits/sStateTime
Accepted: codecbits/ssizefpscodecbits/sfreqchan + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + live streams + + + + + + + + + + + + vod streams + + + + + + + + + + + + + #cccccc + #dddddd + + + + + + var d=document.getElementById('-'); + d.style.display=d.style.display=='none'?'':'none'; + return false + + + + [EMPTY] + + + + + +    + + + + + + + + + + + + + + + +   + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + +
IdStateAddressFlash versionPage URLSWF URLDroppedTimestampA-VTime
+ + +
+ + + + + + + + + + + d + + + + h + + + + m + + + s + + + + + + + + + + + + + T + + + G + + + M + + K + + + + b + B + + /s + + + + + + active + idle + + + + + + + publishing + playing + + + + + + + + + #cccccc + #eeeeee + + + + + + + + http://apps.db.ripe.net/search/query.html?searchtext= + + whois + + + + + + + + + + + + + + + + + + + + + + + + + + publishing + + + + active + + + + x + + +
diff -Nru nginx-1.9.15/debian/patches/ubuntu-branding.patch nginx-1.10.0/debian/patches/ubuntu-branding.patch --- nginx-1.9.15/debian/patches/ubuntu-branding.patch 2016-04-19 16:44:10.000000000 +0000 +++ nginx-1.10.0/debian/patches/ubuntu-branding.patch 2016-04-26 14:51:14.000000000 +0000 @@ -9,8 +9,8 @@ +++ b/src/core/nginx.h @@ -11,7 +11,7 @@ - #define nginx_version 1009015 - #define NGINX_VERSION "1.9.15" + #define nginx_version 1010000 + #define NGINX_VERSION "1.10.0" -#define NGINX_VER "nginx/" NGINX_VERSION +#define NGINX_VER "nginx/" NGINX_VERSION " (Ubuntu)" diff -Nru nginx-1.9.15/debian/rules nginx-1.10.0/debian/rules --- nginx-1.9.15/debian/rules 2016-04-18 19:37:48.000000000 +0000 +++ nginx-1.10.0/debian/rules 2016-05-31 10:02:32.000000000 +0000 @@ -81,6 +81,7 @@ --without-http_userid_module \ --without-http_uwsgi_module \ --add-module=$(MODULESDIR)/nginx-echo + --add-module=$(MODULESDIR)/nginx-rtmp-module full_configure_flags := \ $(common_configure_flags) \ @@ -103,6 +104,7 @@ --add-module=$(MODULESDIR)/nginx-echo \ --add-module=$(MODULESDIR)/nginx-upstream-fair \ --add-module=$(MODULESDIR)/ngx_http_substitutions_filter_module + --add-module=$(MODULESDIR)/nginx-rtmp-module extras_configure_flags := \ $(common_configure_flags) \ @@ -137,6 +139,7 @@ --add-module=$(MODULESDIR)/nginx-upload-progress \ --add-module=$(MODULESDIR)/nginx-upstream-fair \ --add-module=$(MODULESDIR)/ngx_http_substitutions_filter_module + --add-module=$(MODULESDIR)/nginx-rtmp-module %: dh $@ --with systemd diff -Nru nginx-1.9.15/src/core/nginx.h nginx-1.10.0/src/core/nginx.h --- nginx-1.9.15/src/core/nginx.h 2016-04-19 16:02:38.000000000 +0000 +++ nginx-1.10.0/src/core/nginx.h 2016-04-26 13:31:19.000000000 +0000 @@ -9,8 +9,8 @@ #define _NGINX_H_INCLUDED_ -#define nginx_version 1009015 -#define NGINX_VERSION "1.9.15" +#define nginx_version 1010000 +#define NGINX_VERSION "1.10.0" #define NGINX_VER "nginx/" NGINX_VERSION #ifdef NGX_BUILD