Repository: arut/nginx-rtmp-module Branch: master Commit: 6c7719d0ba32 Files: 78 Total size: 927.9 KB Directory structure: gitextract_2a2ek2fd/ ├── AUTHORS ├── LICENSE ├── README.md ├── config ├── dash/ │ ├── ngx_rtmp_dash_module.c │ ├── ngx_rtmp_mp4.c │ └── ngx_rtmp_mp4.h ├── doc/ │ └── README.md ├── hls/ │ ├── ngx_rtmp_hls_module.c │ ├── ngx_rtmp_mpegts.c │ └── ngx_rtmp_mpegts.h ├── ngx_rtmp.c ├── ngx_rtmp.h ├── ngx_rtmp_access_module.c ├── ngx_rtmp_amf.c ├── ngx_rtmp_amf.h ├── ngx_rtmp_auto_push_module.c ├── ngx_rtmp_bandwidth.c ├── ngx_rtmp_bandwidth.h ├── ngx_rtmp_bitop.c ├── ngx_rtmp_bitop.h ├── ngx_rtmp_cmd_module.c ├── ngx_rtmp_cmd_module.h ├── ngx_rtmp_codec_module.c ├── ngx_rtmp_codec_module.h ├── ngx_rtmp_control_module.c ├── ngx_rtmp_core_module.c ├── ngx_rtmp_eval.c ├── ngx_rtmp_eval.h ├── ngx_rtmp_exec_module.c ├── ngx_rtmp_flv_module.c ├── ngx_rtmp_handler.c ├── ngx_rtmp_handshake.c ├── ngx_rtmp_init.c ├── ngx_rtmp_limit_module.c ├── ngx_rtmp_live_module.c ├── ngx_rtmp_live_module.h ├── ngx_rtmp_log_module.c ├── ngx_rtmp_mp4_module.c ├── ngx_rtmp_netcall_module.c ├── ngx_rtmp_netcall_module.h ├── ngx_rtmp_notify_module.c ├── ngx_rtmp_play_module.c ├── ngx_rtmp_play_module.h ├── ngx_rtmp_proxy_protocol.c ├── ngx_rtmp_proxy_protocol.h ├── ngx_rtmp_receive.c ├── ngx_rtmp_record_module.c ├── ngx_rtmp_record_module.h ├── ngx_rtmp_relay_module.c ├── ngx_rtmp_relay_module.h ├── ngx_rtmp_send.c ├── ngx_rtmp_shared.c ├── ngx_rtmp_stat_module.c ├── ngx_rtmp_streams.h ├── ngx_rtmp_version.h ├── stat.xsl └── test/ ├── README.md ├── dump.sh ├── ffstream.sh ├── nginx.conf ├── play.sh ├── rtmp-publisher/ │ ├── README.md │ ├── RtmpPlayer.mxml │ ├── RtmpPlayer.swf │ ├── RtmpPlayerLight.mxml │ ├── RtmpPlayerLight.swf │ ├── RtmpPublisher.mxml │ ├── RtmpPublisher.swf │ ├── player.html │ ├── publisher.html │ └── swfobject.js └── www/ ├── index.html ├── jwplayer/ │ ├── jwplayer.flash.swf │ └── jwplayer.js ├── jwplayer_old/ │ ├── player.swf │ └── swfobject.js └── record.html ================================================ FILE CONTENTS ================================================ ================================================ FILE: AUTHORS ================================================ Project author: Roman Arutyunyan Moscow, Russia Contacts: arut@qip.ru arutyunyan.roman@gmail.com ================================================ FILE: LICENSE ================================================ 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. ================================================ FILE: README.md ================================================ # 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; } } } ================================================ FILE: config ================================================ 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_auto_push_index_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 ================================================ FILE: dash/ngx_rtmp_dash_module.c ================================================ #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; 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:00Z")]; static u_char pub_time[sizeof("1970-09-28T12:00:00Z")]; 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_gmtime(ctx->start_time, &tm); ngx_sprintf(start_time, "%4d-%02d-%02dT%02d:%02d:%02dZ%Z", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); ngx_libc_gmtime(ngx_time(), &tm); ngx_sprintf(pub_time, "%4d-%02d-%02dT%02d:%02d:%02dZ%Z", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); last = buffer + sizeof(buffer); p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_MANIFEST_HEADER, start_time, pub_time, (ngx_uint_t) (dacf->fraglen / 1000), (ngx_uint_t) (dacf->fraglen / 1000), (ngx_uint_t) (dacf->fraglen / 250 + 1)); /* * timeShiftBufferDepth formula: * 2 * minBufferTime + max_fragment_length + 1 */ 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_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); /* keep fragment lengths within 2x factor for dash.js */ if (f->duration >= dacf->fraglen * 2) { boundary = 1; } } 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++; } } #if (nginx_version >= 1011005) static ngx_msec_t #else static time_t #endif ngx_rtmp_dash_cleanup(void *data) { ngx_rtmp_dash_cleanup_t *cleanup = data; ngx_rtmp_dash_cleanup_dir(&cleanup->path, cleanup->playlen); #if (nginx_version >= 1011005) return cleanup->playlen * 2; #else return cleanup->playlen / 500; #endif } 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; } ================================================ FILE: dash/ngx_rtmp_mp4.c ================================================ #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; } ================================================ FILE: dash/ngx_rtmp_mp4.h ================================================ #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_ */ ================================================ FILE: doc/README.md ================================================ Documentation is available here: https://github.com/arut/nginx-rtmp-module/wiki ================================================ FILE: hls/ngx_rtmp_hls_module.c ================================================ /* * 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"); } /* fall through */ 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++; } } #if (nginx_version >= 1011005) static ngx_msec_t #else static time_t #endif ngx_rtmp_hls_cleanup(void *data) { ngx_rtmp_hls_cleanup_t *cleanup = data; ngx_rtmp_hls_cleanup_dir(&cleanup->path, cleanup->playlen); #if (nginx_version >= 1011005) return cleanup->playlen * 2; #else return cleanup->playlen / 500; #endif } 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; } ================================================ FILE: hls/ngx_rtmp_mpegts.c ================================================ /* * 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; } ================================================ FILE: hls/ngx_rtmp_mpegts.h ================================================ /* * 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_ */ ================================================ FILE: ngx_rtmp.c ================================================ /* * 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_module_t **modules; 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 */ #if (nginx_version >= 1009011) ngx_rtmp_max_module = ngx_count_modules(cf->cycle, NGX_RTMP_MODULE); #else 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++; } #endif /* 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 */ #if (nginx_version >= 1009011) modules = cf->cycle->modules; #else modules = ngx_modules; #endif for (m = 0; modules[m]; m++) { if (modules[m]->type != NGX_RTMP_MODULE) { continue; } module = modules[m]->ctx; mi = 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; modules[m]; m++) { if (modules[m]->type != NGX_RTMP_MODULE) { continue; } module = 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; modules[m]; m++) { if (modules[m]->type != NGX_RTMP_MODULE) { continue; } module = modules[m]->ctx; mi = 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; modules[m]; m++) { if (modules[m]->type != NGX_RTMP_MODULE) { continue; } module = 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 ls->wildcard = addr[i].wildcard; 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; } ================================================ FILE: ngx_rtmp.h ================================================ /* * 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_MAX_CHUNK_SIZE 10485760 #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_ */ ================================================ FILE: ngx_rtmp_access_module.c ================================================ /* * 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; } #endif /* fall through */ 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; } ================================================ FILE: ngx_rtmp_amf.c ================================================ /* * 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 && elts->type & NGX_RTMP_AMF_OPTIONAL) { return NGX_OK; } /* fall through */ 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; } /* fall through */ 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; } /* fall through */ 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; } ================================================ FILE: ngx_rtmp_amf.h ================================================ /* * 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_ */ ================================================ FILE: ngx_rtmp_auto_push_module.c ================================================ /* * 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 }; static ngx_rtmp_module_t ngx_rtmp_auto_push_index_module_ctx = { NULL, /* preconfiguration */ NULL, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ NULL, /* create app configuration */ NULL /* merge app configuration */ }; ngx_module_t ngx_rtmp_auto_push_index_module = { NGX_MODULE_V1, &ngx_rtmp_auto_push_index_module_ctx, /* module context */ NULL, /* module directives */ NGX_RTMP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; #define NGX_RTMP_AUTO_PUSH_SOCKNAME "nginx-rtmp" 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_index_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_index_module); if (ctx == NULL) { ctx = ngx_palloc(s->connection->pool, sizeof(ngx_rtmp_auto_push_ctx_t)); if (ctx == NULL) { goto next; } ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_auto_push_index_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_index_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_index_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 */ ================================================ FILE: ngx_rtmp_bandwidth.c ================================================ /* * 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; } ================================================ FILE: ngx_rtmp_bandwidth.h ================================================ /* * 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_ */ ================================================ FILE: ngx_rtmp_bitop.c ================================================ /* * 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; } ================================================ FILE: ngx_rtmp_bitop.h ================================================ /* * 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_ */ ================================================ FILE: ngx_rtmp_cmd_module.c ================================================ /* * 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; } ================================================ FILE: ngx_rtmp_cmd_module.h ================================================ /* * 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_ */ ================================================ FILE: ngx_rtmp_codec_module.c ================================================ /* * 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; } ================================================ FILE: ngx_rtmp_codec_module.h ================================================ /* * 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_ */ ================================================ FILE: ngx_rtmp_control_module.c ================================================ /* * 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); } ================================================ FILE: ngx_rtmp_core_module.c ================================================ /* * 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_module_t **modules; 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; } #if (nginx_version >= 1009011) modules = cf->cycle->modules; #else modules = ngx_modules; #endif for (m = 0; modules[m]; m++) { if (modules[m]->type != NGX_RTMP_MODULE) { continue; } module = modules[m]->ctx; if (module->create_srv_conf) { mconf = module->create_srv_conf(cf); if (mconf == NULL) { return NGX_CONF_ERROR; } ctx->srv_conf[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[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_module_t **modules; 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; } #if (nginx_version >= 1009011) modules = cf->cycle->modules; #else modules = ngx_modules; #endif for (i = 0; modules[i]; i++) { if (modules[i]->type != NGX_RTMP_MODULE) { continue; } module = modules[i]->ctx; if (module->create_app_conf) { ctx->app_conf[modules[i]->ctx_index] = module->create_app_conf(cf); if (ctx->app_conf[modules[i]->ctx_index] == NULL) { return NGX_CONF_ERROR; } } } 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; ngx_module_t **modules; 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_char *) &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_char *) &u.sockaddr, u.socklen); ls->socklen = u.socklen; ls->wildcard = u.wildcard; ls->ctx = cf->ctx; #if (nginx_version >= 1009011) modules = cf->cycle->modules; #else modules = ngx_modules; #endif for (m = 0; modules[m]; m++) { if (modules[m]->type != NGX_RTMP_MODULE) { continue; } } 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; } ================================================ FILE: ngx_rtmp_eval.c ================================================ /* * 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_str_t v; ngx_rtmp_eval_t *ee; for (; *e; ++e) { for (ee = *e; ee->handler; ++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); /* fall through */ case NORMAL: switch (c) { case '$': name.data = p + 1; state = NAME; continue; case '\\': state = ESCAPE; continue; } /* fall through */ 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 } ================================================ FILE: ngx_rtmp_eval.h ================================================ /* * 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_ */ ================================================ FILE: ngx_rtmp_exec_module.c ================================================ /* * 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; } ================================================ FILE: ngx_rtmp_flv_module.c ================================================ /* * 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; } ================================================ FILE: ngx_rtmp_handler.c ================================================ /* * 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; size = ngx_min((size_t) (b->end - b->start), old_size); b->last = ngx_movemem(b->pos, old_pos, size); if (s->in_chunk_size_changing) { ngx_rtmp_finalize_set_chunk_size(s); } } 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; #if (NGX_DEBUG) ngx_int_t nbufs; #endif 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; #if (NGX_DEBUG) nbufs = 0; #endif for(l = out; l; l = l->next) { mlen += (l->buf->last - l->buf->pos); #if (NGX_DEBUG) ++nbufs; #endif } 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); if (size > NGX_RTMP_MAX_CHUNK_SIZE) { ngx_log_error(NGX_LOG_ALERT, s->connection->log, 0, "too big RTMP chunk size:%ui", size); return NGX_ERROR; } 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; } ================================================ FILE: ngx_rtmp_handshake.c ================================================ /* * 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; unsigned int len; if (hmac == NULL) { #if OPENSSL_VERSION_NUMBER < 0x10100000L static HMAC_CTX shmac; hmac = &shmac; HMAC_CTX_init(hmac); #else hmac = HMAC_CTX_new(); if (hmac == NULL) { return NGX_ERROR; } #endif } HMAC_Init_ex(hmac, key->data, key->len, EVP_sha256(), NULL); 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); } ================================================ FILE: ngx_rtmp_init.c ================================================ /* * 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; /* fall through */ 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; /* fall through */ 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_SSL) if (c->ssl) { if (ngx_ssl_shutdown(c) == NGX_AGAIN) { c->ssl->handler = ngx_rtmp_close_connection; return; } } #endif #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); } ================================================ FILE: ngx_rtmp_limit_module.c ================================================ /* * 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; } ================================================ FILE: ngx_rtmp_live_module.c ================================================ /* * 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; } ================================================ FILE: ngx_rtmp_live_module.h ================================================ /* * 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_ */ ================================================ FILE: ngx_rtmp_log_module.c ================================================ /* * 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; } ================================================ FILE: ngx_rtmp_mp4_module.c ================================================ /* * 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; } ================================================ FILE: ngx_rtmp_netcall_module.c ================================================ /* * 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; } ================================================ FILE: ngx_rtmp_netcall_module.h ================================================ /* * 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_ */ ================================================ FILE: ngx_rtmp_notify_module.c ================================================ /* * 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; /* fall through */ 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; /* fall through */ 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; } ================================================ FILE: ngx_rtmp_play_module.c ================================================ /* * 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; } ================================================ FILE: ngx_rtmp_play_module.h ================================================ /* * 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_ */ ================================================ FILE: ngx_rtmp_proxy_protocol.c ================================================ /* * 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); } ================================================ FILE: ngx_rtmp_proxy_protocol.h ================================================ /* * 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_ */ ================================================ FILE: ngx_rtmp_receive.c ================================================ /* * 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); } ================================================ FILE: ngx_rtmp_record_module.c ================================================ /* * 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_module_t **modules; ngx_rtmp_module_t *module; ngx_rtmp_core_app_conf_t *cacf, **pcacf, *rcacf; ngx_rtmp_record_app_conf_t *racf, **pracf, *rracf; 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; } #if (nginx_version >= 1009011) modules = cf->cycle->modules; #else modules = ngx_modules; #endif for (i = 0; modules[i]; i++) { if (modules[i]->type != NGX_RTMP_MODULE) { continue; } module = modules[i]->ctx; if (module->create_app_conf) { ctx->app_conf[modules[i]->ctx_index] = module->create_app_conf(cf); if (ctx->app_conf[modules[i]->ctx_index] == NULL) { return NGX_CONF_ERROR; } } } /* 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; } ================================================ FILE: ngx_rtmp_record_module.h ================================================ /* * 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_ */ ================================================ FILE: ngx_rtmp_relay_module.c ================================================ /* * 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; } ================================================ FILE: ngx_rtmp_relay_module.h ================================================ /* * 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_ */ ================================================ FILE: ngx_rtmp_send.c ================================================ /* * 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)); } ================================================ FILE: ngx_rtmp_shared.c ================================================ /* * 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; } ================================================ FILE: ngx_rtmp_stat_module.c ================================================ /* * 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; } ================================================ FILE: ngx_rtmp_streams.h ================================================ /* * 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_ */ ================================================ FILE: ngx_rtmp_version.h ================================================ /* * 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_ */ ================================================ FILE: stat.xsl ================================================ RTMP statistics
Generated by nginx-rtmp-module , nginx , pid , built  
RTMP #clients Video Audio In bytes Out bytes In bits/s Out bits/s State Time
Accepted: codec bits/s size fps codec bits/s freq chan
live streams vod streams #cccccc #dddddd var d=document.getElementById('-'); d.style.display=d.style.display=='none'?'':'none'; return false [EMPTY]      -
Id State Address Flash version Page URL SWF URL Dropped Timestamp A-V Time
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
================================================ FILE: test/README.md ================================================ # RTMP tests nginx.conf is sample config for testing nginx-rtmp. Please update paths in it before using. RTMP port: 1935, HTTP port: 8080 * http://localhost:8080/ - play myapp/mystream with JWPlayer * http://localhost:8080/record.html - capture myapp/mystream from webcam with old JWPlayer * http://localhost:8080/rtmp-publisher/player.html - play myapp/mystream with the test flash applet * http://localhost:8080/rtmp-publisher/publisher.html - capture myapp/mystream with the test flash applet ================================================ FILE: test/dump.sh ================================================ rtmpdump -v -r "rtmp://localhost/myapp/mystream" ================================================ FILE: test/ffstream.sh ================================================ ffmpeg -loglevel verbose -re -i ~/movie.avi -f flv rtmp://localhost/myapp/mystream ================================================ FILE: test/nginx.conf ================================================ worker_processes 1; error_log logs/error.log debug; events { worker_connections 1024; } rtmp { server { listen 1935; application myapp { live on; #record keyframes; #record_path /tmp; #record_max_size 128K; #record_interval 30s; #record_suffix .this.is.flv; #on_publish http://localhost:8080/publish; #on_play http://localhost:8080/play; #on_record_done http://localhost:8080/record_done; } } } http { server { listen 8080; location /stat { rtmp_stat all; rtmp_stat_stylesheet stat.xsl; } location /stat.xsl { root /path/to/nginx-rtmp-module/; } location /control { rtmp_control all; } #location /publish { # return 201; #} #location /play { # return 202; #} #location /record_done { # return 203; #} location /rtmp-publisher { root /path/to/nginx-rtmp-module/test; } location / { root /path/to/nginx-rtmp-module/test/www; } } } ================================================ FILE: test/play.sh ================================================ ffplay -loglevel verbose "rtmp://localhost/myapp/mystream" ================================================ FILE: test/rtmp-publisher/README.md ================================================ # RTMP Publisher Simple RTMP publisher. Edit the following flashvars in publisher.html & player.html to suite your needs. streamer: RTMP endpoint file: live stream name ## Compile Install flex sdk http://www.adobe.com/devnet/flex/flex-sdk-download.html mxmlc RtmpPublisher.mxml mxmlc RtmpPlayer.mxml ================================================ FILE: test/rtmp-publisher/RtmpPlayer.mxml ================================================ ================================================ FILE: test/rtmp-publisher/RtmpPlayerLight.mxml ================================================ ================================================ FILE: test/rtmp-publisher/RtmpPublisher.mxml ================================================ ================================================ FILE: test/rtmp-publisher/player.html ================================================ RTMP Player

