├── 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 ├── bg.jpg ├── index.html ├── jwplayer ├── jwplayer.flash.swf └── jwplayer.js ├── jwplayer_old ├── player.swf └── swfobject.js └── record.html /AUTHORS: -------------------------------------------------------------------------------- 1 | Project author: 2 | 3 | Roman Arutyunyan 4 | Moscow, Russia 5 | 6 | Contacts: 7 | arut@qip.ru 8 | arutyunyan.roman@gmail.com 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2014, Roman Arutyunyan 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NGINX-based Media Streaming Server 2 | ## nginx-rtmp-module (add hevc support for rtmp and hls) 3 | 4 | 5 | ### Project blog 6 | 7 | http://nginx-rtmp.blogspot.com 8 | 9 | ### Wiki manual 10 | 11 | https://github.com/arut/nginx-rtmp-module/wiki/Directives 12 | 13 | ### Google group 14 | 15 | https://groups.google.com/group/nginx-rtmp 16 | 17 | https://groups.google.com/group/nginx-rtmp-ru (Russian) 18 | 19 | ### Donation page (Paypal etc) 20 | 21 | http://arut.github.com/nginx-rtmp-module/ 22 | 23 | ### Features 24 | 25 | * RTMP/HLS/MPEG-DASH live streaming 26 | 27 | * RTMP Video on demand FLV/MP4, 28 | playing from local filesystem or HTTP 29 | 30 | * Stream relay support for distributed 31 | streaming: push & pull models 32 | 33 | * Recording streams in multiple FLVs 34 | 35 | * H264/AAC support 36 | 37 | * Online transcoding with FFmpeg 38 | 39 | * HTTP callbacks (publish/play/record/update etc) 40 | 41 | * Running external programs on certain events (exec) 42 | 43 | * HTTP control module for recording audio/video and dropping clients 44 | 45 | * Advanced buffering techniques 46 | to keep memory allocations at a minimum 47 | level for faster streaming and low 48 | memory footprint 49 | 50 | * Proved to work with Wirecast, FMS, Wowza, 51 | JWPlayer, FlowPlayer, StrobeMediaPlayback, 52 | ffmpeg, avconv, rtmpdump, flvstreamer 53 | and many more 54 | 55 | * Statistics in XML/XSL in machine- & human- 56 | readable form 57 | 58 | * Linux/FreeBSD/MacOS/Windows 59 | 60 | ### Build 61 | 62 | cd to NGINX source directory & run this: 63 | 64 | ./configure --add-module=/path/to/nginx-rtmp-module 65 | make 66 | make install 67 | 68 | Several versions of nginx (1.3.14 - 1.5.0) require http_ssl_module to be 69 | added as well: 70 | 71 | ./configure --add-module=/path/to/nginx-rtmp-module --with-http_ssl_module 72 | 73 | For building debug version of nginx add `--with-debug` 74 | 75 | ./configure --add-module=/path/to-nginx/rtmp-module --with-debug 76 | 77 | [Read more about debug log](https://github.com/arut/nginx-rtmp-module/wiki/Debug-log) 78 | 79 | ### Windows limitations 80 | 81 | Windows support is limited. These features are not supported 82 | 83 | * execs 84 | * static pulls 85 | * auto_push 86 | 87 | ### RTMP URL format 88 | 89 | rtmp://rtmp.example.com/app[/name] 90 | 91 | app - should match one of application {} 92 | blocks in config 93 | 94 | name - interpreted by each application 95 | can be empty 96 | 97 | 98 | ### Multi-worker live streaming 99 | 100 | Module supports multi-worker live 101 | streaming through automatic stream pushing 102 | to nginx workers. This option is toggled with 103 | rtmp_auto_push directive. 104 | 105 | 106 | ### Example nginx.conf 107 | 108 | rtmp { 109 | 110 | server { 111 | 112 | listen 1935; 113 | 114 | chunk_size 4000; 115 | 116 | # TV mode: one publisher, many subscribers 117 | application mytv { 118 | 119 | # enable live streaming 120 | live on; 121 | 122 | # record first 1K of stream 123 | record all; 124 | record_path /tmp/av; 125 | record_max_size 1K; 126 | 127 | # append current timestamp to each flv 128 | record_unique on; 129 | 130 | # publish only from localhost 131 | allow publish 127.0.0.1; 132 | deny publish all; 133 | 134 | #allow play all; 135 | } 136 | 137 | # Transcoding (ffmpeg needed) 138 | application big { 139 | live on; 140 | 141 | # On every pusblished stream run this command (ffmpeg) 142 | # with substitutions: $app/${app}, $name/${name} for application & stream name. 143 | # 144 | # This ffmpeg call receives stream from this application & 145 | # reduces the resolution down to 32x32. The stream is the published to 146 | # 'small' application (see below) under the same name. 147 | # 148 | # ffmpeg can do anything with the stream like video/audio 149 | # transcoding, resizing, altering container/codec params etc 150 | # 151 | # Multiple exec lines can be specified. 152 | 153 | exec ffmpeg -re -i rtmp://localhost:1935/$app/$name -vcodec flv -acodec copy -s 32x32 154 | -f flv rtmp://localhost:1935/small/${name}; 155 | } 156 | 157 | application small { 158 | live on; 159 | # Video with reduced resolution comes here from ffmpeg 160 | } 161 | 162 | application webcam { 163 | live on; 164 | 165 | # Stream from local webcam 166 | exec_static ffmpeg -f video4linux2 -i /dev/video0 -c:v libx264 -an 167 | -f flv rtmp://localhost:1935/webcam/mystream; 168 | } 169 | 170 | application mypush { 171 | live on; 172 | 173 | # Every stream published here 174 | # is automatically pushed to 175 | # these two machines 176 | push rtmp1.example.com; 177 | push rtmp2.example.com:1934; 178 | } 179 | 180 | application mypull { 181 | live on; 182 | 183 | # Pull all streams from remote machine 184 | # and play locally 185 | pull rtmp://rtmp3.example.com pageUrl=www.example.com/index.html; 186 | } 187 | 188 | application mystaticpull { 189 | live on; 190 | 191 | # Static pull is started at nginx start 192 | pull rtmp://rtmp4.example.com pageUrl=www.example.com/index.html name=mystream static; 193 | } 194 | 195 | # video on demand 196 | application vod { 197 | play /var/flvs; 198 | } 199 | 200 | application vod2 { 201 | play /var/mp4s; 202 | } 203 | 204 | # Many publishers, many subscribers 205 | # no checks, no recording 206 | application videochat { 207 | 208 | live on; 209 | 210 | # The following notifications receive all 211 | # the session variables as well as 212 | # particular call arguments in HTTP POST 213 | # request 214 | 215 | # Make HTTP request & use HTTP retcode 216 | # to decide whether to allow publishing 217 | # from this connection or not 218 | on_publish http://localhost:8080/publish; 219 | 220 | # Same with playing 221 | on_play http://localhost:8080/play; 222 | 223 | # Publish/play end (repeats on disconnect) 224 | on_done http://localhost:8080/done; 225 | 226 | # All above mentioned notifications receive 227 | # standard connect() arguments as well as 228 | # play/publish ones. If any arguments are sent 229 | # with GET-style syntax to play & publish 230 | # these are also included. 231 | # Example URL: 232 | # rtmp://localhost/myapp/mystream?a=b&c=d 233 | 234 | # record 10 video keyframes (no audio) every 2 minutes 235 | record keyframes; 236 | record_path /tmp/vc; 237 | record_max_frames 10; 238 | record_interval 2m; 239 | 240 | # Async notify about an flv recorded 241 | on_record_done http://localhost:8080/record_done; 242 | 243 | } 244 | 245 | 246 | # HLS 247 | 248 | # For HLS to work please create a directory in tmpfs (/tmp/hls here) 249 | # for the fragments. The directory contents is served via HTTP (see 250 | # http{} section in config) 251 | # 252 | # Incoming stream must be in H264/AAC. For iPhones use baseline H264 253 | # profile (see ffmpeg example). 254 | # This example creates RTMP stream from movie ready for HLS: 255 | # 256 | # ffmpeg -loglevel verbose -re -i movie.avi -vcodec libx264 257 | # -vprofile baseline -acodec libmp3lame -ar 44100 -ac 1 258 | # -f flv rtmp://localhost:1935/hls/movie 259 | # 260 | # If you need to transcode live stream use 'exec' feature. 261 | # 262 | application hls { 263 | live on; 264 | hls on; 265 | hls_path /tmp/hls; 266 | } 267 | 268 | # MPEG-DASH is similar to HLS 269 | 270 | application dash { 271 | live on; 272 | dash on; 273 | dash_path /tmp/dash; 274 | } 275 | } 276 | } 277 | 278 | # HTTP can be used for accessing RTMP stats 279 | http { 280 | 281 | server { 282 | 283 | listen 8080; 284 | 285 | # This URL provides RTMP statistics in XML 286 | location /stat { 287 | rtmp_stat all; 288 | 289 | # Use this stylesheet to view XML as web page 290 | # in browser 291 | rtmp_stat_stylesheet stat.xsl; 292 | } 293 | 294 | location /stat.xsl { 295 | # XML stylesheet to view RTMP stats. 296 | # Copy stat.xsl wherever you want 297 | # and put the full directory path here 298 | root /path/to/stat.xsl/; 299 | } 300 | 301 | location /hls { 302 | # Serve HLS fragments 303 | types { 304 | application/vnd.apple.mpegurl m3u8; 305 | video/mp2t ts; 306 | } 307 | root /tmp; 308 | add_header Cache-Control no-cache; 309 | } 310 | 311 | location /dash { 312 | # Serve DASH fragments 313 | root /tmp; 314 | add_header Cache-Control no-cache; 315 | } 316 | } 317 | } 318 | 319 | 320 | ### Multi-worker streaming example 321 | 322 | rtmp_auto_push on; 323 | 324 | rtmp { 325 | server { 326 | listen 1935; 327 | 328 | application mytv { 329 | live on; 330 | } 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | ngx_addon_name="ngx_rtmp_module" 2 | 3 | RTMP_CORE_MODULES=" \ 4 | ngx_rtmp_module \ 5 | ngx_rtmp_core_module \ 6 | ngx_rtmp_cmd_module \ 7 | ngx_rtmp_codec_module \ 8 | ngx_rtmp_access_module \ 9 | ngx_rtmp_record_module \ 10 | ngx_rtmp_live_module \ 11 | ngx_rtmp_play_module \ 12 | ngx_rtmp_flv_module \ 13 | ngx_rtmp_mp4_module \ 14 | ngx_rtmp_netcall_module \ 15 | ngx_rtmp_relay_module \ 16 | ngx_rtmp_exec_module \ 17 | ngx_rtmp_auto_push_module \ 18 | ngx_rtmp_auto_push_index_module \ 19 | ngx_rtmp_notify_module \ 20 | ngx_rtmp_log_module \ 21 | ngx_rtmp_limit_module \ 22 | ngx_rtmp_hls_module \ 23 | ngx_rtmp_dash_module \ 24 | " 25 | 26 | 27 | RTMP_HTTP_MODULES=" \ 28 | ngx_rtmp_stat_module \ 29 | ngx_rtmp_control_module \ 30 | " 31 | 32 | 33 | RTMP_DEPS=" \ 34 | $ngx_addon_dir/ngx_rtmp_amf.h \ 35 | $ngx_addon_dir/ngx_rtmp_bandwidth.h \ 36 | $ngx_addon_dir/ngx_rtmp_cmd_module.h \ 37 | $ngx_addon_dir/ngx_rtmp_codec_module.h \ 38 | $ngx_addon_dir/ngx_rtmp_eval.h \ 39 | $ngx_addon_dir/ngx_rtmp.h \ 40 | $ngx_addon_dir/ngx_rtmp_version.h \ 41 | $ngx_addon_dir/ngx_rtmp_live_module.h \ 42 | $ngx_addon_dir/ngx_rtmp_netcall_module.h \ 43 | $ngx_addon_dir/ngx_rtmp_play_module.h \ 44 | $ngx_addon_dir/ngx_rtmp_record_module.h \ 45 | $ngx_addon_dir/ngx_rtmp_relay_module.h \ 46 | $ngx_addon_dir/ngx_rtmp_streams.h \ 47 | $ngx_addon_dir/ngx_rtmp_bitop.h \ 48 | $ngx_addon_dir/ngx_rtmp_proxy_protocol.h \ 49 | $ngx_addon_dir/hls/ngx_rtmp_mpegts.h \ 50 | $ngx_addon_dir/dash/ngx_rtmp_mp4.h \ 51 | " 52 | 53 | 54 | RTMP_CORE_SRCS=" \ 55 | $ngx_addon_dir/ngx_rtmp.c \ 56 | $ngx_addon_dir/ngx_rtmp_init.c \ 57 | $ngx_addon_dir/ngx_rtmp_handshake.c \ 58 | $ngx_addon_dir/ngx_rtmp_handler.c \ 59 | $ngx_addon_dir/ngx_rtmp_amf.c \ 60 | $ngx_addon_dir/ngx_rtmp_send.c \ 61 | $ngx_addon_dir/ngx_rtmp_shared.c \ 62 | $ngx_addon_dir/ngx_rtmp_eval.c \ 63 | $ngx_addon_dir/ngx_rtmp_receive.c \ 64 | $ngx_addon_dir/ngx_rtmp_core_module.c \ 65 | $ngx_addon_dir/ngx_rtmp_cmd_module.c \ 66 | $ngx_addon_dir/ngx_rtmp_codec_module.c \ 67 | $ngx_addon_dir/ngx_rtmp_access_module.c \ 68 | $ngx_addon_dir/ngx_rtmp_record_module.c \ 69 | $ngx_addon_dir/ngx_rtmp_live_module.c \ 70 | $ngx_addon_dir/ngx_rtmp_play_module.c \ 71 | $ngx_addon_dir/ngx_rtmp_flv_module.c \ 72 | $ngx_addon_dir/ngx_rtmp_mp4_module.c \ 73 | $ngx_addon_dir/ngx_rtmp_netcall_module.c \ 74 | $ngx_addon_dir/ngx_rtmp_relay_module.c \ 75 | $ngx_addon_dir/ngx_rtmp_bandwidth.c \ 76 | $ngx_addon_dir/ngx_rtmp_exec_module.c \ 77 | $ngx_addon_dir/ngx_rtmp_auto_push_module.c \ 78 | $ngx_addon_dir/ngx_rtmp_notify_module.c \ 79 | $ngx_addon_dir/ngx_rtmp_log_module.c \ 80 | $ngx_addon_dir/ngx_rtmp_limit_module.c \ 81 | $ngx_addon_dir/ngx_rtmp_bitop.c \ 82 | $ngx_addon_dir/ngx_rtmp_proxy_protocol.c \ 83 | $ngx_addon_dir/hls/ngx_rtmp_hls_module.c \ 84 | $ngx_addon_dir/dash/ngx_rtmp_dash_module.c \ 85 | $ngx_addon_dir/hls/ngx_rtmp_mpegts.c \ 86 | $ngx_addon_dir/dash/ngx_rtmp_mp4.c \ 87 | " 88 | 89 | 90 | RTMP_HTTP_SRCS=" \ 91 | $ngx_addon_dir/ngx_rtmp_stat_module.c \ 92 | $ngx_addon_dir/ngx_rtmp_control_module.c \ 93 | " 94 | 95 | if [ -f auto/module ] ; then 96 | ngx_module_incs=$ngx_addon_dir 97 | ngx_module_deps=$RTMP_DEPS 98 | 99 | if [ $ngx_module_link = DYNAMIC ] ; then 100 | ngx_module_name="$RTMP_CORE_MODULES $RTMP_HTTP_MODULES" 101 | ngx_module_srcs="$RTMP_CORE_SRCS $RTMP_HTTP_SRCS" 102 | 103 | . auto/module 104 | 105 | else 106 | ngx_module_type=CORE 107 | ngx_module_name=$RTMP_CORE_MODULES 108 | ngx_module_srcs=$RTMP_CORE_SRCS 109 | 110 | . auto/module 111 | 112 | 113 | ngx_module_type=HTTP 114 | ngx_module_name=$RTMP_HTTP_MODULES 115 | ngx_module_incs= 116 | ngx_module_deps= 117 | ngx_module_srcs=$RTMP_HTTP_SRCS 118 | 119 | . auto/module 120 | fi 121 | 122 | else 123 | CORE_MODULES="$CORE_MODULES $RTMP_CORE_MODULES" 124 | HTTP_MODULES="$HTTP_MODULES $RTMP_HTTP_MODULES" 125 | 126 | NGX_ADDON_DEPS="$NGX_ADDON_DEPS $RTMP_DEPS" 127 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $RTMP_CORE_SRCS $RTMP_HTTP_SRCS" 128 | 129 | CFLAGS="$CFLAGS -I$ngx_addon_dir" 130 | fi 131 | 132 | USE_OPENSSL=YES 133 | 134 | -------------------------------------------------------------------------------- /dash/ngx_rtmp_mp4.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ifndef _NGX_RTMP_MP4_H_INCLUDED_ 4 | #define _NGX_RTMP_MP4_H_INCLUDED_ 5 | 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | #define NGX_RTMP_MP4_SAMPLE_SIZE 0x01 13 | #define NGX_RTMP_MP4_SAMPLE_DURATION 0x02 14 | #define NGX_RTMP_MP4_SAMPLE_DELAY 0x04 15 | #define NGX_RTMP_MP4_SAMPLE_KEY 0x08 16 | 17 | 18 | typedef struct { 19 | uint32_t size; 20 | uint32_t duration; 21 | uint32_t delay; 22 | uint32_t timestamp; 23 | unsigned key:1; 24 | } ngx_rtmp_mp4_sample_t; 25 | 26 | 27 | typedef enum { 28 | NGX_RTMP_MP4_FILETYPE_INIT, 29 | NGX_RTMP_MP4_FILETYPE_SEG 30 | } ngx_rtmp_mp4_file_type_t; 31 | 32 | 33 | typedef enum { 34 | NGX_RTMP_MP4_VIDEO_TRACK, 35 | NGX_RTMP_MP4_AUDIO_TRACK 36 | } ngx_rtmp_mp4_track_type_t; 37 | 38 | 39 | ngx_int_t ngx_rtmp_mp4_write_ftyp(ngx_buf_t *b); 40 | ngx_int_t ngx_rtmp_mp4_write_styp(ngx_buf_t *b); 41 | ngx_int_t ngx_rtmp_mp4_write_moov(ngx_rtmp_session_t *s, ngx_buf_t *b, 42 | ngx_rtmp_mp4_track_type_t ttype); 43 | ngx_int_t ngx_rtmp_mp4_write_moof(ngx_buf_t *b, uint32_t earliest_pres_time, 44 | uint32_t sample_count, ngx_rtmp_mp4_sample_t *samples, 45 | ngx_uint_t sample_mask, uint32_t index); 46 | ngx_int_t ngx_rtmp_mp4_write_sidx(ngx_buf_t *b, 47 | ngx_uint_t reference_size, uint32_t earliest_pres_time, 48 | uint32_t latest_pres_time); 49 | ngx_uint_t ngx_rtmp_mp4_write_mdat(ngx_buf_t *b, ngx_uint_t size); 50 | 51 | 52 | #endif /* _NGX_RTMP_MP4_H_INCLUDED_ */ 53 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | Documentation is available here: 2 | https://github.com/arut/nginx-rtmp-module/wiki 3 | -------------------------------------------------------------------------------- /hls/ngx_rtmp_mpegts.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Roman Arutyunyan 4 | */ 5 | 6 | 7 | #include 8 | #include 9 | #include "ngx_rtmp_mpegts.h" 10 | #include "ngx_rtmp_bitop.h" 11 | #include "ngx_rtmp.h" 12 | 13 | 14 | static u_char ngx_rtmp_mpegts_header[] = { 15 | 16 | /* TS */ 17 | 0x47, 0x40, 0x00, 0x10, 0x00, 18 | /* PSI */ 19 | 0x00, 0xb0, 0x0d, 0x00, 0x01, 0xc1, 0x00, 0x00, 20 | 21 | /* 0x00, 0x01, 0xf0, 0x01, */ 22 | /* 0x2e, 0x70, 0x19, 0x05, */ 23 | 24 | 0x00, 0x01, 0xf0, 0x00, 25 | 0x2a, 0xb1, 0x04, 0xb2, 26 | 27 | /* stuffing 167 bytes */ 28 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 29 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 30 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 31 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 32 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 33 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 34 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 35 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 36 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 37 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 38 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 39 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 40 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 41 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 42 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 43 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 44 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 45 | 46 | /* TS */ 47 | /* 0x47, 0x50, 0x01, 0x10, 0x00, */ 48 | 0x47, 0x50, 0x00, 0x10, 0x00, 49 | /* PSI */ 50 | 0x02, 0xb0, 0x17, 0x00, 0x01, 0xc1, 0x00, 0x00, 51 | /* PMT */ 52 | 0xe1, 0x00, 53 | 0xf0, 0x00, 54 | 0x1b, 0xe1, 0x00, 0xf0, 0x00, /* h264 */ 55 | 0x0f, 0xe1, 0x01, 0xf0, 0x00, /* aac */ 56 | /*0x03, 0xe1, 0x01, 0xf0, 0x00,*/ /* mp3 */ 57 | /* CRC */ 58 | /* crc for h264 aac */ 59 | 0x2f, 0x44, 0xb9, 0x9b, 60 | 61 | /* crc for h264 mp3 */ 62 | /* 0x4e, 0x59, 0x3d, 0x1e, */ 63 | 64 | /* crc for h265 aac */ 65 | /* 0xc7, 0x72, 0xb7, 0xcb, */ 66 | 67 | /* stuffing 157 bytes */ 68 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 69 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 70 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 71 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 72 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 73 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 74 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 75 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 76 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 77 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 78 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 79 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 80 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 81 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 82 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 83 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff 84 | }; 85 | 86 | 87 | /* 700 ms PCR delay */ 88 | #define NGX_RTMP_HLS_DELAY 63000 89 | 90 | 91 | static ngx_int_t 92 | ngx_rtmp_mpegts_write_file(ngx_rtmp_mpegts_file_t *file, u_char *in, 93 | size_t in_size) 94 | { 95 | u_char *out; 96 | size_t out_size, n; 97 | ssize_t rc; 98 | 99 | static u_char buf[1024]; 100 | 101 | if (!file->encrypt) { 102 | ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0, 103 | "mpegts: write %uz bytes", in_size); 104 | 105 | rc = ngx_write_fd(file->fd, in, in_size); 106 | if (rc < 0) { 107 | return NGX_ERROR; 108 | } 109 | 110 | return NGX_OK; 111 | } 112 | 113 | /* encrypt */ 114 | 115 | ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0, 116 | "mpegts: write %uz encrypted bytes", in_size); 117 | 118 | out = buf; 119 | out_size = sizeof(buf); 120 | 121 | if (file->size > 0 && file->size + in_size >= 16) { 122 | ngx_memcpy(file->buf + file->size, in, 16 - file->size); 123 | 124 | in += 16 - file->size; 125 | in_size -= 16 - file->size; 126 | 127 | AES_cbc_encrypt(file->buf, out, 16, &file->key, file->iv, AES_ENCRYPT); 128 | 129 | out += 16; 130 | out_size -= 16; 131 | 132 | file->size = 0; 133 | } 134 | 135 | for ( ;; ) { 136 | n = in_size & ~0x0f; 137 | 138 | if (n > 0) { 139 | if (n > out_size) { 140 | n = out_size; 141 | } 142 | 143 | AES_cbc_encrypt(in, out, n, &file->key, file->iv, AES_ENCRYPT); 144 | 145 | in += n; 146 | in_size -= n; 147 | 148 | } else if (out == buf) { 149 | break; 150 | } 151 | 152 | rc = ngx_write_fd(file->fd, buf, out - buf + n); 153 | if (rc < 0) { 154 | return NGX_ERROR; 155 | } 156 | 157 | out = buf; 158 | out_size = sizeof(buf); 159 | } 160 | 161 | if (in_size) { 162 | ngx_memcpy(file->buf + file->size, in, in_size); 163 | file->size += in_size; 164 | } 165 | 166 | return NGX_OK; 167 | } 168 | 169 | 170 | static ngx_int_t 171 | ngx_rtmp_mpegts_write_header(ngx_rtmp_mpegts_file_t *file) 172 | { 173 | //tag codec id hevc=12 avc=7 174 | if(file->video_codec_id == NGX_RTMP_VIDEOTAG_CODECID_HEVC){ 175 | //hevc stream_type=0x24 176 | ngx_rtmp_mpegts_header[205] = 0x24; 177 | //crc for h265 and aac 178 | ngx_rtmp_mpegts_header[215] = 0xc7; 179 | ngx_rtmp_mpegts_header[216] = 0x72; 180 | ngx_rtmp_mpegts_header[217] = 0xb7; 181 | ngx_rtmp_mpegts_header[218] = 0xcb; 182 | }else if(file->video_codec_id == NGX_RTMP_VIDEOTAG_CODECID_AVC){ 183 | //avc stream_type=0x1b 184 | ngx_rtmp_mpegts_header[205] = 0x1b; 185 | //crc for h264 and aac 186 | ngx_rtmp_mpegts_header[215] = 0x2f; 187 | ngx_rtmp_mpegts_header[216] = 0x44; 188 | ngx_rtmp_mpegts_header[217] = 0xb9; 189 | ngx_rtmp_mpegts_header[218] = 0x9b; 190 | } 191 | return ngx_rtmp_mpegts_write_file(file, ngx_rtmp_mpegts_header, sizeof(ngx_rtmp_mpegts_header)); 192 | } 193 | 194 | 195 | static u_char * 196 | ngx_rtmp_mpegts_write_pcr(u_char *p, uint64_t pcr) 197 | { 198 | *p++ = (u_char) (pcr >> 25); 199 | *p++ = (u_char) (pcr >> 17); 200 | *p++ = (u_char) (pcr >> 9); 201 | *p++ = (u_char) (pcr >> 1); 202 | *p++ = (u_char) (pcr << 7 | 0x7e); 203 | *p++ = 0; 204 | 205 | return p; 206 | } 207 | 208 | 209 | static u_char * 210 | ngx_rtmp_mpegts_write_pts(u_char *p, ngx_uint_t fb, uint64_t pts) 211 | { 212 | ngx_uint_t val; 213 | 214 | val = fb << 4 | (((pts >> 30) & 0x07) << 1) | 1; 215 | *p++ = (u_char) val; 216 | 217 | val = (((pts >> 15) & 0x7fff) << 1) | 1; 218 | *p++ = (u_char) (val >> 8); 219 | *p++ = (u_char) val; 220 | 221 | val = (((pts) & 0x7fff) << 1) | 1; 222 | *p++ = (u_char) (val >> 8); 223 | *p++ = (u_char) val; 224 | 225 | return p; 226 | } 227 | 228 | 229 | ngx_int_t 230 | ngx_rtmp_mpegts_write_frame(ngx_rtmp_mpegts_file_t *file, 231 | ngx_rtmp_mpegts_frame_t *f, ngx_buf_t *b) 232 | { 233 | ngx_uint_t pes_size, header_size, body_size, in_size, stuff_size, flags; 234 | u_char packet[188], *p, *base; 235 | ngx_int_t first, rc; 236 | 237 | ngx_log_debug6(NGX_LOG_DEBUG_CORE, file->log, 0, 238 | "mpegts: pid=%ui, sid=%ui, pts=%uL, " 239 | "dts=%uL, key=%ui, size=%ui", 240 | f->pid, f->sid, f->pts, f->dts, 241 | (ngx_uint_t) f->key, (size_t) (b->last - b->pos)); 242 | 243 | first = 1; 244 | 245 | while (b->pos < b->last) { 246 | p = packet; 247 | /* f->cc++; */ 248 | 249 | *p++ = 0x47; 250 | *p++ = (u_char) (f->pid >> 8); 251 | 252 | if (first) { 253 | p[-1] |= 0x40; 254 | } 255 | 256 | *p++ = (u_char) f->pid; 257 | *p++ = 0x10 | (f->cc & 0x0f); /* payload */ 258 | 259 | if (first) { 260 | 261 | if (f->key) { 262 | packet[3] |= 0x20; /* adaptation */ 263 | 264 | *p++ = 7; /* size */ 265 | *p++ = 0x50; /* random access + PCR */ 266 | 267 | p = ngx_rtmp_mpegts_write_pcr(p, f->dts - NGX_RTMP_HLS_DELAY); 268 | } 269 | 270 | /* PES header */ 271 | 272 | *p++ = 0x00; 273 | *p++ = 0x00; 274 | *p++ = 0x01; 275 | *p++ = (u_char) f->sid; 276 | 277 | header_size = 5; 278 | flags = 0x80; /* PTS */ 279 | 280 | if (f->dts != f->pts) { 281 | header_size += 5; 282 | flags |= 0x40; /* DTS */ 283 | } 284 | 285 | pes_size = (b->last - b->pos) + header_size + 3; 286 | if (pes_size > 0xffff) { 287 | pes_size = 0; 288 | } 289 | 290 | /* *p++ = (u_char) (pes_size >> 8); */ 291 | /* *p++ = (u_char) pes_size; */ 292 | 293 | *p++ = 0x00; 294 | *p++ = 0x00; 295 | 296 | *p++ = 0x80; /* H222 */ 297 | *p++ = (u_char) flags; 298 | *p++ = (u_char) header_size; 299 | 300 | p = ngx_rtmp_mpegts_write_pts(p, flags >> 6, f->pts + 301 | NGX_RTMP_HLS_DELAY); 302 | 303 | if (f->dts != f->pts) { 304 | p = ngx_rtmp_mpegts_write_pts(p, 1, f->dts + 305 | NGX_RTMP_HLS_DELAY); 306 | } 307 | 308 | first = 0; 309 | } 310 | 311 | body_size = (ngx_uint_t) (packet + sizeof(packet) - p); 312 | in_size = (ngx_uint_t) (b->last - b->pos); 313 | 314 | if (body_size <= in_size) { 315 | ngx_memcpy(p, b->pos, body_size); 316 | b->pos += body_size; 317 | 318 | } else { 319 | stuff_size = (body_size - in_size); 320 | 321 | if (packet[3] & 0x20) { 322 | 323 | /* has adaptation */ 324 | 325 | base = &packet[5] + packet[4]; 326 | p = ngx_movemem(base + stuff_size, base, p - base); 327 | ngx_memset(base, 0xff, stuff_size); 328 | packet[4] += (u_char) stuff_size; 329 | 330 | } else { 331 | 332 | /* no adaptation */ 333 | 334 | packet[3] |= 0x20; 335 | p = ngx_movemem(&packet[4] + stuff_size, &packet[4], 336 | p - &packet[4]); 337 | 338 | packet[4] = (u_char) (stuff_size - 1); 339 | if (stuff_size >= 2) { 340 | packet[5] = 0; 341 | ngx_memset(&packet[6], 0xff, stuff_size - 2); 342 | } 343 | } 344 | 345 | ngx_memcpy(p, b->pos, in_size); 346 | b->pos = b->last; 347 | } 348 | ngx_rtmp_hex_dump(file->log, "ngx_rtmp_mpegts_write_frame ts:", packet, packet+188); 349 | 350 | rc = ngx_rtmp_mpegts_write_file(file, packet, sizeof(packet)); 351 | if (rc != NGX_OK) { 352 | return rc; 353 | } 354 | 355 | //ffmpeg idr frame cc bgein from 0 356 | f->cc++; 357 | } 358 | 359 | return NGX_OK; 360 | } 361 | 362 | 363 | ngx_int_t 364 | ngx_rtmp_mpegts_init_encryption(ngx_rtmp_mpegts_file_t *file, 365 | u_char *key, size_t key_len, uint64_t iv) 366 | { 367 | if (AES_set_encrypt_key(key, key_len * 8, &file->key)) { 368 | return NGX_ERROR; 369 | } 370 | 371 | ngx_memzero(file->iv, 8); 372 | 373 | file->iv[8] = (u_char) (iv >> 56); 374 | file->iv[9] = (u_char) (iv >> 48); 375 | file->iv[10] = (u_char) (iv >> 40); 376 | file->iv[11] = (u_char) (iv >> 32); 377 | file->iv[12] = (u_char) (iv >> 24); 378 | file->iv[13] = (u_char) (iv >> 16); 379 | file->iv[14] = (u_char) (iv >> 8); 380 | file->iv[15] = (u_char) (iv); 381 | 382 | file->encrypt = 1; 383 | 384 | return NGX_OK; 385 | } 386 | 387 | 388 | ngx_int_t 389 | ngx_rtmp_mpegts_open_file(ngx_rtmp_mpegts_file_t *file, u_char *path, 390 | ngx_log_t *log) 391 | { 392 | file->log = log; 393 | 394 | file->fd = ngx_open_file(path, NGX_FILE_WRONLY, NGX_FILE_TRUNCATE, 395 | NGX_FILE_DEFAULT_ACCESS); 396 | 397 | if (file->fd == NGX_INVALID_FILE) { 398 | ngx_log_error(NGX_LOG_ERR, log, ngx_errno, 399 | "hls: error creating fragment file"); 400 | return NGX_ERROR; 401 | } 402 | 403 | file->size = 0; 404 | 405 | if (ngx_rtmp_mpegts_write_header(file) != NGX_OK) { 406 | ngx_log_error(NGX_LOG_ERR, log, ngx_errno, 407 | "hls: error writing fragment header"); 408 | ngx_close_file(file->fd); 409 | return NGX_ERROR; 410 | } 411 | 412 | return NGX_OK; 413 | } 414 | 415 | 416 | ngx_int_t 417 | ngx_rtmp_mpegts_close_file(ngx_rtmp_mpegts_file_t *file) 418 | { 419 | u_char buf[16]; 420 | ssize_t rc; 421 | 422 | if (file->encrypt) { 423 | ngx_memset(file->buf + file->size, 16 - file->size, 16 - file->size); 424 | 425 | AES_cbc_encrypt(file->buf, buf, 16, &file->key, file->iv, AES_ENCRYPT); 426 | 427 | rc = ngx_write_fd(file->fd, buf, 16); 428 | if (rc < 0) { 429 | return NGX_ERROR; 430 | } 431 | } 432 | 433 | ngx_close_file(file->fd); 434 | 435 | return NGX_OK; 436 | } 437 | -------------------------------------------------------------------------------- /hls/ngx_rtmp_mpegts.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Roman Arutyunyan 4 | */ 5 | 6 | 7 | #ifndef _NGX_RTMP_MPEGTS_H_INCLUDED_ 8 | #define _NGX_RTMP_MPEGTS_H_INCLUDED_ 9 | 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | 16 | typedef struct { 17 | ngx_fd_t fd; 18 | ngx_log_t *log; 19 | unsigned encrypt:1; 20 | unsigned size:4; 21 | u_char buf[16]; 22 | u_char iv[16]; 23 | AES_KEY key; 24 | unsigned video_codec_id; 25 | } ngx_rtmp_mpegts_file_t; 26 | 27 | 28 | typedef struct { 29 | uint64_t pts; 30 | uint64_t dts; 31 | ngx_uint_t pid; 32 | ngx_uint_t sid; 33 | ngx_uint_t cc; 34 | unsigned key:1; 35 | } ngx_rtmp_mpegts_frame_t; 36 | 37 | 38 | ngx_int_t ngx_rtmp_mpegts_init_encryption(ngx_rtmp_mpegts_file_t *file, 39 | u_char *key, size_t key_len, uint64_t iv); 40 | ngx_int_t ngx_rtmp_mpegts_open_file(ngx_rtmp_mpegts_file_t *file, u_char *path, 41 | ngx_log_t *log); 42 | ngx_int_t ngx_rtmp_mpegts_close_file(ngx_rtmp_mpegts_file_t *file); 43 | ngx_int_t ngx_rtmp_mpegts_write_frame(ngx_rtmp_mpegts_file_t *file, 44 | ngx_rtmp_mpegts_frame_t *f, ngx_buf_t *b); 45 | 46 | 47 | #endif /* _NGX_RTMP_MPEGTS_H_INCLUDED_ */ 48 | -------------------------------------------------------------------------------- /ngx_rtmp_access_module.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Roman Arutyunyan 4 | */ 5 | 6 | 7 | #include 8 | #include 9 | #include "ngx_rtmp.h" 10 | #include "ngx_rtmp_cmd_module.h" 11 | 12 | 13 | static ngx_rtmp_publish_pt next_publish; 14 | static ngx_rtmp_play_pt next_play; 15 | 16 | 17 | #define NGX_RTMP_ACCESS_PUBLISH 0x01 18 | #define NGX_RTMP_ACCESS_PLAY 0x02 19 | 20 | 21 | static char * ngx_rtmp_access_rule(ngx_conf_t *cf, ngx_command_t *cmd, 22 | void *conf); 23 | static ngx_int_t ngx_rtmp_access_postconfiguration(ngx_conf_t *cf); 24 | static void * ngx_rtmp_access_create_app_conf(ngx_conf_t *cf); 25 | static char * ngx_rtmp_access_merge_app_conf(ngx_conf_t *cf, 26 | void *parent, void *child); 27 | 28 | 29 | typedef struct { 30 | in_addr_t mask; 31 | in_addr_t addr; 32 | ngx_uint_t deny; 33 | ngx_uint_t flags; 34 | } ngx_rtmp_access_rule_t; 35 | 36 | 37 | #if (NGX_HAVE_INET6) 38 | 39 | typedef struct { 40 | struct in6_addr addr; 41 | struct in6_addr mask; 42 | ngx_uint_t deny; 43 | ngx_uint_t flags; 44 | } ngx_rtmp_access_rule6_t; 45 | 46 | #endif 47 | 48 | 49 | typedef struct { 50 | ngx_array_t rules; /* array of ngx_rtmp_access_rule_t */ 51 | #if (NGX_HAVE_INET6) 52 | ngx_array_t rules6; /* array of ngx_rtmp_access_rule6_t */ 53 | #endif 54 | } ngx_rtmp_access_app_conf_t; 55 | 56 | 57 | static ngx_command_t ngx_rtmp_access_commands[] = { 58 | 59 | { ngx_string("allow"), 60 | NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE12, 61 | ngx_rtmp_access_rule, 62 | NGX_RTMP_APP_CONF_OFFSET, 63 | 0, 64 | NULL }, 65 | 66 | { ngx_string("deny"), 67 | NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE12, 68 | ngx_rtmp_access_rule, 69 | NGX_RTMP_APP_CONF_OFFSET, 70 | 0, 71 | NULL }, 72 | 73 | ngx_null_command 74 | }; 75 | 76 | 77 | static ngx_rtmp_module_t ngx_rtmp_access_module_ctx = { 78 | NULL, /* preconfiguration */ 79 | ngx_rtmp_access_postconfiguration, /* postconfiguration */ 80 | NULL, /* create main configuration */ 81 | NULL, /* init main configuration */ 82 | NULL, /* create server configuration */ 83 | NULL, /* merge server configuration */ 84 | ngx_rtmp_access_create_app_conf, /* create app configuration */ 85 | ngx_rtmp_access_merge_app_conf, /* merge app configuration */ 86 | }; 87 | 88 | 89 | ngx_module_t ngx_rtmp_access_module = { 90 | NGX_MODULE_V1, 91 | &ngx_rtmp_access_module_ctx, /* module context */ 92 | ngx_rtmp_access_commands, /* module directives */ 93 | NGX_RTMP_MODULE, /* module type */ 94 | NULL, /* init master */ 95 | NULL, /* init module */ 96 | NULL, /* init process */ 97 | NULL, /* init thread */ 98 | NULL, /* exit thread */ 99 | NULL, /* exit process */ 100 | NULL, /* exit master */ 101 | NGX_MODULE_V1_PADDING 102 | }; 103 | 104 | 105 | static void * 106 | ngx_rtmp_access_create_app_conf(ngx_conf_t *cf) 107 | { 108 | ngx_rtmp_access_app_conf_t *aacf; 109 | 110 | aacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_access_app_conf_t)); 111 | if (aacf == NULL) { 112 | return NULL; 113 | } 114 | 115 | if (ngx_array_init(&aacf->rules, cf->pool, 1, 116 | sizeof(ngx_rtmp_access_rule_t)) 117 | != NGX_OK) 118 | { 119 | return NULL; 120 | } 121 | 122 | #if (NGX_HAVE_INET6) 123 | if (ngx_array_init(&aacf->rules6, cf->pool, 1, 124 | sizeof(ngx_rtmp_access_rule6_t)) 125 | != NGX_OK) 126 | { 127 | return NULL; 128 | } 129 | #endif 130 | 131 | return aacf; 132 | } 133 | 134 | 135 | static ngx_int_t 136 | ngx_rtmp_access_merge_rules(ngx_array_t *prev, ngx_array_t *rules) 137 | { 138 | void *p; 139 | 140 | if (prev->nelts == 0) { 141 | return NGX_OK; 142 | } 143 | 144 | if (rules->nelts == 0) { 145 | *rules = *prev; 146 | return NGX_OK; 147 | } 148 | 149 | p = ngx_array_push_n(rules, prev->nelts); 150 | if (p == NULL) { 151 | return NGX_ERROR; 152 | } 153 | 154 | ngx_memcpy(p, prev->elts, prev->size * prev->nelts); 155 | 156 | return NGX_OK; 157 | } 158 | 159 | 160 | static char * 161 | ngx_rtmp_access_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) 162 | { 163 | ngx_rtmp_access_app_conf_t *prev = parent; 164 | ngx_rtmp_access_app_conf_t *conf = child; 165 | 166 | if (ngx_rtmp_access_merge_rules(&prev->rules, &conf->rules) != NGX_OK) { 167 | return NGX_CONF_ERROR; 168 | } 169 | 170 | #if (NGX_HAVE_INET6) 171 | if (ngx_rtmp_access_merge_rules(&prev->rules6, &conf->rules6) != NGX_OK) { 172 | return NGX_CONF_ERROR; 173 | } 174 | #endif 175 | 176 | return NGX_CONF_OK; 177 | } 178 | 179 | 180 | static ngx_int_t 181 | ngx_rtmp_access_found(ngx_rtmp_session_t *s, ngx_uint_t deny) 182 | { 183 | if (deny) { 184 | ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, 185 | "access forbidden by rule"); 186 | return NGX_ERROR; 187 | } 188 | 189 | return NGX_OK; 190 | } 191 | 192 | 193 | static ngx_int_t 194 | ngx_rtmp_access_inet(ngx_rtmp_session_t *s, in_addr_t addr, ngx_uint_t flag) 195 | { 196 | ngx_uint_t i; 197 | ngx_rtmp_access_rule_t *rule; 198 | ngx_rtmp_access_app_conf_t *ascf; 199 | 200 | ascf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_access_module); 201 | 202 | rule = ascf->rules.elts; 203 | for (i = 0; i < ascf->rules.nelts; i++) { 204 | 205 | ngx_log_debug3(NGX_LOG_DEBUG_HTTP, s->connection->log, 0, 206 | "access: %08XD %08XD %08XD", 207 | addr, rule[i].mask, rule[i].addr); 208 | 209 | if ((addr & rule[i].mask) == rule[i].addr && (flag & rule[i].flags)) { 210 | return ngx_rtmp_access_found(s, rule[i].deny); 211 | } 212 | } 213 | 214 | return NGX_OK; 215 | } 216 | 217 | 218 | #if (NGX_HAVE_INET6) 219 | 220 | static ngx_int_t 221 | ngx_rtmp_access_inet6(ngx_rtmp_session_t *s, u_char *p, ngx_uint_t flag) 222 | { 223 | ngx_uint_t n; 224 | ngx_uint_t i; 225 | ngx_rtmp_access_rule6_t *rule6; 226 | ngx_rtmp_access_app_conf_t *ascf; 227 | 228 | ascf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_access_module); 229 | 230 | rule6 = ascf->rules6.elts; 231 | for (i = 0; i < ascf->rules6.nelts; i++) { 232 | 233 | #if (NGX_DEBUG) 234 | { 235 | size_t cl, ml, al; 236 | u_char ct[NGX_INET6_ADDRSTRLEN]; 237 | u_char mt[NGX_INET6_ADDRSTRLEN]; 238 | u_char at[NGX_INET6_ADDRSTRLEN]; 239 | 240 | cl = ngx_inet6_ntop(p, ct, NGX_INET6_ADDRSTRLEN); 241 | ml = ngx_inet6_ntop(rule6[i].mask.s6_addr, mt, NGX_INET6_ADDRSTRLEN); 242 | al = ngx_inet6_ntop(rule6[i].addr.s6_addr, at, NGX_INET6_ADDRSTRLEN); 243 | 244 | ngx_log_debug6(NGX_LOG_DEBUG_HTTP, s->connection->log, 0, 245 | "access: %*s %*s %*s", cl, ct, ml, mt, al, at); 246 | } 247 | #endif 248 | 249 | for (n = 0; n < 16; n++) { 250 | if ((p[n] & rule6[i].mask.s6_addr[n]) != rule6[i].addr.s6_addr[n]) { 251 | goto next; 252 | } 253 | } 254 | 255 | if (flag & rule6[i].flags) { 256 | return ngx_rtmp_access_found(s, rule6[i].deny); 257 | } 258 | 259 | next: 260 | continue; 261 | } 262 | 263 | return NGX_OK; 264 | } 265 | 266 | #endif 267 | 268 | 269 | static ngx_int_t 270 | ngx_rtmp_access(ngx_rtmp_session_t *s, ngx_uint_t flag) 271 | { 272 | struct sockaddr_in *sin; 273 | ngx_rtmp_access_app_conf_t *ascf; 274 | #if (NGX_HAVE_INET6) 275 | u_char *p; 276 | in_addr_t addr; 277 | struct sockaddr_in6 *sin6; 278 | #endif 279 | 280 | ascf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_access_module); 281 | if (ascf == NULL) { 282 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, s->connection->log, 0, 283 | "access: NULL app conf"); 284 | return NGX_ERROR; 285 | } 286 | 287 | /* relay etc */ 288 | if (s->connection->sockaddr == NULL) { 289 | return NGX_OK; 290 | } 291 | 292 | switch (s->connection->sockaddr->sa_family) { 293 | 294 | case AF_INET: 295 | sin = (struct sockaddr_in *) s->connection->sockaddr; 296 | return ngx_rtmp_access_inet(s, sin->sin_addr.s_addr, flag); 297 | 298 | #if (NGX_HAVE_INET6) 299 | 300 | case AF_INET6: 301 | sin6 = (struct sockaddr_in6 *) s->connection->sockaddr; 302 | p = sin6->sin6_addr.s6_addr; 303 | 304 | if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { 305 | addr = p[12] << 24; 306 | addr += p[13] << 16; 307 | addr += p[14] << 8; 308 | addr += p[15]; 309 | return ngx_rtmp_access_inet(s, htonl(addr), flag); 310 | } 311 | 312 | return ngx_rtmp_access_inet6(s, p, flag); 313 | 314 | #endif 315 | } 316 | 317 | return NGX_OK; 318 | } 319 | 320 | 321 | static char * 322 | ngx_rtmp_access_rule(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 323 | { 324 | ngx_rtmp_access_app_conf_t *ascf = conf; 325 | 326 | ngx_int_t rc; 327 | ngx_uint_t all; 328 | ngx_str_t *value; 329 | ngx_cidr_t cidr; 330 | ngx_rtmp_access_rule_t *rule; 331 | #if (NGX_HAVE_INET6) 332 | ngx_rtmp_access_rule6_t *rule6; 333 | #endif 334 | size_t n; 335 | ngx_uint_t flags; 336 | 337 | ngx_memzero(&cidr, sizeof(ngx_cidr_t)); 338 | 339 | value = cf->args->elts; 340 | 341 | n = 1; 342 | flags = 0; 343 | 344 | if (cf->args->nelts == 2) { 345 | 346 | flags = NGX_RTMP_ACCESS_PUBLISH | NGX_RTMP_ACCESS_PLAY; 347 | 348 | } else { 349 | 350 | for(; n < cf->args->nelts - 1; ++n) { 351 | 352 | if (value[n].len == sizeof("publish") - 1 && 353 | ngx_strcmp(value[1].data, "publish") == 0) 354 | { 355 | flags |= NGX_RTMP_ACCESS_PUBLISH; 356 | continue; 357 | 358 | } 359 | 360 | if (value[n].len == sizeof("play") - 1 && 361 | ngx_strcmp(value[1].data, "play") == 0) 362 | { 363 | flags |= NGX_RTMP_ACCESS_PLAY; 364 | continue; 365 | 366 | } 367 | 368 | ngx_log_error(NGX_LOG_ERR, cf->log, 0, 369 | "unexpected access specified: '%V'", &value[n]); 370 | return NGX_CONF_ERROR; 371 | } 372 | } 373 | 374 | all = (value[n].len == 3 && ngx_strcmp(value[n].data, "all") == 0); 375 | 376 | if (!all) { 377 | 378 | rc = ngx_ptocidr(&value[n], &cidr); 379 | 380 | if (rc == NGX_ERROR) { 381 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 382 | "invalid parameter \"%V\"", &value[1]); 383 | return NGX_CONF_ERROR; 384 | } 385 | 386 | if (rc == NGX_DONE) { 387 | ngx_conf_log_error(NGX_LOG_WARN, cf, 0, 388 | "low address bits of %V are meaningless", 389 | &value[1]); 390 | } 391 | } 392 | 393 | switch (cidr.family) { 394 | 395 | #if (NGX_HAVE_INET6) 396 | case AF_INET6: 397 | case 0: /* all */ 398 | 399 | rule6 = ngx_array_push(&ascf->rules6); 400 | if (rule6 == NULL) { 401 | return NGX_CONF_ERROR; 402 | } 403 | 404 | rule6->mask = cidr.u.in6.mask; 405 | rule6->addr = cidr.u.in6.addr; 406 | rule6->deny = (value[0].data[0] == 'd') ? 1 : 0; 407 | rule6->flags = flags; 408 | 409 | if (!all) { 410 | break; 411 | } 412 | 413 | #endif 414 | /* fall through */ 415 | 416 | default: /* AF_INET */ 417 | 418 | rule = ngx_array_push(&ascf->rules); 419 | if (rule == NULL) { 420 | return NGX_CONF_ERROR; 421 | } 422 | 423 | rule->mask = cidr.u.in.mask; 424 | rule->addr = cidr.u.in.addr; 425 | rule->deny = (value[0].data[0] == 'd') ? 1 : 0; 426 | rule->flags = flags; 427 | } 428 | 429 | return NGX_CONF_OK; 430 | } 431 | 432 | 433 | static ngx_int_t 434 | ngx_rtmp_access_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) 435 | { 436 | if (s->auto_pushed) { 437 | goto next; 438 | } 439 | 440 | if (ngx_rtmp_access(s, NGX_RTMP_ACCESS_PUBLISH) != NGX_OK) { 441 | return NGX_ERROR; 442 | } 443 | 444 | next: 445 | return next_publish(s, v); 446 | } 447 | 448 | 449 | static ngx_int_t 450 | ngx_rtmp_access_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v) 451 | { 452 | if (ngx_rtmp_access(s, NGX_RTMP_ACCESS_PLAY) != NGX_OK) { 453 | return NGX_ERROR; 454 | } 455 | 456 | return next_play(s, v); 457 | } 458 | 459 | 460 | static ngx_int_t 461 | ngx_rtmp_access_postconfiguration(ngx_conf_t *cf) 462 | { 463 | /* chain handlers */ 464 | next_publish = ngx_rtmp_publish; 465 | ngx_rtmp_publish = ngx_rtmp_access_publish; 466 | 467 | next_play = ngx_rtmp_play; 468 | ngx_rtmp_play = ngx_rtmp_access_play; 469 | 470 | return NGX_OK; 471 | } 472 | -------------------------------------------------------------------------------- /ngx_rtmp_amf.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Roman Arutyunyan 4 | */ 5 | 6 | 7 | #include 8 | #include 9 | #include "ngx_rtmp_amf.h" 10 | #include "ngx_rtmp.h" 11 | #include 12 | 13 | 14 | static ngx_inline void* 15 | ngx_rtmp_amf_reverse_copy(void *dst, void* src, size_t len) 16 | { 17 | size_t k; 18 | 19 | if (dst == NULL || src == NULL) { 20 | return NULL; 21 | } 22 | 23 | for(k = 0; k < len; ++k) { 24 | ((u_char*)dst)[k] = ((u_char*)src)[len - 1 - k]; 25 | } 26 | 27 | return dst; 28 | } 29 | 30 | #define NGX_RTMP_AMF_DEBUG_SIZE 16 31 | 32 | #ifdef NGX_DEBUG 33 | static void 34 | ngx_rtmp_amf_debug(const char* op, ngx_log_t *log, u_char *p, size_t n) 35 | { 36 | u_char hstr[3 * NGX_RTMP_AMF_DEBUG_SIZE + 1]; 37 | u_char str[NGX_RTMP_AMF_DEBUG_SIZE + 1]; 38 | u_char *hp, *sp; 39 | static u_char hex[] = "0123456789ABCDEF"; 40 | size_t i; 41 | 42 | hp = hstr; 43 | sp = str; 44 | 45 | for(i = 0; i < n && i < NGX_RTMP_AMF_DEBUG_SIZE; ++i) { 46 | *hp++ = ' '; 47 | if (p) { 48 | *hp++ = hex[(*p & 0xf0) >> 4]; 49 | *hp++ = hex[*p & 0x0f]; 50 | *sp++ = (*p >= 0x20 && *p <= 0x7e) ? 51 | *p : (u_char)'?'; 52 | ++p; 53 | } else { 54 | *hp++ = 'X'; 55 | *hp++ = 'X'; 56 | *sp++ = '?'; 57 | } 58 | } 59 | *hp = *sp = '\0'; 60 | 61 | ngx_log_debug4(NGX_LOG_DEBUG_RTMP, log, 0, 62 | "AMF %s (%d)%s '%s'", op, n, hstr, str); 63 | } 64 | #endif 65 | 66 | static ngx_int_t 67 | ngx_rtmp_amf_get(ngx_rtmp_amf_ctx_t *ctx, void *p, size_t n) 68 | { 69 | size_t size; 70 | ngx_chain_t *l; 71 | size_t offset; 72 | u_char *pos, *last; 73 | #ifdef NGX_DEBUG 74 | void *op = p; 75 | size_t on = n; 76 | #endif 77 | 78 | if (!n) 79 | return NGX_OK; 80 | 81 | for(l = ctx->link, offset = ctx->offset; l; l = l->next, offset = 0) { 82 | 83 | pos = l->buf->pos + offset; 84 | last = l->buf->last; 85 | 86 | if (last >= pos + n) { 87 | if (p) { 88 | p = ngx_cpymem(p, pos, n); 89 | } 90 | ctx->offset = offset + n; 91 | ctx->link = l; 92 | 93 | #ifdef NGX_DEBUG 94 | ngx_rtmp_amf_debug("read", ctx->log, (u_char*)op, on); 95 | #endif 96 | 97 | return NGX_OK; 98 | } 99 | 100 | size = last - pos; 101 | 102 | if (p) { 103 | p = ngx_cpymem(p, pos, size); 104 | } 105 | 106 | n -= size; 107 | } 108 | 109 | ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ctx->log, 0, 110 | "AMF read eof (%d)", n); 111 | 112 | return NGX_DONE; 113 | } 114 | 115 | 116 | static ngx_int_t 117 | ngx_rtmp_amf_put(ngx_rtmp_amf_ctx_t *ctx, void *p, size_t n) 118 | { 119 | ngx_buf_t *b; 120 | size_t size; 121 | ngx_chain_t *l, *ln; 122 | 123 | #ifdef NGX_DEBUG 124 | ngx_rtmp_amf_debug("write", ctx->log, (u_char*)p, n); 125 | #endif 126 | 127 | l = ctx->link; 128 | 129 | if (ctx->link && ctx->first == NULL) { 130 | ctx->first = ctx->link; 131 | } 132 | 133 | while(n) { 134 | b = l ? l->buf : NULL; 135 | 136 | if (b == NULL || b->last == b->end) { 137 | 138 | ln = ctx->alloc(ctx->arg); 139 | if (ln == NULL) { 140 | return NGX_ERROR; 141 | } 142 | 143 | if (ctx->first == NULL) { 144 | ctx->first = ln; 145 | } 146 | 147 | if (l) { 148 | l->next = ln; 149 | } 150 | 151 | l = ln; 152 | ctx->link = l; 153 | b = l->buf; 154 | } 155 | 156 | size = b->end - b->last; 157 | 158 | if (size >= n) { 159 | b->last = ngx_cpymem(b->last, p, n); 160 | return NGX_OK; 161 | } 162 | 163 | b->last = ngx_cpymem(b->last, p, size); 164 | p = (u_char*)p + size; 165 | n -= size; 166 | } 167 | 168 | return NGX_OK; 169 | } 170 | 171 | 172 | static ngx_int_t 173 | ngx_rtmp_amf_read_object(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_amf_elt_t *elts, 174 | size_t nelts) 175 | { 176 | uint8_t type; 177 | uint16_t len; 178 | size_t n, namelen, maxlen; 179 | ngx_int_t rc; 180 | u_char buf[2]; 181 | 182 | maxlen = 0; 183 | for(n = 0; n < nelts; ++n) { 184 | namelen = elts[n].name.len; 185 | if (namelen > maxlen) 186 | maxlen = namelen; 187 | } 188 | 189 | for( ;; ) { 190 | 191 | #if !(NGX_WIN32) 192 | char name[maxlen]; 193 | #else 194 | char name[1024]; 195 | if (maxlen > sizeof(name)) { 196 | return NGX_ERROR; 197 | } 198 | #endif 199 | /* read key */ 200 | switch (ngx_rtmp_amf_get(ctx, buf, 2)) { 201 | case NGX_DONE: 202 | /* Envivio sends unfinalized arrays */ 203 | return NGX_OK; 204 | case NGX_OK: 205 | break; 206 | default: 207 | return NGX_ERROR; 208 | } 209 | 210 | ngx_rtmp_amf_reverse_copy(&len, buf, 2); 211 | 212 | if (!len) 213 | break; 214 | 215 | if (len <= maxlen) { 216 | rc = ngx_rtmp_amf_get(ctx, name, len); 217 | 218 | } else { 219 | rc = ngx_rtmp_amf_get(ctx, name, maxlen); 220 | if (rc != NGX_OK) 221 | return NGX_ERROR; 222 | rc = ngx_rtmp_amf_get(ctx, 0, len - maxlen); 223 | } 224 | 225 | if (rc != NGX_OK) 226 | return NGX_ERROR; 227 | 228 | /* TODO: if we require array to be sorted on name 229 | * then we could be able to use binary search */ 230 | for(n = 0; n < nelts 231 | && (len != elts[n].name.len 232 | || ngx_strncmp(name, elts[n].name.data, len)); 233 | ++n); 234 | 235 | if (ngx_rtmp_amf_read(ctx, n < nelts ? &elts[n] : NULL, 1) != NGX_OK) 236 | return NGX_ERROR; 237 | } 238 | 239 | if (ngx_rtmp_amf_get(ctx, &type, 1) != NGX_OK 240 | || type != NGX_RTMP_AMF_END) 241 | { 242 | return NGX_ERROR; 243 | } 244 | 245 | return NGX_OK; 246 | } 247 | 248 | 249 | static ngx_int_t 250 | ngx_rtmp_amf_read_array(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_amf_elt_t *elts, 251 | size_t nelts) 252 | { 253 | uint32_t len; 254 | size_t n; 255 | u_char buf[4]; 256 | 257 | /* read length */ 258 | if (ngx_rtmp_amf_get(ctx, buf, 4) != NGX_OK) 259 | return NGX_ERROR; 260 | 261 | ngx_rtmp_amf_reverse_copy(&len, buf, 4); 262 | 263 | for (n = 0; n < len; ++n) { 264 | if (ngx_rtmp_amf_read(ctx, n < nelts ? &elts[n] : NULL, 1) != NGX_OK) 265 | return NGX_ERROR; 266 | } 267 | 268 | return NGX_OK; 269 | } 270 | 271 | 272 | static ngx_int_t 273 | ngx_rtmp_amf_read_variant(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_amf_elt_t *elts, 274 | size_t nelts) 275 | { 276 | uint8_t type; 277 | ngx_int_t rc; 278 | size_t n; 279 | ngx_rtmp_amf_elt_t elt; 280 | 281 | rc = ngx_rtmp_amf_get(ctx, &type, 1); 282 | if (rc != NGX_OK) { 283 | return rc; 284 | } 285 | 286 | ngx_memzero(&elt, sizeof(elt)); 287 | for (n = 0; n < nelts; ++n, ++elts) { 288 | if (type == elts->type) { 289 | elt.data = elts->data; 290 | elt.len = elts->len; 291 | } 292 | } 293 | 294 | elt.type = type | NGX_RTMP_AMF_TYPELESS; 295 | 296 | return ngx_rtmp_amf_read(ctx, &elt, 1); 297 | } 298 | 299 | 300 | static ngx_int_t 301 | ngx_rtmp_amf_is_compatible_type(uint8_t t1, uint8_t t2) 302 | { 303 | return t1 == t2 304 | || (t1 == NGX_RTMP_AMF_OBJECT && t2 == NGX_RTMP_AMF_MIXED_ARRAY) 305 | || (t2 == NGX_RTMP_AMF_OBJECT && t1 == NGX_RTMP_AMF_MIXED_ARRAY); 306 | } 307 | 308 | 309 | ngx_int_t 310 | ngx_rtmp_amf_read(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_amf_elt_t *elts, 311 | size_t nelts) 312 | { 313 | void *data; 314 | ngx_int_t type; 315 | uint8_t type8; 316 | size_t n; 317 | uint16_t len; 318 | ngx_int_t rc; 319 | u_char buf[8]; 320 | uint32_t max_index; 321 | 322 | for(n = 0; n < nelts; ++n) { 323 | 324 | if (elts && elts->type & NGX_RTMP_AMF_TYPELESS) { 325 | type = elts->type & ~NGX_RTMP_AMF_TYPELESS; 326 | data = elts->data; 327 | 328 | } else { 329 | switch (ngx_rtmp_amf_get(ctx, &type8, 1)) { 330 | case NGX_DONE: 331 | if (elts->type & NGX_RTMP_AMF_OPTIONAL) { 332 | return NGX_OK; 333 | } 334 | /* fall through */ 335 | case NGX_ERROR: 336 | return NGX_ERROR; 337 | } 338 | type = type8; 339 | data = (elts && 340 | ngx_rtmp_amf_is_compatible_type( 341 | (uint8_t) (elts->type & 0xff), (uint8_t) type)) 342 | ? elts->data 343 | : NULL; 344 | 345 | if (elts && (elts->type & NGX_RTMP_AMF_CONTEXT)) { 346 | if (data) { 347 | *(ngx_rtmp_amf_ctx_t *) data = *ctx; 348 | } 349 | data = NULL; 350 | } 351 | } 352 | 353 | switch (type) { 354 | case NGX_RTMP_AMF_NUMBER: 355 | if (ngx_rtmp_amf_get(ctx, buf, 8) != NGX_OK) { 356 | return NGX_ERROR; 357 | } 358 | ngx_rtmp_amf_reverse_copy(data, buf, 8); 359 | break; 360 | 361 | case NGX_RTMP_AMF_BOOLEAN: 362 | if (ngx_rtmp_amf_get(ctx, data, 1) != NGX_OK) { 363 | return NGX_ERROR; 364 | } 365 | break; 366 | 367 | case NGX_RTMP_AMF_STRING: 368 | if (ngx_rtmp_amf_get(ctx, buf, 2) != NGX_OK) { 369 | return NGX_ERROR; 370 | } 371 | ngx_rtmp_amf_reverse_copy(&len, buf, 2); 372 | 373 | if (data == NULL) { 374 | rc = ngx_rtmp_amf_get(ctx, data, len); 375 | 376 | } else if (elts->len <= len) { 377 | rc = ngx_rtmp_amf_get(ctx, data, elts->len - 1); 378 | if (rc != NGX_OK) 379 | return NGX_ERROR; 380 | ((char*)data)[elts->len - 1] = 0; 381 | rc = ngx_rtmp_amf_get(ctx, NULL, len - elts->len + 1); 382 | 383 | } else { 384 | rc = ngx_rtmp_amf_get(ctx, data, len); 385 | ((char*)data)[len] = 0; 386 | } 387 | 388 | if (rc != NGX_OK) { 389 | return NGX_ERROR; 390 | } 391 | 392 | break; 393 | 394 | case NGX_RTMP_AMF_NULL: 395 | case NGX_RTMP_AMF_ARRAY_NULL: 396 | break; 397 | 398 | case NGX_RTMP_AMF_MIXED_ARRAY: 399 | if (ngx_rtmp_amf_get(ctx, &max_index, 4) != NGX_OK) { 400 | return NGX_ERROR; 401 | } 402 | /* fall through */ 403 | 404 | case NGX_RTMP_AMF_OBJECT: 405 | if (ngx_rtmp_amf_read_object(ctx, data, 406 | data && elts ? elts->len / sizeof(ngx_rtmp_amf_elt_t) : 0 407 | ) != NGX_OK) 408 | { 409 | return NGX_ERROR; 410 | } 411 | break; 412 | 413 | case NGX_RTMP_AMF_ARRAY: 414 | if (ngx_rtmp_amf_read_array(ctx, data, 415 | data && elts ? elts->len / sizeof(ngx_rtmp_amf_elt_t) : 0 416 | ) != NGX_OK) 417 | { 418 | return NGX_ERROR; 419 | } 420 | break; 421 | 422 | case NGX_RTMP_AMF_VARIANT_: 423 | if (ngx_rtmp_amf_read_variant(ctx, data, 424 | data && elts ? elts->len / sizeof(ngx_rtmp_amf_elt_t) : 0 425 | ) != NGX_OK) 426 | { 427 | return NGX_ERROR; 428 | } 429 | break; 430 | 431 | case NGX_RTMP_AMF_INT8: 432 | if (ngx_rtmp_amf_get(ctx, data, 1) != NGX_OK) { 433 | return NGX_ERROR; 434 | } 435 | break; 436 | 437 | case NGX_RTMP_AMF_INT16: 438 | if (ngx_rtmp_amf_get(ctx, buf, 2) != NGX_OK) { 439 | return NGX_ERROR; 440 | } 441 | ngx_rtmp_amf_reverse_copy(data, buf, 2); 442 | break; 443 | 444 | case NGX_RTMP_AMF_INT32: 445 | if (ngx_rtmp_amf_get(ctx, buf, 4) != NGX_OK) { 446 | return NGX_ERROR; 447 | } 448 | ngx_rtmp_amf_reverse_copy(data, buf, 4); 449 | break; 450 | 451 | case NGX_RTMP_AMF_END: 452 | return NGX_OK; 453 | 454 | default: 455 | return NGX_ERROR; 456 | } 457 | 458 | if (elts) { 459 | ++elts; 460 | } 461 | } 462 | 463 | return NGX_OK; 464 | } 465 | 466 | 467 | static ngx_int_t 468 | ngx_rtmp_amf_write_object(ngx_rtmp_amf_ctx_t *ctx, 469 | ngx_rtmp_amf_elt_t *elts, size_t nelts) 470 | { 471 | uint16_t len; 472 | size_t n; 473 | u_char buf[2]; 474 | 475 | for(n = 0; n < nelts; ++n) { 476 | 477 | len = (uint16_t) elts[n].name.len; 478 | 479 | if (ngx_rtmp_amf_put(ctx, 480 | ngx_rtmp_amf_reverse_copy(buf, 481 | &len, 2), 2) != NGX_OK) 482 | { 483 | return NGX_ERROR; 484 | } 485 | 486 | if (ngx_rtmp_amf_put(ctx, elts[n].name.data, len) != NGX_OK) { 487 | return NGX_ERROR; 488 | } 489 | 490 | if (ngx_rtmp_amf_write(ctx, &elts[n], 1) != NGX_OK) { 491 | return NGX_ERROR; 492 | } 493 | } 494 | 495 | if (ngx_rtmp_amf_put(ctx, "\0\0", 2) != NGX_OK) { 496 | return NGX_ERROR; 497 | } 498 | 499 | return NGX_OK; 500 | } 501 | 502 | 503 | static ngx_int_t 504 | ngx_rtmp_amf_write_array(ngx_rtmp_amf_ctx_t *ctx, 505 | ngx_rtmp_amf_elt_t *elts, size_t nelts) 506 | { 507 | uint32_t len; 508 | size_t n; 509 | u_char buf[4]; 510 | 511 | len = nelts; 512 | if (ngx_rtmp_amf_put(ctx, 513 | ngx_rtmp_amf_reverse_copy(buf, 514 | &len, 4), 4) != NGX_OK) 515 | { 516 | return NGX_ERROR; 517 | } 518 | 519 | for(n = 0; n < nelts; ++n) { 520 | if (ngx_rtmp_amf_write(ctx, &elts[n], 1) != NGX_OK) { 521 | return NGX_ERROR; 522 | } 523 | } 524 | 525 | return NGX_OK; 526 | } 527 | 528 | 529 | ngx_int_t 530 | ngx_rtmp_amf_write(ngx_rtmp_amf_ctx_t *ctx, 531 | ngx_rtmp_amf_elt_t *elts, size_t nelts) 532 | { 533 | size_t n; 534 | ngx_int_t type; 535 | uint8_t type8; 536 | void *data; 537 | uint16_t len; 538 | uint32_t max_index; 539 | u_char buf[8]; 540 | 541 | for(n = 0; n < nelts; ++n) { 542 | 543 | type = elts[n].type; 544 | data = elts[n].data; 545 | len = (uint16_t) elts[n].len; 546 | 547 | if (type & NGX_RTMP_AMF_TYPELESS) { 548 | type &= ~NGX_RTMP_AMF_TYPELESS; 549 | } else { 550 | type8 = (uint8_t)type; 551 | if (ngx_rtmp_amf_put(ctx, &type8, 1) != NGX_OK) 552 | return NGX_ERROR; 553 | } 554 | 555 | switch(type) { 556 | case NGX_RTMP_AMF_NUMBER: 557 | if (ngx_rtmp_amf_put(ctx, 558 | ngx_rtmp_amf_reverse_copy(buf, 559 | data, 8), 8) != NGX_OK) 560 | { 561 | return NGX_ERROR; 562 | } 563 | break; 564 | 565 | case NGX_RTMP_AMF_BOOLEAN: 566 | if (ngx_rtmp_amf_put(ctx, data, 1) != NGX_OK) { 567 | return NGX_ERROR; 568 | } 569 | break; 570 | 571 | case NGX_RTMP_AMF_STRING: 572 | if (len == 0 && data) { 573 | len = (uint16_t) ngx_strlen((u_char*) data); 574 | } 575 | 576 | if (ngx_rtmp_amf_put(ctx, 577 | ngx_rtmp_amf_reverse_copy(buf, 578 | &len, 2), 2) != NGX_OK) 579 | { 580 | return NGX_ERROR; 581 | } 582 | 583 | if (ngx_rtmp_amf_put(ctx, data, len) != NGX_OK) { 584 | return NGX_ERROR; 585 | } 586 | break; 587 | 588 | case NGX_RTMP_AMF_NULL: 589 | case NGX_RTMP_AMF_ARRAY_NULL: 590 | break; 591 | 592 | case NGX_RTMP_AMF_MIXED_ARRAY: 593 | max_index = 0; 594 | if (ngx_rtmp_amf_put(ctx, &max_index, 4) != NGX_OK) { 595 | return NGX_ERROR; 596 | } 597 | /* fall through */ 598 | 599 | case NGX_RTMP_AMF_OBJECT: 600 | type8 = NGX_RTMP_AMF_END; 601 | if (ngx_rtmp_amf_write_object(ctx, data, 602 | elts[n].len / sizeof(ngx_rtmp_amf_elt_t)) != NGX_OK 603 | || ngx_rtmp_amf_put(ctx, &type8, 1) != NGX_OK) 604 | { 605 | return NGX_ERROR; 606 | } 607 | break; 608 | 609 | case NGX_RTMP_AMF_ARRAY: 610 | if (ngx_rtmp_amf_write_array(ctx, data, 611 | elts[n].len / sizeof(ngx_rtmp_amf_elt_t)) != NGX_OK) 612 | { 613 | return NGX_ERROR; 614 | } 615 | break; 616 | 617 | case NGX_RTMP_AMF_INT8: 618 | if (ngx_rtmp_amf_put(ctx, data, 1) != NGX_OK) { 619 | return NGX_ERROR; 620 | } 621 | break; 622 | 623 | case NGX_RTMP_AMF_INT16: 624 | if (ngx_rtmp_amf_put(ctx, 625 | ngx_rtmp_amf_reverse_copy(buf, 626 | data, 2), 2) != NGX_OK) 627 | { 628 | return NGX_ERROR; 629 | } 630 | break; 631 | 632 | case NGX_RTMP_AMF_INT32: 633 | if (ngx_rtmp_amf_put(ctx, 634 | ngx_rtmp_amf_reverse_copy(buf, 635 | data, 4), 4) != NGX_OK) 636 | { 637 | return NGX_ERROR; 638 | } 639 | break; 640 | 641 | default: 642 | return NGX_ERROR; 643 | } 644 | } 645 | 646 | return NGX_OK; 647 | } 648 | 649 | -------------------------------------------------------------------------------- /ngx_rtmp_amf.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Roman Arutyunyan 4 | */ 5 | 6 | 7 | #ifndef _NGX_RTMP_AMF_H_INCLUDED_ 8 | #define _NGX_RTMP_AMF_H_INCLUDED_ 9 | 10 | 11 | #include 12 | #include 13 | 14 | 15 | /* basic types */ 16 | #define NGX_RTMP_AMF_NUMBER 0x00 17 | #define NGX_RTMP_AMF_BOOLEAN 0x01 18 | #define NGX_RTMP_AMF_STRING 0x02 19 | #define NGX_RTMP_AMF_OBJECT 0x03 20 | #define NGX_RTMP_AMF_NULL 0x05 21 | #define NGX_RTMP_AMF_ARRAY_NULL 0x06 22 | #define NGX_RTMP_AMF_MIXED_ARRAY 0x08 23 | #define NGX_RTMP_AMF_END 0x09 24 | #define NGX_RTMP_AMF_ARRAY 0x0a 25 | 26 | /* extended types */ 27 | #define NGX_RTMP_AMF_INT8 0x0100 28 | #define NGX_RTMP_AMF_INT16 0x0101 29 | #define NGX_RTMP_AMF_INT32 0x0102 30 | #define NGX_RTMP_AMF_VARIANT_ 0x0103 31 | 32 | /* r/w flags */ 33 | #define NGX_RTMP_AMF_OPTIONAL 0x1000 34 | #define NGX_RTMP_AMF_TYPELESS 0x2000 35 | #define NGX_RTMP_AMF_CONTEXT 0x4000 36 | 37 | #define NGX_RTMP_AMF_VARIANT (NGX_RTMP_AMF_VARIANT_\ 38 | |NGX_RTMP_AMF_TYPELESS) 39 | 40 | 41 | typedef struct { 42 | ngx_int_t type; 43 | ngx_str_t name; 44 | void *data; 45 | size_t len; 46 | } ngx_rtmp_amf_elt_t; 47 | 48 | 49 | typedef ngx_chain_t * (*ngx_rtmp_amf_alloc_pt)(void *arg); 50 | 51 | 52 | typedef struct { 53 | ngx_chain_t *link, *first; 54 | size_t offset; 55 | ngx_rtmp_amf_alloc_pt alloc; 56 | void *arg; 57 | ngx_log_t *log; 58 | } ngx_rtmp_amf_ctx_t; 59 | 60 | 61 | /* reading AMF */ 62 | ngx_int_t ngx_rtmp_amf_read(ngx_rtmp_amf_ctx_t *ctx, 63 | ngx_rtmp_amf_elt_t *elts, size_t nelts); 64 | 65 | /* writing AMF */ 66 | ngx_int_t ngx_rtmp_amf_write(ngx_rtmp_amf_ctx_t *ctx, 67 | ngx_rtmp_amf_elt_t *elts, size_t nelts); 68 | 69 | 70 | #endif /* _NGX_RTMP_AMF_H_INCLUDED_ */ 71 | 72 | -------------------------------------------------------------------------------- /ngx_rtmp_bandwidth.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Roman Arutyunyan 4 | */ 5 | 6 | 7 | #include 8 | #include 9 | #include "ngx_rtmp_bandwidth.h" 10 | 11 | 12 | void 13 | ngx_rtmp_update_bandwidth(ngx_rtmp_bandwidth_t *bw, uint32_t bytes) 14 | { 15 | if (ngx_cached_time->sec > bw->intl_end) { 16 | bw->bandwidth = ngx_cached_time->sec > 17 | bw->intl_end + NGX_RTMP_BANDWIDTH_INTERVAL 18 | ? 0 19 | : bw->intl_bytes / NGX_RTMP_BANDWIDTH_INTERVAL; 20 | bw->intl_bytes = 0; 21 | bw->intl_end = ngx_cached_time->sec + NGX_RTMP_BANDWIDTH_INTERVAL; 22 | } 23 | 24 | bw->bytes += bytes; 25 | bw->intl_bytes += bytes; 26 | } 27 | -------------------------------------------------------------------------------- /ngx_rtmp_bandwidth.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Roman Arutyunyan 4 | */ 5 | 6 | 7 | #ifndef _NGX_RTMP_BANDWIDTH_H_INCLUDED_ 8 | #define _NGX_RTMP_BANDWIDTH_H_INCLUDED_ 9 | 10 | 11 | #include 12 | #include 13 | 14 | 15 | /* Bandwidth update interval in seconds */ 16 | #define NGX_RTMP_BANDWIDTH_INTERVAL 10 17 | 18 | 19 | typedef struct { 20 | uint64_t bytes; 21 | uint64_t bandwidth; /* bytes/sec */ 22 | 23 | time_t intl_end; 24 | uint64_t intl_bytes; 25 | } ngx_rtmp_bandwidth_t; 26 | 27 | 28 | void ngx_rtmp_update_bandwidth(ngx_rtmp_bandwidth_t *bw, uint32_t bytes); 29 | 30 | 31 | #endif /* _NGX_RTMP_BANDWIDTH_H_INCLUDED_ */ 32 | -------------------------------------------------------------------------------- /ngx_rtmp_bitop.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Roman Arutyunyan 4 | */ 5 | 6 | 7 | #include 8 | #include 9 | #include "ngx_rtmp_bitop.h" 10 | 11 | 12 | //ngx_log_debug increase buffer, e.g.: 13 | //sed -i 's/#define NGX_MAX_ERROR_STR 2048/#define NGX_MAX_ERROR_STR 1024*1024/' ./src/core/ngx_log.h 14 | void 15 | ngx_rtmp_hex_dump(ngx_log_t *log, const char * tag, u_char * start, u_char * end) 16 | { 17 | u_char buf[1024*1024], *p, *pp; 18 | u_char hex[] = "0123456789abcdef"; 19 | 20 | for (pp = buf, p = start; 21 | p < end && pp < buf + sizeof(buf) - 1; 22 | ++p) 23 | { 24 | *pp++ = hex[*p >> 4]; 25 | *pp++ = hex[*p & 0x0f]; 26 | *pp++ = ' '; 27 | } 28 | 29 | *pp = 0; 30 | 31 | ngx_log_debug2(NGX_LOG_DEBUG_CORE, log, 0, "[hex][%s][%s] ", tag, buf); 32 | } 33 | 34 | 35 | 36 | void 37 | ngx_rtmp_bit_init_reader(ngx_rtmp_bit_reader_t *br, u_char *pos, u_char *last) 38 | { 39 | ngx_memzero(br, sizeof(ngx_rtmp_bit_reader_t)); 40 | 41 | br->pos = pos; 42 | br->last = last; 43 | } 44 | 45 | 46 | uint64_t 47 | ngx_rtmp_bit_read(ngx_rtmp_bit_reader_t *br, ngx_uint_t n) 48 | { 49 | uint64_t v; 50 | ngx_uint_t d; 51 | 52 | v = 0; 53 | 54 | while (n) { 55 | 56 | if (br->pos >= br->last) { 57 | br->err = 1; 58 | return 0; 59 | } 60 | 61 | d = (br->offs + n > 8 ? (ngx_uint_t) (8 - br->offs) : n); 62 | 63 | v <<= d; 64 | v += (*br->pos >> (8 - br->offs - d)) & ((u_char) 0xff >> (8 - d)); 65 | 66 | br->offs += d; 67 | n -= d; 68 | 69 | if (br->offs == 8) { 70 | br->pos++; 71 | br->offs = 0; 72 | } 73 | } 74 | 75 | return v; 76 | } 77 | 78 | 79 | uint64_t 80 | ngx_rtmp_bit_read_golomb(ngx_rtmp_bit_reader_t *br) 81 | { 82 | ngx_uint_t n; 83 | 84 | for (n = 0; ngx_rtmp_bit_read(br, 1) == 0 && !br->err; n++); 85 | 86 | return ((uint64_t) 1 << n) + ngx_rtmp_bit_read(br, n) - 1; 87 | } 88 | -------------------------------------------------------------------------------- /ngx_rtmp_bitop.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Roman Arutyunyan 4 | */ 5 | 6 | 7 | #ifndef _NGX_RTMP_BITOP_H_INCLUDED_ 8 | #define _NGX_RTMP_BITOP_H_INCLUDED_ 9 | 10 | 11 | #include 12 | #include 13 | 14 | 15 | typedef struct { 16 | u_char *pos; 17 | u_char *last; 18 | ngx_uint_t offs; 19 | ngx_uint_t err; 20 | } ngx_rtmp_bit_reader_t; 21 | 22 | void ngx_rtmp_hex_dump(ngx_log_t *log,const char * tag,u_char * start, u_char * end); 23 | 24 | void ngx_rtmp_bit_init_reader(ngx_rtmp_bit_reader_t *br, u_char *pos, 25 | u_char *last); 26 | uint64_t ngx_rtmp_bit_read(ngx_rtmp_bit_reader_t *br, ngx_uint_t n); 27 | uint64_t ngx_rtmp_bit_read_golomb(ngx_rtmp_bit_reader_t *br); 28 | 29 | 30 | #define ngx_rtmp_bit_read_err(br) ((br)->err) 31 | 32 | #define ngx_rtmp_bit_read_eof(br) ((br)->pos == (br)->last) 33 | 34 | #define ngx_rtmp_bit_read_8(br) \ 35 | ((uint8_t) ngx_rtmp_bit_read(br, 8)) 36 | 37 | #define ngx_rtmp_bit_read_16(br) \ 38 | ((uint16_t) ngx_rtmp_bit_read(br, 16)) 39 | 40 | #define ngx_rtmp_bit_read_32(br) \ 41 | ((uint32_t) ngx_rtmp_bit_read(br, 32)) 42 | 43 | #define ngx_rtmp_bit_read_64(br) \ 44 | ((uint64_t) ngx_rtmp_read(br, 64)) 45 | 46 | 47 | #endif /* _NGX_RTMP_BITOP_H_INCLUDED_ */ 48 | -------------------------------------------------------------------------------- /ngx_rtmp_cmd_module.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Roman Arutyunyan 4 | */ 5 | 6 | 7 | #ifndef _NGX_RTMP_CMD_H_INCLUDED_ 8 | #define _NGX_RTMP_CMD_H_INCLUDED_ 9 | 10 | 11 | #include 12 | #include 13 | #include 14 | #include "ngx_rtmp.h" 15 | 16 | 17 | #define NGX_RTMP_MAX_NAME 256 18 | #define NGX_RTMP_MAX_URL 256 19 | #define NGX_RTMP_MAX_ARGS NGX_RTMP_MAX_NAME 20 | 21 | 22 | /* Basic RTMP call support */ 23 | 24 | typedef struct { 25 | double trans; 26 | u_char app[NGX_RTMP_MAX_NAME]; 27 | u_char args[NGX_RTMP_MAX_ARGS]; 28 | u_char flashver[32]; 29 | u_char swf_url[NGX_RTMP_MAX_URL]; 30 | u_char tc_url[NGX_RTMP_MAX_URL]; 31 | double acodecs; 32 | double vcodecs; 33 | u_char page_url[NGX_RTMP_MAX_URL]; 34 | double object_encoding; 35 | } ngx_rtmp_connect_t; 36 | 37 | 38 | typedef struct { 39 | double trans; 40 | double stream; 41 | } ngx_rtmp_create_stream_t; 42 | 43 | 44 | typedef struct { 45 | double stream; 46 | } ngx_rtmp_delete_stream_t; 47 | 48 | 49 | typedef struct { 50 | double stream; 51 | } ngx_rtmp_close_stream_t; 52 | 53 | 54 | typedef struct { 55 | u_char name[NGX_RTMP_MAX_NAME]; 56 | u_char args[NGX_RTMP_MAX_ARGS]; 57 | u_char type[16]; 58 | int silent; 59 | } ngx_rtmp_publish_t; 60 | 61 | 62 | typedef struct { 63 | u_char name[NGX_RTMP_MAX_NAME]; 64 | u_char args[NGX_RTMP_MAX_ARGS]; 65 | double start; 66 | double duration; 67 | int reset; 68 | int silent; 69 | } ngx_rtmp_play_t; 70 | 71 | 72 | typedef struct { 73 | double offset; 74 | } ngx_rtmp_seek_t; 75 | 76 | 77 | typedef struct { 78 | uint8_t pause; 79 | double position; 80 | } ngx_rtmp_pause_t; 81 | 82 | 83 | typedef struct { 84 | uint32_t msid; 85 | } ngx_rtmp_msid_t; 86 | 87 | 88 | typedef ngx_rtmp_msid_t ngx_rtmp_stream_begin_t; 89 | typedef ngx_rtmp_msid_t ngx_rtmp_stream_eof_t; 90 | typedef ngx_rtmp_msid_t ngx_rtmp_stream_dry_t; 91 | typedef ngx_rtmp_msid_t ngx_rtmp_recorded_t; 92 | 93 | 94 | typedef struct { 95 | uint32_t msid; 96 | uint32_t buflen; 97 | } ngx_rtmp_set_buflen_t; 98 | 99 | 100 | void ngx_rtmp_cmd_fill_args(u_char name[NGX_RTMP_MAX_NAME], 101 | u_char args[NGX_RTMP_MAX_ARGS]); 102 | 103 | 104 | typedef ngx_int_t (*ngx_rtmp_connect_pt)(ngx_rtmp_session_t *s, 105 | ngx_rtmp_connect_t *v); 106 | typedef ngx_int_t (*ngx_rtmp_disconnect_pt)(ngx_rtmp_session_t *s); 107 | typedef ngx_int_t (*ngx_rtmp_create_stream_pt)(ngx_rtmp_session_t *s, 108 | ngx_rtmp_create_stream_t *v); 109 | typedef ngx_int_t (*ngx_rtmp_close_stream_pt)(ngx_rtmp_session_t *s, 110 | ngx_rtmp_close_stream_t *v); 111 | typedef ngx_int_t (*ngx_rtmp_delete_stream_pt)(ngx_rtmp_session_t *s, 112 | ngx_rtmp_delete_stream_t *v); 113 | typedef ngx_int_t (*ngx_rtmp_publish_pt)(ngx_rtmp_session_t *s, 114 | ngx_rtmp_publish_t *v); 115 | typedef ngx_int_t (*ngx_rtmp_play_pt)(ngx_rtmp_session_t *s, 116 | ngx_rtmp_play_t *v); 117 | typedef ngx_int_t (*ngx_rtmp_seek_pt)(ngx_rtmp_session_t *s, 118 | ngx_rtmp_seek_t *v); 119 | typedef ngx_int_t (*ngx_rtmp_pause_pt)(ngx_rtmp_session_t *s, 120 | ngx_rtmp_pause_t *v); 121 | 122 | typedef ngx_int_t (*ngx_rtmp_stream_begin_pt)(ngx_rtmp_session_t *s, 123 | ngx_rtmp_stream_begin_t *v); 124 | typedef ngx_int_t (*ngx_rtmp_stream_eof_pt)(ngx_rtmp_session_t *s, 125 | ngx_rtmp_stream_eof_t *v); 126 | typedef ngx_int_t (*ngx_rtmp_stream_dry_pt)(ngx_rtmp_session_t *s, 127 | ngx_rtmp_stream_dry_t *v); 128 | typedef ngx_int_t (*ngx_rtmp_recorded_pt)(ngx_rtmp_session_t *s, 129 | ngx_rtmp_recorded_t *v); 130 | typedef ngx_int_t (*ngx_rtmp_set_buflen_pt)(ngx_rtmp_session_t *s, 131 | ngx_rtmp_set_buflen_t *v); 132 | 133 | 134 | extern ngx_rtmp_connect_pt ngx_rtmp_connect; 135 | extern ngx_rtmp_disconnect_pt ngx_rtmp_disconnect; 136 | extern ngx_rtmp_create_stream_pt ngx_rtmp_create_stream; 137 | extern ngx_rtmp_close_stream_pt ngx_rtmp_close_stream; 138 | extern ngx_rtmp_delete_stream_pt ngx_rtmp_delete_stream; 139 | extern ngx_rtmp_publish_pt ngx_rtmp_publish; 140 | extern ngx_rtmp_play_pt ngx_rtmp_play; 141 | extern ngx_rtmp_seek_pt ngx_rtmp_seek; 142 | extern ngx_rtmp_pause_pt ngx_rtmp_pause; 143 | 144 | extern ngx_rtmp_stream_begin_pt ngx_rtmp_stream_begin; 145 | extern ngx_rtmp_stream_eof_pt ngx_rtmp_stream_eof; 146 | extern ngx_rtmp_stream_dry_pt ngx_rtmp_stream_dry; 147 | extern ngx_rtmp_set_buflen_pt ngx_rtmp_set_buflen; 148 | extern ngx_rtmp_recorded_pt ngx_rtmp_recorded; 149 | 150 | 151 | #endif /*_NGX_RTMP_CMD_H_INCLUDED_ */ 152 | -------------------------------------------------------------------------------- /ngx_rtmp_codec_module.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Roman Arutyunyan 4 | */ 5 | 6 | 7 | #ifndef _NGX_RTMP_CODEC_H_INCLUDED_ 8 | #define _NGX_RTMP_CODEC_H_INCLUDED_ 9 | 10 | 11 | #include 12 | #include 13 | #include "ngx_rtmp.h" 14 | 15 | 16 | /* Audio codecs */ 17 | enum { 18 | /* Uncompressed codec id is actually 0, 19 | * but we use another value for consistency */ 20 | NGX_RTMP_AUDIO_UNCOMPRESSED = 16, 21 | NGX_RTMP_AUDIO_ADPCM = 1, 22 | NGX_RTMP_AUDIO_MP3 = 2, 23 | NGX_RTMP_AUDIO_LINEAR_LE = 3, 24 | NGX_RTMP_AUDIO_NELLY16 = 4, 25 | NGX_RTMP_AUDIO_NELLY8 = 5, 26 | NGX_RTMP_AUDIO_NELLY = 6, 27 | NGX_RTMP_AUDIO_G711A = 7, 28 | NGX_RTMP_AUDIO_G711U = 8, 29 | NGX_RTMP_AUDIO_AAC = 10, 30 | NGX_RTMP_AUDIO_SPEEX = 11, 31 | NGX_RTMP_AUDIO_MP3_8 = 14, 32 | NGX_RTMP_AUDIO_DEVSPEC = 15, 33 | }; 34 | 35 | 36 | /* Video codecs */ 37 | enum { 38 | NGX_RTMP_VIDEO_JPEG = 1, 39 | NGX_RTMP_VIDEO_SORENSON_H263 = 2, 40 | NGX_RTMP_VIDEO_SCREEN = 3, 41 | NGX_RTMP_VIDEO_ON2_VP6 = 4, 42 | NGX_RTMP_VIDEO_ON2_VP6_ALPHA = 5, 43 | NGX_RTMP_VIDEO_SCREEN2 = 6, 44 | NGX_RTMP_VIDEO_H264 = 7, 45 | NGX_RTMP_VIDEO_H265 = 12 46 | }; 47 | 48 | 49 | u_char * ngx_rtmp_get_audio_codec_name(ngx_uint_t id); 50 | u_char * ngx_rtmp_get_video_codec_name(ngx_uint_t id); 51 | 52 | 53 | typedef struct { 54 | ngx_uint_t width; 55 | ngx_uint_t height; 56 | ngx_uint_t duration; 57 | ngx_uint_t frame_rate; 58 | ngx_uint_t video_data_rate; 59 | ngx_uint_t video_codec_id; 60 | ngx_uint_t audio_data_rate; 61 | ngx_uint_t audio_codec_id; 62 | ngx_uint_t aac_profile; 63 | ngx_uint_t aac_chan_conf; 64 | ngx_uint_t aac_sbr; 65 | ngx_uint_t aac_ps; 66 | ngx_uint_t avc_profile; 67 | ngx_uint_t avc_compat; 68 | ngx_uint_t avc_level; 69 | ngx_uint_t avc_nal_bytes; 70 | ngx_uint_t avc_ref_frames; 71 | ngx_uint_t sample_rate; /* 5512, 11025, 22050, 44100 */ 72 | ngx_uint_t sample_size; /* 1=8bit, 2=16bit */ 73 | ngx_uint_t audio_channels; /* 1, 2 */ 74 | u_char profile[32]; 75 | u_char level[32]; 76 | 77 | ngx_chain_t *avc_header; 78 | ngx_chain_t *aac_header; 79 | 80 | ngx_chain_t *meta; 81 | ngx_uint_t meta_version; 82 | } ngx_rtmp_codec_ctx_t; 83 | 84 | 85 | extern ngx_module_t ngx_rtmp_codec_module; 86 | 87 | 88 | #endif /* _NGX_RTMP_LIVE_H_INCLUDED_ */ 89 | -------------------------------------------------------------------------------- /ngx_rtmp_eval.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Roman Arutyunyan 4 | */ 5 | 6 | 7 | #include 8 | #include 9 | #include "ngx_rtmp_eval.h" 10 | 11 | 12 | #define NGX_RTMP_EVAL_BUFLEN 16 13 | 14 | 15 | static void 16 | ngx_rtmp_eval_session_str(void *ctx, ngx_rtmp_eval_t *e, ngx_str_t *ret) 17 | { 18 | *ret = *(ngx_str_t *) ((u_char *) ctx + e->offset); 19 | } 20 | 21 | 22 | static void 23 | ngx_rtmp_eval_connection_str(void *ctx, ngx_rtmp_eval_t *e, ngx_str_t *ret) 24 | { 25 | ngx_rtmp_session_t *s = ctx; 26 | 27 | *ret = *(ngx_str_t *) ((u_char *) s->connection + e->offset); 28 | } 29 | 30 | 31 | ngx_rtmp_eval_t ngx_rtmp_eval_session[] = { 32 | 33 | { ngx_string("app"), 34 | ngx_rtmp_eval_session_str, 35 | offsetof(ngx_rtmp_session_t, app) }, 36 | 37 | { ngx_string("flashver"), 38 | ngx_rtmp_eval_session_str, 39 | offsetof(ngx_rtmp_session_t, flashver) }, 40 | 41 | { ngx_string("swfurl"), 42 | ngx_rtmp_eval_session_str, 43 | offsetof(ngx_rtmp_session_t, swf_url) }, 44 | 45 | { ngx_string("tcurl"), 46 | ngx_rtmp_eval_session_str, 47 | offsetof(ngx_rtmp_session_t, tc_url) }, 48 | 49 | { ngx_string("pageurl"), 50 | ngx_rtmp_eval_session_str, 51 | offsetof(ngx_rtmp_session_t, page_url) }, 52 | 53 | { ngx_string("addr"), 54 | ngx_rtmp_eval_connection_str, 55 | offsetof(ngx_connection_t, addr_text) }, 56 | 57 | ngx_rtmp_null_eval 58 | }; 59 | 60 | 61 | static void 62 | ngx_rtmp_eval_append(ngx_buf_t *b, void *data, size_t len, ngx_log_t *log) 63 | { 64 | size_t buf_len; 65 | 66 | if (b->last + len > b->end) { 67 | buf_len = 2 * (b->last - b->pos) + len; 68 | 69 | b->start = ngx_alloc(buf_len, log); 70 | if (b->start == NULL) { 71 | return; 72 | } 73 | 74 | b->last = ngx_cpymem(b->start, b->pos, b->last - b->pos); 75 | b->pos = b->start; 76 | b->end = b->start + buf_len; 77 | } 78 | 79 | b->last = ngx_cpymem(b->last, data, len); 80 | } 81 | 82 | 83 | static void 84 | ngx_rtmp_eval_append_var(void *ctx, ngx_buf_t *b, ngx_rtmp_eval_t **e, 85 | ngx_str_t *name, ngx_log_t *log) 86 | { 87 | ngx_uint_t k; 88 | ngx_str_t v; 89 | ngx_rtmp_eval_t *ee; 90 | 91 | for (; *e; ++e) { 92 | for (k = 0, ee = *e; ee->handler; ++k, ++ee) { 93 | if (ee->name.len == name->len && 94 | ngx_memcmp(ee->name.data, name->data, name->len) == 0) 95 | { 96 | ee->handler(ctx, ee, &v); 97 | ngx_rtmp_eval_append(b, v.data, v.len, log); 98 | } 99 | } 100 | } 101 | } 102 | 103 | 104 | ngx_int_t 105 | ngx_rtmp_eval(void *ctx, ngx_str_t *in, ngx_rtmp_eval_t **e, ngx_str_t *out, 106 | ngx_log_t *log) 107 | { 108 | u_char c, *p; 109 | ngx_str_t name; 110 | ngx_buf_t b; 111 | ngx_uint_t n; 112 | 113 | enum { 114 | NORMAL, 115 | ESCAPE, 116 | NAME, 117 | SNAME 118 | } state = NORMAL; 119 | 120 | b.pos = b.last = b.start = ngx_alloc(NGX_RTMP_EVAL_BUFLEN, log); 121 | if (b.pos == NULL) { 122 | return NGX_ERROR; 123 | } 124 | 125 | b.end = b.pos + NGX_RTMP_EVAL_BUFLEN; 126 | name.data = NULL; 127 | 128 | for (n = 0; n < in->len; ++n) { 129 | p = &in->data[n]; 130 | c = *p; 131 | 132 | switch (state) { 133 | case SNAME: 134 | if (c != '}') { 135 | continue; 136 | } 137 | 138 | name.len = p - name.data; 139 | ngx_rtmp_eval_append_var(ctx, &b, e, &name, log); 140 | 141 | state = NORMAL; 142 | 143 | continue; 144 | 145 | case NAME: 146 | if (c == '{' && name.data == p) { 147 | ++name.data; 148 | state = SNAME; 149 | continue; 150 | } 151 | if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { 152 | continue; 153 | } 154 | 155 | name.len = p - name.data; 156 | ngx_rtmp_eval_append_var(ctx, &b, e, &name, log); 157 | /* fall through */ 158 | 159 | case NORMAL: 160 | switch (c) { 161 | case '$': 162 | name.data = p + 1; 163 | state = NAME; 164 | continue; 165 | case '\\': 166 | state = ESCAPE; 167 | continue; 168 | } 169 | 170 | case ESCAPE: 171 | ngx_rtmp_eval_append(&b, &c, 1, log); 172 | state = NORMAL; 173 | break; 174 | 175 | } 176 | } 177 | 178 | if (state == NAME) { 179 | p = &in->data[n]; 180 | name.len = p - name.data; 181 | ngx_rtmp_eval_append_var(ctx, &b, e, &name, log); 182 | } 183 | 184 | c = 0; 185 | ngx_rtmp_eval_append(&b, &c, 1, log); 186 | 187 | out->data = b.pos; 188 | out->len = b.last - b.pos - 1; 189 | 190 | return NGX_OK; 191 | } 192 | 193 | 194 | ngx_int_t 195 | ngx_rtmp_eval_streams(ngx_str_t *in) 196 | { 197 | #if !(NGX_WIN32) 198 | ngx_int_t mode, create, v, close_src; 199 | ngx_fd_t dst, src; 200 | u_char *path; 201 | 202 | path = in->data; 203 | 204 | while (*path >= '0' && *path <= '9') { 205 | path++; 206 | } 207 | 208 | switch ((char) *path) { 209 | 210 | case '>': 211 | 212 | v = (path == in->data ? 1 : ngx_atoi(in->data, path - in->data)); 213 | if (v == NGX_ERROR) { 214 | return NGX_ERROR; 215 | } 216 | 217 | dst = (ngx_fd_t) v; 218 | mode = NGX_FILE_WRONLY; 219 | create = NGX_FILE_TRUNCATE; 220 | path++; 221 | 222 | if (*path == (u_char) '>') { 223 | mode = NGX_FILE_APPEND; 224 | create = NGX_FILE_CREATE_OR_OPEN; 225 | path++; 226 | } 227 | 228 | break; 229 | 230 | case '<': 231 | 232 | v = (path == in->data ? 0 : ngx_atoi(in->data, path - in->data)); 233 | if (v == NGX_ERROR) { 234 | return NGX_ERROR; 235 | } 236 | 237 | dst = (ngx_fd_t) v; 238 | mode = NGX_FILE_RDONLY; 239 | create = NGX_FILE_OPEN; 240 | path++; 241 | 242 | break; 243 | 244 | default: 245 | 246 | return NGX_DONE; 247 | } 248 | 249 | if (*path == (u_char) '&') { 250 | 251 | path++; 252 | v = ngx_atoi(path, in->data + in->len - path); 253 | if (v == NGX_ERROR) { 254 | return NGX_ERROR; 255 | } 256 | src = (ngx_fd_t) v; 257 | close_src = 0; 258 | 259 | } else { 260 | 261 | src = ngx_open_file(path, mode, create, NGX_FILE_DEFAULT_ACCESS); 262 | if (src == NGX_INVALID_FILE) { 263 | return NGX_ERROR; 264 | } 265 | close_src = 1; 266 | 267 | } 268 | 269 | if (src == dst) { 270 | return NGX_OK; 271 | } 272 | 273 | dup2(src, dst); 274 | 275 | if (close_src) { 276 | ngx_close_file(src); 277 | } 278 | return NGX_OK; 279 | 280 | #else 281 | return NGX_DONE; 282 | #endif 283 | } 284 | -------------------------------------------------------------------------------- /ngx_rtmp_eval.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Roman Arutyunyan 4 | */ 5 | 6 | 7 | #ifndef _NGX_RTMP_EVAL_H_INCLUDED_ 8 | #define _NGX_RTMP_EVAL_H_INCLUDED_ 9 | 10 | 11 | #include 12 | #include 13 | #include "ngx_rtmp.h" 14 | 15 | 16 | typedef struct ngx_rtmp_eval_s ngx_rtmp_eval_t; 17 | 18 | 19 | typedef void (* ngx_rtmp_eval_pt)(void *ctx, ngx_rtmp_eval_t *e, 20 | ngx_str_t *ret); 21 | 22 | 23 | struct ngx_rtmp_eval_s { 24 | ngx_str_t name; 25 | ngx_rtmp_eval_pt handler; 26 | ngx_uint_t offset; 27 | }; 28 | 29 | 30 | #define ngx_rtmp_null_eval { ngx_null_string, NULL, 0 } 31 | 32 | 33 | /* standard session eval variables */ 34 | extern ngx_rtmp_eval_t ngx_rtmp_eval_session[]; 35 | 36 | 37 | ngx_int_t ngx_rtmp_eval(void *ctx, ngx_str_t *in, ngx_rtmp_eval_t **e, 38 | ngx_str_t *out, ngx_log_t *log); 39 | 40 | 41 | ngx_int_t ngx_rtmp_eval_streams(ngx_str_t *in); 42 | 43 | 44 | #endif /* _NGX_RTMP_EVAL_H_INCLUDED_ */ 45 | -------------------------------------------------------------------------------- /ngx_rtmp_init.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Roman Arutyunyan 4 | */ 5 | 6 | 7 | #include 8 | #include 9 | #include "ngx_rtmp.h" 10 | #include "ngx_rtmp_proxy_protocol.h" 11 | 12 | 13 | static void ngx_rtmp_close_connection(ngx_connection_t *c); 14 | static u_char * ngx_rtmp_log_error(ngx_log_t *log, u_char *buf, size_t len); 15 | 16 | 17 | void 18 | ngx_rtmp_init_connection(ngx_connection_t *c) 19 | { 20 | ngx_uint_t i; 21 | ngx_rtmp_port_t *port; 22 | struct sockaddr *sa; 23 | struct sockaddr_in *sin; 24 | ngx_rtmp_in_addr_t *addr; 25 | ngx_rtmp_session_t *s; 26 | ngx_rtmp_addr_conf_t *addr_conf; 27 | ngx_int_t unix_socket; 28 | #if (NGX_HAVE_INET6) 29 | struct sockaddr_in6 *sin6; 30 | ngx_rtmp_in6_addr_t *addr6; 31 | #endif 32 | 33 | ++ngx_rtmp_naccepted; 34 | 35 | /* find the server configuration for the address:port */ 36 | 37 | /* AF_INET only */ 38 | 39 | port = c->listening->servers; 40 | unix_socket = 0; 41 | 42 | if (port->naddrs > 1) { 43 | 44 | /* 45 | * There are several addresses on this port and one of them 46 | * is the "*:port" wildcard so getsockname() is needed to determine 47 | * the server address. 48 | * 49 | * AcceptEx() already gave this address. 50 | */ 51 | 52 | if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) { 53 | ngx_rtmp_close_connection(c); 54 | return; 55 | } 56 | 57 | sa = c->local_sockaddr; 58 | 59 | switch (sa->sa_family) { 60 | 61 | #if (NGX_HAVE_INET6) 62 | case AF_INET6: 63 | sin6 = (struct sockaddr_in6 *) sa; 64 | 65 | addr6 = port->addrs; 66 | 67 | /* the last address is "*" */ 68 | 69 | for (i = 0; i < port->naddrs - 1; i++) { 70 | if (ngx_memcmp(&addr6[i].addr6, &sin6->sin6_addr, 16) == 0) { 71 | break; 72 | } 73 | } 74 | 75 | addr_conf = &addr6[i].conf; 76 | 77 | break; 78 | #endif 79 | 80 | case AF_UNIX: 81 | unix_socket = 1; 82 | /* fall through */ 83 | 84 | default: /* AF_INET */ 85 | sin = (struct sockaddr_in *) sa; 86 | 87 | addr = port->addrs; 88 | 89 | /* the last address is "*" */ 90 | 91 | for (i = 0; i < port->naddrs - 1; i++) { 92 | if (addr[i].addr == sin->sin_addr.s_addr) { 93 | break; 94 | } 95 | } 96 | 97 | addr_conf = &addr[i].conf; 98 | 99 | break; 100 | } 101 | 102 | } else { 103 | switch (c->local_sockaddr->sa_family) { 104 | 105 | #if (NGX_HAVE_INET6) 106 | case AF_INET6: 107 | addr6 = port->addrs; 108 | addr_conf = &addr6[0].conf; 109 | break; 110 | #endif 111 | 112 | case AF_UNIX: 113 | unix_socket = 1; 114 | /* fall through */ 115 | 116 | default: /* AF_INET */ 117 | addr = port->addrs; 118 | addr_conf = &addr[0].conf; 119 | break; 120 | } 121 | } 122 | 123 | ngx_log_error(NGX_LOG_INFO, c->log, 0, "*%ui client connected '%V'", 124 | c->number, &c->addr_text); 125 | 126 | s = ngx_rtmp_init_session(c, addr_conf); 127 | if (s == NULL) { 128 | return; 129 | } 130 | 131 | /* only auto-pushed connections are 132 | * done through unix socket */ 133 | 134 | s->auto_pushed = unix_socket; 135 | 136 | if (addr_conf->proxy_protocol) { 137 | ngx_rtmp_proxy_protocol(s); 138 | 139 | } else { 140 | ngx_rtmp_handshake(s); 141 | } 142 | } 143 | 144 | 145 | ngx_rtmp_session_t * 146 | ngx_rtmp_init_session(ngx_connection_t *c, ngx_rtmp_addr_conf_t *addr_conf) 147 | { 148 | ngx_rtmp_session_t *s; 149 | ngx_rtmp_core_srv_conf_t *cscf; 150 | ngx_rtmp_error_log_ctx_t *ctx; 151 | 152 | s = ngx_pcalloc(c->pool, sizeof(ngx_rtmp_session_t) + 153 | sizeof(ngx_chain_t *) * ((ngx_rtmp_core_srv_conf_t *) 154 | addr_conf->ctx-> srv_conf[ngx_rtmp_core_module 155 | .ctx_index])->out_queue); 156 | if (s == NULL) { 157 | ngx_rtmp_close_connection(c); 158 | return NULL; 159 | } 160 | 161 | s->main_conf = addr_conf->ctx->main_conf; 162 | s->srv_conf = addr_conf->ctx->srv_conf; 163 | 164 | s->addr_text = &addr_conf->addr_text; 165 | 166 | c->data = s; 167 | s->connection = c; 168 | 169 | ctx = ngx_palloc(c->pool, sizeof(ngx_rtmp_error_log_ctx_t)); 170 | if (ctx == NULL) { 171 | ngx_rtmp_close_connection(c); 172 | return NULL; 173 | } 174 | 175 | ctx->client = &c->addr_text; 176 | ctx->session = s; 177 | 178 | c->log->connection = c->number; 179 | c->log->handler = ngx_rtmp_log_error; 180 | c->log->data = ctx; 181 | c->log->action = NULL; 182 | 183 | c->log_error = NGX_ERROR_INFO; 184 | 185 | s->ctx = ngx_pcalloc(c->pool, sizeof(void *) * ngx_rtmp_max_module); 186 | if (s->ctx == NULL) { 187 | ngx_rtmp_close_connection(c); 188 | return NULL; 189 | } 190 | 191 | cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); 192 | 193 | s->out_queue = cscf->out_queue; 194 | s->out_cork = cscf->out_cork; 195 | s->in_streams = ngx_pcalloc(c->pool, sizeof(ngx_rtmp_stream_t) 196 | * cscf->max_streams); 197 | if (s->in_streams == NULL) { 198 | ngx_rtmp_close_connection(c); 199 | return NULL; 200 | } 201 | 202 | #if (nginx_version >= 1007005) 203 | ngx_queue_init(&s->posted_dry_events); 204 | #endif 205 | 206 | s->epoch = ngx_current_msec; 207 | s->timeout = cscf->timeout; 208 | s->buflen = cscf->buflen; 209 | ngx_rtmp_set_chunk_size(s, NGX_RTMP_DEFAULT_CHUNK_SIZE); 210 | 211 | 212 | if (ngx_rtmp_fire_event(s, NGX_RTMP_CONNECT, NULL, NULL) != NGX_OK) { 213 | ngx_rtmp_finalize_session(s); 214 | return NULL; 215 | } 216 | 217 | return s; 218 | } 219 | 220 | 221 | static u_char * 222 | ngx_rtmp_log_error(ngx_log_t *log, u_char *buf, size_t len) 223 | { 224 | u_char *p; 225 | ngx_rtmp_session_t *s; 226 | ngx_rtmp_error_log_ctx_t *ctx; 227 | 228 | if (log->action) { 229 | p = ngx_snprintf(buf, len, " while %s", log->action); 230 | len -= p - buf; 231 | buf = p; 232 | } 233 | 234 | ctx = log->data; 235 | 236 | p = ngx_snprintf(buf, len, ", client: %V", ctx->client); 237 | len -= p - buf; 238 | buf = p; 239 | 240 | s = ctx->session; 241 | 242 | if (s == NULL) { 243 | return p; 244 | } 245 | 246 | p = ngx_snprintf(buf, len, ", server: %V", s->addr_text); 247 | len -= p - buf; 248 | buf = p; 249 | 250 | return p; 251 | } 252 | 253 | 254 | static void 255 | ngx_rtmp_close_connection(ngx_connection_t *c) 256 | { 257 | ngx_pool_t *pool; 258 | 259 | ngx_log_debug0(NGX_LOG_DEBUG_RTMP, c->log, 0, "close connection"); 260 | 261 | #if (NGX_STAT_STUB) 262 | (void) ngx_atomic_fetch_add(ngx_stat_active, -1); 263 | #endif 264 | 265 | pool = c->pool; 266 | ngx_close_connection(c); 267 | ngx_destroy_pool(pool); 268 | } 269 | 270 | 271 | static void 272 | ngx_rtmp_close_session_handler(ngx_event_t *e) 273 | { 274 | ngx_rtmp_session_t *s; 275 | ngx_connection_t *c; 276 | ngx_rtmp_core_srv_conf_t *cscf; 277 | 278 | s = e->data; 279 | c = s->connection; 280 | 281 | cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); 282 | 283 | ngx_log_debug0(NGX_LOG_DEBUG_RTMP, c->log, 0, "close session"); 284 | 285 | ngx_rtmp_fire_event(s, NGX_RTMP_DISCONNECT, NULL, NULL); 286 | 287 | if (s->ping_evt.timer_set) { 288 | ngx_del_timer(&s->ping_evt); 289 | } 290 | 291 | if (s->in_old_pool) { 292 | ngx_destroy_pool(s->in_old_pool); 293 | } 294 | 295 | if (s->in_pool) { 296 | ngx_destroy_pool(s->in_pool); 297 | } 298 | 299 | ngx_rtmp_free_handshake_buffers(s); 300 | 301 | while (s->out_pos != s->out_last) { 302 | ngx_rtmp_free_shared_chain(cscf, s->out[s->out_pos++]); 303 | s->out_pos %= s->out_queue; 304 | } 305 | 306 | ngx_rtmp_close_connection(c); 307 | } 308 | 309 | 310 | void 311 | ngx_rtmp_finalize_session(ngx_rtmp_session_t *s) 312 | { 313 | ngx_event_t *e; 314 | ngx_connection_t *c; 315 | 316 | c = s->connection; 317 | if (c->destroyed) { 318 | return; 319 | } 320 | 321 | ngx_log_debug0(NGX_LOG_DEBUG_RTMP, c->log, 0, "finalize session"); 322 | 323 | c->destroyed = 1; 324 | e = &s->close; 325 | e->data = s; 326 | e->handler = ngx_rtmp_close_session_handler; 327 | e->log = c->log; 328 | 329 | ngx_post_event(e, &ngx_posted_events); 330 | } 331 | 332 | -------------------------------------------------------------------------------- /ngx_rtmp_limit_module.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Roman Arutyunyan 4 | */ 5 | 6 | 7 | #include 8 | #include 9 | #include "ngx_rtmp.h" 10 | 11 | 12 | typedef struct { 13 | ngx_int_t max_conn; 14 | ngx_shm_zone_t *shm_zone; 15 | } ngx_rtmp_limit_main_conf_t; 16 | 17 | 18 | static ngx_str_t shm_name = ngx_string("rtmp_limit"); 19 | 20 | 21 | static ngx_int_t ngx_rtmp_limit_postconfiguration(ngx_conf_t *cf); 22 | static void *ngx_rtmp_limit_create_main_conf(ngx_conf_t *cf); 23 | 24 | 25 | static ngx_command_t ngx_rtmp_limit_commands[] = { 26 | 27 | { ngx_string("max_connections"), 28 | NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1, 29 | ngx_conf_set_num_slot, 30 | NGX_RTMP_MAIN_CONF_OFFSET, 31 | offsetof(ngx_rtmp_limit_main_conf_t, max_conn), 32 | NULL }, 33 | 34 | ngx_null_command 35 | }; 36 | 37 | 38 | static ngx_rtmp_module_t ngx_rtmp_limit_module_ctx = { 39 | NULL, /* preconfiguration */ 40 | ngx_rtmp_limit_postconfiguration, /* postconfiguration */ 41 | ngx_rtmp_limit_create_main_conf, /* create main configuration */ 42 | NULL, /* init main configuration */ 43 | NULL, /* create server configuration */ 44 | NULL, /* merge server configuration */ 45 | NULL, /* create app configuration */ 46 | NULL /* merge app configuration */ 47 | }; 48 | 49 | 50 | ngx_module_t ngx_rtmp_limit_module = { 51 | NGX_MODULE_V1, 52 | &ngx_rtmp_limit_module_ctx, /* module context */ 53 | ngx_rtmp_limit_commands, /* module directives */ 54 | NGX_RTMP_MODULE, /* module type */ 55 | NULL, /* init master */ 56 | NULL, /* init module */ 57 | NULL, /* init process */ 58 | NULL, /* init thread */ 59 | NULL, /* exit thread */ 60 | NULL, /* exit process */ 61 | NULL, /* exit master */ 62 | NGX_MODULE_V1_PADDING 63 | }; 64 | 65 | 66 | static void * 67 | ngx_rtmp_limit_create_main_conf(ngx_conf_t *cf) 68 | { 69 | ngx_rtmp_limit_main_conf_t *lmcf; 70 | 71 | lmcf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_limit_main_conf_t)); 72 | if (lmcf == NULL) { 73 | return NULL; 74 | } 75 | 76 | lmcf->max_conn = NGX_CONF_UNSET; 77 | 78 | return lmcf; 79 | } 80 | 81 | 82 | static ngx_int_t 83 | ngx_rtmp_limit_connect(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, 84 | ngx_chain_t *in) 85 | { 86 | ngx_rtmp_limit_main_conf_t *lmcf; 87 | ngx_slab_pool_t *shpool; 88 | ngx_shm_zone_t *shm_zone; 89 | uint32_t *nconn, n; 90 | ngx_int_t rc; 91 | 92 | lmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_limit_module); 93 | if (lmcf->max_conn == NGX_CONF_UNSET) { 94 | return NGX_OK; 95 | } 96 | 97 | shm_zone = lmcf->shm_zone; 98 | shpool = (ngx_slab_pool_t *) shm_zone->shm.addr; 99 | nconn = shm_zone->data; 100 | 101 | ngx_shmtx_lock(&shpool->mutex); 102 | n = ++*nconn; 103 | ngx_shmtx_unlock(&shpool->mutex); 104 | 105 | rc = n > (ngx_uint_t) lmcf->max_conn ? NGX_ERROR : NGX_OK; 106 | 107 | ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 108 | "limit: inc conection counter: %uD", n); 109 | 110 | if (rc != NGX_OK) { 111 | ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, 112 | "limit: too many connections: %uD > %i", 113 | n, lmcf->max_conn); 114 | } 115 | 116 | return rc; 117 | } 118 | 119 | 120 | static ngx_int_t 121 | ngx_rtmp_limit_disconnect(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, 122 | ngx_chain_t *in) 123 | { 124 | ngx_rtmp_limit_main_conf_t *lmcf; 125 | ngx_slab_pool_t *shpool; 126 | ngx_shm_zone_t *shm_zone; 127 | uint32_t *nconn, n; 128 | 129 | lmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_limit_module); 130 | if (lmcf->max_conn == NGX_CONF_UNSET) { 131 | return NGX_OK; 132 | } 133 | 134 | shm_zone = lmcf->shm_zone; 135 | shpool = (ngx_slab_pool_t *) shm_zone->shm.addr; 136 | nconn = shm_zone->data; 137 | 138 | ngx_shmtx_lock(&shpool->mutex); 139 | n = --*nconn; 140 | ngx_shmtx_unlock(&shpool->mutex); 141 | 142 | (void) n; 143 | ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 144 | "limit: dec conection counter: %uD", n); 145 | 146 | return NGX_OK; 147 | } 148 | 149 | 150 | static ngx_int_t 151 | ngx_rtmp_limit_shm_init(ngx_shm_zone_t *shm_zone, void *data) 152 | { 153 | ngx_slab_pool_t *shpool; 154 | uint32_t *nconn; 155 | 156 | if (data) { 157 | shm_zone->data = data; 158 | return NGX_OK; 159 | } 160 | 161 | shpool = (ngx_slab_pool_t *) shm_zone->shm.addr; 162 | 163 | nconn = ngx_slab_alloc(shpool, 4); 164 | if (nconn == NULL) { 165 | return NGX_ERROR; 166 | } 167 | 168 | *nconn = 0; 169 | 170 | shm_zone->data = nconn; 171 | 172 | return NGX_OK; 173 | } 174 | 175 | 176 | static ngx_int_t 177 | ngx_rtmp_limit_postconfiguration(ngx_conf_t *cf) 178 | { 179 | ngx_rtmp_core_main_conf_t *cmcf; 180 | ngx_rtmp_limit_main_conf_t *lmcf; 181 | ngx_rtmp_handler_pt *h; 182 | 183 | cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module); 184 | 185 | h = ngx_array_push(&cmcf->events[NGX_RTMP_CONNECT]); 186 | *h = ngx_rtmp_limit_connect; 187 | 188 | h = ngx_array_push(&cmcf->events[NGX_RTMP_DISCONNECT]); 189 | *h = ngx_rtmp_limit_disconnect; 190 | 191 | lmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_limit_module); 192 | if (lmcf->max_conn == NGX_CONF_UNSET) { 193 | return NGX_OK; 194 | } 195 | 196 | lmcf->shm_zone = ngx_shared_memory_add(cf, &shm_name, ngx_pagesize * 2, 197 | &ngx_rtmp_limit_module); 198 | if (lmcf->shm_zone == NULL) { 199 | return NGX_ERROR; 200 | } 201 | 202 | lmcf->shm_zone->init = ngx_rtmp_limit_shm_init; 203 | 204 | return NGX_OK; 205 | } 206 | -------------------------------------------------------------------------------- /ngx_rtmp_live_module.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Roman Arutyunyan 4 | */ 5 | 6 | 7 | #ifndef _NGX_RTMP_LIVE_H_INCLUDED_ 8 | #define _NGX_RTMP_LIVE_H_INCLUDED_ 9 | 10 | 11 | #include 12 | #include 13 | #include "ngx_rtmp.h" 14 | #include "ngx_rtmp_cmd_module.h" 15 | #include "ngx_rtmp_bandwidth.h" 16 | #include "ngx_rtmp_streams.h" 17 | 18 | 19 | typedef struct ngx_rtmp_live_ctx_s ngx_rtmp_live_ctx_t; 20 | typedef struct ngx_rtmp_live_stream_s ngx_rtmp_live_stream_t; 21 | 22 | 23 | typedef struct { 24 | unsigned active:1; 25 | uint32_t timestamp; 26 | uint32_t csid; 27 | uint32_t dropped; 28 | } ngx_rtmp_live_chunk_stream_t; 29 | 30 | 31 | struct ngx_rtmp_live_ctx_s { 32 | ngx_rtmp_session_t *session; 33 | ngx_rtmp_live_stream_t *stream; 34 | ngx_rtmp_live_ctx_t *next; 35 | ngx_uint_t ndropped; 36 | ngx_rtmp_live_chunk_stream_t cs[2]; 37 | ngx_uint_t meta_version; 38 | ngx_event_t idle_evt; 39 | unsigned active:1; 40 | unsigned publishing:1; 41 | unsigned silent:1; 42 | unsigned paused:1; 43 | }; 44 | 45 | 46 | struct ngx_rtmp_live_stream_s { 47 | u_char name[NGX_RTMP_MAX_NAME]; 48 | ngx_rtmp_live_stream_t *next; 49 | ngx_rtmp_live_ctx_t *ctx; 50 | ngx_rtmp_bandwidth_t bw_in; 51 | ngx_rtmp_bandwidth_t bw_in_audio; 52 | ngx_rtmp_bandwidth_t bw_in_video; 53 | ngx_rtmp_bandwidth_t bw_out; 54 | ngx_msec_t epoch; 55 | unsigned active:1; 56 | unsigned publishing:1; 57 | }; 58 | 59 | 60 | typedef struct { 61 | ngx_int_t nbuckets; 62 | ngx_rtmp_live_stream_t **streams; 63 | ngx_flag_t live; 64 | ngx_flag_t meta; 65 | ngx_msec_t sync; 66 | ngx_msec_t idle_timeout; 67 | ngx_flag_t atc; 68 | ngx_flag_t interleave; 69 | ngx_flag_t wait_key; 70 | ngx_flag_t wait_video; 71 | ngx_flag_t publish_notify; 72 | ngx_flag_t play_restart; 73 | ngx_flag_t idle_streams; 74 | ngx_msec_t buflen; 75 | ngx_pool_t *pool; 76 | ngx_rtmp_live_stream_t *free_streams; 77 | } ngx_rtmp_live_app_conf_t; 78 | 79 | 80 | extern ngx_module_t ngx_rtmp_live_module; 81 | 82 | 83 | #endif /* _NGX_RTMP_LIVE_H_INCLUDED_ */ 84 | -------------------------------------------------------------------------------- /ngx_rtmp_netcall_module.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Roman Arutyunyan 4 | */ 5 | 6 | 7 | #ifndef _NGX_RTMP_NETCALL_H_INCLUDED_ 8 | #define _NGX_RTMP_NETCALL_H_INCLUDED_ 9 | 10 | 11 | #include 12 | #include 13 | #include "ngx_rtmp.h" 14 | 15 | 16 | typedef ngx_chain_t * (*ngx_rtmp_netcall_create_pt)(ngx_rtmp_session_t *s, 17 | void *arg, ngx_pool_t *pool); 18 | typedef ngx_int_t (*ngx_rtmp_netcall_filter_pt)(ngx_chain_t *in); 19 | typedef ngx_int_t (*ngx_rtmp_netcall_sink_pt)(ngx_rtmp_session_t *s, 20 | ngx_chain_t *in); 21 | typedef ngx_int_t (*ngx_rtmp_netcall_handle_pt)(ngx_rtmp_session_t *s, 22 | void *arg, ngx_chain_t *in); 23 | 24 | #define NGX_RTMP_NETCALL_HTTP_GET 0 25 | #define NGX_RTMP_NETCALL_HTTP_POST 1 26 | 27 | 28 | /* If handle is NULL then netcall is created detached 29 | * which means it's completely independent of RTMP 30 | * session and its result is never visible to anyone. 31 | * 32 | * WARNING: It's not recommended to create non-detached 33 | * netcalls from disconect handlers. Netcall disconnect 34 | * handler which detaches active netcalls is executed 35 | * BEFORE your handler. It leads to a crash 36 | * after netcall connection is closed */ 37 | typedef struct { 38 | ngx_url_t *url; 39 | ngx_rtmp_netcall_create_pt create; 40 | ngx_rtmp_netcall_filter_pt filter; 41 | ngx_rtmp_netcall_sink_pt sink; 42 | ngx_rtmp_netcall_handle_pt handle; 43 | void *arg; 44 | size_t argsize; 45 | } ngx_rtmp_netcall_init_t; 46 | 47 | 48 | ngx_int_t ngx_rtmp_netcall_create(ngx_rtmp_session_t *s, 49 | ngx_rtmp_netcall_init_t *ci); 50 | 51 | 52 | /* HTTP handling */ 53 | ngx_chain_t * ngx_rtmp_netcall_http_format_session(ngx_rtmp_session_t *s, 54 | ngx_pool_t *pool); 55 | ngx_chain_t * ngx_rtmp_netcall_http_format_request(ngx_int_t method, 56 | ngx_str_t *host, ngx_str_t *uri, ngx_chain_t *args, ngx_chain_t *body, 57 | ngx_pool_t *pool, ngx_str_t *content_type); 58 | ngx_chain_t * ngx_rtmp_netcall_http_skip_header(ngx_chain_t *in); 59 | 60 | 61 | /* Memcache handling */ 62 | ngx_chain_t * ngx_rtmp_netcall_memcache_set(ngx_rtmp_session_t *s, 63 | ngx_pool_t *pool, ngx_str_t *key, ngx_str_t *value, 64 | ngx_uint_t flags, ngx_uint_t sec); 65 | 66 | 67 | #endif /* _NGX_RTMP_NETCALL_H_INCLUDED_ */ 68 | -------------------------------------------------------------------------------- /ngx_rtmp_play_module.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Roman Arutyunyan 4 | */ 5 | 6 | 7 | #ifndef _NGX_RTMP_PLAY_H_INCLUDED_ 8 | #define _NGX_RTMP_PLAY_H_INCLUDED_ 9 | 10 | 11 | #include 12 | #include 13 | #include "ngx_rtmp.h" 14 | #include "ngx_rtmp_cmd_module.h" 15 | 16 | 17 | typedef ngx_int_t (*ngx_rtmp_play_init_pt) (ngx_rtmp_session_t *s, 18 | ngx_file_t *f, ngx_int_t aindex, ngx_int_t vindex); 19 | typedef ngx_int_t (*ngx_rtmp_play_done_pt) (ngx_rtmp_session_t *s, 20 | ngx_file_t *f); 21 | typedef ngx_int_t (*ngx_rtmp_play_start_pt) (ngx_rtmp_session_t *s, 22 | ngx_file_t *f); 23 | typedef ngx_int_t (*ngx_rtmp_play_seek_pt) (ngx_rtmp_session_t *s, 24 | ngx_file_t *f, ngx_uint_t offs); 25 | typedef ngx_int_t (*ngx_rtmp_play_stop_pt) (ngx_rtmp_session_t *s, 26 | ngx_file_t *f); 27 | typedef ngx_int_t (*ngx_rtmp_play_send_pt) (ngx_rtmp_session_t *s, 28 | ngx_file_t *f, ngx_uint_t *ts); 29 | 30 | 31 | typedef struct { 32 | ngx_str_t name; 33 | ngx_str_t pfx; 34 | ngx_str_t sfx; 35 | 36 | ngx_rtmp_play_init_pt init; 37 | ngx_rtmp_play_done_pt done; 38 | ngx_rtmp_play_start_pt start; 39 | ngx_rtmp_play_seek_pt seek; 40 | ngx_rtmp_play_stop_pt stop; 41 | ngx_rtmp_play_send_pt send; 42 | } ngx_rtmp_play_fmt_t; 43 | 44 | 45 | typedef struct ngx_rtmp_play_ctx_s ngx_rtmp_play_ctx_t; 46 | 47 | 48 | struct ngx_rtmp_play_ctx_s { 49 | ngx_rtmp_session_t *session; 50 | ngx_file_t file; 51 | ngx_rtmp_play_fmt_t *fmt; 52 | ngx_event_t send_evt; 53 | unsigned playing:1; 54 | unsigned opened:1; 55 | unsigned joined:1; 56 | ngx_uint_t ncrs; 57 | ngx_uint_t nheader; 58 | ngx_uint_t nbody; 59 | size_t pfx_size; 60 | ngx_str_t sfx; 61 | ngx_uint_t file_id; 62 | ngx_int_t aindex, vindex; 63 | ngx_uint_t nentry; 64 | ngx_uint_t post_seek; 65 | u_char name[NGX_RTMP_MAX_NAME]; 66 | ngx_rtmp_play_ctx_t *next; 67 | }; 68 | 69 | 70 | typedef struct { 71 | ngx_str_t *root; 72 | ngx_url_t *url; 73 | } ngx_rtmp_play_entry_t; 74 | 75 | 76 | typedef struct { 77 | ngx_str_t temp_path; 78 | ngx_str_t local_path; 79 | ngx_array_t entries; /* ngx_rtmp_play_entry_t * */ 80 | ngx_uint_t nbuckets; 81 | ngx_rtmp_play_ctx_t **ctx; 82 | } ngx_rtmp_play_app_conf_t; 83 | 84 | 85 | typedef struct { 86 | ngx_array_t fmts; /* ngx_rtmp_play_fmt_t * */ 87 | } ngx_rtmp_play_main_conf_t; 88 | 89 | 90 | extern ngx_module_t ngx_rtmp_play_module; 91 | 92 | 93 | #endif /* _NGX_RTMP_PLAY_H_INCLUDED_ */ 94 | -------------------------------------------------------------------------------- /ngx_rtmp_proxy_protocol.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Roman Arutyunyan 4 | */ 5 | 6 | 7 | #include 8 | #include 9 | #include 10 | #include "ngx_rtmp_proxy_protocol.h" 11 | 12 | 13 | static void ngx_rtmp_proxy_protocol_recv(ngx_event_t *rev); 14 | 15 | 16 | void 17 | ngx_rtmp_proxy_protocol(ngx_rtmp_session_t *s) 18 | { 19 | ngx_event_t *rev; 20 | ngx_connection_t *c; 21 | 22 | c = s->connection; 23 | rev = c->read; 24 | rev->handler = ngx_rtmp_proxy_protocol_recv; 25 | 26 | ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 27 | "proxy_protocol: start"); 28 | 29 | if (rev->ready) { 30 | /* the deferred accept(), rtsig, aio, iocp */ 31 | 32 | if (ngx_use_accept_mutex) { 33 | ngx_post_event(rev, &ngx_posted_events); 34 | return; 35 | } 36 | 37 | rev->handler(rev); 38 | return; 39 | } 40 | 41 | ngx_add_timer(rev, s->timeout); 42 | 43 | if (ngx_handle_read_event(rev, 0) != NGX_OK) { 44 | ngx_rtmp_finalize_session(s); 45 | return; 46 | } 47 | } 48 | 49 | 50 | static void 51 | ngx_rtmp_proxy_protocol_recv(ngx_event_t *rev) 52 | { 53 | u_char buf[107], *p, *pp, *text; 54 | size_t len; 55 | ssize_t n; 56 | ngx_err_t err; 57 | ngx_int_t i; 58 | ngx_addr_t addr; 59 | ngx_connection_t *c; 60 | ngx_rtmp_session_t *s; 61 | 62 | c = rev->data; 63 | s = c->data; 64 | 65 | if (c->destroyed) { 66 | return; 67 | } 68 | 69 | if (rev->timedout) { 70 | ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, 71 | "proxy_protocol: recv: client timed out"); 72 | c->timedout = 1; 73 | ngx_rtmp_finalize_session(s); 74 | return; 75 | } 76 | 77 | if (rev->timer_set) { 78 | ngx_del_timer(rev); 79 | } 80 | 81 | n = recv(c->fd, (char *) buf, sizeof(buf), MSG_PEEK); 82 | 83 | err = ngx_socket_errno; 84 | 85 | ngx_log_debug1(NGX_LOG_DEBUG_RTMP, c->log, 0, "recv(): %d", n); 86 | 87 | if (n == -1) { 88 | 89 | if (err == NGX_EAGAIN) { 90 | ngx_add_timer(rev, s->timeout); 91 | 92 | if (ngx_handle_read_event(c->read, 0) != NGX_OK) { 93 | ngx_rtmp_finalize_session(s); 94 | } 95 | 96 | return; 97 | } 98 | 99 | ngx_rtmp_finalize_session(s); 100 | 101 | return; 102 | } 103 | 104 | p = buf; 105 | 106 | if (n <= 8 && ngx_strncmp(p, "PROXY ", 6) != 0) { 107 | goto bad_header; 108 | } 109 | 110 | n -= 6; 111 | p += 6; 112 | 113 | ngx_memzero(&addr, sizeof(ngx_addr_t)); 114 | 115 | if (n >= 7 && ngx_strncmp(p, "UNKNOWN", 7) == 0) { 116 | n -= 7; 117 | p += 7; 118 | goto skip; 119 | } 120 | 121 | if (n < 5 || ngx_strncmp(p, "TCP", 3) != 0 122 | || (p[3] != '4' && p[3] != '6') || p[4] != ' ') 123 | { 124 | goto bad_header; 125 | } 126 | 127 | n -= 5; 128 | p += 5; 129 | 130 | pp = ngx_strlchr(p, p + n, ' '); 131 | 132 | if (pp == NULL) { 133 | goto bad_header; 134 | } 135 | 136 | if (ngx_parse_addr(s->connection->pool, &addr, p, pp - p) != NGX_OK) { 137 | goto bad_header; 138 | } 139 | 140 | n -= pp - p; 141 | p = pp; 142 | 143 | skip: 144 | 145 | for (i = 0; i + 1 < n; i++) { 146 | if (p[i] == CR && p[i + 1] == LF) { 147 | break; 148 | } 149 | } 150 | 151 | if (i + 1 >= n) { 152 | goto bad_header; 153 | } 154 | 155 | n = p - buf + i + 2; 156 | 157 | if (c->recv(c, buf, n) != n) { 158 | goto failed; 159 | } 160 | 161 | if (addr.socklen) { 162 | text = ngx_palloc(s->connection->pool, NGX_SOCKADDR_STRLEN); 163 | 164 | if (text == NULL) { 165 | goto failed; 166 | } 167 | 168 | len = ngx_sock_ntop(addr.sockaddr, 169 | #if (nginx_version >= 1005003) 170 | addr.socklen, 171 | #endif 172 | text, NGX_SOCKADDR_STRLEN, 0); 173 | if (len == 0) { 174 | goto failed; 175 | } 176 | 177 | c->sockaddr = addr.sockaddr; 178 | c->socklen = addr.socklen; 179 | c->addr_text.data = text; 180 | c->addr_text.len = len; 181 | 182 | ngx_log_debug1(NGX_LOG_DEBUG_RTMP, c->log, 0, 183 | "proxy_protocol: remote_addr:'%V'", &c->addr_text); 184 | } 185 | 186 | ngx_rtmp_handshake(s); 187 | 188 | return; 189 | 190 | bad_header: 191 | 192 | ngx_log_error(NGX_LOG_INFO, c->log, 0, "proxy_protocol: bad header"); 193 | 194 | failed: 195 | 196 | ngx_rtmp_finalize_session(s); 197 | } 198 | -------------------------------------------------------------------------------- /ngx_rtmp_proxy_protocol.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Roman Arutyunyan 4 | */ 5 | 6 | 7 | #ifndef _NGX_RTMP_PROXY_PROTOCOL_H_INCLUDED_ 8 | #define _NGX_RTMP_PROXY_PROTOCOL_H_INCLUDED_ 9 | 10 | 11 | #include 12 | #include 13 | #include "ngx_rtmp.h" 14 | 15 | 16 | void ngx_rtmp_proxy_protocol(ngx_rtmp_session_t *c); 17 | 18 | 19 | #endif /* _NGX_RTMP_PROXY_PROTOCOL_H_INCLUDED_ */ 20 | -------------------------------------------------------------------------------- /ngx_rtmp_receive.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Roman Arutyunyan 4 | */ 5 | 6 | 7 | #include 8 | #include 9 | #include "ngx_rtmp.h" 10 | #include "ngx_rtmp_amf.h" 11 | #include "ngx_rtmp_cmd_module.h" 12 | #include 13 | 14 | 15 | ngx_int_t 16 | ngx_rtmp_protocol_message_handler(ngx_rtmp_session_t *s, 17 | ngx_rtmp_header_t *h, ngx_chain_t *in) 18 | { 19 | ngx_buf_t *b; 20 | u_char *p; 21 | uint32_t val; 22 | uint8_t limit; 23 | 24 | b = in->buf; 25 | 26 | if (b->last - b->pos < 4) { 27 | ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 28 | "too small buffer for %d message: %d", 29 | (int)h->type, b->last - b->pos); 30 | return NGX_OK; 31 | } 32 | 33 | p = (u_char*)&val; 34 | p[0] = b->pos[3]; 35 | p[1] = b->pos[2]; 36 | p[2] = b->pos[1]; 37 | p[3] = b->pos[0]; 38 | 39 | switch(h->type) { 40 | case NGX_RTMP_MSG_CHUNK_SIZE: 41 | /* set chunk size =val */ 42 | ngx_rtmp_set_chunk_size(s, val); 43 | break; 44 | 45 | case NGX_RTMP_MSG_ABORT: 46 | /* abort chunk stream =val */ 47 | break; 48 | 49 | case NGX_RTMP_MSG_ACK: 50 | /* receive ack with sequence number =val */ 51 | ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 52 | "receive ack seq=%uD", val); 53 | break; 54 | 55 | case NGX_RTMP_MSG_ACK_SIZE: 56 | /* receive window size =val */ 57 | ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 58 | "receive ack_size=%uD", val); 59 | s->ack_size = val; 60 | break; 61 | 62 | case NGX_RTMP_MSG_BANDWIDTH: 63 | if (b->last - b->pos >= 5) { 64 | limit = *(uint8_t*)&b->pos[4]; 65 | 66 | (void)val; 67 | (void)limit; 68 | 69 | ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 70 | "receive bandwidth=%uD limit=%d", 71 | val, (int)limit); 72 | 73 | /* receive window size =val 74 | * && limit */ 75 | } 76 | break; 77 | 78 | default: 79 | return NGX_ERROR; 80 | } 81 | 82 | return NGX_OK; 83 | } 84 | 85 | 86 | ngx_int_t 87 | ngx_rtmp_user_message_handler(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, 88 | ngx_chain_t *in) 89 | { 90 | ngx_buf_t *b; 91 | u_char *p; 92 | uint16_t evt; 93 | uint32_t val; 94 | 95 | b = in->buf; 96 | 97 | if (b->last - b->pos < 6) { 98 | ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 99 | "too small buffer for user message: %d", 100 | b->last - b->pos); 101 | return NGX_OK; 102 | } 103 | 104 | p = (u_char*)&evt; 105 | 106 | p[0] = b->pos[1]; 107 | p[1] = b->pos[0]; 108 | 109 | ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 110 | "RTMP recv user evt %s (%i)", 111 | ngx_rtmp_user_message_type(evt), (ngx_int_t) evt); 112 | 113 | p = (u_char *) &val; 114 | 115 | p[0] = b->pos[5]; 116 | p[1] = b->pos[4]; 117 | p[2] = b->pos[3]; 118 | p[3] = b->pos[2]; 119 | 120 | switch(evt) { 121 | case NGX_RTMP_USER_STREAM_BEGIN: 122 | { 123 | ngx_rtmp_stream_begin_t v; 124 | 125 | v.msid = val; 126 | 127 | ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 128 | "receive: stream_begin msid=%uD", v.msid); 129 | 130 | return ngx_rtmp_stream_begin(s, &v); 131 | } 132 | 133 | case NGX_RTMP_USER_STREAM_EOF: 134 | { 135 | ngx_rtmp_stream_eof_t v; 136 | 137 | v.msid = val; 138 | 139 | ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 140 | "receive: stream_eof msid=%uD", v.msid); 141 | 142 | return ngx_rtmp_stream_eof(s, &v); 143 | } 144 | 145 | case NGX_RTMP_USER_STREAM_DRY: 146 | { 147 | ngx_rtmp_stream_dry_t v; 148 | 149 | v.msid = val; 150 | 151 | ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 152 | "receive: stream_dry msid=%uD", v.msid); 153 | 154 | return ngx_rtmp_stream_dry(s, &v); 155 | } 156 | 157 | case NGX_RTMP_USER_SET_BUFLEN: 158 | { 159 | ngx_rtmp_set_buflen_t v; 160 | 161 | v.msid = val; 162 | 163 | if (b->last - b->pos < 10) { 164 | return NGX_OK; 165 | } 166 | 167 | p = (u_char *) &v.buflen; 168 | 169 | p[0] = b->pos[9]; 170 | p[1] = b->pos[8]; 171 | p[2] = b->pos[7]; 172 | p[3] = b->pos[6]; 173 | 174 | ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 175 | "receive: set_buflen msid=%uD buflen=%uD", 176 | v.msid, v.buflen); 177 | 178 | /*TODO: move this to play module */ 179 | s->buflen = v.buflen; 180 | 181 | return ngx_rtmp_set_buflen(s, &v); 182 | } 183 | 184 | case NGX_RTMP_USER_RECORDED: 185 | { 186 | ngx_rtmp_recorded_t v; 187 | 188 | v.msid = val; 189 | 190 | ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 191 | "receive: recorded msid=%uD", v.msid); 192 | 193 | return ngx_rtmp_recorded(s, &v); 194 | } 195 | 196 | case NGX_RTMP_USER_PING_REQUEST: 197 | return ngx_rtmp_send_ping_response(s, val); 198 | 199 | case NGX_RTMP_USER_PING_RESPONSE: 200 | 201 | /* val = incoming timestamp */ 202 | 203 | ngx_rtmp_reset_ping(s); 204 | 205 | return NGX_OK; 206 | 207 | default: 208 | ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 209 | "unexpected user event: %i", (ngx_int_t) evt); 210 | 211 | return NGX_OK; 212 | } 213 | } 214 | 215 | 216 | static ngx_int_t 217 | ngx_rtmp_fetch(ngx_chain_t **in, u_char *ret) 218 | { 219 | while (*in && (*in)->buf->pos >= (*in)->buf->last) { 220 | *in = (*in)->next; 221 | } 222 | 223 | if (*in == NULL) { 224 | return NGX_DONE; 225 | } 226 | 227 | *ret = *(*in)->buf->pos++; 228 | 229 | return NGX_OK; 230 | } 231 | 232 | 233 | static ngx_int_t 234 | ngx_rtmp_fetch_uint8(ngx_chain_t **in, uint8_t *ret) 235 | { 236 | return ngx_rtmp_fetch(in, (u_char *) ret); 237 | } 238 | 239 | 240 | static ngx_int_t 241 | ngx_rtmp_fetch_uint32(ngx_chain_t **in, uint32_t *ret, ngx_int_t n) 242 | { 243 | u_char *r = (u_char *) ret; 244 | ngx_int_t rc; 245 | 246 | *ret = 0; 247 | 248 | while (--n >= 0) { 249 | rc = ngx_rtmp_fetch(in, &r[n]); 250 | if (rc != NGX_OK) { 251 | return rc; 252 | } 253 | } 254 | 255 | return NGX_OK; 256 | } 257 | 258 | 259 | ngx_int_t 260 | ngx_rtmp_aggregate_message_handler(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, 261 | ngx_chain_t *in) 262 | { 263 | uint32_t base_time, timestamp, prev_size; 264 | size_t len; 265 | ngx_int_t first; 266 | u_char *last; 267 | ngx_int_t rc; 268 | ngx_buf_t *b; 269 | ngx_chain_t *cl, *next; 270 | ngx_rtmp_header_t ch; 271 | 272 | ch = *h; 273 | 274 | first = 1; 275 | base_time = 0; 276 | 277 | while (in) { 278 | if (ngx_rtmp_fetch_uint8(&in, &ch.type) != NGX_OK) { 279 | return NGX_OK; 280 | } 281 | 282 | if (ngx_rtmp_fetch_uint32(&in, &ch.mlen, 3) != NGX_OK) { 283 | return NGX_ERROR; 284 | } 285 | 286 | if (ngx_rtmp_fetch_uint32(&in, ×tamp, 3) != NGX_OK) { 287 | return NGX_ERROR; 288 | } 289 | 290 | if (ngx_rtmp_fetch_uint8(&in, (uint8_t *) ×tamp + 3) != NGX_OK) 291 | { 292 | return NGX_ERROR; 293 | } 294 | 295 | if (ngx_rtmp_fetch_uint32(&in, &ch.msid, 3) != NGX_OK) 296 | { 297 | return NGX_ERROR; 298 | } 299 | 300 | if (first) { 301 | base_time = timestamp; 302 | first = 0; 303 | } 304 | 305 | ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 306 | "RTMP aggregate %s (%d) len=%uD time=%uD (+%D) msid=%uD", 307 | ngx_rtmp_message_type(ch.type), 308 | (ngx_int_t) ch.type, ch.mlen, ch.timestamp, 309 | timestamp - base_time, ch.msid); 310 | 311 | /* limit chain */ 312 | 313 | len = 0; 314 | cl = in; 315 | while (cl) { 316 | b = cl->buf; 317 | len += (b->last - b->pos); 318 | if (len > ch.mlen) { 319 | break; 320 | } 321 | cl = cl->next; 322 | } 323 | 324 | if (cl == NULL) { 325 | ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, 326 | "RTMP error parsing aggregate"); 327 | return NGX_ERROR; 328 | } 329 | 330 | next = cl->next; 331 | cl->next = NULL; 332 | b = cl->buf; 333 | last = b->last; 334 | b->last -= (len - ch.mlen); 335 | 336 | /* handle aggregated message */ 337 | 338 | ch.timestamp = h->timestamp + timestamp - base_time; 339 | 340 | rc = ngx_rtmp_receive_message(s, &ch, in); 341 | 342 | /* restore chain before checking the result */ 343 | 344 | in = cl; 345 | in->next = next; 346 | b->pos = b->last; 347 | b->last = last; 348 | 349 | if (rc != NGX_OK) { 350 | return rc; 351 | } 352 | 353 | /* read 32-bit previous tag size */ 354 | 355 | if (ngx_rtmp_fetch_uint32(&in, &prev_size, 4) != NGX_OK) { 356 | return NGX_OK; 357 | } 358 | 359 | ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 360 | "RTMP aggregate prev_size=%uD", prev_size); 361 | } 362 | 363 | return NGX_OK; 364 | } 365 | 366 | 367 | ngx_int_t 368 | ngx_rtmp_amf_message_handler(ngx_rtmp_session_t *s, 369 | ngx_rtmp_header_t *h, ngx_chain_t *in) 370 | { 371 | ngx_rtmp_amf_ctx_t act; 372 | ngx_rtmp_core_main_conf_t *cmcf; 373 | ngx_array_t *ch; 374 | ngx_rtmp_handler_pt *ph; 375 | size_t len, n; 376 | 377 | static u_char func[128]; 378 | 379 | static ngx_rtmp_amf_elt_t elts[] = { 380 | 381 | { NGX_RTMP_AMF_STRING, 382 | ngx_null_string, 383 | func, sizeof(func) }, 384 | }; 385 | 386 | /* AMF command names come with string type, but shared object names 387 | * come without type */ 388 | if (h->type == NGX_RTMP_MSG_AMF_SHARED || 389 | h->type == NGX_RTMP_MSG_AMF3_SHARED) 390 | { 391 | elts[0].type |= NGX_RTMP_AMF_TYPELESS; 392 | } else { 393 | elts[0].type &= ~NGX_RTMP_AMF_TYPELESS; 394 | } 395 | 396 | if ((h->type == NGX_RTMP_MSG_AMF3_SHARED || 397 | h->type == NGX_RTMP_MSG_AMF3_META || 398 | h->type == NGX_RTMP_MSG_AMF3_CMD) 399 | && in->buf->last > in->buf->pos) 400 | { 401 | ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 402 | "AMF3 prefix: %ui", (ngx_int_t)*in->buf->pos); 403 | ++in->buf->pos; 404 | } 405 | 406 | cmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_core_module); 407 | 408 | /* read AMF func name & transaction id */ 409 | ngx_memzero(&act, sizeof(act)); 410 | act.link = in; 411 | act.log = s->connection->log; 412 | memset(func, 0, sizeof(func)); 413 | 414 | if (ngx_rtmp_amf_read(&act, elts, 415 | sizeof(elts) / sizeof(elts[0])) != NGX_OK) 416 | { 417 | ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 418 | "AMF cmd failed"); 419 | return NGX_ERROR; 420 | } 421 | 422 | /* skip name */ 423 | in = act.link; 424 | in->buf->pos += act.offset; 425 | 426 | len = ngx_strlen(func); 427 | 428 | ch = ngx_hash_find(&cmcf->amf_hash, 429 | ngx_hash_strlow(func, func, len), func, len); 430 | 431 | if (ch && ch->nelts) { 432 | ph = ch->elts; 433 | for (n = 0; n < ch->nelts; ++n, ++ph) { 434 | ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 435 | "AMF func '%s' passed to handler %d/%d", 436 | func, n, ch->nelts); 437 | switch ((*ph)(s, h, in)) { 438 | case NGX_ERROR: 439 | return NGX_ERROR; 440 | case NGX_DONE: 441 | return NGX_OK; 442 | } 443 | } 444 | } else { 445 | ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 446 | "AMF cmd '%s' no handler", func); 447 | } 448 | 449 | return NGX_OK; 450 | } 451 | 452 | 453 | ngx_int_t 454 | ngx_rtmp_receive_amf(ngx_rtmp_session_t *s, ngx_chain_t *in, 455 | ngx_rtmp_amf_elt_t *elts, size_t nelts) 456 | { 457 | ngx_rtmp_amf_ctx_t act; 458 | 459 | ngx_memzero(&act, sizeof(act)); 460 | act.link = in; 461 | act.log = s->connection->log; 462 | 463 | return ngx_rtmp_amf_read(&act, elts, nelts); 464 | } 465 | -------------------------------------------------------------------------------- /ngx_rtmp_record_module.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Roman Arutyunyan 4 | */ 5 | 6 | 7 | #ifndef _NGX_RTMP_RECORD_H_INCLUDED_ 8 | #define _NGX_RTMP_RECORD_H_INCLUDED_ 9 | 10 | 11 | #include 12 | #include 13 | #include "ngx_rtmp.h" 14 | 15 | 16 | #define NGX_RTMP_RECORD_OFF 0x01 17 | #define NGX_RTMP_RECORD_AUDIO 0x02 18 | #define NGX_RTMP_RECORD_VIDEO 0x04 19 | #define NGX_RTMP_RECORD_KEYFRAMES 0x08 20 | #define NGX_RTMP_RECORD_MANUAL 0x10 21 | 22 | 23 | typedef struct { 24 | ngx_str_t id; 25 | ngx_uint_t flags; 26 | ngx_str_t path; 27 | size_t max_size; 28 | size_t max_frames; 29 | ngx_msec_t interval; 30 | ngx_str_t suffix; 31 | ngx_flag_t unique; 32 | ngx_flag_t append; 33 | ngx_flag_t lock_file; 34 | ngx_flag_t notify; 35 | ngx_url_t *url; 36 | 37 | void **rec_conf; 38 | ngx_array_t rec; /* ngx_rtmp_record_app_conf_t * */ 39 | } ngx_rtmp_record_app_conf_t; 40 | 41 | 42 | typedef struct { 43 | ngx_rtmp_record_app_conf_t *conf; 44 | ngx_file_t file; 45 | ngx_uint_t nframes; 46 | uint32_t epoch, time_shift; 47 | ngx_time_t last; 48 | time_t timestamp; 49 | unsigned failed:1; 50 | unsigned initialized:1; 51 | unsigned aac_header_sent:1; 52 | unsigned avc_header_sent:1; 53 | unsigned video_key_sent:1; 54 | unsigned audio:1; 55 | unsigned video:1; 56 | } ngx_rtmp_record_rec_ctx_t; 57 | 58 | 59 | typedef struct { 60 | ngx_array_t rec; /* ngx_rtmp_record_rec_ctx_t */ 61 | u_char name[NGX_RTMP_MAX_NAME]; 62 | u_char args[NGX_RTMP_MAX_ARGS]; 63 | } ngx_rtmp_record_ctx_t; 64 | 65 | 66 | ngx_uint_t ngx_rtmp_record_find(ngx_rtmp_record_app_conf_t *racf, 67 | ngx_str_t *id); 68 | 69 | 70 | /* Manual recording control, 71 | * 'n' is record node index in config array. 72 | * Note: these functions allocate path in static buffer */ 73 | 74 | ngx_int_t ngx_rtmp_record_open(ngx_rtmp_session_t *s, ngx_uint_t n, 75 | ngx_str_t *path); 76 | ngx_int_t ngx_rtmp_record_close(ngx_rtmp_session_t *s, ngx_uint_t n, 77 | ngx_str_t *path); 78 | 79 | 80 | typedef struct { 81 | ngx_str_t recorder; 82 | ngx_str_t path; 83 | } ngx_rtmp_record_done_t; 84 | 85 | 86 | typedef ngx_int_t (*ngx_rtmp_record_done_pt)(ngx_rtmp_session_t *s, 87 | ngx_rtmp_record_done_t *v); 88 | 89 | 90 | extern ngx_rtmp_record_done_pt ngx_rtmp_record_done; 91 | 92 | 93 | extern ngx_module_t ngx_rtmp_record_module; 94 | 95 | 96 | #endif /* _NGX_RTMP_RECORD_H_INCLUDED_ */ 97 | -------------------------------------------------------------------------------- /ngx_rtmp_relay_module.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Roman Arutyunyan 4 | */ 5 | 6 | 7 | #ifndef _NGX_RTMP_RELAY_H_INCLUDED_ 8 | #define _NGX_RTMP_RELAY_H_INCLUDED_ 9 | 10 | 11 | #include 12 | #include 13 | #include "ngx_rtmp.h" 14 | 15 | 16 | typedef struct { 17 | ngx_url_t url; 18 | ngx_str_t app; 19 | ngx_str_t name; 20 | ngx_str_t tc_url; 21 | ngx_str_t page_url; 22 | ngx_str_t swf_url; 23 | ngx_str_t flash_ver; 24 | ngx_str_t play_path; 25 | ngx_int_t live; 26 | ngx_int_t start; 27 | ngx_int_t stop; 28 | 29 | void *tag; /* usually module reference */ 30 | void *data; /* module-specific data */ 31 | ngx_uint_t counter; /* mutable connection counter */ 32 | } ngx_rtmp_relay_target_t; 33 | 34 | 35 | typedef struct ngx_rtmp_relay_ctx_s ngx_rtmp_relay_ctx_t; 36 | 37 | struct ngx_rtmp_relay_ctx_s { 38 | ngx_str_t name; 39 | ngx_str_t url; 40 | ngx_log_t log; 41 | ngx_rtmp_session_t *session; 42 | ngx_rtmp_relay_ctx_t *publish; 43 | ngx_rtmp_relay_ctx_t *play; 44 | ngx_rtmp_relay_ctx_t *next; 45 | 46 | ngx_str_t app; 47 | ngx_str_t tc_url; 48 | ngx_str_t page_url; 49 | ngx_str_t swf_url; 50 | ngx_str_t flash_ver; 51 | ngx_str_t play_path; 52 | ngx_int_t live; 53 | ngx_int_t start; 54 | ngx_int_t stop; 55 | 56 | ngx_event_t push_evt; 57 | ngx_event_t *static_evt; 58 | void *tag; 59 | void *data; 60 | }; 61 | 62 | 63 | extern ngx_module_t ngx_rtmp_relay_module; 64 | 65 | 66 | ngx_int_t ngx_rtmp_relay_pull(ngx_rtmp_session_t *s, ngx_str_t *name, 67 | ngx_rtmp_relay_target_t *target); 68 | ngx_int_t ngx_rtmp_relay_push(ngx_rtmp_session_t *s, ngx_str_t *name, 69 | ngx_rtmp_relay_target_t *target); 70 | 71 | 72 | #endif /* _NGX_RTMP_RELAY_H_INCLUDED_ */ 73 | -------------------------------------------------------------------------------- /ngx_rtmp_send.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Roman Arutyunyan 4 | */ 5 | 6 | 7 | #include 8 | #include 9 | #include "ngx_rtmp.h" 10 | #include "ngx_rtmp_amf.h" 11 | #include "ngx_rtmp_streams.h" 12 | 13 | 14 | #define NGX_RTMP_USER_START(s, tp) \ 15 | ngx_rtmp_header_t __h; \ 16 | ngx_chain_t *__l; \ 17 | ngx_buf_t *__b; \ 18 | ngx_rtmp_core_srv_conf_t *__cscf; \ 19 | \ 20 | __cscf = ngx_rtmp_get_module_srv_conf( \ 21 | s, ngx_rtmp_core_module); \ 22 | memset(&__h, 0, sizeof(__h)); \ 23 | __h.type = tp; \ 24 | __h.csid = 2; \ 25 | __l = ngx_rtmp_alloc_shared_buf(__cscf); \ 26 | if (__l == NULL) { \ 27 | return NULL; \ 28 | } \ 29 | __b = __l->buf; 30 | 31 | #define NGX_RTMP_UCTL_START(s, type, utype) \ 32 | NGX_RTMP_USER_START(s, type); \ 33 | *(__b->last++) = (u_char)((utype) >> 8); \ 34 | *(__b->last++) = (u_char)(utype); 35 | 36 | #define NGX_RTMP_USER_OUT1(v) \ 37 | *(__b->last++) = ((u_char*)&v)[0]; 38 | 39 | #define NGX_RTMP_USER_OUT4(v) \ 40 | *(__b->last++) = ((u_char*)&v)[3]; \ 41 | *(__b->last++) = ((u_char*)&v)[2]; \ 42 | *(__b->last++) = ((u_char*)&v)[1]; \ 43 | *(__b->last++) = ((u_char*)&v)[0]; 44 | 45 | #define NGX_RTMP_USER_END(s) \ 46 | ngx_rtmp_prepare_message(s, &__h, NULL, __l); \ 47 | return __l; 48 | 49 | 50 | static ngx_int_t 51 | ngx_rtmp_send_shared_packet(ngx_rtmp_session_t *s, ngx_chain_t *cl) 52 | { 53 | ngx_rtmp_core_srv_conf_t *cscf; 54 | ngx_int_t rc; 55 | 56 | if (cl == NULL) { 57 | return NGX_ERROR; 58 | } 59 | 60 | cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); 61 | 62 | rc = ngx_rtmp_send_message(s, cl, 0); 63 | 64 | ngx_rtmp_free_shared_chain(cscf, cl); 65 | 66 | return rc; 67 | } 68 | 69 | 70 | /* Protocol control messages */ 71 | 72 | ngx_chain_t * 73 | ngx_rtmp_create_chunk_size(ngx_rtmp_session_t *s, uint32_t chunk_size) 74 | { 75 | ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 76 | "chunk_size=%uD", chunk_size); 77 | 78 | { 79 | NGX_RTMP_USER_START(s, NGX_RTMP_MSG_CHUNK_SIZE); 80 | 81 | NGX_RTMP_USER_OUT4(chunk_size); 82 | 83 | NGX_RTMP_USER_END(s); 84 | } 85 | } 86 | 87 | 88 | ngx_int_t 89 | ngx_rtmp_send_chunk_size(ngx_rtmp_session_t *s, uint32_t chunk_size) 90 | { 91 | return ngx_rtmp_send_shared_packet(s, 92 | ngx_rtmp_create_chunk_size(s, chunk_size)); 93 | } 94 | 95 | 96 | ngx_chain_t * 97 | ngx_rtmp_create_abort(ngx_rtmp_session_t *s, uint32_t csid) 98 | { 99 | ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 100 | "create: abort csid=%uD", csid); 101 | 102 | { 103 | NGX_RTMP_USER_START(s, NGX_RTMP_MSG_CHUNK_SIZE); 104 | 105 | NGX_RTMP_USER_OUT4(csid); 106 | 107 | NGX_RTMP_USER_END(s); 108 | } 109 | } 110 | 111 | 112 | ngx_int_t 113 | ngx_rtmp_send_abort(ngx_rtmp_session_t *s, uint32_t csid) 114 | { 115 | return ngx_rtmp_send_shared_packet(s, 116 | ngx_rtmp_create_abort(s, csid)); 117 | } 118 | 119 | 120 | ngx_chain_t * 121 | ngx_rtmp_create_ack(ngx_rtmp_session_t *s, uint32_t seq) 122 | { 123 | ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 124 | "create: ack seq=%uD", seq); 125 | 126 | { 127 | NGX_RTMP_USER_START(s, NGX_RTMP_MSG_ACK); 128 | 129 | NGX_RTMP_USER_OUT4(seq); 130 | 131 | NGX_RTMP_USER_END(s); 132 | } 133 | } 134 | 135 | 136 | ngx_int_t 137 | ngx_rtmp_send_ack(ngx_rtmp_session_t *s, uint32_t seq) 138 | { 139 | return ngx_rtmp_send_shared_packet(s, 140 | ngx_rtmp_create_ack(s, seq)); 141 | } 142 | 143 | 144 | ngx_chain_t * 145 | ngx_rtmp_create_ack_size(ngx_rtmp_session_t *s, uint32_t ack_size) 146 | { 147 | ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 148 | "create: ack_size=%uD", ack_size); 149 | 150 | { 151 | NGX_RTMP_USER_START(s, NGX_RTMP_MSG_ACK_SIZE); 152 | 153 | NGX_RTMP_USER_OUT4(ack_size); 154 | 155 | NGX_RTMP_USER_END(s); 156 | } 157 | } 158 | 159 | 160 | ngx_int_t 161 | ngx_rtmp_send_ack_size(ngx_rtmp_session_t *s, uint32_t ack_size) 162 | { 163 | return ngx_rtmp_send_shared_packet(s, 164 | ngx_rtmp_create_ack_size(s, ack_size)); 165 | } 166 | 167 | 168 | ngx_chain_t * 169 | ngx_rtmp_create_bandwidth(ngx_rtmp_session_t *s, uint32_t ack_size, 170 | uint8_t limit_type) 171 | { 172 | ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 173 | "create: bandwidth ack_size=%uD limit=%d", 174 | ack_size, (int)limit_type); 175 | 176 | { 177 | NGX_RTMP_USER_START(s, NGX_RTMP_MSG_BANDWIDTH); 178 | 179 | NGX_RTMP_USER_OUT4(ack_size); 180 | NGX_RTMP_USER_OUT1(limit_type); 181 | 182 | NGX_RTMP_USER_END(s); 183 | } 184 | } 185 | 186 | 187 | ngx_int_t 188 | ngx_rtmp_send_bandwidth(ngx_rtmp_session_t *s, uint32_t ack_size, 189 | uint8_t limit_type) 190 | { 191 | return ngx_rtmp_send_shared_packet(s, 192 | ngx_rtmp_create_bandwidth(s, ack_size, limit_type)); 193 | } 194 | 195 | 196 | /* User control messages */ 197 | 198 | ngx_chain_t * 199 | ngx_rtmp_create_stream_begin(ngx_rtmp_session_t *s, uint32_t msid) 200 | { 201 | ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 202 | "create: stream_begin msid=%uD", msid); 203 | 204 | { 205 | NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_STREAM_BEGIN); 206 | 207 | NGX_RTMP_USER_OUT4(msid); 208 | 209 | NGX_RTMP_USER_END(s); 210 | } 211 | } 212 | 213 | 214 | ngx_int_t 215 | ngx_rtmp_send_stream_begin(ngx_rtmp_session_t *s, uint32_t msid) 216 | { 217 | return ngx_rtmp_send_shared_packet(s, 218 | ngx_rtmp_create_stream_begin(s, msid)); 219 | } 220 | 221 | 222 | ngx_chain_t * 223 | ngx_rtmp_create_stream_eof(ngx_rtmp_session_t *s, uint32_t msid) 224 | { 225 | ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 226 | "create: stream_end msid=%uD", msid); 227 | 228 | { 229 | NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_STREAM_EOF); 230 | 231 | NGX_RTMP_USER_OUT4(msid); 232 | 233 | NGX_RTMP_USER_END(s); 234 | } 235 | } 236 | 237 | 238 | ngx_int_t 239 | ngx_rtmp_send_stream_eof(ngx_rtmp_session_t *s, uint32_t msid) 240 | { 241 | return ngx_rtmp_send_shared_packet(s, 242 | ngx_rtmp_create_stream_eof(s, msid)); 243 | } 244 | 245 | 246 | ngx_chain_t * 247 | ngx_rtmp_create_stream_dry(ngx_rtmp_session_t *s, uint32_t msid) 248 | { 249 | ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 250 | "create: stream_dry msid=%uD", msid); 251 | 252 | { 253 | NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_STREAM_DRY); 254 | 255 | NGX_RTMP_USER_OUT4(msid); 256 | 257 | NGX_RTMP_USER_END(s); 258 | } 259 | } 260 | 261 | 262 | ngx_int_t 263 | ngx_rtmp_send_stream_dry(ngx_rtmp_session_t *s, uint32_t msid) 264 | { 265 | return ngx_rtmp_send_shared_packet(s, 266 | ngx_rtmp_create_stream_dry(s, msid)); 267 | } 268 | 269 | 270 | ngx_chain_t * 271 | ngx_rtmp_create_set_buflen(ngx_rtmp_session_t *s, uint32_t msid, 272 | uint32_t buflen_msec) 273 | { 274 | ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 275 | "create: set_buflen msid=%uD buflen=%uD", 276 | msid, buflen_msec); 277 | 278 | { 279 | NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_SET_BUFLEN); 280 | 281 | NGX_RTMP_USER_OUT4(msid); 282 | NGX_RTMP_USER_OUT4(buflen_msec); 283 | 284 | NGX_RTMP_USER_END(s); 285 | } 286 | } 287 | 288 | 289 | ngx_int_t 290 | ngx_rtmp_send_set_buflen(ngx_rtmp_session_t *s, uint32_t msid, 291 | uint32_t buflen_msec) 292 | { 293 | return ngx_rtmp_send_shared_packet(s, 294 | ngx_rtmp_create_set_buflen(s, msid, buflen_msec)); 295 | } 296 | 297 | 298 | ngx_chain_t * 299 | ngx_rtmp_create_recorded(ngx_rtmp_session_t *s, uint32_t msid) 300 | { 301 | ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 302 | "create: recorded msid=%uD", msid); 303 | 304 | { 305 | NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_RECORDED); 306 | 307 | NGX_RTMP_USER_OUT4(msid); 308 | 309 | NGX_RTMP_USER_END(s); 310 | } 311 | } 312 | 313 | 314 | ngx_int_t 315 | ngx_rtmp_send_recorded(ngx_rtmp_session_t *s, uint32_t msid) 316 | { 317 | return ngx_rtmp_send_shared_packet(s, 318 | ngx_rtmp_create_recorded(s, msid)); 319 | } 320 | 321 | 322 | ngx_chain_t * 323 | ngx_rtmp_create_ping_request(ngx_rtmp_session_t *s, uint32_t timestamp) 324 | { 325 | ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 326 | "create: ping_request timestamp=%uD", timestamp); 327 | 328 | { 329 | NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_PING_REQUEST); 330 | 331 | NGX_RTMP_USER_OUT4(timestamp); 332 | 333 | NGX_RTMP_USER_END(s); 334 | } 335 | } 336 | 337 | 338 | ngx_int_t 339 | ngx_rtmp_send_ping_request(ngx_rtmp_session_t *s, uint32_t timestamp) 340 | { 341 | return ngx_rtmp_send_shared_packet(s, 342 | ngx_rtmp_create_ping_request(s, timestamp)); 343 | } 344 | 345 | 346 | ngx_chain_t * 347 | ngx_rtmp_create_ping_response(ngx_rtmp_session_t *s, uint32_t timestamp) 348 | { 349 | ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 350 | "create: ping_response timestamp=%uD", timestamp); 351 | 352 | { 353 | NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_PING_RESPONSE); 354 | 355 | NGX_RTMP_USER_OUT4(timestamp); 356 | 357 | NGX_RTMP_USER_END(s); 358 | } 359 | } 360 | 361 | 362 | ngx_int_t 363 | ngx_rtmp_send_ping_response(ngx_rtmp_session_t *s, uint32_t timestamp) 364 | { 365 | return ngx_rtmp_send_shared_packet(s, 366 | ngx_rtmp_create_ping_response(s, timestamp)); 367 | } 368 | 369 | 370 | static ngx_chain_t * 371 | ngx_rtmp_alloc_amf_buf(void *arg) 372 | { 373 | return ngx_rtmp_alloc_shared_buf((ngx_rtmp_core_srv_conf_t *)arg); 374 | } 375 | 376 | 377 | /* AMF sender */ 378 | 379 | /* NOTE: this function does not free shared bufs on error */ 380 | ngx_int_t 381 | ngx_rtmp_append_amf(ngx_rtmp_session_t *s, 382 | ngx_chain_t **first, ngx_chain_t **last, 383 | ngx_rtmp_amf_elt_t *elts, size_t nelts) 384 | { 385 | ngx_rtmp_amf_ctx_t act; 386 | ngx_rtmp_core_srv_conf_t *cscf; 387 | ngx_int_t rc; 388 | 389 | cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); 390 | 391 | memset(&act, 0, sizeof(act)); 392 | act.arg = cscf; 393 | act.alloc = ngx_rtmp_alloc_amf_buf; 394 | act.log = s->connection->log; 395 | 396 | if (first) { 397 | act.first = *first; 398 | } 399 | 400 | if (last) { 401 | act.link = *last; 402 | } 403 | 404 | rc = ngx_rtmp_amf_write(&act, elts, nelts); 405 | 406 | if (first) { 407 | *first = act.first; 408 | } 409 | 410 | if (last) { 411 | *last = act.link; 412 | } 413 | 414 | return rc; 415 | } 416 | 417 | 418 | ngx_chain_t * 419 | ngx_rtmp_create_amf(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, 420 | ngx_rtmp_amf_elt_t *elts, size_t nelts) 421 | { 422 | ngx_chain_t *first; 423 | ngx_int_t rc; 424 | ngx_rtmp_core_srv_conf_t *cscf; 425 | 426 | ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 427 | "create: amf nelts=%ui", nelts); 428 | 429 | cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); 430 | 431 | first = NULL; 432 | 433 | rc = ngx_rtmp_append_amf(s, &first, NULL, elts, nelts); 434 | 435 | if (rc != NGX_OK && first) { 436 | ngx_rtmp_free_shared_chain(cscf, first); 437 | first = NULL; 438 | } 439 | 440 | if (first) { 441 | ngx_rtmp_prepare_message(s, h, NULL, first); 442 | } 443 | 444 | return first; 445 | } 446 | 447 | 448 | ngx_int_t 449 | ngx_rtmp_send_amf(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, 450 | ngx_rtmp_amf_elt_t *elts, size_t nelts) 451 | { 452 | return ngx_rtmp_send_shared_packet(s, 453 | ngx_rtmp_create_amf(s, h, elts, nelts)); 454 | } 455 | 456 | 457 | ngx_chain_t * 458 | ngx_rtmp_create_status(ngx_rtmp_session_t *s, char *code, char* level, 459 | char *desc) 460 | { 461 | ngx_rtmp_header_t h; 462 | static double trans; 463 | 464 | static ngx_rtmp_amf_elt_t out_inf[] = { 465 | 466 | { NGX_RTMP_AMF_STRING, 467 | ngx_string("level"), 468 | NULL, 0 }, 469 | 470 | { NGX_RTMP_AMF_STRING, 471 | ngx_string("code"), 472 | NULL, 0 }, 473 | 474 | { NGX_RTMP_AMF_STRING, 475 | ngx_string("description"), 476 | NULL, 0 }, 477 | }; 478 | 479 | static ngx_rtmp_amf_elt_t out_elts[] = { 480 | 481 | { NGX_RTMP_AMF_STRING, 482 | ngx_null_string, 483 | "onStatus", 0 }, 484 | 485 | { NGX_RTMP_AMF_NUMBER, 486 | ngx_null_string, 487 | &trans, 0 }, 488 | 489 | { NGX_RTMP_AMF_NULL, 490 | ngx_null_string, 491 | NULL, 0 }, 492 | 493 | { NGX_RTMP_AMF_OBJECT, 494 | ngx_null_string, 495 | out_inf, 496 | sizeof(out_inf) }, 497 | }; 498 | 499 | ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 500 | "create: status code='%s' level='%s' desc='%s'", 501 | code, level, desc); 502 | 503 | out_inf[0].data = level; 504 | out_inf[1].data = code; 505 | out_inf[2].data = desc; 506 | 507 | memset(&h, 0, sizeof(h)); 508 | 509 | h.type = NGX_RTMP_MSG_AMF_CMD; 510 | h.csid = NGX_RTMP_CSID_AMF; 511 | h.msid = NGX_RTMP_MSID; 512 | 513 | return ngx_rtmp_create_amf(s, &h, out_elts, 514 | sizeof(out_elts) / sizeof(out_elts[0])); 515 | } 516 | 517 | 518 | ngx_int_t 519 | ngx_rtmp_send_status(ngx_rtmp_session_t *s, char *code, char* level, char *desc) 520 | { 521 | return ngx_rtmp_send_shared_packet(s, 522 | ngx_rtmp_create_status(s, code, level, desc)); 523 | } 524 | 525 | 526 | ngx_chain_t * 527 | ngx_rtmp_create_play_status(ngx_rtmp_session_t *s, char *code, char* level, 528 | ngx_uint_t duration, ngx_uint_t bytes) 529 | { 530 | ngx_rtmp_header_t h; 531 | static double dduration; 532 | static double dbytes; 533 | 534 | static ngx_rtmp_amf_elt_t out_inf[] = { 535 | 536 | { NGX_RTMP_AMF_STRING, 537 | ngx_string("code"), 538 | NULL, 0 }, 539 | 540 | { NGX_RTMP_AMF_STRING, 541 | ngx_string("level"), 542 | NULL, 0 }, 543 | 544 | { NGX_RTMP_AMF_NUMBER, 545 | ngx_string("duration"), 546 | &dduration, 0 }, 547 | 548 | { NGX_RTMP_AMF_NUMBER, 549 | ngx_string("bytes"), 550 | &dbytes, 0 }, 551 | }; 552 | 553 | static ngx_rtmp_amf_elt_t out_elts[] = { 554 | 555 | { NGX_RTMP_AMF_STRING, 556 | ngx_null_string, 557 | "onPlayStatus", 0 }, 558 | 559 | { NGX_RTMP_AMF_OBJECT, 560 | ngx_null_string, 561 | out_inf, 562 | sizeof(out_inf) }, 563 | }; 564 | 565 | ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 566 | "create: play_status code='%s' level='%s' " 567 | "duration=%ui bytes=%ui", 568 | code, level, duration, bytes); 569 | 570 | out_inf[0].data = code; 571 | out_inf[1].data = level; 572 | 573 | dduration = duration; 574 | dbytes = bytes; 575 | 576 | memset(&h, 0, sizeof(h)); 577 | 578 | h.type = NGX_RTMP_MSG_AMF_META; 579 | h.csid = NGX_RTMP_CSID_AMF; 580 | h.msid = NGX_RTMP_MSID; 581 | h.timestamp = duration; 582 | 583 | return ngx_rtmp_create_amf(s, &h, out_elts, 584 | sizeof(out_elts) / sizeof(out_elts[0])); 585 | } 586 | 587 | 588 | ngx_int_t 589 | ngx_rtmp_send_play_status(ngx_rtmp_session_t *s, char *code, char* level, 590 | ngx_uint_t duration, ngx_uint_t bytes) 591 | { 592 | return ngx_rtmp_send_shared_packet(s, 593 | ngx_rtmp_create_play_status(s, code, level, duration, bytes)); 594 | } 595 | 596 | 597 | ngx_chain_t * 598 | ngx_rtmp_create_sample_access(ngx_rtmp_session_t *s) 599 | { 600 | ngx_rtmp_header_t h; 601 | 602 | static int access = 1; 603 | 604 | static ngx_rtmp_amf_elt_t access_elts[] = { 605 | 606 | { NGX_RTMP_AMF_STRING, 607 | ngx_null_string, 608 | "|RtmpSampleAccess", 0 }, 609 | 610 | { NGX_RTMP_AMF_BOOLEAN, 611 | ngx_null_string, 612 | &access, 0 }, 613 | 614 | { NGX_RTMP_AMF_BOOLEAN, 615 | ngx_null_string, 616 | &access, 0 }, 617 | }; 618 | 619 | memset(&h, 0, sizeof(h)); 620 | 621 | h.type = NGX_RTMP_MSG_AMF_META; 622 | h.csid = NGX_RTMP_CSID_AMF; 623 | h.msid = NGX_RTMP_MSID; 624 | 625 | return ngx_rtmp_create_amf(s, &h, access_elts, 626 | sizeof(access_elts) / sizeof(access_elts[0])); 627 | } 628 | 629 | 630 | ngx_int_t 631 | ngx_rtmp_send_sample_access(ngx_rtmp_session_t *s) 632 | { 633 | return ngx_rtmp_send_shared_packet(s, 634 | ngx_rtmp_create_sample_access(s)); 635 | } 636 | -------------------------------------------------------------------------------- /ngx_rtmp_shared.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Roman Arutyunyan 4 | */ 5 | 6 | 7 | #include 8 | #include 9 | #include "ngx_rtmp.h" 10 | 11 | 12 | ngx_chain_t * 13 | ngx_rtmp_alloc_shared_buf(ngx_rtmp_core_srv_conf_t *cscf) 14 | { 15 | u_char *p; 16 | ngx_chain_t *out; 17 | ngx_buf_t *b; 18 | size_t size; 19 | 20 | if (cscf->free) { 21 | out = cscf->free; 22 | cscf->free = out->next; 23 | 24 | } else { 25 | 26 | size = cscf->chunk_size + NGX_RTMP_MAX_CHUNK_HEADER; 27 | 28 | p = ngx_pcalloc(cscf->pool, NGX_RTMP_REFCOUNT_BYTES 29 | + sizeof(ngx_chain_t) 30 | + sizeof(ngx_buf_t) 31 | + size); 32 | if (p == NULL) { 33 | return NULL; 34 | } 35 | 36 | p += NGX_RTMP_REFCOUNT_BYTES; 37 | out = (ngx_chain_t *)p; 38 | 39 | p += sizeof(ngx_chain_t); 40 | out->buf = (ngx_buf_t *)p; 41 | 42 | p += sizeof(ngx_buf_t); 43 | out->buf->start = p; 44 | out->buf->end = p + size; 45 | } 46 | 47 | out->next = NULL; 48 | b = out->buf; 49 | b->pos = b->last = b->start + NGX_RTMP_MAX_CHUNK_HEADER; 50 | b->memory = 1; 51 | 52 | /* buffer has refcount =1 when created! */ 53 | ngx_rtmp_ref_set(out, 1); 54 | 55 | return out; 56 | } 57 | 58 | 59 | void 60 | ngx_rtmp_free_shared_chain(ngx_rtmp_core_srv_conf_t *cscf, ngx_chain_t *in) 61 | { 62 | ngx_chain_t *cl; 63 | 64 | if (ngx_rtmp_ref_put(in)) { 65 | return; 66 | } 67 | 68 | for (cl = in; ; cl = cl->next) { 69 | if (cl->next == NULL) { 70 | cl->next = cscf->free; 71 | cscf->free = in; 72 | return; 73 | } 74 | } 75 | } 76 | 77 | 78 | ngx_chain_t * 79 | ngx_rtmp_append_shared_bufs(ngx_rtmp_core_srv_conf_t *cscf, 80 | ngx_chain_t *head, ngx_chain_t *in) 81 | { 82 | ngx_chain_t *l, **ll; 83 | u_char *p; 84 | size_t size; 85 | 86 | ll = &head; 87 | p = in->buf->pos; 88 | l = head; 89 | 90 | if (l) { 91 | for(; l->next; l = l->next); 92 | ll = &l->next; 93 | } 94 | 95 | for ( ;; ) { 96 | 97 | if (l == NULL || l->buf->last == l->buf->end) { 98 | l = ngx_rtmp_alloc_shared_buf(cscf); 99 | if (l == NULL || l->buf == NULL) { 100 | break; 101 | } 102 | 103 | *ll = l; 104 | ll = &l->next; 105 | } 106 | 107 | while (l->buf->end - l->buf->last >= in->buf->last - p) { 108 | l->buf->last = ngx_cpymem(l->buf->last, p, 109 | in->buf->last - p); 110 | in = in->next; 111 | if (in == NULL) { 112 | goto done; 113 | } 114 | p = in->buf->pos; 115 | } 116 | 117 | size = l->buf->end - l->buf->last; 118 | l->buf->last = ngx_cpymem(l->buf->last, p, size); 119 | p += size; 120 | } 121 | 122 | done: 123 | *ll = NULL; 124 | 125 | return head; 126 | } 127 | -------------------------------------------------------------------------------- /ngx_rtmp_streams.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Roman Arutyunyan 4 | */ 5 | 6 | 7 | #ifndef _NGX_RTMP_STREAMS_H_INCLUDED_ 8 | #define _NGX_RTMP_STREAMS_H_INCLUDED_ 9 | 10 | 11 | #define NGX_RTMP_MSID 1 12 | 13 | #define NGX_RTMP_CSID_AMF_INI 3 14 | #define NGX_RTMP_CSID_AMF 5 15 | #define NGX_RTMP_CSID_AUDIO 6 16 | #define NGX_RTMP_CSID_VIDEO 7 17 | 18 | 19 | #endif /* _NGX_RTMP_STREAMS_H_INCLUDED_ */ 20 | -------------------------------------------------------------------------------- /ngx_rtmp_version.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Roman Arutyunyan 4 | */ 5 | 6 | 7 | #ifndef _NGX_RTMP_VERSION_H_INCLUDED_ 8 | #define _NGX_RTMP_VERSION_H_INCLUDED_ 9 | 10 | 11 | #define nginx_rtmp_version 1001004 12 | #define NGINX_RTMP_VERSION "1.1.4" 13 | 14 | 15 | #endif /* _NGX_RTMP_VERSION_H_INCLUDED_ */ 16 | -------------------------------------------------------------------------------- /stat.xsl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | RTMP statistics 16 | 17 | 18 | 19 |
20 | Generated by 21 | nginx-rtmp-module , 22 | nginx , 23 | pid , 24 | built   25 | 26 | 27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 58 | 63 | 70 | 77 | 83 | 84 | 85 |
RTMP#clientsVideoAudioIn bytesOut bytesIn bits/sOut bits/sStateTime
Accepted: codecbits/ssizefpscodecbits/sfreqchan 54 | 55 | 56 | 57 | 59 | 60 | 61 | 62 | 64 | 65 | 66 | 67 | 68 | 69 | 71 | 72 | 73 | 74 | 75 | 76 | 78 | 79 | 80 | 81 | 82 |
86 |
87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | live streams 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | vod streams 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | #cccccc 131 | #dddddd 132 | 133 | 134 | 135 | 136 | 137 | var d=document.getElementById('-'); 138 | d.style.display=d.style.display=='none'?'':'none'; 139 | return false 140 | 141 | 142 | 143 | [EMPTY] 144 | 145 | 146 | 147 | 148 | 149 |    150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 |   166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | - 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 |
IdStateAddressFlash versionPage URLSWF URLDroppedTimestampA-VTime
231 | 232 | 233 |
234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | d 245 | 246 | 247 | 248 | h 249 | 250 | 251 | 252 | m 253 | 254 | 255 | s 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | T 269 | 270 | 271 | G 272 | 273 | 274 | M 275 | 276 | K 277 | 278 | 279 | 280 | b 281 | B 282 | 283 | /s 284 | 285 | 286 | 287 | 288 | 289 | active 290 | idle 291 | 292 | 293 | 294 | 295 | 296 | 297 | publishing 298 | playing 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | #cccccc 308 | #eeeeee 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | http://apps.db.ripe.net/search/query.html?searchtext= 317 | 318 | whois 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | publishing 345 | 346 | 347 | 348 | active 349 | 350 | 351 | 352 | x 353 | 354 | 355 |
356 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # RTMP tests 2 | 3 | nginx.conf is sample config for testing nginx-rtmp. 4 | Please update paths in it before using. 5 | 6 | RTMP port: 1935, HTTP port: 8080 7 | 8 | * http://localhost:8080/ - play myapp/mystream with JWPlayer 9 | * http://localhost:8080/record.html - capture myapp/mystream from webcam with old JWPlayer 10 | * http://localhost:8080/rtmp-publisher/player.html - play myapp/mystream with the test flash applet 11 | * http://localhost:8080/rtmp-publisher/publisher.html - capture myapp/mystream with the test flash applet 12 | -------------------------------------------------------------------------------- /test/dump.sh: -------------------------------------------------------------------------------- 1 | rtmpdump -v -r "rtmp://localhost/myapp/mystream" 2 | -------------------------------------------------------------------------------- /test/ffstream.sh: -------------------------------------------------------------------------------- 1 | ffmpeg -loglevel verbose -re -i ~/movie.avi -f flv rtmp://localhost/myapp/mystream 2 | -------------------------------------------------------------------------------- /test/nginx.conf: -------------------------------------------------------------------------------- 1 | worker_processes 1; 2 | 3 | error_log logs/error.log debug; 4 | 5 | events { 6 | worker_connections 1024; 7 | } 8 | 9 | rtmp { 10 | server { 11 | listen 1935; 12 | 13 | application myapp { 14 | live on; 15 | 16 | #record keyframes; 17 | #record_path /tmp; 18 | #record_max_size 128K; 19 | #record_interval 30s; 20 | #record_suffix .this.is.flv; 21 | 22 | #on_publish http://localhost:8080/publish; 23 | #on_play http://localhost:8080/play; 24 | #on_record_done http://localhost:8080/record_done; 25 | } 26 | } 27 | } 28 | 29 | http { 30 | server { 31 | listen 8080; 32 | 33 | location /stat { 34 | rtmp_stat all; 35 | rtmp_stat_stylesheet stat.xsl; 36 | } 37 | 38 | location /stat.xsl { 39 | root /path/to/nginx-rtmp-module/; 40 | } 41 | 42 | location /control { 43 | rtmp_control all; 44 | } 45 | 46 | #location /publish { 47 | # return 201; 48 | #} 49 | 50 | #location /play { 51 | # return 202; 52 | #} 53 | 54 | #location /record_done { 55 | # return 203; 56 | #} 57 | 58 | location /rtmp-publisher { 59 | root /path/to/nginx-rtmp-module/test; 60 | } 61 | 62 | location / { 63 | root /path/to/nginx-rtmp-module/test/www; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /test/play.sh: -------------------------------------------------------------------------------- 1 | ffplay -loglevel verbose "rtmp://localhost/myapp/mystream" 2 | -------------------------------------------------------------------------------- /test/rtmp-publisher/README.md: -------------------------------------------------------------------------------- 1 | # RTMP Publisher 2 | 3 | Simple RTMP publisher. 4 | 5 | Edit the following flashvars in publisher.html & player.html to suite your needs. 6 | 7 | streamer: RTMP endpoint 8 | file: live stream name 9 | 10 | ## Compile 11 | 12 | Install flex sdk http://www.adobe.com/devnet/flex/flex-sdk-download.html 13 | 14 | mxmlc RtmpPublisher.mxml 15 | mxmlc RtmpPlayer.mxml 16 | -------------------------------------------------------------------------------- /test/rtmp-publisher/RtmpPlayer.mxml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /test/rtmp-publisher/RtmpPlayer.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aileone/nginx-rtmp-module/71a06d685d33117adece81c510c6d0b246d62468/test/rtmp-publisher/RtmpPlayer.swf -------------------------------------------------------------------------------- /test/rtmp-publisher/RtmpPlayerLight.mxml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /test/rtmp-publisher/RtmpPlayerLight.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aileone/nginx-rtmp-module/71a06d685d33117adece81c510c6d0b246d62468/test/rtmp-publisher/RtmpPlayerLight.swf -------------------------------------------------------------------------------- /test/rtmp-publisher/RtmpPublisher.mxml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /test/rtmp-publisher/RtmpPublisher.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aileone/nginx-rtmp-module/71a06d685d33117adece81c510c6d0b246d62468/test/rtmp-publisher/RtmpPublisher.swf -------------------------------------------------------------------------------- /test/rtmp-publisher/player.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RTMP Player 5 | 6 | 16 | 17 | 18 |
19 |

Flash not installed

20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /test/rtmp-publisher/publisher.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RTMP Publisher 5 | 6 | 13 | 14 | 15 |
16 |

Flash not installed

17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /test/rtmp-publisher/swfobject.js: -------------------------------------------------------------------------------- 1 | /* SWFObject v2.2 2 | is released under the MIT License 3 | */ 4 | 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 2 |
3 | 4 | 5 |
Loading the player ...
6 | 20 | -------------------------------------------------------------------------------- /test/www/jwplayer/jwplayer.flash.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aileone/nginx-rtmp-module/71a06d685d33117adece81c510c6d0b246d62468/test/www/jwplayer/jwplayer.flash.swf -------------------------------------------------------------------------------- /test/www/jwplayer_old/player.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aileone/nginx-rtmp-module/71a06d685d33117adece81c510c6d0b246d62468/test/www/jwplayer_old/player.swf -------------------------------------------------------------------------------- /test/www/jwplayer_old/swfobject.js: -------------------------------------------------------------------------------- 1 | /* SWFObject v2.1 2 | Copyright (c) 2007-2008 Geoff Stearns, Michael Williams, and Bobby van der Sluis 3 | This software is released under the MIT License 4 | */ 5 | 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(" 4 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | --------------------------------------------------------------------------------