Flash not installed

================================================ FILE: test/rtmp-publisher/publisher.html ================================================ RTMP Publisher

Flash not installed

================================================ FILE: test/rtmp-publisher/swfobject.js ================================================ /* SWFObject v2.2 is released under the MIT License */ var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y0){for(var af=0;af0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad'}}aa.outerHTML='"+af+"";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;abPlay | Record
Loading the player ...
================================================ FILE: test/www/jwplayer/jwplayer.js ================================================ "undefined"==typeof jwplayer&&(jwplayer=function(d){if(jwplayer.api)return jwplayer.api.selectPlayer(d)},jwplayer.version="6.3.3242",jwplayer.vid=document.createElement("video"),jwplayer.audio=document.createElement("audio"),jwplayer.source=document.createElement("source"),function(d){function a(b){return function(){return e(b)}}var h=document,g=window,c=navigator,b=d.utils=function(){};b.exists=function(b){switch(typeof b){case "string":return 0g||g>c)}else c=void 0;if(c)return a;c=e.substring(0,e.indexOf("://")+3);var g=e.substring(c.length,e.indexOf("/",c.length+1)),k;0===a.indexOf("/")?k=a.split("/"):(k=e.split("?")[0],k=k.substring(c.length+g.length+1,k.lastIndexOf("/")),k=k.split("/").concat(a.split("/"))); for(var f=[],d=0;de&&0>c&&(!a||!isNaN(a))?k.CDN:k.RELATIVE}};b.getPluginName=function(a){return a.replace(/^(.*\/)?([^-]*)-?.*\.(swf|js)$/,"$2")};b.getPluginVersion=function(a){return a.replace(/[^-]*-?([^\.]*).*$/,"$1")}; b.isYouTube=function(a){return-1=e.length&&(e[1]=0);for(var k=a.strToLongs(g.encode(b).slice(0,16)),l=e.length,d=e[l-1],n=e[0],q,j=Math.floor(6+52/l),f=0;0>>2&3;for(var r=0;r>>5^n<<2)+(n>>>3^d<<4)^(f^n)+(k[r&3^q]^d),d=e[r]+=d}e=a.longsToStr(e);return h.encode(e)};a.decrypt=function(c,b){if(0==c.length)return"";for(var e=a.strToLongs(h.decode(c)),k=a.strToLongs(g.encode(b).slice(0,16)),d=e.length, m=e[d-1],n=e[0],q,j=2654435769*Math.floor(6+52/d);0!=j;){q=j>>>2&3;for(var f=d-1;0<=f;f--)m=e[0>>5^n<<2)+(n>>>3^m<<4)^(j^n)+(k[f&3^q]^m),n=e[f]-=m;j-=2654435769}e=a.longsToStr(e);e=e.replace(/\0+$/,"");return g.decode(e)};a.strToLongs=function(a){for(var b=Array(Math.ceil(a.length/4)),e=0;e>>8&255,a[e]>>>16&255,a[e]>>>24&255);return b.join("")};var h={code:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\x3d",encode:function(a,b){var e,k,d,m,n=[],q="",j,f,r=h.code;f=("undefined"==typeof b?0:b)?g.encode(a):a;j=f.length%3;if(0j++;)q+="\x3d",f+="\x00";for(j=0;j>18&63,k=m>>12&63,d=m>>6&63,m&=63,n[j/3]=r.charAt(e)+r.charAt(k)+r.charAt(d)+ r.charAt(m);n=n.join("");return n=n.slice(0,n.length-q.length)+q},decode:function(a,b){b="undefined"==typeof b?!1:b;var e,k,d,m,n,q=[],j,f=h.code;j=b?g.decode(a):a;for(var r=0;r>>16&255,k=d>>>8&255,d&=255,q[r/4]=String.fromCharCode(e,k,d),64==n&&(q[r/4]=String.fromCharCode(e,k)),64==m&&(q[r/4]=String.fromCharCode(e));m=q.join("");return b?g.decode(m):m}}, g={encode:function(a){a=a.replace(/[\u0080-\u07ff]/g,function(a){a=a.charCodeAt(0);return String.fromCharCode(192|a>>6,128|a&63)});return a=a.replace(/[\u0800-\uffff]/g,function(a){a=a.charCodeAt(0);return String.fromCharCode(224|a>>12,128|a>>6&63,128|a&63)})},decode:function(a){a=a.replace(/[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g,function(a){a=(a.charCodeAt(0)&15)<<12|(a.charCodeAt(1)&63)<<6|a.charCodeAt(2)&63;return String.fromCharCode(a)});return a=a.replace(/[\u00c0-\u00df][\u0080-\u00bf]/g, function(a){a=(a.charCodeAt(0)&31)<<6|a.charCodeAt(1)&63;return String.fromCharCode(a)})}}}(jwplayer.utils),function(d){d.events={COMPLETE:"COMPLETE",ERROR:"ERROR",API_READY:"jwplayerAPIReady",JWPLAYER_READY:"jwplayerReady",JWPLAYER_FULLSCREEN:"jwplayerFullscreen",JWPLAYER_RESIZE:"jwplayerResize",JWPLAYER_ERROR:"jwplayerError",JWPLAYER_MEDIA_BEFOREPLAY:"jwplayerMediaBeforePlay",JWPLAYER_MEDIA_BEFORECOMPLETE:"jwplayerMediaBeforeComplete",JWPLAYER_COMPONENT_SHOW:"jwplayerComponentShow",JWPLAYER_COMPONENT_HIDE:"jwplayerComponentHide", JWPLAYER_MEDIA_BUFFER:"jwplayerMediaBuffer",JWPLAYER_MEDIA_BUFFER_FULL:"jwplayerMediaBufferFull",JWPLAYER_MEDIA_ERROR:"jwplayerMediaError",JWPLAYER_MEDIA_LOADED:"jwplayerMediaLoaded",JWPLAYER_MEDIA_COMPLETE:"jwplayerMediaComplete",JWPLAYER_MEDIA_SEEK:"jwplayerMediaSeek",JWPLAYER_MEDIA_TIME:"jwplayerMediaTime",JWPLAYER_MEDIA_VOLUME:"jwplayerMediaVolume",JWPLAYER_MEDIA_META:"jwplayerMediaMeta",JWPLAYER_MEDIA_MUTE:"jwplayerMediaMute",JWPLAYER_MEDIA_LEVELS:"jwplayerMediaLevels",JWPLAYER_MEDIA_LEVEL_CHANGED:"jwplayerMediaLevelChanged", JWPLAYER_CAPTIONS_CHANGED:"jwplayerCaptionsChanged",JWPLAYER_CAPTIONS_LIST:"jwplayerCaptionsList",JWPLAYER_PLAYER_STATE:"jwplayerPlayerState",state:{BUFFERING:"BUFFERING",IDLE:"IDLE",PAUSED:"PAUSED",PLAYING:"PLAYING"},JWPLAYER_PLAYLIST_LOADED:"jwplayerPlaylistLoaded",JWPLAYER_PLAYLIST_ITEM:"jwplayerPlaylistItem",JWPLAYER_PLAYLIST_COMPLETE:"jwplayerPlaylistComplete",JWPLAYER_DISPLAY_CLICK:"jwplayerViewClick",JWPLAYER_CONTROLS:"jwplayerViewControls",JWPLAYER_INSTREAM_CLICK:"jwplayerInstreamClicked", JWPLAYER_INSTREAM_DESTROYED:"jwplayerInstreamDestroyed"}}(jwplayer),function(d){var a=jwplayer.utils;d.eventdispatcher=function(d,g){var c,b;this.resetEventListeners=function(){c={};b=[]};this.resetEventListeners();this.addEventListener=function(b,k,d){try{a.exists(c[b])||(c[b]=[]),"string"==a.typeOf(k)&&(k=(new Function("return "+k))()),c[b].push({listener:k,count:d})}catch(g){a.log("error",g)}return!1};this.removeEventListener=function(b,d){if(c[b]){try{for(var g=0;gparseFloat(d.version)))m=!0,n="Incompatible player version",b()}0==e&&b()}}var k=a.loaderstatus.NEW,l=!1,m=!1,n,q=c,j=new h.eventdispatcher;a.extend(this,j);this.setupPlugins=function(b,e,c){var f={length:0,plugins:{}},d=0,k={},h=g.getPlugins(),l;for(l in e.plugins){var j=a.getPluginName(l),m=h[j],B=m.getFlashPath(), n=m.getJS(),q=m.getURL();B&&(f.plugins[B]=a.extend({},e.plugins[l]),f.plugins[B].pluginmode=m.getPluginmode(),f.length++);try{if(n&&e.plugins&&e.plugins[q]){var v=document.createElement("div");v.id=b.id+"_"+j;v.style.position="absolute";v.style.top=0;v.style.zIndex=d+10;k[j]=m.getNewInstance(b,a.extend({},e.plugins[q]),v);d++;b.onReady(c(k[j],v,!0));b.onResize(c(k[j],v))}}catch(C){a.log("ERROR: Failed to load "+j+".")}}b.plugins=k;return f};this.load=function(){if(!(a.exists(c)&&"object"!=a.typeOf(c))){k= a.loaderstatus.LOADING;for(var b in c)if(a.exists(b)){var d=g.addPlugin(b);d.addEventListener(h.COMPLETE,e);d.addEventListener(h.ERROR,f)}d=g.getPlugins();for(b in d)d[b].load()}e()};var f=this.pluginFailed=function(){m||(m=!0,n="File not found",b())};this.getStatus=function(){return k}}}(jwplayer),function(d){d.playlist=function(a){var h=[];if("array"==d.utils.typeOf(a))for(var g=0;gm.playlist.length&&(0==m.playlist.length||!m.playlist[0].sources||0==m.playlist[0].sources.length))k();else if(s.getStatus()==a.loaderstatus.COMPLETE){for(var d=0;df)return j.sendEvent(h.ERROR,{message:"Flash version must be 10.0 or greater"}), !1;var d,p=a.extend({},e);c.id+"_wrapper"==c.parentNode.id?document.getElementById(c.id+"_wrapper"):(d=document.createElement("div"),d.id=c.id+"_wrapper",d.style.position="relative",d.style.width=a.styleDimension(p.width),d.style.height=a.styleDimension(p.height),c.parentNode.replaceChild(d,c),d.appendChild(c));d=k.setupPlugins(l,p,n);0=p.height?"transparent":"opaque";for(var s="height width modes events primary base fallback volume".split(" "),u=0;u Copyright (c) 2007-2008 Geoff Stearns, Michael Williams, and Bobby van der Sluis This software is released under the MIT License */ var swfobject=function(){var b="undefined",Q="object",n="Shockwave Flash",p="ShockwaveFlash.ShockwaveFlash",P="application/x-shockwave-flash",m="SWFObjectExprInst",j=window,K=document,T=navigator,o=[],N=[],i=[],d=[],J,Z=null,M=null,l=null,e=false,A=false;var h=function(){var v=typeof K.getElementById!=b&&typeof K.getElementsByTagName!=b&&typeof K.createElement!=b,AC=[0,0,0],x=null;if(typeof T.plugins!=b&&typeof T.plugins[n]==Q){x=T.plugins[n].description;if(x&&!(typeof T.mimeTypes!=b&&T.mimeTypes[P]&&!T.mimeTypes[P].enabledPlugin)){x=x.replace(/^.*\s+(\S+\s+\S+$)/,"$1");AC[0]=parseInt(x.replace(/^(.*)\..*$/,"$1"),10);AC[1]=parseInt(x.replace(/^.*\.(.*)\s.*$/,"$1"),10);AC[2]=/r/.test(x)?parseInt(x.replace(/^.*r(.*)$/,"$1"),10):0}}else{if(typeof j.ActiveXObject!=b){var y=null,AB=false;try{y=new ActiveXObject(p+".7")}catch(t){try{y=new ActiveXObject(p+".6");AC=[6,0,21];y.AllowScriptAccess="always"}catch(t){if(AC[0]==6){AB=true}}if(!AB){try{y=new ActiveXObject(p)}catch(t){}}}if(!AB&&y){try{x=y.GetVariable("$version");if(x){x=x.split(" ")[1].split(",");AC=[parseInt(x[0],10),parseInt(x[1],10),parseInt(x[2],10)]}}catch(t){}}}}var AD=T.userAgent.toLowerCase(),r=T.platform.toLowerCase(),AA=/webkit/.test(AD)?parseFloat(AD.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,q=false,z=r?/win/.test(r):/win/.test(AD),w=r?/mac/.test(r):/mac/.test(AD);/*@cc_on q=true;@if(@_win32)z=true;@elif(@_mac)w=true;@end@*/return{w3cdom:v,pv:AC,webkit:AA,ie:q,win:z,mac:w}}();var L=function(){if(!h.w3cdom){return }f(H);if(h.ie&&h.win){try{K.write("