diff --git a/.gitignore b/.gitignore index d2533b109c..399419fa4f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ *.swp /trunk/Makefile /trunk/objs +/trunk/src/build-qt-Desktop-Debug /trunk/research/librtmp/objs /trunk/3rdparty/ccache/ccache-3.1.9 /trunk/3rdparty/gprof/graphviz-2.36.0 @@ -24,3 +25,6 @@ /trunk/research/api-server/static-dir/forward /trunk/research/api-server/static-dir/live /trunk/research/api-server/static-dir/players + +# Apple-specific garbage files. +.AppleDouble diff --git a/AUTHORS.txt b/AUTHORS.txt index 1d3b681838..cd0e3318b3 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -1,13 +1,10 @@ There are three types of people that have contributed to the SRS project: -1. PRIMARY: Contribute important features and >10% code. Names of all PRIMARY response in NetConnection.connect and metadata. -2. AUTHORS: Contribute features and 1%~10% code. Names of all PRIMARY response in NetConnection.connect and metadata. -3. CONTRIBUTORS: Submit patches, report bugs, add translations, help answer newbie questions, and generally make SRS that much better. - -PRIMARY ordered by first contribution. -* winlin "Plan, arch, implement SRS1.0 and SRS2.0" +1. AUTHORS: Contribute important features. Names of all PRIMARY response in NetConnection.connect and metadata. +2. CONTRIBUTORS: Submit patches, report bugs, add translations, help answer newbie questions, and generally make SRS that much better. AUTHORS ordered by first contribution. -* wenjie.zhao<740936897@qq.com> "The bandwidth test module" +* winlin "Plan, arch, implement SRS1.0 and SRS2.0" +* wenjie.zhao<740936897@qq.com> "The bandwidth test module, HDS and bug fixed." CONTRIBUTORS ordered by first contribution. * xiangcheng.liu "Bug fixed" @@ -21,4 +18,7 @@ CONTRIBUTORS ordered by first contribution. * StevenLiu "Build SRS on Darwin OSX" * zhengfl "Bug fixed" * tufang14 "Bug fixed" +* allspace "The srs-librtmp windows support" +* niesongsong "Configure support relative path" +* rudeb0t "Bug fixed" diff --git a/DONATIONS.txt b/DONATIONS.txt index 271c0f9fec..6b670090e0 100644 --- a/DONATIONS.txt +++ b/DONATIONS.txt @@ -1,5 +1,34 @@ Donations ordered by first donation. +=========================================================== +2015 + +RMB 10000+ +* [2015-03-03 13:25] 郭强 + +RMB 1000-9999 +* [2015-04-29 09:20] 王光辉 +* [2015-04-04 16:19] 蔡汉城 + +RMB 500-999 +* [2015-04-11 12:48] 丁一 + +RMB 100-499 +* [2015-05-06 22:04] 姜庆东 +* [2015-04-10 19:52] 阳成飞 +* [2015-03-30 13:34] 扶凯 +* [2015-03-29 11-07] 姚伟斌 +* [2015-03-14 20:21] 万伟 +* [2015-03-11 09:44] 叶发养 +* [2015-02-08 21:10] 韩友洪 +* [2015-01-09 16:08] 李理 + +RMB 50-99 +* [2015-03-03 17:30] flybird + +RMB 0.01-49 +* [2015-xx-xx xx:xx] xxx + =========================================================== 2014 diff --git a/LICENSE b/LICENSE index c09eb45796..67b63a1597 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/README.md b/README.md index 2f1bf6958a..8ea7cb4033 100755 --- a/README.md +++ b/README.md @@ -1,52 +1,51 @@ #Simple-RTMP-Server -SRS/1.0,开发代号:[HuKaiqun](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Product#release10) +SRS/2.0,开发代号:[ZhouGuowen](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Product#release20) SRS定位是运营级的互联网直播服务器集群,追求更好的概念完整性和最简单实现的代码。 -下载发布版(github): +Download from github.io: [Centos6-x86_64](http://winlinvip.github.io/srs.release/releases/files/SRS-CentOS6-x86_64-1.0.27.zip) -其他[more...](http://winlinvip.github.io/srs.release/releases/)
-下载发布版(国内阿里云镜像): -[Centos6-x86_64](http://www.ossrs.net/srs.release/releases/files/SRS-CentOS6-x86_64-1.0.27.zip) -其他[more...](http://www.ossrs.net/srs.release/releases/)
-QQ群: 365936885, by wenjie
-同类产品:[BLS](https://github.com/wenjiegit/Bull-Live-Server)/[BLE](https://github.com/wenjiegit/Bull-Live-Encoder), [NGINX-RTMP](https://github.com/arut/nginx-rtmp-module), [CRTMPD](http://www.rtmpd.com/), [RED5](http://www.red5.org/), [WOWZA](http://www.wowza.com/), [FMS/AMS](http://www.adobe.com/products/adobe-media-server-standard.html) - -获得源码(github): [https://github.com/winlinvip/simple-rtmp-server](https://github.com/winlinvip/simple-rtmp-server) [GIT使用方法](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Git) - -```bash -git clone https://github.com/winlinvip/simple-rtmp-server.git -``` - -获得源码(国内CSDN镜像): [https://code.csdn.net/winlinvip/srs-csdn](https://code.csdn.net/winlinvip/srs-csdn) [GIT使用方法](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Git) - -```bash -git clone https://code.csdn.net/winlinvip/srs-csdn.git -``` - -获得源码(国内OSChina镜像): [http://git.oschina.net/winlinvip/srs.oschina](http://git.oschina.net/winlinvip/srs.oschina) [GIT使用方法](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Git) - -```bash -git clone https://git.oschina.net/winlinvip/srs.oschina.git -``` +[more...](http://winlinvip.github.io/srs.release/releases/) -报告问题(BugReport): [https://github.com/winlinvip/simple-rtmp-server/issues/new](https://github.com/winlinvip/simple-rtmp-server/issues/new)
-中文资料(Wiki): [https://github.com/winlinvip/simple-rtmp-server/wiki](https://github.com/winlinvip/simple-rtmp-server/wiki)
-使用步骤(Usage): [https://github.com/winlinvip/simple-rtmp-server#usage](#usage)
-公用机器(LiveShow): [https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_LiveShow](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_LiveShow)
-捐款(Donation): [GitHub](http://winlinvip.github.io/srs.release/donation/index.html) -或 [阿里云镜像](http://www.ossrs.net/srs.release/donation/index.html) ,查看 -[捐献墙(Donations)](https://github.com/winlinvip/simple-rtmp-server/blob/develop/DONATIONS.txt)
+Download from ossrs.net: +[Centos6-x86_64](http://www.ossrs.net/srs.release/releases/files/SRS-CentOS6-x86_64-1.0.27.zip) +[more...](http://www.ossrs.net/srs.release/releases/) + +Contact by QQ or Skype, read [Contact](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Contact) + +## Why SRS? + +1. Completely rewrite HLS following m3u8/ts spec, and HLS support h.264+aac/mp3. +1. High efficient RTMP deliverying support 7k+ concurrency, vhost based, both origin and edge. +1. Embeded simplified media HTTP server for HLS, api and HTTP flv/ts/mp3/aac streaming. +1. Variety input: RTMP, pull by ingest file or stream(HTTP/RTMP/RTSP), push by stream caster +RTSP/MPEGTS-over-UDP. +1. Popular internet delivery: RTMP/HDS for flash, HLS for mobile(IOS/IPad/MAC/Android), HTTP +flv/ts/mp3/aac streaming for user prefered. +1. Enhanced DVR and hstrs: segment/session/append plan, customer path and HTTP callback. +the hstrs(http stream trigger rtmp source) enable the http-flv stream standby util encoder +start publish, similar to rtmp, which will trigger edge to fetch from origin. +1. Multiple feature: transcode, forward, ingest, http hooks, dvr, hls, rtsp, http streaming, +http api, refer, log, bandwith test and srs-librtmp. +1. Best maintainess: simple arch over state-threads(coroutine), single thread, single process +and for linux/osx platform, common server x86-64/i386/arm/mips cpus, rich comments, strictly +follows RTMP/HLS/RTSP spec. +1. Easy to use: both English and Chinese wiki, typically config files in trunk/conf, traceable +and session based log, linux service script and install script. +1. MIT license, open source with product management and evolution. + +Enjoy it! ## About SRS(SIMPLE RTMP Server) over state-threads created in 2013.10. -SRS delivers rtmp/hls live on x86/x64/arm/mips linux, +SRS delivers rtmp/hls/http/hds live on x86/x64/arm/mips linux/osx, supports origin/edge/vhost and transcode/ingest and dvr/forward and http-api/http-callback/reload, introduces tracable session-oriented log, exports client srs-librtmp, +with stream caster to push MPEGTS-over-UDP/RTSP to SRS, provides EN/CN wiki and the most simple architecture. SRS focus on small problem domain, which is the most complex for all software(see OOAD). @@ -54,110 +53,132 @@ Because of lack of deveoper resource, SRS only provides features which is the mo for internet. SRS is simple for and only for problem domain is simplified. SRS is a simple, RTMP( -[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_DeliveryRTMP), -[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_DeliveryRTMP) +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_DeliveryRTMP), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_DeliveryRTMP) ), HLS( -[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_DeliveryHLS), -[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_DeliveryHLS) +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_DeliveryHLS), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_DeliveryHLS) +), +HDS( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_DeliveryHDS), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_DeliveryHDS) ), -high-performance(2.7k+ clients)( -[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Performance), -[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_Performance) +HTTP( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v2_CN_DeliveryHttpStream), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v2_CN_DeliveryHttpStream) +), +high-performance(10k+ clients)( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Performance), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_Performance) +), +low-latency(0.1s+)( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v2_CN_LowLatency), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v2_EN_LowLatency) ), single processes, edge/origin live server, x86/x64/arm( -[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SrsLinuxArm), -[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_SrsLinuxArm) +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SrsLinuxArm), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_SrsLinuxArm) ), compile depends on st( -[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Architecture), -[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_Architecture) +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Architecture), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_Architecture) )(required), [ssl](http://www.openssl.org/) and [http-parser](https://github.com/joyent/http-parser), use [nginx](http://nginx.org/), [ffmpeg](http://ffmpeg.org/) and [cherrypy](http://www.cherrypy.org/) as external tools. that is, only need st to run srs for minimum run. see Build( -[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Build), -[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_Build) +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Build), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_Build) ). SRS supports vhost( -[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_RtmpUrlVhost), -[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_RtmpUrlVhost) +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_RtmpUrlVhost), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_RtmpUrlVhost) ), rtmp(encoder push( -[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_DeliveryRTMP), -[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_DeliveryRTMP) +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_DeliveryRTMP), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_DeliveryRTMP) ), client/edge( -[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Edge), -[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_Edge), +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Edge), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_Edge), ) pull), ingester(srs pull)( -[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Ingest), -[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_Ingest) +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Ingest), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_Ingest) ), HLS( -[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_DeliveryHLS), -[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_DeliveryHLS) +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_DeliveryHLS), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_DeliveryHLS) ), HLS audio only( -[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_DeliveryHLS#hlsaudioonly), -[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_DeliveryHLS#hlsaudioonly) +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_DeliveryHLS#hlsaudioonly), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_DeliveryHLS#hlsaudioonly) ), transcoding( -[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_FFMPEG), -[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_FFMPEG) +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_FFMPEG), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_FFMPEG) ), forward( -[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_FFMPEG), -[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_FFMPEG) +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_FFMPEG), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_FFMPEG) ), http hooks( -[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_HTTPCallback), -[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_HTTPCallback) +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_HTTPCallback), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_HTTPCallback) ), http api( -[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_HTTPApi), -[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_HTTPApi) +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_HTTPApi), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_HTTPApi) ), http server( -[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_HTTPServer), -[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_HTTPServer) +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_HTTPServer), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_HTTPServer) ), dvr( -[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_DVR), -[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_DVR) +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_DVR), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_DVR) ) and SRS-librtmp( -[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SrsLibrtmp), -[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_SrsLibrtmp) +[CN](https://github.com/simple-rtmp-server/srs/wiki/v2_CN_SrsLibrtmp), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v2_EN_SrsLibrtmp) ). SRS-librtmp( -[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SrsLibrtmp), -[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_SrsLibrtmp) +[CN](https://github.com/simple-rtmp-server/srs/wiki/v2_CN_SrsLibrtmp), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v2_EN_SrsLibrtmp) ) is a client library, only depends on c++ and socket, with examples( -[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SrsLibrtmp#srs-librtmp-examples), -[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_SrsLibrtmp#srs-librtmp-examples) +[CN](https://github.com/simple-rtmp-server/srs/wiki/v2_CN_SrsLibrtmp#srs-librtmp-examples), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v2_EN_SrsLibrtmp#srs-librtmp-examples) )(to play, publish, ingest flv/rtmp, inject flv, +publish h264 raw stream( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v2_CN_SrsLibrtmp#publish-h264-raw-data), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v2_EN_SrsLibrtmp#publish-h264-raw-data) +), +exported as seperate project or single cpp file by configure( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v2_CN_SrsLibrtmp#export-srs-librtmp), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v2_EN_SrsLibrtmp#export-srs-librtmp) +). SRS-librtmp( -[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SrsLibrtmp), -[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_SrsLibrtmp) +[CN](https://github.com/simple-rtmp-server/srs/wiki/v2_CN_SrsLibrtmp), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v2_EN_SrsLibrtmp) ) -provides apis to support RTMP, FLV and AMF0. +provides apis to support RTMP, FLV, AMF0 and +h.264 raw stream( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v2_CN_SrsLibrtmp#publish-h264-raw-data), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v2_EN_SrsLibrtmp#publish-h264-raw-data) +). +Report Bug: [https://github.com/simple-rtmp-server/srs/issues/new](https://github.com/simple-rtmp-server/srs/issues/new)
WebSite: [http://ossrs.net](http://ossrs.net)
Release: [http://winlinvip.github.io/srs.release](http://winlinvip.github.io/srs.release)
Blog: [http://blog.csdn.net/win_lin](http://blog.csdn.net/win_lin)
-CSDN mirror: [https://code.csdn.net/winlinvip/srs-csdn](https://code.csdn.net/winlinvip/srs-csdn)
-See also: [https://github.com/winlinvip/simple-rtmp-server](https://github.com/winlinvip/simple-rtmp-server)
-Github DEMO: [demo with your SRS](http://winlinvip.github.io/srs.release/trunk/research/players/srs_player.html?server=192.168.1.170&vhost=192.168.1.170)
-Wiki: [https://github.com/winlinvip/simple-rtmp-server/wiki](https://github.com/winlinvip/simple-rtmp-server/wiki)
+Wiki: [https://github.com/simple-rtmp-server/srs/wiki](https://github.com/simple-rtmp-server/srs/wiki)
StreamServers:[BLS](https://github.com/wenjiegit/Bull-Live-Server)/[BLE](https://github.com/wenjiegit/Bull-Live-Encoder), [NGINX-RTMP](https://github.com/arut/nginx-rtmp-module), [CRTMPD](http://www.rtmpd.com/), [RED5](http://www.red5.org/), [WOWZA](http://www.wowza.com/), @@ -166,18 +187,16 @@ StreamServers:[BLS](https://github.com/wenjiegit/Bull-Live-Server)/[BLE](https ## AUTHORS There are three types of people that have contributed to the SRS project: -* PRIMARY: Contribute important features and >10% code. Names of all -PRIMARY response in NetConnection.connect and metadata. -* AUTHORS: Contribute features and 1%~10% code. Names of all +* AUTHORS: Contribute important features. Names of all PRIMARY response in NetConnection.connect and metadata. * CONTRIBUTORS: Submit patches, report bugs, add translations, help answer newbie questions, and generally make SRS that much better. About all PRIMARY, AUTHORS and CONTRIBUTORS, read -[AUTHORS.txt](https://github.com/winlinvip/simple-rtmp-server/blob/master/AUTHORS.txt). +[AUTHORS.txt](https://github.com/simple-rtmp-server/srs/blob/develop/AUTHORS.txt). A big THANK YOU goes to: -* [chnvideo](chnvideo.com) co-founders([wiseyoung](mailto:wiseyoung@chnvideo.com), [trueice](mailto:trueice@chnvideo.com), [leijian](mailto:leijian@chnvideo.com)) for [big supports](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Product#bigthanks). +* All friends of SRS for [big supports](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Product#bigthanks). * Genes amd Mabbott for creating [st](https://github.com/winlinvip/state-threads)([state-threads](http://sourceforge.net/projects/state-threads/)). * Michael Talyanksy for introducing us to use st. * Roman Arutyunyan for creating [nginx-rtmp](https://github.com/arut/nginx-rtmp-module) for SRS to refer to. @@ -188,20 +207,20 @@ A big THANK YOU goes to: ## Mirrors -Github: [https://github.com/winlinvip/simple-rtmp-server](https://github.com/winlinvip/simple-rtmp-server), +Github: [https://github.com/simple-rtmp-server/srs](https://github.com/simple-rtmp-server/srs), the GIT usage( -[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Git), -[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_Git) +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Git), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_Git) ) ```bash -git clone https://github.com/winlinvip/simple-rtmp-server.git +git clone https://github.com/simple-rtmp-server/srs.git ``` CSDN: [https://code.csdn.net/winlinvip/srs-csdn](https://code.csdn.net/winlinvip/srs-csdn) , the GIT usage( -[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Git), -[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_Git) +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Git), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_Git) ) ```bash @@ -210,8 +229,8 @@ git clone https://code.csdn.net/winlinvip/srs-csdn.git OSChina: [http://git.oschina.net/winlinvip/srs.oschina](http://git.oschina.net/winlinvip/srs.oschina) , the GIT usage( -[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Git), -[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_Git) +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Git), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_Git) ) ```bash @@ -220,8 +239,8 @@ git clone https://git.oschina.net/winlinvip/srs.oschina.git Gitlab: [https://gitlab.com/winlinvip/srs-gitlab](https://gitlab.com/winlinvip/srs-gitlab) , the GIT usage( -[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Git), -[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_Git) +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Git), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_Git) ) ```bash @@ -233,14 +252,14 @@ git clone https://gitlab.com/winlinvip/srs-gitlab.git Step 1: get SRS
-git clone https://github.com/winlinvip/simple-rtmp-server &&
+git clone https://github.com/simple-rtmp-server/srs &&
 cd simple-rtmp-server/trunk
 
Step 2: build SRS, Requires Centos6.x/Ubuntu12 32/64bits, others see Build( -[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Build), -[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_Build) +[CN](https://github.com/simple-rtmp-server/srs/wiki/v2_CN_Build), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v2_EN_Build) ).
@@ -255,52 +274,56 @@ cd simple-rtmp-server/trunk
 
 See also:
 * Usage: How to delivery RTMP?(
-[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleRTMP),
-[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_SampleRTMP)
+[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleRTMP),
+[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_SampleRTMP)
 )
 * Usage: How to delivery HLS?(
-[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleHLS),
-[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_SampleHLS)
+[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleHLS),
+[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_SampleHLS)
 )
 * Usage: How to delivery HLS for other codec?(
-[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleTranscode2HLS),
-[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_SampleTranscode2HLS)
+[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleTranscode2HLS),
+[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_SampleTranscode2HLS)
 )
 * Usage: How to transode RTMP stream by SRS?(
-[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleFFMPEG),
-[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_SampleFFMPEG)
+[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleFFMPEG),
+[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_SampleFFMPEG)
 )
 * Usage: How to forward stream to other server?(
-[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleForward),
-[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_SampleForward)
+[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleForward),
+[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_SampleForward)
 )
 * Usage: How to deploy low lantency application?(
-[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleRealtime),
-[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_SampleRealtime)
+[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleRealtime),
+[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_SampleRealtime)
 )
 * Usage: How to deploy SRS on ARM?(
-[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleARM),
-[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_SampleARM)
+[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleARM),
+[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_SampleARM)
 )
 * Usage: How to ingest file/stream/device to SRS?(
-[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleIngest),
-[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_SampleIngest)
+[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleIngest),
+[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_SampleIngest)
 )
 * Usage: How to use SRS-HTTP-server to delivery HTTP/HLS stream?(
-[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleHTTP),
-[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_SampleHTTP)
+[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleHTTP),
+[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_SampleHTTP)
 )
 * Usage: How to show the demo of SRS?(
-[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleDemo),
-[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_SampleDemo)
+[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleDemo),
+[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_SampleDemo)
+)
+* Usage: How to publish h.264 raw stream to SRS?(
+[CN](https://github.com/simple-rtmp-server/srs/wiki/v2_CN_SrsLibrtmp#publish-h264-raw-data),
+[EN](https://github.com/simple-rtmp-server/srs/wiki/v2_EN_SrsLibrtmp#publish-h264-raw-data)
 )
 * Usage: Solution using SRS?(
-[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Sample),
-[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_Sample)
+[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Sample),
+[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_Sample)
 )
 * Usage: Why SRS?(
-[CN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Product),
-[EN](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_Product)
+[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Product),
+[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_Product)
 )
 
 ## Wiki
@@ -308,8 +331,14 @@ cd simple-rtmp-server/trunk
 SRS 1.0 wiki
 
 Please select your language:
-* [SRS 1.0 English](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_EN_Home)
-* [SRS 1.0 Chinese](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Home)
+* [SRS 1.0 English](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_Home)
+* [SRS 1.0 Chinese](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Home)
+
+SRS 2.0 wiki
+
+Please select your language:
+* [SRS 2.0 English](https://github.com/simple-rtmp-server/srs/wiki/v2_EN_Home)
+* [SRS 2.0 Chinese](https://github.com/simple-rtmp-server/srs/wiki/v2_CN_Home)
 
 ## Donation
 
@@ -318,180 +347,422 @@ Donation:
[http://www.ossrs.net/srs.release/donation/index.html](http://www.ossrs.net/srs.release/donation/index.html) Donations:
-[https://github.com/winlinvip/simple-rtmp-server/blob/develop/DONATIONS.txt] -(https://github.com/winlinvip/simple-rtmp-server/blob/develop/DONATIONS.txt) +[https://github.com/simple-rtmp-server/srs/blob/develop/DONATIONS.txt] +(https://github.com/simple-rtmp-server/srs/blob/develop/DONATIONS.txt) ## System Requirements Supported operating systems and hardware: * All Linux , both 32 and 64 bits +* Apple OSX(Darwin), both 32 and 64bits. * All hardware with x86/x86_64/arm/mips cpu. ## Summary -1. 简洁稳定:Simple, also stable enough. -1. 高性能:[High-performance](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Performance): single-thread, async socket, event/st-thread driven. -1. 高并发:[High-concurrency](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Performance), 1800 connections(500kbps), 900Mbps, CPU 90.2%, 41MB -1. RTMP源站:Support [RTMP Origin Server](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_DeliveryRTMP). -1. CDN边缘(上下行加速):Support [RTMP Edge Server](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Edge) for CDN, push/pull stream from any RTMP server -1. 单进程(无多进程):Support single process; no multiple processes. -1. 支持Vhost:Support [Vhost](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_RtmpUrlVhost), support \_\_defaultVhost\_\_. -1. 直播(无点播):Support [RTMP](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_DeliveryRTMP) live streaming; no vod streaming. -1. 苹果HLS:Support Apple [HLS(m3u8)](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_DeliveryHLS) live streaming. -1. 支持纯音频HLS:Support [HLS audio-only](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_DeliveryHLS#hlsaudioonly) live streaming. -1. 支持Reload:Support [Reload](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Reload) config to enable changes. -1. 支持GopCache:Support [cache last gop](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_LowLatency#gop-cache) for flash player to fast startup. -1. 侦听多端口:Support listen at multiple ports. -1. 长时间推流:Support long time(>4.6hours) publish/play. -1. 转发流:Support [Forward](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Forward) in master-slave mode. -1. 流转码:Support live stream [Transcoding](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_FFMPEG) by ffmpeg. -1. 支持FFMPEG滤镜:Support [ffmpeg](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_FFMPEG) filters(logo/overlay/crop), x264 params, copy/vn/an. -1. 只转码音频:Support audio [transcode](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_FFMPEG) only, speex/mp3 to aac -1. 支持HTTP回调:Support [http callback api hooks](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_HTTPCallback)(for authentication and injection). -1. 带宽测速:Support [bandwidth test](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_BandwidthTestTool) api and flash client. -1. 演示页面:Player, publisher(encoder), and [demo pages(jquery+bootstrap)](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleDemo). -1. 视频会议演示:[Demo](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleDemo) video meeting or chat(SRS+cherrypy+jquery+bootstrap). -1. 中文Wiki:Full documents in [wiki](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Home), in Chineses. -1. 客户端库:Support RTMP(play-publish) library: [srs-librtmp](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SrsLibrtmp) -1. 支持ARM平台:Support ARM([debian armhf, v7cpu](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SrsLinuxArm)) with rtmp/ssl/hls/librtmp. -1. 支持Init.d脚本:Support [init.d](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_LinuxService) and packge script, log to file. -1. 支持ATC:Support [RTMP ATC](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_RTMP-ATC) for HLS/HDS to support backup(failover) -1. 支持HTTP-RESTful-API:Support [HTTP RESTful management api](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_HTTPApi). -1. 采集流:Support [Ingest](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Ingest) FILE/HTTP/RTMP/RTSP(RTP, SDP) to RTMP using external tools(e.g ffmepg). -1. 支持录制:Support [DVR](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_DVR), record live to flv file for vod. -1. 可追溯日志:Support [tracable log, session based log](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SrsLog). -1. 支持FMS-Token穿越:Support DRM [token traverse](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_DRM#tokentraverse) for fms origin authenticate. -1. 全面的Utest:Support system full utest on gtest. -1. Stable [1.0release branch](https://github.com/winlinvip/simple-rtmp-server/tree/1.0release) and -[2.0dev branch](https://github.com/winlinvip/simple-rtmp-server/tree/master). +1. Simple, also stable enough. +1. High-performance( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Performance), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_Performance) +): single-thread, async socket, event/st-thread driven. +1. High-concurrency( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Performance), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_Performance) +), 6000+ connections(500kbps), 900Mbps, CPU 90.2%, 41MB +1. Support RTMP Origin Server( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_DeliveryRTMP), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_DeliveryRTMP) +) +1. Support RTMP Edge Server( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Edge), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_Edge) +) for CDN, push/pull stream from any RTMP server +1. Support single process; no multiple processes. +1. Support Vhost( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_RtmpUrlVhost), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_RtmpUrlVhost) +), support \_\_defaultVhost\_\_. +1. Support RTMP( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_DeliveryRTMP), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_DeliveryRTMP) +) live streaming; no vod streaming. +1. Support Apple HLS(m3u8)( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_DeliveryHLS), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_DeliveryHLS) +) live streaming. +1. Support HLS audio-only( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_DeliveryHLS#hlsaudioonly), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_DeliveryHLS#hlsaudioonly) +) live streaming. +1. Support Reload( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Reload), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_Reload) +) config to enable changes. +1. Support cache last gop( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_LowLatency#gop-cache), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_LowLatency#gop-cache) +) for flash player to fast startup. +1. Support listen at multiple ports. +1. Support long time(>4.6hours) publish/play. +1. Support Forward( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Forward), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_Forward) +) in master-slave mode. +1. Support live stream Transcoding( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_FFMPEG), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_FFMPEG) +) by ffmpeg. +1. Support ffmpeg( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_FFMPEG), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_FFMPEG) +) filters(logo/overlay/crop), x264 params, copy/vn/an. +1. Support audio transcode( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_FFMPEG), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_FFMPEG) +) only, speex/mp3 to aac +1. Support http callback api hooks( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_HTTPCallback), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_HTTPCallback) +)(for authentication and injection). +1. Support bandwidth test( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_BandwidthTestTool), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_BandwidthTestTool) +) api and flash client. +1. Player, publisher(encoder), and demo pages(jquery+bootstrap)( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleDemo), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_SampleDemo) +). +1. Demo( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleDemo), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_SampleDemo) +) video meeting or chat(SRS+cherrypy+jquery+bootstrap). +1. Full documents in wiki( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Home), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_Home) +), both Chinese and English. +1. Support RTMP(play-publish) library: srs-librtmp( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v2_CN_SrsLibrtmp), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v2_EN_SrsLibrtmp) +) +1. Support ARM cpu arch( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SrsLinuxArm), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_SrsLinuxArm) +) with rtmp/ssl/hls/librtmp. +1. Support init.d( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_LinuxService), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_LinuxService) +) and packge script, log to file. +1. Support RTMP ATC( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_RTMP-ATC), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_RTMP-ATC) +) for HLS/HDS to support backup(failover) +1. Support HTTP RESTful management api( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_HTTPApi), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_HTTPApi) +). +1. Support Ingest( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Ingest), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_Ingest) +) FILE/HTTP/RTMP/RTSP(RTP, SDP) to RTMP using external tools(e.g ffmepg). +1. Support DVR( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_DVR), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_DVR) +), record live to flv file for vod. +1. Support tracable log, session based log( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SrsLog), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_SrsLog) +). +1. Support DRM token traverse( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_DRM#tokentraverse), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_DRM#tokentraverse) +) for fms origin authenticate. +1. Support system full utest on gtest. +1. Support embeded HTTP server( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v2_CN_SampleHTTP), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v2_EN_SampleHTTP) +) for hls(live/vod) +1. Support vod stream(http flv/hls vod stream)( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v2_CN_FlvVodStream), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v2_EN_FlvVodStream) +). +1. Stable [1.0release branch](https://github.com/simple-rtmp-server/srs/tree/1.0release). +1. Support publish h264 raw stream( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v2_CN_SrsLibrtmp#publish-h264-raw-data), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v2_EN_SrsLibrtmp#publish-h264-raw-data) +) by srs-librtmp. +1. Support [6k+ clients](https://github.com/simple-rtmp-server/srs/issues/194), 3Gbps per process. +1. Suppport [English wiki](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_Home). +1. Research and simplify st, [bug #182](https://github.com/simple-rtmp-server/srs/issues/182). +1. Support compile [srs-librtmp on windows](https://github.com/winlinvip/srs.librtmp), +[bug #213](https://github.com/simple-rtmp-server/srs/issues/213). +1. Support [10k+ clients](https://github.com/simple-rtmp-server/srs/issues/251), 4Gbps per process. +1. Support publish aac adts raw stream( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v2_CN_SrsLibrtmp#publish-audio-raw-stream), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v2_EN_SrsLibrtmp#publish-audio-raw-stream) +) by srs-librtmp. +1. Support 0.1s+ latency, read [#257](https://github.com/simple-rtmp-server/srs/issues/257). +1. Support allow/deny publish/play for all or specified ip( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v2_CN_Security), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v2_EN_Security) +). +1. Support custom dvr path and http callback, read +[#179](https://github.com/simple-rtmp-server/srs/issues/179) and +[274](https://github.com/simple-rtmp-server/srs/issues/274). +1. Support rtmp remux to http flv/mp3/aac/ts live stream, read +[#293](https://github.com/simple-rtmp-server/srs/issues/293)( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v2_CN_DeliveryHttpStream), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v2_CN_DeliveryHttpStream) +). +1. Support HLS(h.264+mp3) streaming, read +[#301](https://github.com/simple-rtmp-server/srs/issues/301). +1. Rewrite HLS(h.264+aac/mp3) streaming, read +[#304](https://github.com/simple-rtmp-server/srs/issues/304). +1. Support Adobe HDS(f4m)( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_DeliveryHDS), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v1_EN_DeliveryHDS) +) dynamic streaming. +1. [experiment] Support push MPEG-TS over UDP to SRS, read +[#250](https://github.com/simple-rtmp-server/srs/issues/250). +1. [experiment] Support push RTSP to SRS, read +[#133](https://github.com/simple-rtmp-server/srs/issues/133). +1. Start [2.0release branch](https://github.com/simple-rtmp-server/srs/tree/2.0release). +1. [no-plan] Support <500ms latency, FRSC(Fast RTMP-compatible Stream Channel tech). +1. [no-plan] Support RTMP 302 redirect [#92](https://github.com/simple-rtmp-server/srs/issues/92). +1. [no-plan] Support multiple processes, for both origin and edge +1. [no-plan] Support adobe RTMFP(flash p2p) protocol. +1. [no-plan] Support adobe flash refer/token/swf verification. +1. [no-plan] Support adobe amf3 codec. +1. [no-plan] Support encryption: RTMPE/RTMPS, HLS DRM +1. [no-plan] Support RTMPT, http to tranverse firewalls +1. [no-plan] Support file source, transcoding file to live stream ## Releases -* 2015-02-12, [Release v1.0r2](https://github.com/winlinvip/simple-rtmp-server/releases/tag/1.0r2), bug fixed, 1.0.27, 59507 lines.
-* 2015-01-15, [Release v1.0r1](https://github.com/winlinvip/simple-rtmp-server/releases/tag/1.0r1), bug fixed, 1.0.21, 59472 lines.
-* 2014-12-05, [Release v1.0](https://github.com/winlinvip/simple-rtmp-server/releases/tag/1.0), all bug fixed, 1.0.10, 59391 lines.
-* 2014-10-09, [Release v1.0-beta](https://github.com/winlinvip/simple-rtmp-server/releases/tag/1.0.beta), all bug fixed, 1.0.0, 59316 lines.
-* 2014-08-03, [Release v1.0-mainline7](https://github.com/winlinvip/simple-rtmp-server/releases/tag/1.0.mainline7), config utest, all bug fixed. 57432 lines.
-* 2014-07-13, [Release v1.0-mainline6](https://github.com/winlinvip/simple-rtmp-server/releases/tag/1.0.mainline6), core/kernel/rtmp utest, refine bandwidth(as/js/srslibrtmp library). 50029 lines.
-* 2014-06-27, [Release v1.0-mainline5](https://github.com/winlinvip/simple-rtmp-server/releases/tag/1.0.mainline5), refine perf 3k+ clients, edge token traverse, [srs monitor](http://ossrs.net:1977), 30days online. 41573 lines.
-* 2014-05-28, [Release v1.0-mainline4](https://github.com/winlinvip/simple-rtmp-server/releases/tag/1.0.mainline4), support heartbeat, tracable log, fix mem leak and bugs. 39200 lines.
-* 2014-05-18, [Release v1.0-mainline3](https://github.com/winlinvip/simple-rtmp-server/releases/tag/1.0.mainline3), support mips, fms origin, json(http-api). 37594 lines.
-* 2014-04-28, [Release v1.0-mainline2](https://github.com/winlinvip/simple-rtmp-server/releases/tag/1.0.mainline2), support [dvr](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_DVR), android, [edge](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Edge). 35255 lines.
-* 2014-04-07, [Release v1.0-mainline](https://github.com/winlinvip/simple-rtmp-server/releases/tag/1.0.mainline), support [arm](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SrsLinuxArm), [init.d](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_LinuxService), http [server](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_HTTPServer)/[api](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_HTTPApi), [ingest](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleIngest). 30000 lines.
-* 2013-12-25, [Release v0.9](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.9), support bandwidth test, player/encoder/chat [demos](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleDemo). 20926 lines.
-* 2013-12-08, [Release v0.8](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.8), support [http hooks callback](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_HTTPCallback), update [st_load](https://github.com/winlinvip/st-load). 19186 lines.
-* 2013-12-03, [Release v0.7](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.7), support [live stream transcoding](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_FFMPEG). 17605 lines.
-* 2013-11-29, [Release v0.6](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.6), support [forward](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Forward) stream to origin/edge. 16094 lines.
-* 2013-11-26, [Release v0.5](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.5), support [HLS(m3u8)](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_DeliveryHLS), fragment and window. 14449 lines.
-* 2013-11-10, [Release v0.4](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.4), support [reload](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Reload) config, pause, longtime publish/play. 12500 lines.
-* 2013-11-04, [Release v0.3](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.3), support [vhost](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_RtmpUrlVhost), refer, gop cache, listen multiple ports. 11773 lines.
-* 2013-10-25, [Release v0.2](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.2), support [rtmp](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_RTMPHandshake) flash publish, h264, time jitter correct. 10125 lines.
-* 2013-10-23, [Release v0.1](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.1), support [rtmp FMLE/FFMPEG publish](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_DeliveryRTMP), vp6. 8287 lines.
+* 2015-02-12, [Release v1.0r2](https://github.com/simple-rtmp-server/srs/releases/tag/1.0r2), bug fixed, 1.0.27, 59507 lines.
+* 2015-01-15, [Release v1.0r1](https://github.com/simple-rtmp-server/srs/releases/tag/1.0r1), bug fixed, 1.0.21, 59472 lines.
+* 2014-12-05, [Release v1.0r0](https://github.com/simple-rtmp-server/srs/releases/tag/1.0r0), all bug fixed, 1.0.10, 59391 lines.
+* 2014-10-09, [Release v1.0-beta](https://github.com/simple-rtmp-server/srs/releases/tag/1.0.beta), all bug fixed, 1.0.0, 59316 lines.
+* 2014-08-03, [Release v1.0-mainline7](https://github.com/simple-rtmp-server/srs/releases/tag/1.0.mainline7), config utest, all bug fixed. 57432 lines.
+* 2014-07-13, [Release v1.0-mainline6](https://github.com/simple-rtmp-server/srs/releases/tag/1.0.mainline6), core/kernel/rtmp utest, refine bandwidth(as/js/srslibrtmp library). 50029 lines.
+* 2014-06-27, [Release v1.0-mainline5](https://github.com/simple-rtmp-server/srs/releases/tag/1.0.mainline5), refine perf 3k+ clients, edge token traverse, [srs monitor](http://ossrs.net:1977), 30days online. 41573 lines.
+* 2014-05-28, [Release v1.0-mainline4](https://github.com/simple-rtmp-server/srs/releases/tag/1.0.mainline4), support heartbeat, tracable log, fix mem leak and bugs. 39200 lines.
+* 2014-05-18, [Release v1.0-mainline3](https://github.com/simple-rtmp-server/srs/releases/tag/1.0.mainline3), support mips, fms origin, json(http-api). 37594 lines.
+* 2014-04-28, [Release v1.0-mainline2](https://github.com/simple-rtmp-server/srs/releases/tag/1.0.mainline2), support [dvr](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_DVR), android, [edge](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Edge). 35255 lines.
+* 2014-04-07, [Release v1.0-mainline](https://github.com/simple-rtmp-server/srs/releases/tag/1.0.mainline), support [arm](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SrsLinuxArm), [init.d](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_LinuxService), http [server](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_HTTPServer)/[api](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_HTTPApi), [ingest](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleIngest). 30000 lines.
+* 2013-12-25, [Release v0.9](https://github.com/simple-rtmp-server/srs/releases/tag/0.9), support bandwidth test, player/encoder/chat [demos](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleDemo). 20926 lines.
+* 2013-12-08, [Release v0.8](https://github.com/simple-rtmp-server/srs/releases/tag/0.8), support [http hooks callback](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_HTTPCallback), update [st_load](https://github.com/winlinvip/st-load). 19186 lines.
+* 2013-12-03, [Release v0.7](https://github.com/simple-rtmp-server/srs/releases/tag/0.7), support [live stream transcoding](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_FFMPEG). 17605 lines.
+* 2013-11-29, [Release v0.6](https://github.com/simple-rtmp-server/srs/releases/tag/0.6), support [forward](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Forward) stream to origin/edge. 16094 lines.
+* 2013-11-26, [Release v0.5](https://github.com/simple-rtmp-server/srs/releases/tag/0.5), support [HLS(m3u8)](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_DeliveryHLS), fragment and window. 14449 lines.
+* 2013-11-10, [Release v0.4](https://github.com/simple-rtmp-server/srs/releases/tag/0.4), support [reload](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Reload) config, pause, longtime publish/play. 12500 lines.
+* 2013-11-04, [Release v0.3](https://github.com/simple-rtmp-server/srs/releases/tag/0.3), support [vhost](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_RtmpUrlVhost), refer, gop cache, listen multiple ports. 11773 lines.
+* 2013-10-25, [Release v0.2](https://github.com/simple-rtmp-server/srs/releases/tag/0.2), support [rtmp](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_RTMPHandshake) flash publish, h264, time jitter correct. 10125 lines.
+* 2013-10-23, [Release v0.1](https://github.com/simple-rtmp-server/srs/releases/tag/0.1), support [rtmp FMLE/FFMPEG publish](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_DeliveryRTMP), vp6. 8287 lines.
* 2013-10-17, Created.
## History +### SRS 2.0 history + +* v2.0, 2015-04-20, support ingest hls live stream to RTMP. +* v2.0, 2015-04-15, for [#383](https://github.com/simple-rtmp-server/srs/issues/383), support mix_correct algorithm. 2.0.161. +* v2.0, 2015-04-13, for [#381](https://github.com/simple-rtmp-server/srs/issues/381), support reap hls/ts by gop or not. 2.0.160. +* v2.0, 2015-04-10, enhanced on_hls_notify, support HTTP GET when reap ts. +* v2.0, 2015-04-10, refine the hls deviation for floor algorithm. +* v2.0, 2015-04-08, for [#375](https://github.com/simple-rtmp-server/srs/issues/375), fix hls bug, keep cc continous between ts files. 2.0.159. +* v2.0, 2015-04-04, for [#304](https://github.com/simple-rtmp-server/srs/issues/304), rewrite annexb mux for ts, refer to apple sample. 2.0.157. +* v2.0, 2015-04-03, enhanced avc decode, parse the sps get width+height. 2.0.156. +* v2.0, 2015-04-03, for [#372](https://github.com/simple-rtmp-server/srs/issues/372), support transform vhost of edge 2.0.155. +* v2.0, 2015-03-30, for [#366](https://github.com/simple-rtmp-server/srs/issues/366), config hls to disable cleanup of ts. 2.0.154. +* v2.0, 2015-03-31, support server cycle handler. 2.0.153. +* v2.0, 2015-03-31, support on_hls for http hooks. 2.0.152. +* v2.0, 2015-03-31, enhanced hls, support deviation for duration. 2.0.151. +* v2.0, 2015-03-30, for [#351](https://github.com/simple-rtmp-server/srs/issues/351), support config the m3u8/ts path for hls. 2.0.149. +* v2.0, 2015-03-17, for [#155](https://github.com/simple-rtmp-server/srs/issues/155), osx(darwin) support demo with nginx and ffmpeg. 2.0.143. +* v2.0, 2015-03-15, start [2.0release branch](https://github.com/simple-rtmp-server/srs/tree/2.0release), 80773 lines. +* v2.0, 2015-03-14, fix [#324](https://github.com/simple-rtmp-server/srs/issues/324), support hstrs(http stream trigger rtmp source) edge mode. 2.0.140. +* v2.0, 2015-03-14, for [#324](https://github.com/simple-rtmp-server/srs/issues/324), support hstrs(http stream trigger rtmp source) origin mode. 2.0.139. +* v2.0, 2015-03-12, fix [#328](https://github.com/simple-rtmp-server/srs/issues/328), support adobe hds. 2.0.138. +* v2.0, 2015-03-10, fix [#155](https://github.com/simple-rtmp-server/srs/issues/155), support osx(darwin) for mac pro. 2.0.137. +* v2.0, 2015-03-08, fix [#316](https://github.com/simple-rtmp-server/srs/issues/316), http api provides stream/vhost/srs/server bytes, codec and count. 2.0.136. +* v2.0, 2015-03-08, fix [#310](https://github.com/simple-rtmp-server/srs/issues/310), refine aac LC, support aac HE/HEv2. 2.0.134. +* v2.0, 2015-03-06, for [#322](https://github.com/simple-rtmp-server/srs/issues/322), fix http-flv stream bug, support multiple streams. 2.0.133. +* v2.0, 2015-03-06, refine http request parse. 2.0.132. +* v2.0, 2015-03-01, for [#179](https://github.com/simple-rtmp-server/srs/issues/179), revert dvr http api. 2.0.128. +* v2.0, 2015-02-24, for [#304](https://github.com/simple-rtmp-server/srs/issues/304), fix hls bug, write pts/dts error. 2.0.124 +* v2.0, 2015-02-19, refine dvr, append file when dvr file exists. 2.0.122. +* v2.0, 2015-02-19, refine pithy print to more easyer to use. 2.0.121. +* v2.0, 2015-02-18, fix [#133](https://github.com/simple-rtmp-server/srs/issues/133), support push rtsp to srs. 2.0.120. +* v2.0, 2015-02-17, the join maybe failed, should use a variable to ensure thread terminated. 2.0.119. +* v2.0, 2015-02-15, for [#304](https://github.com/simple-rtmp-server/srs/issues/304), support config default acodec/vcodec. 2.0.118. +* v2.0, 2015-02-15, for [#304](https://github.com/simple-rtmp-server/srs/issues/304), rewrite hls/ts code, support h.264+mp3 for hls. 2.0.117. +* v2.0, 2015-02-12, for [#304](https://github.com/simple-rtmp-server/srs/issues/304), use stringstream to generate m3u8, add hls_td_ratio. 2.0.116. +* v2.0, 2015-02-11, dev code ZhouGuowen for 2.0.115. +* v2.0, 2015-02-10, for [#311](https://github.com/simple-rtmp-server/srs/issues/311), set pcr_base to dts. 2.0.114. +* v2.0, 2015-02-10, fix [the bug](https://github.com/simple-rtmp-server/srs/commit/87519aaae835199e5adb60c0ae2c1cd24939448c) of ibmf format which decoded in annexb. +* v2.0, 2015-02-10, for [#310](https://github.com/simple-rtmp-server/srs/issues/310), downcast aac SSR to LC. 2.0.113 +* v2.0, 2015-02-03, fix [#136](https://github.com/simple-rtmp-server/srs/issues/136), support hls without io(in ram). 2.0.112 +* v2.0, 2015-01-31, for [#250](https://github.com/simple-rtmp-server/srs/issues/250), support push MPEGTS over UDP to SRS. 2.0.111 +* v2.0, 2015-01-29, build libfdk-aac in ffmpeg. 2.0.108 +* v2.0, 2015-01-25, for [#301](https://github.com/simple-rtmp-server/srs/issues/301), hls support h.264+mp3, ok for vlc. 2.0.107 +* v2.0, 2015-01-25, for [#301](https://github.com/simple-rtmp-server/srs/issues/301), http ts stream support h.264+mp3. 2.0.106 +* v2.0, 2015-01-25, hotfix [#268](https://github.com/simple-rtmp-server/srs/issues/268), refine the pcr start at 0, dts/pts plus delay. 2.0.105 +* v2.0, 2015-01-25, hotfix [#151](https://github.com/simple-rtmp-server/srs/issues/151), refine pcr=dts-800ms and use dts/pts directly. 2.0.104 +* v2.0, 2015-01-23, hotfix [#151](https://github.com/simple-rtmp-server/srs/issues/151), use absolutely overflow to make jwplayer happy. 2.0.103 +* v2.0, 2015-01-22, for [#293](https://github.com/simple-rtmp-server/srs/issues/293), support http live ts stream. 2.0.101. +* v2.0, 2015-01-19, for [#293](https://github.com/simple-rtmp-server/srs/issues/293), support http live flv/aac/mp3 stream with fast cache. 2.0.100. +* v2.0, 2015-01-18, for [#293](https://github.com/simple-rtmp-server/srs/issues/293), support rtmp remux to http flv live stream. 2.0.99. +* v2.0, 2015-01-17, fix [#277](https://github.com/simple-rtmp-server/srs/issues/277), refine http server refer to go http-framework. 2.0.98 +* v2.0, 2015-01-17, for [#277](https://github.com/simple-rtmp-server/srs/issues/277), refine http api refer to go http-framework. 2.0.97 +* v2.0, 2015-01-17, hotfix [#290](https://github.com/simple-rtmp-server/srs/issues/290), use iformat only for rtmp input. 2.0.95 +* v2.0, 2015-01-08, hotfix [#281](https://github.com/simple-rtmp-server/srs/issues/281), fix hls bug ignore type-9 send aud. 2.0.93 +* v2.0, 2015-01-03, fix [#274](https://github.com/simple-rtmp-server/srs/issues/274), http-callback support on_dvr when reap a dvr file. 2.0.89 +* v2.0, 2015-01-03, hotfix to remove the pageUrl for http callback. 2.0.88 +* v2.0, 2015-01-03, fix [#179](https://github.com/simple-rtmp-server/srs/issues/179), dvr support custom filepath by variables. 2.0.87 +* v2.0, 2015-01-02, fix [#211](https://github.com/simple-rtmp-server/srs/issues/211), support security allow/deny publish/play all/ip. 2.0.86 +* v2.0, 2015-01-02, hotfix [#207](https://github.com/simple-rtmp-server/srs/issues/207), trim the last 0 of log. 2.0.85 +* v2.0, 2014-01-02, fix [#158](https://github.com/simple-rtmp-server/srs/issues/158), http-callback check http status code ok(200). 2.0.84 +* v2.0, 2015-01-02, hotfix [#216](https://github.com/simple-rtmp-server/srs/issues/216), http-callback post in application/json content-type. 2.0.83 +* v2.0, 2014-01-02, fix [#263](https://github.com/simple-rtmp-server/srs/issues/263), srs-librtmp flv read tag should init size. 2.0.82 +* v2.0, 2015-01-01, hotfix [#270](https://github.com/simple-rtmp-server/srs/issues/270), memory leak for http client post. 2.0.81 +* v2.0, 2014-12-12, fix [#266](https://github.com/simple-rtmp-server/srs/issues/266), aac profile is object id plus one. 2.0.80 +* v2.0, 2014-12-29, hotfix [#267](https://github.com/simple-rtmp-server/srs/issues/267), the forward dest ep should use server. 2.0.79 +* v2.0, 2014-12-29, hotfix [#268](https://github.com/simple-rtmp-server/srs/issues/268), the hls pcr is negative when startup. 2.0.78 +* v2.0, 2014-12-22, hotfix [#264](https://github.com/simple-rtmp-server/srs/issues/264), ignore NALU when sequence header to make HLS happy. 2.0.76 +* v2.0, 2014-12-20, hotfix [#264](https://github.com/simple-rtmp-server/srs/issues/264), support disconnect publish connect when hls error. 2.0.75 +* v2.0, 2014-12-12, fix [#257](https://github.com/simple-rtmp-server/srs/issues/257), support 0.1s+ latency. 2.0.70 +* v2.0, 2014-12-08, update wiki for mr([EN](https://github.com/simple-rtmp-server/srs/wiki/v2_EN_LowLatency#merged-read), [CN](https://github.com/simple-rtmp-server/srs/wiki/v2_CN_LowLatency#merged-read)) and mw([EN](https://github.com/simple-rtmp-server/srs/wiki/v2_EN_LowLatency#merged-write), [CN](https://github.com/simple-rtmp-server/srs/wiki/v2_CN_LowLatency#merged-write)). +* v2.0, 2014-12-07, fix [#251](https://github.com/simple-rtmp-server/srs/issues/251), 10k+ clients, use queue cond wait and fast vector. 2.0.67 +* v2.0, 2014-12-05, fix [#251](https://github.com/simple-rtmp-server/srs/issues/251), 9k+ clients, use fast cache for msgs queue. 2.0.57 +* v2.0, 2014-12-04, fix [#241](https://github.com/simple-rtmp-server/srs/issues/241), add mw(merged-write) config. 2.0.53 +* v2.0, 2014-12-04, for [#241](https://github.com/simple-rtmp-server/srs/issues/241), support mr(merged-read) config and reload. 2.0.52. +* v2.0, 2014-12-04, enable [#241](https://github.com/simple-rtmp-server/srs/issues/241) and [#248](https://github.com/simple-rtmp-server/srs/issues/248), +25% performance, 2.5k publisher. 2.0.50 +* v2.0, 2014-12-04, fix [#248](https://github.com/simple-rtmp-server/srs/issues/248), improve about 15% performance for fast buffer. 2.0.49 +* v2.0, 2014-12-03, fix [#244](https://github.com/simple-rtmp-server/srs/issues/244), conn thread use cond to wait for recv thread error. 2.0.47. +* v2.0, 2014-12-02, merge [#239](https://github.com/simple-rtmp-server/srs/pull/239), traverse the token before response connect. 2.0.45. +* v2.0, 2014-12-02, srs-librtmp support hijack io apis for st-load. 2.0.42. +* v2.0, 2014-12-01, for [#237](https://github.com/simple-rtmp-server/srs/issues/237), refine syscall for recv, supports 1.5k clients. 2.0.41. +* v2.0, 2014-11-30, add qtcreate project file trunk/src/qt/srs/srs-qt.pro. 2.0.39. +* v2.0, 2014-11-29, fix [#235](https://github.com/simple-rtmp-server/srs/issues/235), refine handshake, replace union with template method. 2.0.38. +* v2.0, 2014-11-28, fix [#215](https://github.com/simple-rtmp-server/srs/issues/215), add srs_rtmp_dump tool. 2.0.37. +* v2.0, 2014-11-25, update PRIMARY, AUTHORS, CONTRIBUTORS rule. 2.0.32. +* v2.0, 2014-11-24, fix [#212](https://github.com/simple-rtmp-server/srs/issues/212), support publish aac adts raw stream. 2.0.31. +* v2.0, 2014-11-22, fix [#217](https://github.com/simple-rtmp-server/srs/issues/217), remove timeout recv, support 7.5k+ 250kbps clients. 2.0.30. +* v2.0, 2014-11-21, srs-librtmp add rtmp prefix for rtmp/utils/human apis. 2.0.29. +* v2.0, 2014-11-21, refine examples of srs-librtmp, add srs_print_rtmp_packet. 2.0.28. +* v2.0, 2014-11-20, fix [#212](https://github.com/simple-rtmp-server/srs/issues/212), support publish audio raw frames. 2.0.27 +* v2.0, 2014-11-19, fix [#213](https://github.com/simple-rtmp-server/srs/issues/213), support compile [srs-librtmp on windows](https://github.com/winlinvip/srs.librtmp), [bug #213](https://github.com/simple-rtmp-server/srs/issues/213). 2.0.26 +* v2.0, 2014-11-18, all wiki translated to English. 2.0.23. +* v2.0, 2014-11-15, fix [#204](https://github.com/simple-rtmp-server/srs/issues/204), srs-librtmp drop duplicated sps/pps(sequence header). 2.0.22. +* v2.0, 2014-11-15, fix [#203](https://github.com/simple-rtmp-server/srs/issues/203), srs-librtmp drop any video before sps/pps(sequence header). 2.0.21. +* v2.0, 2014-11-15, fix [#202](https://github.com/simple-rtmp-server/srs/issues/202), fix memory leak of h.264 raw packet send in srs-librtmp. 2.0.20. +* v2.0, 2014-11-13, fix [#200](https://github.com/simple-rtmp-server/srs/issues/200), deadloop when read/write 0 and ETIME. 2.0.16. +* v2.0, 2014-11-13, fix [#194](https://github.com/simple-rtmp-server/srs/issues/194), writev multiple msgs, support 6k+ 250kbps clients. 2.0.15. +* v2.0, 2014-11-12, fix [#194](https://github.com/simple-rtmp-server/srs/issues/194), optmized st for timeout recv. pulse to 500ms. 2.0.14. +* v2.0, 2014-11-11, fix [#195](https://github.com/simple-rtmp-server/srs/issues/195), remove the confuse code st_usleep(0). 2.0.13. +* v2.0, 2014-11-08, fix [#191](https://github.com/simple-rtmp-server/srs/issues/191), configure --export-librtmp-project and --export-librtmp-single. 2.0.11. +* v2.0, 2014-11-08, fix [#66](https://github.com/simple-rtmp-server/srs/issues/66), srs-librtmp support write h264 raw packet. 2.0.9. +* v2.0, 2014-10-25, fix [#185](https://github.com/simple-rtmp-server/srs/issues/185), AMF0 support 0x0B the date type codec. 2.0.7. +* v2.0, 2014-10-24, fix [#186](https://github.com/simple-rtmp-server/srs/issues/186), hotfix for bug #186, drop connect args when not object. 2.0.6. +* v2.0, 2014-10-24, rename wiki/xxx to wiki/v1_CN_xxx. 2.0.3. +* v2.0, 2014-10-19, fix [#184](https://github.com/simple-rtmp-server/srs/issues/184), support AnnexB in RTMP body for HLS. 2.0.2 +* v2.0, 2014-10-18, remove supports for OSX(darwin). 2.0.1. +* v2.0, 2014-10-16, revert github srs README to English. 2.0.0. + +### SRS 1.0 history + * v1.0, 2015-02-17, the join maybe failed, should use a variable to ensure thread terminated. 1.0.28. -* v1.0, 2015-02-12, [1.0r2 release(1.0.27)](https://github.com/winlinvip/simple-rtmp-server/releases/tag/1.0r2) released. 59507 lines. +* v1.0, 2015-02-12, [1.0r2 release(1.0.27)](https://github.com/simple-rtmp-server/srs/releases/tag/1.0r2) released. 59507 lines. * v1.0, 2015-02-11, dev code HuKaiqun for 1.0.27. -* v1.0, 2015-02-10, for [#310](https://github.com/winlinvip/simple-rtmp-server/issues/310), the aac profile must be object plus one. 1.0.26 -* v1.0, 2015-01-25, hotfix [#268](https://github.com/winlinvip/simple-rtmp-server/issues/268), refine the pcr start at 0, dts/pts plus delay. 1.0.25 -* v1.0, 2015-01-25, hotfix [#151](https://github.com/winlinvip/simple-rtmp-server/issues/151), refine pcr=dts-800ms and use dts/pts directly. 1.0.24 -* v1.0, 2015-01-23, hotfix [#151](https://github.com/winlinvip/simple-rtmp-server/issues/151), use absolutely overflow to make jwplayer happy. 1.0.23 -* v1.0, 2015-01-17, hotfix [#290](https://github.com/winlinvip/simple-rtmp-server/issues/290), use iformat only for rtmp input. 1.0.22 -* v1.0, 2015-01-15, [1.0r1 release(1.0.21)](https://github.com/winlinvip/simple-rtmp-server/releases/tag/1.0r1) released. 59472 lines. -* v1.0, 2015-01-08, hotfix [#281](https://github.com/winlinvip/simple-rtmp-server/issues/281), fix hls bug ignore type-9 send aud. 1.0.20 +* v1.0, 2015-02-10, for [#310](https://github.com/simple-rtmp-server/srs/issues/310), the aac profile must be object plus one. 1.0.26 +* v1.0, 2015-01-25, hotfix [#268](https://github.com/simple-rtmp-server/srs/issues/268), refine the pcr start at 0, dts/pts plus delay. 1.0.25 +* v1.0, 2015-01-25, hotfix [#151](https://github.com/simple-rtmp-server/srs/issues/151), refine pcr=dts-800ms and use dts/pts directly. 1.0.24 +* v1.0, 2015-01-23, hotfix [#151](https://github.com/simple-rtmp-server/srs/issues/151), use absolutely overflow to make jwplayer happy. 1.0.23 +* v1.0, 2015-01-17, hotfix [#290](https://github.com/simple-rtmp-server/srs/issues/290), use iformat only for rtmp input. 1.0.22 +* v1.0, 2015-01-15, [1.0r1 release(1.0.21)](https://github.com/simple-rtmp-server/srs/releases/tag/1.0r1) released. 59472 lines. +* v1.0, 2015-01-08, hotfix [#281](https://github.com/simple-rtmp-server/srs/issues/281), fix hls bug ignore type-9 send aud. 1.0.20 * v1.0, 2015-01-03, hotfix to remove the pageUrl for http callback. 1.0.19 -* v1.0, 2015-01-02, hotfix [#207](https://github.com/winlinvip/simple-rtmp-server/issues/207), trim the last 0 of log. 1.0.18 -* v1.0, 2015-01-02, hotfix [#216](https://github.com/winlinvip/simple-rtmp-server/issues/216), http-callback post in application/json content-type. 1.0.17 -* v1.0, 2015-01-01, hotfix [#270](https://github.com/winlinvip/simple-rtmp-server/issues/270), memory leak for http client post. 1.0.16 -* v1.0, 2014-12-29, hotfix [#267](https://github.com/winlinvip/simple-rtmp-server/issues/267), the forward dest ep should use server. 1.0.15 -* v1.0, 2014-12-29, hotfix [#268](https://github.com/winlinvip/simple-rtmp-server/issues/268), the hls pcr is negative when startup. 1.0.14 -* v1.0, 2014-12-22, hotfix [#264](https://github.com/winlinvip/simple-rtmp-server/issues/264), ignore NALU when sequence header to make HLS happy. 1.0.12 -* v1.0, 2014-12-20, hotfix [#264](https://github.com/winlinvip/simple-rtmp-server/issues/264), support disconnect publish connect when hls error. 1.0.11 -* v1.0, 2014-12-05, [1.0 release(1.0.10)](https://github.com/winlinvip/simple-rtmp-server/releases/tag/1.0) released. 59391 lines. -* v1.0, 2014-12-02, hotfix [#239](https://github.com/winlinvip/simple-rtmp-server/pull/239), traverse the token before response connect. 1.0.10. -* v1.0, 2014-11-25, update PRIMARY, AUTHORS, CONTRIBUTORS of SRS. 1.0.8. -* v1.0, 2014-11-18, all wiki translated to English. 1.0.7. -* v1.0, 2014-11-13, hotfix [#200](https://github.com/winlinvip/simple-rtmp-server/issues/200), deadloop when read/write 0 and ETIME. 1.0.6. -* v1.0, 2014-11-06, use number for macro VERSION_MAJOR, VERSION_MINOR and VERSION_REVISION. 1.0.5. -* v1.0, 2014-10-24, hotfix [#186](https://github.com/winlinvip/simple-rtmp-server/issues/186), drop connect args when not object. 1.0.3. -* v1.0, 2014-10-24, rename wiki/xxx to wiki/v1_CN_xxx. 1.0.2. -* v1.0, 2014-10-19, hotfix [#183](https://github.com/winlinvip/simple-rtmp-server/issues/183), donot support AnnexB when decoding RTMP body for HLS. 1.0.1. -* v1.0, 2014-10-09, [1.0 beta(1.0.0)](https://github.com/winlinvip/simple-rtmp-server/releases/tag/1.0.beta) released. 59316 lines. -* v1.0, 2014-10-08, fix [#151](https://github.com/winlinvip/simple-rtmp-server/issues/151), always reap ts whatever audio or video packet. 0.9.223. -* v1.0, 2014-10-08, fix [#162](https://github.com/winlinvip/simple-rtmp-server/issues/162), failed if no epoll. 0.9.222. -* v1.0, 2014-09-30, fix [#180](https://github.com/winlinvip/simple-rtmp-server/issues/180), crash for multiple edge publishing the same stream. 0.9.220. +* v1.0, 2015-01-02, hotfix [#207](https://github.com/simple-rtmp-server/srs/issues/207), trim the last 0 of log. 1.0.18 +* v1.0, 2015-01-02, hotfix [#216](https://github.com/simple-rtmp-server/srs/issues/216), http-callback post in application/json content-type. 1.0.17 +* v1.0, 2015-01-01, hotfix [#270](https://github.com/simple-rtmp-server/srs/issues/270), memory leak for http client post. 1.0.16 +* v1.0, 2014-12-29, hotfix [#267](https://github.com/simple-rtmp-server/srs/issues/267), the forward dest ep should use server. 1.0.15 +* v1.0, 2014-12-29, hotfix [#268](https://github.com/simple-rtmp-server/srs/issues/268), the hls pcr is negative when startup. 1.0.14 +* v1.0, 2014-12-22, hotfix [#264](https://github.com/simple-rtmp-server/srs/issues/264), ignore NALU when sequence header to make HLS happy. 1.0.12 +* v1.0, 2014-12-20, hotfix [#264](https://github.com/simple-rtmp-server/srs/issues/264), support disconnect publish connect when hls error. 1.0.11 +* v1.0, 2014-12-05, [1.0 release(1.0.10)](https://github.com/simple-rtmp-server/srs/releases/tag/1.0) released. 59391 lines. +* v1.0, 2014-10-09, [1.0 beta(1.0.0)](https://github.com/simple-rtmp-server/srs/releases/tag/1.0.beta) released. 59316 lines. +* v1.0, 2014-10-08, fix [#151](https://github.com/simple-rtmp-server/srs/issues/151), always reap ts whatever audio or video packet. 0.9.223. +* v1.0, 2014-10-08, fix [#162](https://github.com/simple-rtmp-server/srs/issues/162), failed if no epoll. 0.9.222. +* v1.0, 2014-09-30, fix [#180](https://github.com/simple-rtmp-server/srs/issues/180), crash for multiple edge publishing the same stream. 0.9.220. * v1.0, 2014-09-26, fix hls bug, refine config and log, according to clion of jetbrains. 0.9.216. -* v1.0, 2014-09-25, fix [#177](https://github.com/winlinvip/simple-rtmp-server/issues/177), dvr segment add config dvr_wait_keyframe. 0.9.213. -* v1.0, 2014-08-28, fix [#167](https://github.com/winlinvip/simple-rtmp-server/issues/167), add openssl includes to utest. 0.9.209. +* v1.0, 2014-09-25, fix [#177](https://github.com/simple-rtmp-server/srs/issues/177), dvr segment add config dvr_wait_keyframe. 0.9.213. +* v1.0, 2014-08-28, fix [#167](https://github.com/simple-rtmp-server/srs/issues/167), add openssl includes to utest. 0.9.209. * v1.0, 2014-08-27, max connections is 32756, for st use mmap default. 0.9.209 -* v1.0, 2014-08-24, fix [#150](https://github.com/winlinvip/simple-rtmp-server/issues/150), forward should forward the sequence header when retry. 0.9.208. -* v1.0, 2014-08-22, for [#165](https://github.com/winlinvip/simple-rtmp-server/issues/165), refine dh wrapper, ensure public key is 128bytes. 0.9.206. -* v1.0, 2014-08-19, for [#160](https://github.com/winlinvip/simple-rtmp-server/issues/160), support forward/edge to flussonic, disable debug_srs_upnode to make flussonic happy. 0.9.201. -* v1.0, 2014-08-17, for [#155](https://github.com/winlinvip/simple-rtmp-server/issues/155), refine for osx, with ssl/http, disable statistics. 0.9.198. -* v1.0, 2014-08-06, fix [#148](https://github.com/winlinvip/simple-rtmp-server/issues/148), simplify the RTMP handshake key generation. 0.9.191. -* v1.0, 2014-08-06, fix [#147](https://github.com/winlinvip/simple-rtmp-server/issues/147), support identify the srs edge. 0.9.190. -* v1.0, 2014-08-03, [1.0 mainline7(0.9.189)](https://github.com/winlinvip/simple-rtmp-server/releases/tag/1.0.mainline7) released. 57432 lines. -* v1.0, 2014-08-03, fix [#79](https://github.com/winlinvip/simple-rtmp-server/issues/79), fix the reload remove edge assert bug. 0.9.189. -* v1.0, 2014-08-03, fix [#57](https://github.com/winlinvip/simple-rtmp-server/issues/57), use lock(acquire/release publish) to avoid duplicated publishing. 0.9.188. -* v1.0, 2014-08-03, fix [#85](https://github.com/winlinvip/simple-rtmp-server/issues/85), fix the segment-dvr sequence header missing. 0.9.187. -* v1.0, 2014-08-03, fix [#145](https://github.com/winlinvip/simple-rtmp-server/issues/145), refine ffmpeg log, check abitrate for libaacplus. 0.9.186. -* v1.0, 2014-08-03, fix [#143](https://github.com/winlinvip/simple-rtmp-server/issues/143), fix retrieve sys stat bug for all linux. 0.9.185. -* v1.0, 2014-08-02, fix [#138](https://github.com/winlinvip/simple-rtmp-server/issues/138), fix http hooks bug, regression bug. 0.9.184. -* v1.0, 2014-08-02, fix [#142](https://github.com/winlinvip/simple-rtmp-server/issues/142), fix tcp stat slow bug, use /proc/net/sockstat instead, refer to 'ss -s'. 0.9.183. -* v1.0, 2014-07-31, fix [#141](https://github.com/winlinvip/simple-rtmp-server/issues/141), support tun0(vpn network device) ip retrieve. 0.9.179. +* v1.0, 2014-08-24, fix [#150](https://github.com/simple-rtmp-server/srs/issues/150), forward should forward the sequence header when retry. 0.9.208. +* v1.0, 2014-08-22, for [#165](https://github.com/simple-rtmp-server/srs/issues/165), refine dh wrapper, ensure public key is 128bytes. 0.9.206. +* v1.0, 2014-08-19, for [#160](https://github.com/simple-rtmp-server/srs/issues/160), support forward/edge to flussonic, disable debug_srs_upnode to make flussonic happy. 0.9.201. +* v1.0, 2014-08-17, for [#155](https://github.com/simple-rtmp-server/srs/issues/155), refine for osx, with ssl/http, disable statistics. 0.9.198. +* v1.0, 2014-08-06, fix [#148](https://github.com/simple-rtmp-server/srs/issues/148), simplify the RTMP handshake key generation. 0.9.191. +* v1.0, 2014-08-06, fix [#147](https://github.com/simple-rtmp-server/srs/issues/147), support identify the srs edge. 0.9.190. +* v1.0, 2014-08-03, [1.0 mainline7(0.9.189)](https://github.com/simple-rtmp-server/srs/releases/tag/1.0.mainline7) released. 57432 lines. +* v1.0, 2014-08-03, fix [#79](https://github.com/simple-rtmp-server/srs/issues/79), fix the reload remove edge assert bug. 0.9.189. +* v1.0, 2014-08-03, fix [#57](https://github.com/simple-rtmp-server/srs/issues/57), use lock(acquire/release publish) to avoid duplicated publishing. 0.9.188. +* v1.0, 2014-08-03, fix [#85](https://github.com/simple-rtmp-server/srs/issues/85), fix the segment-dvr sequence header missing. 0.9.187. +* v1.0, 2014-08-03, fix [#145](https://github.com/simple-rtmp-server/srs/issues/145), refine ffmpeg log, check abitrate for libaacplus. 0.9.186. +* v1.0, 2014-08-03, fix [#143](https://github.com/simple-rtmp-server/srs/issues/143), fix retrieve sys stat bug for all linux. 0.9.185. +* v1.0, 2014-08-02, fix [#138](https://github.com/simple-rtmp-server/srs/issues/138), fix http hooks bug, regression bug. 0.9.184. +* v1.0, 2014-08-02, fix [#142](https://github.com/simple-rtmp-server/srs/issues/142), fix tcp stat slow bug, use /proc/net/sockstat instead, refer to 'ss -s'. 0.9.183. +* v1.0, 2014-07-31, fix [#141](https://github.com/simple-rtmp-server/srs/issues/141), support tun0(vpn network device) ip retrieve. 0.9.179. * v1.0, 2014-07-27, support partially build on OSX(Darwin). 0.9.177 * v1.0, 2014-07-27, api connections add udp, add disk iops. 0.9.176 * v1.0, 2014-07-26, complete config utest. 0.9.173 -* v1.0, 2014-07-26, fix [#124](https://github.com/winlinvip/simple-rtmp-server/issues/124), gop cache support disable video in publishing. 0.9.171. -* v1.0, 2014-07-23, fix [#121](https://github.com/winlinvip/simple-rtmp-server/issues/121), srs_info detail log compile failed. 0.9.168. -* v1.0, 2014-07-19, fix [#119](https://github.com/winlinvip/simple-rtmp-server/issues/119), use iformat and oformat for ffmpeg transcode. 0.9.163. -* v1.0, 2014-07-13, [1.0 mainline6(0.9.160)](https://github.com/winlinvip/simple-rtmp-server/releases/tag/1.0.mainline6) released. 50029 lines. +* v1.0, 2014-07-26, fix [#124](https://github.com/simple-rtmp-server/srs/issues/124), gop cache support disable video in publishing. 0.9.171. +* v1.0, 2014-07-23, fix [#121](https://github.com/simple-rtmp-server/srs/issues/121), srs_info detail log compile failed. 0.9.168. +* v1.0, 2014-07-19, fix [#119](https://github.com/simple-rtmp-server/srs/issues/119), use iformat and oformat for ffmpeg transcode. 0.9.163. +* v1.0, 2014-07-13, [1.0 mainline6(0.9.160)](https://github.com/simple-rtmp-server/srs/releases/tag/1.0.mainline6) released. 50029 lines. * v1.0, 2014-07-13, refine the bandwidth check/test, add as/js library, use srs-librtmp for linux tool. 0.9.159 * v1.0, 2014-07-12, complete rtmp stack utest. 0.9.156 -* v1.0, 2014-07-06, fix [#81](https://github.com/winlinvip/simple-rtmp-server/issues/81), fix HLS codec info, IOS ok. 0.9.153. -* v1.0, 2014-07-06, fix [#103](https://github.com/winlinvip/simple-rtmp-server/issues/103), support all aac sample rate. 0.9.150. +* v1.0, 2014-07-06, fix [#81](https://github.com/simple-rtmp-server/srs/issues/81), fix HLS codec info, IOS ok. 0.9.153. +* v1.0, 2014-07-06, fix [#103](https://github.com/simple-rtmp-server/srs/issues/103), support all aac sample rate. 0.9.150. * v1.0, 2014-07-05, complete kernel utest. 0.9.149 -* v1.0, 2014-06-30, fix [#111](https://github.com/winlinvip/simple-rtmp-server/issues/111), always use 31bits timestamp. 0.9.143. +* v1.0, 2014-06-30, fix [#111](https://github.com/simple-rtmp-server/srs/issues/111), always use 31bits timestamp. 0.9.143. * v1.0, 2014-06-28, response the call message with null. 0.9.137 -* v1.0, 2014-06-28, fix [#110](https://github.com/winlinvip/simple-rtmp-server/issues/110), thread start segment fault, thread cycle stop destroy thread. 0.9.136 -* v1.0, 2014-06-27, fix [#109](https://github.com/winlinvip/simple-rtmp-server/issues/109), fix the system jump time, adjust system startup time. 0.9.135 -* v1.0, 2014-06-27, [1.0 mainline5(0.9.134)](https://github.com/winlinvip/simple-rtmp-server/releases/tag/1.0.mainline5) released. 41573 lines. +* v1.0, 2014-06-28, fix [#110](https://github.com/simple-rtmp-server/srs/issues/110), thread start segment fault, thread cycle stop destroy thread. 0.9.136 +* v1.0, 2014-06-27, fix [#109](https://github.com/simple-rtmp-server/srs/issues/109), fix the system jump time, adjust system startup time. 0.9.135 +* v1.0, 2014-06-27, [1.0 mainline5(0.9.134)](https://github.com/simple-rtmp-server/srs/releases/tag/1.0.mainline5) released. 41573 lines. * v1.0, 2014-06-27, SRS online 30days with RTMP/HLS. -* v1.0, 2014-06-25, fix [#108](https://github.com/winlinvip/simple-rtmp-server/issues/108), support config time jitter for encoder non-monotonical stream. 0.9.133 +* v1.0, 2014-06-25, fix [#108](https://github.com/simple-rtmp-server/srs/issues/108), support config time jitter for encoder non-monotonical stream. 0.9.133 * v1.0, 2014-06-23, support report summaries in heartbeat. 0.9.132 -* v1.0, 2014-06-22, performance refine, support [3k+](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Performance#%E6%80%A7%E8%83%BD%E4%BE%8B%E8%A1%8C%E6%8A%A5%E5%91%8A4k) connections(270kbps). 0.9.130 -* v1.0, 2014-06-21, support edge [token traverse](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_DRM#tokentraverse), fix [#104](https://github.com/winlinvip/simple-rtmp-server/issues/104). 0.9.129 +* v1.0, 2014-06-22, performance refine, support [3k+](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Performance#%E6%80%A7%E8%83%BD%E4%BE%8B%E8%A1%8C%E6%8A%A5%E5%91%8A4k) connections(270kbps). 0.9.130 +* v1.0, 2014-06-21, support edge [token traverse](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_DRM#tokentraverse), fix [#104](https://github.com/simple-rtmp-server/srs/issues/104). 0.9.129 * v1.0, 2014-06-19, add connections count to api summaries. 0.9.127 * v1.0, 2014-06-19, add srs bytes and kbps to api summaries. 0.9.126 * v1.0, 2014-06-18, add network bytes to api summaries. 0.9.125 -* v1.0, 2014-06-14, fix [#98](https://github.com/winlinvip/simple-rtmp-server/issues/98), workaround for librtmp ping(fmt=1,cid=2 fresh stream). 0.9.124 +* v1.0, 2014-06-14, fix [#98](https://github.com/simple-rtmp-server/srs/issues/98), workaround for librtmp ping(fmt=1,cid=2 fresh stream). 0.9.124 * v1.0, 2014-05-29, support flv inject and flv http streaming with start=bytes. 0.9.122 -* v1.0, 2014-05-28, [1.0 mainline4(0.9.120)](https://github.com/winlinvip/simple-rtmp-server/releases/tag/1.0.mainline4) released. 39200 lines. -* v1.0, 2014-05-27, fix [#87](https://github.com/winlinvip/simple-rtmp-server/issues/87), add source id for full trackable log. 0.9.120 -* v1.0, 2014-05-27, fix [#84](https://github.com/winlinvip/simple-rtmp-server/issues/84), unpublish when edge disconnect. 0.9.119 -* v1.0, 2014-05-27, fix [#89](https://github.com/winlinvip/simple-rtmp-server/issues/89), config to /dev/null to disable ffmpeg log. 0.9.117 -* v1.0, 2014-05-25, fix [#76](https://github.com/winlinvip/simple-rtmp-server/issues/76), allow edge vhost to add or remove. 0.9.114 +* v1.0, 2014-05-28, [1.0 mainline4(0.9.120)](https://github.com/simple-rtmp-server/srs/releases/tag/1.0.mainline4) released. 39200 lines. +* v1.0, 2014-05-27, fix [#87](https://github.com/simple-rtmp-server/srs/issues/87), add source id for full trackable log. 0.9.120 +* v1.0, 2014-05-27, fix [#84](https://github.com/simple-rtmp-server/srs/issues/84), unpublish when edge disconnect. 0.9.119 +* v1.0, 2014-05-27, fix [#89](https://github.com/simple-rtmp-server/srs/issues/89), config to /dev/null to disable ffmpeg log. 0.9.117 +* v1.0, 2014-05-25, fix [#76](https://github.com/simple-rtmp-server/srs/issues/76), allow edge vhost to add or remove. 0.9.114 * v1.0, 2014-05-24, Johnny contribute [ossrs.net](http://ossrs.net). karthikeyan start to translate wiki to English. -* v1.0, 2014-05-22, fix [#78](https://github.com/winlinvip/simple-rtmp-server/issues/78), st joinable thread must be stop by other threads, 0.9.113 +* v1.0, 2014-05-22, fix [#78](https://github.com/simple-rtmp-server/srs/issues/78), st joinable thread must be stop by other threads, 0.9.113 * v1.0, 2014-05-22, support amf0 StrictArray(0x0a). 0.9.111. * v1.0, 2014-05-22, support flv parser, add amf0 to librtmp. 0.9.110 -* v1.0, 2014-05-22, fix [#74](https://github.com/winlinvip/simple-rtmp-server/issues/74), add tcUrl for http callback on_connect, 0.9.109 +* v1.0, 2014-05-22, fix [#74](https://github.com/simple-rtmp-server/srs/issues/74), add tcUrl for http callback on_connect, 0.9.109 * v1.0, 2014-05-19, support http heartbeat, 0.9.107 -* v1.0, 2014-05-18, [1.0 mainline3(0.9.105)](https://github.com/winlinvip/simple-rtmp-server/releases/tag/1.0.mainline3) released. 37594 lines. +* v1.0, 2014-05-18, [1.0 mainline3(0.9.105)](https://github.com/simple-rtmp-server/srs/releases/tag/1.0.mainline3) released. 37594 lines. * v1.0, 2014-05-18, support http api json, to PUT/POST. 0.9.105 -* v1.0, 2014-05-17, fix [#72](https://github.com/winlinvip/simple-rtmp-server/issues/72), also need stream_id for send_and_free_message. 0.9.101 +* v1.0, 2014-05-17, fix [#72](https://github.com/simple-rtmp-server/srs/issues/72), also need stream_id for send_and_free_message. 0.9.101 * v1.0, 2014-05-17, rename struct to class. 0.9.100 -* v1.0, 2014-05-14, fix [#67](https://github.com/winlinvip/simple-rtmp-server/issues/67) pithy print, stage must has a age. 0.9.98 +* v1.0, 2014-05-14, fix [#67](https://github.com/simple-rtmp-server/srs/issues/67) pithy print, stage must has a age. 0.9.98 * v1.0, 2014-05-13, fix mem leak for delete[] SharedPtrMessage array. 0.9.95 * v1.0, 2014-05-12, refine the kbps calc module. 0.9.93 -* v1.0, 2014-05-12, fix bug [#64](https://github.com/winlinvip/simple-rtmp-server/issues/64): install_dir=DESTDIR+PREFIX -* v1.0, 2014-05-08, fix [#36](https://github.com/winlinvip/simple-rtmp-server/issues/36): never directly use \*(int32_t\*) for arm. -* v1.0, 2014-05-08, fix [#60](https://github.com/winlinvip/simple-rtmp-server/issues/60): support aggregate message -* v1.0, 2014-05-08, fix [#59](https://github.com/winlinvip/simple-rtmp-server/issues/59), edge support FMS origin server. 0.9.92 -* v1.0, 2014-05-06, fix [#50](https://github.com/winlinvip/simple-rtmp-server/issues/50), ubuntu14 build error. +* v1.0, 2014-05-12, fix bug [#64](https://github.com/simple-rtmp-server/srs/issues/64): install_dir=DESTDIR+PREFIX +* v1.0, 2014-05-08, fix [#36](https://github.com/simple-rtmp-server/srs/issues/36): never directly use \*(int32_t\*) for arm. +* v1.0, 2014-05-08, fix [#60](https://github.com/simple-rtmp-server/srs/issues/60): support aggregate message +* v1.0, 2014-05-08, fix [#59](https://github.com/simple-rtmp-server/srs/issues/59), edge support FMS origin server. 0.9.92 +* v1.0, 2014-05-06, fix [#50](https://github.com/simple-rtmp-server/srs/issues/50), ubuntu14 build error. * v1.0, 2014-05-04, support mips linux. -* v1.0, 2014-04-30, fix bug [#34](https://github.com/winlinvip/simple-rtmp-server/issues/34): convert signal to io thread. 0.9.85 +* v1.0, 2014-04-30, fix bug [#34](https://github.com/simple-rtmp-server/srs/issues/34): convert signal to io thread. 0.9.85 * v1.0, 2014-04-29, refine RTMP protocol completed, to 0.9.81 -* v1.0, 2014-04-28, [1.0 mainline2(0.9.79)](https://github.com/winlinvip/simple-rtmp-server/releases/tag/1.0.mainline2) released. 35255 lines. +* v1.0, 2014-04-28, [1.0 mainline2(0.9.79)](https://github.com/simple-rtmp-server/srs/releases/tag/1.0.mainline2) released. 35255 lines. * v1.0, 2014-04-28, support full edge RTMP server. 0.9.79 * v1.0, 2014-04-27, support basic edge(play/publish) RTMP server. 0.9.78 * v1.0, 2014-04-25, add donation page. 0.9.76 @@ -500,12 +771,12 @@ Supported operating systems and hardware: * v1.0, 2014-04-17, support dvr(record live to flv file for vod). 0.9.69 * v1.0, 2014-04-11, add speex1.2 to transcode flash encoder stream. 0.9.58 * v1.0, 2014-04-10, support reload ingesters(add/remov/update). 0.9.57 -* v1.0, 2014-04-07, [1.0 mainline(0.9.55)](https://github.com/winlinvip/simple-rtmp-server/releases/tag/1.0.mainline) released. 30000 lines. -* v1.0, 2014-04-07, support [ingest](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleIngest) file/stream/device. -* v1.0, 2014-04-05, support [http api](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_HTTPApi) and [http server](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_HTTPServer). +* v1.0, 2014-04-07, [1.0 mainline(0.9.55)](https://github.com/simple-rtmp-server/srs/releases/tag/1.0.mainline) released. 30000 lines. +* v1.0, 2014-04-07, support [ingest](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleIngest) file/stream/device. +* v1.0, 2014-04-05, support [http api](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_HTTPApi) and [http server](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_HTTPServer). * v1.0, 2014-04-03, implements http framework and api/v1/version. * v1.0, 2014-03-30, fix bug for st detecting epoll failed, force st to use epoll. -* v1.0, 2014-03-29, add wiki [Performance for RaspberryPi](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_RaspberryPi). +* v1.0, 2014-03-29, add wiki [Performance for RaspberryPi](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_RaspberryPi). * v1.0, 2014-03-29, add release binary package for raspberry-pi. * v1.0, 2014-03-26, support RTMP ATC for HLS/HDS to support backup(failover). * v1.0, 2014-03-23, support daemon, default start in daemon. @@ -515,24 +786,24 @@ Supported operating systems and hardware: * v1.0, 2014-03-20, refine hls code, support pure audio HLS. * v1.0, 2014-03-19, add vn/an for FFMPEG to drop video/audio for radio stream. * v1.0, 2014-03-19, refine handshake, client support complex handshake, add utest. -* v1.0, 2014-03-16, fix bug on arm of st, the sp change from 20 to 8, for respberry-pi, @see [commit](https://github.com/winlinvip/simple-rtmp-server/commit/5a4373d4835758188b9a1f03005cea0b6ddc62aa) -* v1.0, 2014-03-16, support ARM([debian armhf, v7cpu](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SrsLinuxArm)) with rtmp/ssl/hls/librtmp. +* v1.0, 2014-03-16, fix bug on arm of st, the sp change from 20 to 8, for respberry-pi, @see [commit](https://github.com/simple-rtmp-server/srs/commit/5a4373d4835758188b9a1f03005cea0b6ddc62aa) +* v1.0, 2014-03-16, support ARM([debian armhf, v7cpu](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SrsLinuxArm)) with rtmp/ssl/hls/librtmp. * v1.0, 2014-03-12, finish utest for amf0 codec. * v1.0, 2014-03-06, add gperftools for mem leak detect, mem/cpu profile. * v1.0, 2014-03-04, add gest framework for utest, build success. -* v1.0, 2014-03-02, add wiki [srs-librtmp](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SrsLibrtmp), [SRS for arm](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SrsLinuxArm), [product](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Product) +* v1.0, 2014-03-02, add wiki [srs-librtmp](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SrsLibrtmp), [SRS for arm](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SrsLinuxArm), [product](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Product) * v1.0, 2014-03-02, srs-librtmp, client publish/play library like librtmp. * v1.0, 2014-03-01, modularity, extract core/kernel/rtmp/app/main module. * v1.0, 2014-02-28, support arm build(SRS/ST), add ssl to 3rdparty package. -* v1.0, 2014-02-28, add wiki [BuildArm](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Build), [FFMPEG](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_FFMPEG), [Reload](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Reload) -* v1.0, 2014-02-27, add wiki [LowLatency](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_LowLatency), [HTTPCallback](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_HTTPCallback), [ServerSideScript](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_ServerSideScript), [IDE](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_IDE) -* v1.0, 2014-01-19, add wiki [DeliveryHLS](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_DeliveryHLS) -* v1.0, 2014-01-12, add wiki [HowToAskQuestion](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_HowToAskQuestion), [RtmpUrlVhost](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_RtmpUrlVhost) +* v1.0, 2014-02-28, add wiki [BuildArm](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Build), [FFMPEG](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_FFMPEG), [Reload](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Reload) +* v1.0, 2014-02-27, add wiki [LowLatency](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_LowLatency), [HTTPCallback](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_HTTPCallback), [ServerSideScript](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_ServerSideScript), [IDE](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_IDE) +* v1.0, 2014-01-19, add wiki [DeliveryHLS](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_DeliveryHLS) +* v1.0, 2014-01-12, add wiki [HowToAskQuestion](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_HowToAskQuestion), [RtmpUrlVhost](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_RtmpUrlVhost) * v1.0, 2014-01-11, fix jw/flower player pause bug, which send closeStream actually. -* v1.0, 2014-01-05, add wiki [Build](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Build), [Performance](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Performance), [Forward](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Forward) +* v1.0, 2014-01-05, add wiki [Build](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Build), [Performance](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Performance), [Forward](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Forward) * v1.0, 2014-01-01, change listen(512), chunk-size(60000), to improve performance. * v1.0, 2013-12-27, merge from wenjie, the bandwidth test feature. -* v0.9, 2013-12-25, [v0.9](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.9) released. 20926 lines. +* v0.9, 2013-12-25, [v0.9](https://github.com/simple-rtmp-server/srs/releases/tag/0.9) released. 20926 lines. * v0.9, 2013-12-25, fix the bitrate bug(in Bps), use enhanced microphone. * v0.9, 2013-12-22, demo video meeting or chat(SRS+cherrypy+jquery+bootstrap). * v0.9, 2013-12-22, merge from wenjie, support banwidth test. @@ -546,8 +817,8 @@ Supported operating systems and hardware: * v0.9, 2013-12-15, support reload the hls/forwarder/transcoder. * v0.9, 2013-12-14, refine the thread model for the retry threads. * v0.9, 2013-12-10, auto install depends tools/libs on centos/ubuntu. -* v0.8, 2013-12-08, [v0.8](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.8) released. 19186 lines. -* v0.8, 2013-12-08, support [http hooks](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_HTTPCallback): on_connect/close/publish/unpublish/play/stop. +* v0.8, 2013-12-08, [v0.8](https://github.com/simple-rtmp-server/srs/releases/tag/0.8) released. 19186 lines. +* v0.8, 2013-12-08, support [http hooks](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_HTTPCallback): on_connect/close/publish/unpublish/play/stop. * v0.8, 2013-12-08, support multiple http hooks for a event. * v0.8, 2013-12-07, support http callback hooks, on_connect. * v0.8, 2013-12-07, support network based cli and json result, add CherryPy 3.2.4. @@ -555,32 +826,32 @@ Supported operating systems and hardware: * v0.8, 2013-12-06, support max_connections, drop if exceed. * v0.8, 2013-12-05, support log_dir, write ffmpeg log to file. * v0.8, 2013-12-05, fix the forward/hls/encoder bug. -* v0.7, 2013-12-03, [v0.7](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.7) released. 17605 lines. +* v0.7, 2013-12-03, [v0.7](https://github.com/simple-rtmp-server/srs/releases/tag/0.7) released. 17605 lines. * v0.7, 2013-12-01, support dead-loop detect for forwarder and transcoder. * v0.7, 2013-12-01, support all ffmpeg filters and params. * v0.7, 2013-11-30, support live stream transcoder by ffmpeg. * v0.7, 2013-11-30, support --with/without -ffmpeg, build ffmpeg-2.1. * v0.7, 2013-11-30, add ffmpeg-2.1, x264-core138, lame-3.99.5, libaacplus-2.0.2. -* v0.6, 2013-11-29, [v0.6](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.6) released. 16094 lines. +* v0.6, 2013-11-29, [v0.6](https://github.com/simple-rtmp-server/srs/releases/tag/0.6) released. 16094 lines. * v0.6, 2013-11-29, add performance summary, 1800 clients, 900Mbps, CPU 90.2%, 41MB. * v0.6, 2013-11-29, support forward stream to other edge server. * v0.6, 2013-11-29, support forward stream to other origin server. * v0.6, 2013-11-28, fix memory leak bug, aac decode bug. * v0.6, 2013-11-27, support --with or --without -hls and -ssl options. * v0.6, 2013-11-27, support AAC 44100HZ sample rate for iphone, adjust the timestamp. -* v0.5, 2013-11-26, [v0.5](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.5) released. 14449 lines. +* v0.5, 2013-11-26, [v0.5](https://github.com/simple-rtmp-server/srs/releases/tag/0.5) released. 14449 lines. * v0.5, 2013-11-24, support HLS(m3u8), fragment and window. * v0.5, 2013-11-24, support record to ts file for HLS. * v0.5, 2013-11-21, add ts_info tool to demux ts file. * v0.5, 2013-11-16, add rtmp players(OSMF/jwplayer5/jwplayer6). -* v0.4, 2013-11-10, [v0.4](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.4) released. 12500 lines. +* v0.4, 2013-11-10, [v0.4](https://github.com/simple-rtmp-server/srs/releases/tag/0.4) released. 12500 lines. * v0.4, 2013-11-10, support config and reload the pithy print. * v0.4, 2013-11-09, support reload config(vhost and its detail). * v0.4, 2013-11-09, support reload config(listen and chunk_size) by SIGHUP(1). * v0.4, 2013-11-09, support longtime(>4.6hours) publish/play. * v0.4, 2013-11-09, support config the chunk_size. * v0.4, 2013-11-09, support pause for live stream. -* v0.3, 2013-11-04, [v0.3](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.3) released. 11773 lines. +* v0.3, 2013-11-04, [v0.3](https://github.com/simple-rtmp-server/srs/releases/tag/0.3) released. 11773 lines. * v0.3, 2013-11-04, support refer/play-refer/publish-refer. * v0.3, 2013-11-04, support vhosts specified config. * v0.3, 2013-11-02, support listen multiple ports. @@ -588,12 +859,12 @@ Supported operating systems and hardware: * v0.3, 2013-10-29, support pithy print log message specified by stage. * v0.3, 2013-10-28, support librtmp without extended-timestamp in 0xCX chunk packet. * v0.3, 2013-10-27, support cache last gop for client fast startup. -* v0.2, 2013-10-25, [v0.2](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.2) released. 10125 lines. +* v0.2, 2013-10-25, [v0.2](https://github.com/simple-rtmp-server/srs/releases/tag/0.2) released. 10125 lines. * v0.2, 2013-10-25, support flash publish. * v0.2, 2013-10-25, support h264/avc codec by rtmp complex handshake. * v0.2, 2013-10-24, support time jitter detect and correct algorithm * v0.2, 2013-10-24, support decode codec type to cache the h264/avc sequence header. -* v0.1, 2013-10-23, [v0.1](https://github.com/winlinvip/simple-rtmp-server/releases/tag/0.1) released. 8287 lines. +* v0.1, 2013-10-23, [v0.1](https://github.com/simple-rtmp-server/srs/releases/tag/0.1) released. 8287 lines. * v0.1, 2013-10-23, support basic amf0 codec, simplify the api using c-style api. * v0.1, 2013-10-23, support shared ptr msg for zero memory copy. * v0.1, 2013-10-22, support vp6 codec with rtmp protocol specified simple handshake. @@ -604,42 +875,277 @@ Supported operating systems and hardware: ## Performance -Performance benchmark history, on virtual box: - -* 2013-11-28, SRS 0.5.0, 1800clients, 90%CPU, 41MB. [benchmark](https://github.com/winlinvip/simple-rtmp-server/commit/023e23bc8261bec15a70a7ae932098fb4f82b679) -* 2014-07-12, SRS 0.9.156, 1800clients, 68%CPU, 38MB. [benchmark](https://github.com/winlinvip/simple-rtmp-server/commit/e2d273f4939348374bf9644df9d54c4293b39c1a) -* 2014-07-12, SRS 0.9.156, 2700clients, 89%CPU, 61MB. [benchmark](https://github.com/winlinvip/simple-rtmp-server/commit/6d12280b7cc54c465b1caf8b1402149e77c4c7d9) -* 2014-11-11, SRS 1.0.5, 2700clients, 85%CPU, 66MB. (1.0 equals 2.0.12) - -Latest benchmark(2014-07-12): - -1. 300 connections, 150Mbps, 500kbps, CPU 5.7%, MEM 9208KB. -1. 600 connections, 300Mbps, 500kbps, CPU 18.3%, MEM 13MB. -1. 900 connections, 450Mbps, 500kbps, CPU 27.9%, MEM 20MB. -1. 1200 connections, 600Mbps, 500kbps, CPU 43.9%, MEM 26MB. -1. 1500 connections, 750Mbps, 500kbps, CPU 55.2%, MEM 32MB. -1. 1800 connections, 900Mbps, 500kbps, CPU 68.8%, MEM 38MB. -1. 2100 connections, 1050Mbps, 500kbps, CPU 75.7%, MEM 46MB. -1. 2400 connections, 1200Mbps, 500kbps, CPU 83.7%, MEM 54MB. -1. 2700 connections, 1350Mbps, 500kbps, CPU 89.9%, MEM 61MB. - -
-[winlin@dev6 srs]$ dstat
-----total-cpu-usage---- -dsk/total- ---net/lo-- ---paging-- ---system--
-usr sys idl wai hiq siq| read  writ| recv  send|  in   out | int   csw 
- 29  17  39   0   0  15|   0  5325B| 163M  163M|   0     0 |4331  3386 
- 30  16  38   0   0  16|   0  5325B| 160M  160M|   0     0 |4252  3332 
- 30  15  37   0   0  17|   0  7646B| 169M  169M|   0     0 |4015  2886 
- 30  17  36   0   0  17|   0  1638B| 197M  197M|   0     0 |4021  3037 
- 31  17  35   0   0  17|   0   410B| 204M  204M|   0     0 |4181  3243 
- 33  17  32   0   0  18|   0  2185B| 191M  191M|   0     0 |4305  3592 
- 31  15  36   0   0  18|   0  1229B| 127M  127M|   0     0 |4446  3822 
- 34  18  30   0   0  18|   0     0 | 231M  231M|   0     0 |4461  3691 
- 32  17  33   0   0  18|   0   410B| 169M  169M|   0     0 |4518  3788 
-
- -* See also: [Performance for x86/x64 Test Guide](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Performance) -* See also: [Performance for RaspberryPi](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_RaspberryPi) +Performance benchmark history, on virtual box. + +* See also: [Performance for x86/x64 Test Guide](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Performance) +* See also: [Performance for RaspberryPi](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_RaspberryPi) + +### Play benchmark + +The play benchmark by [st-load](https://github.com/winlinvip/st-load): + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
UpdateSRSClientsTypeCPUMemoryCommit
2013-11-280.5.01.8k(1800)players90%41MB-
2014-07-120.9.1561.8k(1800)players68%38MB-
2014-07-120.9.1562.7k(2700)players89%61MBcommit
2014-11-111.0.52.7k(2700)players85%66MB-
2014-11-112.0.122.7k(2700)players85%66MB-
2014-11-122.0.142.7k(2700)players69%59MB-
2014-11-122.0.143.5k(3500)players95%78MBcommit
2014-11-132.0.156.0k(6000)players82%203MBcommit
2014-11-222.0.307.5k(7500)players87%320MBcommit
2014-12-052.0.558.0k(8000)players89%360MB(mw_sleep=350)
commit
2014-12-052.0.579.0k(9000)players90%468MBcommit
2014-12-072.0.6710k(10000)players95%656MBcommit
+ +### Publish benchmark + +The publish benchmark by [st-load](https://github.com/winlinvip/st-load): + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
UpdateSRSClientsTypeCPUMemoryCommit
2014-12-031.0.101.2k(1200)publishers96%43MB-
2014-12-032.0.121.2k(1200)publishers96%43MB-
2014-12-032.0.471.2k(1200)publishers84%76MBcommit
2014-12-032.0.471.4k(1400)publishers95%140MB-
2014-12-032.0.481.4k(1400)publishers95%140MBcommit
2014-12-042.0.491.4k(1400)publishers68%144MB-
2014-12-042.0.492.5k(2500)publishers95%404MBcommit
2014-12-042.0.512.5k(2500)publishers91%259MBcommit
2014-12-042.0.524.0k(4000)publishers80%331MB(mr_sleep=350)
commit
+ +### Latency benchmark + +The latency between encoder and player with realtime config( +[CN](https://github.com/simple-rtmp-server/srs/wiki/v2_CN_LowLatency), +[EN](https://github.com/simple-rtmp-server/srs/wiki/v2_EN_LowLatency) +): + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
UpdateSRSVP6H.264VP6+mp3H.264+mp3
2014-12-031.0.100.4s0.4s0.9s1.2s
2014-12-122.0.700.1s0.4s1.0s0.9s
2014-12-162.0.720.1s0.4s0.8s0.6s
+ +We use FMLE as encoder for benchmark. The latency of server is 0.1s+, +and the bottleneck is the encoder. For more information, read +[bug #257](https://github.com/simple-rtmp-server/srs/issues/257#issuecomment-66864413). ## Architecture @@ -692,16 +1198,23 @@ SRS always use the most simple architecture to support complex transaction. +----------------------+-------------------------+----------------+ | Input | SRS(Simple RTMP Server) | Output | +----------------------+-------------------------+----------------+ -| Encoder(1) | +-> RTMP protocol ----+-> Flash Player | -| (FMLE,FFMPEG, -rtmp-+->-+-> HLS/NGINX --------+-> m3u8 player | -| Flash,XSPLIT, | +-> Fowarder ---------+-> RTMP Server | -| ......) | +-> Transcoder -------+-> RTMP Server | -| | +-> DVR --------------+-> FILE | -| | +-> BandwidthTest ----+-> Flash/StLoad | +| Encoder(1) | +-> RTMP protocol ----+-> RTMP player | +| (FMLE,FFMPEG, -rtmp-+->-+-> HLS/HTTP ---------+-> M3u8 player | +| Flash,XSPLIT, | +-> FLV/MP3/Aac/Ts ---+-> HTTP player | +| ......) | +-> Fowarder ---------+-> RTMP server | +| | +-> Transcoder -------+-> RTMP server | +| | +-> DVR --------------+-> Flv file | +| | +-> BandwidthTest ----+-> flash | ++----------------------+ | | +| MediaSource(2) | | | +| (RTSP,FILE, | | | +| HTTP,HLS, --pull-+->-- Ingester ----(rtmp)-+-> SRS | +| Device, | | | +| ......) | | | +----------------------+ | | | MediaSource(2) | | | | (RTSP,FILE, | | | -| HTTP,HLS, ------+->-- Ingester ----(rtmp)-+-> SRS | +| HTTP,HLS, --push-+->-- Streamer ----(rtmp)-+-> SRS | | Device, | | | | ......) | | | +----------------------+-------------------------+----------------+ @@ -710,10 +1223,12 @@ Remark: (1) Encoder: encoder must push RTMP stream to SRS server. (2) MediaSource: any media source, which can be ingest by ffmpeg. (3) Ingester: SRS will fork a process to run ffmpeg(or your application) -to ingest any input to rtmp, push to SRS. +to ingest any input to rtmp, push to SRS. Read Ingest. +(4) Streamer: SRS will listen for some protocol and accept stream push +over some protocol and remux to rtmp to SRS. Read Streamer.
-### [HDS/HLS origin backup](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_RTMP-ATC) +### [HDS/HLS origin backup](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_RTMP-ATC)
                         +----------+        +----------+
@@ -725,10 +1240,10 @@ to ingest any input to rtmp, push to SRS.
                  RTMP   +----------+ RTMP   +----------+
 
-### [RTMP cluster(origin/edge) Architecture](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Edge) +### [RTMP cluster(origin/edge) Architecture](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Edge) -Remark: cluster over edge, see [Edge](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Edge) -Remark: cluster over forward, see [Forward](https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Forward) +Remark: cluster over edge, see [Edge](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Edge) +Remark: cluster over forward, see [Forward](https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Forward)
 +---------+       +-----------------+     +-----------------------+ 
diff --git a/trunk/3rdparty/fdk-aac-0.1.3.zip b/trunk/3rdparty/fdk-aac-0.1.3.zip
new file mode 100644
index 0000000000..c7aeab7bd8
Binary files /dev/null and b/trunk/3rdparty/fdk-aac-0.1.3.zip differ
diff --git a/trunk/3rdparty/libaacplus-2.0.2.zip b/trunk/3rdparty/libaacplus-2.0.2.zip
deleted file mode 100644
index ecf78dbf86..0000000000
Binary files a/trunk/3rdparty/libaacplus-2.0.2.zip and /dev/null differ
diff --git a/trunk/3rdparty/libaacplus-patch-26410-800.zip b/trunk/3rdparty/libaacplus-patch-26410-800.zip
deleted file mode 100644
index bef52eb6e7..0000000000
Binary files a/trunk/3rdparty/libaacplus-patch-26410-800.zip and /dev/null differ
diff --git a/trunk/3rdparty/patches/1.st.arm.patch b/trunk/3rdparty/patches/1.st.arm.patch
index 1e18259405..0e949631e3 100644
--- a/trunk/3rdparty/patches/1.st.arm.patch
+++ b/trunk/3rdparty/patches/1.st.arm.patch
@@ -1,7 +1,5 @@
-Only in .: 1.st.arm.patch
-diff -r -c ./md.h ../st-1.9-patch-arm/md.h
-*** ./md.h	2009-10-02 02:46:43.000000000 +0800
---- ../st-1.9-patch-arm/md.h	2014-03-16 20:49:03.845344804 +0800
+*** md.h	Fri Oct  2 02:46:43 2009
+--- ../st-1.9-patch/md.h	Mon Mar 16 15:11:49 2015
 ***************
 *** 422,428 ****
   #define MD_STACK_GROWS_DOWN
diff --git a/trunk/3rdparty/patches/2.http.parser.patch b/trunk/3rdparty/patches/2.http.parser.patch
new file mode 100644
index 0000000000..1088809443
--- /dev/null
+++ b/trunk/3rdparty/patches/2.http.parser.patch
@@ -0,0 +1,16 @@
+*** Makefile	Wed Mar 27 06:35:20 2013
+--- ../http-parser-2.1-patch/Makefile	Mon Mar 16 15:25:59 2015
+***************
+*** 12,17 ****
+--- 12,22 ----
+  CFLAGS_FAST = $(CFLAGS) -O3 $(CFLAGS_FAST_EXTRA)
+  CFLAGS_LIB = $(CFLAGS_FAST) -fPIC
+  
++ # patch by winlin
++ CPPFLAGS_FAST = $(CPPFLAGS_DEBUG)
++ CFLAGS_FAST = $(CFLAGS_DEBUG)
++ CFLAGS_LIB = $(CFLAGS_FAST) -fPIC
++ 
+  test: test_g test_fast
+  	./test_g
+  	./test_fast
diff --git a/trunk/3rdparty/patches/3.st.osx.kqueue.patch b/trunk/3rdparty/patches/3.st.osx.kqueue.patch
new file mode 100644
index 0000000000..d3e016b104
--- /dev/null
+++ b/trunk/3rdparty/patches/3.st.osx.kqueue.patch
@@ -0,0 +1,19 @@
+*** io.c	Fri Oct  2 06:49:07 2009
+--- ../st-1.9-patch/io.c	Mon Mar 16 15:06:28 2015
+***************
+*** 89,94 ****
+--- 89,102 ----
+    if (fdlim > 0 && rlim.rlim_max > (rlim_t) fdlim) {
+      rlim.rlim_max = fdlim;
+    }
++ 
++   /* when rlimit max is negative, for example, osx, use cur directly. */
++   /* @see https://github.com/winlinvip/simple-rtmp-server/issues/336 */
++   if ((int)rlim.rlim_max < 0) {
++     _st_osfd_limit = (int)(fdlim > 0? fdlim : rlim.rlim_cur);
++     return 0;
++   }
++ 
+    rlim.rlim_cur = rlim.rlim_max;
+    if (setrlimit(RLIMIT_NOFILE, &rlim) < 0)
+      return -1;
diff --git a/trunk/3rdparty/patches/4.st.disable.examples.patch b/trunk/3rdparty/patches/4.st.disable.examples.patch
new file mode 100644
index 0000000000..c775f30df0
--- /dev/null
+++ b/trunk/3rdparty/patches/4.st.disable.examples.patch
@@ -0,0 +1,15 @@
+*** Makefile	Fri Oct  2 06:55:03 2009
+--- ../st-1.9-patch/Makefile	Mon Mar 16 15:30:35 2015
+***************
+*** 310,315 ****
+--- 310,319 ----
+  EXAMPLES    =
+  endif
+  
++ # disable examples for ubuntu crossbuild failed.
++ # @see https://github.com/winlinvip/simple-rtmp-server/issues/308
++ EXAMPLES =
++ 
+  ifeq ($(OS), DARWIN)
+  LINKNAME    = libst.$(DSO_SUFFIX)
+  SONAME      = libst.$(MAJOR).$(DSO_SUFFIX)
diff --git a/trunk/3rdparty/patches/5.x264.osx.gcc.patch b/trunk/3rdparty/patches/5.x264.osx.gcc.patch
new file mode 100644
index 0000000000..2b6411a41d
--- /dev/null
+++ b/trunk/3rdparty/patches/5.x264.osx.gcc.patch
@@ -0,0 +1,19 @@
+*** configure	Sat Nov 30 05:45:08 2013
+--- ../x264-snapshot-20131129-2245-stable-patch/configure	Tue Mar 17 21:42:24 2015
+***************
+*** 466,472 ****
+          ;;
+      darwin*)
+          SYS="MACOSX"
+!         CFLAGS="$CFLAGS -falign-loops=16"
+          libm="-lm"
+          if [ "$pic" = "no" ]; then
+              cc_check "" -mdynamic-no-pic && CFLAGS="$CFLAGS -mdynamic-no-pic"
+--- 466,472 ----
+          ;;
+      darwin*)
+          SYS="MACOSX"
+!         CFLAGS="$CFLAGS"
+          libm="-lm"
+          if [ "$pic" = "no" ]; then
+              cc_check "" -mdynamic-no-pic && CFLAGS="$CFLAGS -mdynamic-no-pic"
diff --git a/trunk/3rdparty/readme.txt b/trunk/3rdparty/readme.txt
index 315a7576d6..094504d8c6 100644
--- a/trunk/3rdparty/readme.txt
+++ b/trunk/3rdparty/readme.txt
@@ -16,13 +16,14 @@ CherryPy-3.2.4.zip
 ffmpeg-2.1.1.tar.gz
 yasm-1.2.0.tar.gz
 lame-3.99.5.tar.gz
-libaacplus-2.0.2.tar.gz
-libaacplus-patch-26410-800.zip (26410-800.zip)
 speex-1.2rc1.zip
 x264-snapshot-20131129-2245-stable.tar.bz2 (core.138)
     for srs to support live stream transcoding.
     remark: we use *.zip for all linux plantform.
 
+fdk-aac-0.1.3.zip
+    https://github.com/mstorsjo/fdk-aac/releases
+
 tools/ccache-3.1.9.zip
     to fast build.
     
@@ -52,12 +53,6 @@ links:
     lame: 
         http://sourceforge.net/projects/lame/ 
         http://nchc.dl.sourceforge.net/project/lame/lame/3.99/lame-3.99.5.tar.gz
-    aacplus: 
-        http://217.20.164.161/~tipok/aacplus/ 
-        http://217.20.164.161/~tipok/aacplus/libaacplus-2.0.2.tar.gz
-    aacplus-patch: 
-        http://www.3gpp.org/DynaReport/26410.htm 
-        http://www.3gpp.org/ftp/Specs/archive/26_series/26.410/26410-800.zip
     yasm:
         http://yasm.tortall.net/
         http://www.tortall.net/projects/yasm/releases/yasm-1.2.0.tar.gz
diff --git a/trunk/CMakeLists.txt b/trunk/CMakeLists.txt
deleted file mode 100644
index 5d7ea46cca..0000000000
--- a/trunk/CMakeLists.txt
+++ /dev/null
@@ -1,51 +0,0 @@
-cmake_minimum_required(VERSION 2.6.4)
-project(srs CXX)
-
-INCLUDE_DIRECTORIES(objs objs/st objs/hp objs/openssl src/core src/kernel src/rtmp src/app)
-
-set(SOURCE_FILES src/main/srs_main_server.cpp)
-AUX_SOURCE_DIRECTORY(src/core SOURCE_FILES)
-AUX_SOURCE_DIRECTORY(src/kernel SOURCE_FILES)
-AUX_SOURCE_DIRECTORY(src/rtmp SOURCE_FILES)
-AUX_SOURCE_DIRECTORY(src/app SOURCE_FILES)
-
-ADD_EXECUTABLE(srs ${SOURCE_FILES})
-TARGET_LINK_LIBRARIES(srs dl)
-TARGET_LINK_LIBRARIES(srs ${PROJECT_SOURCE_DIR}/objs/st/libst.a)
-TARGET_LINK_LIBRARIES(srs ${PROJECT_SOURCE_DIR}/objs/openssl/lib/libssl.a)
-TARGET_LINK_LIBRARIES(srs ${PROJECT_SOURCE_DIR}/objs/openssl/lib/libcrypto.a)
-TARGET_LINK_LIBRARIES(srs ${PROJECT_SOURCE_DIR}/objs/hp/libhttp_parser.a)
-
-IF(NOT EXISTS ${PROJECT_SOURCE_DIR}/objs/st/libst.a)
-    MESSAGE("srs_libs not found")
-    EXEC_PROGRAM(./configure)
-ENDIF(NOT EXISTS ${PROJECT_SOURCE_DIR}/objs/st/libst.a)
-
-MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_IDE#jetbrains")
-MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_IDE#jetbrains")
-MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_IDE#jetbrains")
-MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_IDE#jetbrains")
-MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_IDE#jetbrains")
-MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_IDE#jetbrains")
-MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_IDE#jetbrains")
-MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_IDE#jetbrains")
-MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_IDE#jetbrains")
-MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_IDE#jetbrains")
-MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_IDE#jetbrains")
-MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_IDE#jetbrains")
-MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_IDE#jetbrains")
-MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_IDE#jetbrains")
-MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_IDE#jetbrains")
-MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_IDE#jetbrains")
-MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_IDE#jetbrains")
-MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_IDE#jetbrains")
-MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_IDE#jetbrains")
-MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_IDE#jetbrains")
-MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_IDE#jetbrains")
-MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_IDE#jetbrains")
-MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_IDE#jetbrains")
-MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_IDE#jetbrains")
-MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_IDE#jetbrains")
-MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_IDE#jetbrains")
-MESSAGE(STATUS "use ./configure && make, @see https://github.com/winlinvip/simple-rtmp-server#usage")
-
diff --git a/trunk/auto/apps.sh b/trunk/auto/apps.sh
old mode 100644
new mode 100755
index e6fd7d1a6f..6b9fbf88f2
--- a/trunk/auto/apps.sh
+++ b/trunk/auto/apps.sh
@@ -1,7 +1,8 @@
 # generate the binary
 #
 # params:
-#     $SRS_OBJS the objs directory. ie. objs
+#     $SRS_OBJS the objs directory to store the Makefile. ie. ./objs
+#     $SRS_OBJS_DIR the objs directory for Makefile. ie. objs
 #     $SRS_MAKEFILE the makefile name. ie. Makefile
 #
 #     $MAIN_ENTRANCES array, disable all except the $APP_MAIN itself. ie. ["srs_main_server" "srs_main_bandcheck"]
@@ -14,7 +15,7 @@
 
 FILE=${SRS_OBJS}/${SRS_MAKEFILE}
 
-APP_TARGET="${SRS_OBJS}/${APP_NAME}"
+APP_TARGET="${SRS_OBJS_DIR}/${APP_NAME}"
 
 echo "generate app ${APP_NAME} depends...";
 
@@ -45,7 +46,7 @@ for item in ${MODULE_OBJS[*]}; do
         continue;
     fi
     
-    OBJ_FILE=${SRS_OBJS}/$item
+    OBJ_FILE=${SRS_OBJS_DIR}/$item
     OBJ_FILE="${OBJ_FILE%.*}.o"
     echo -n "${OBJ_FILE} " >> ${FILE}
 done
@@ -76,7 +77,7 @@ for item in ${MODULE_OBJS[*]}; do
         continue;
     fi
     
-    OBJ_FILE=${SRS_OBJS}/$item
+    OBJ_FILE=${SRS_OBJS_DIR}/$item
     OBJ_FILE="${OBJ_FILE%.*}.o"
     echo -n "${OBJ_FILE} " >> ${FILE}
 done
diff --git a/trunk/auto/auto_headers.sh b/trunk/auto/auto_headers.sh
new file mode 100755
index 0000000000..928c35ca56
--- /dev/null
+++ b/trunk/auto/auto_headers.sh
@@ -0,0 +1,257 @@
+#!/bin/bash
+
+# output variables:
+# SRS_AUTO_HEADERS_H: the auto generated header file.
+
+SRS_AUTO_HEADERS_H="${SRS_OBJS}/srs_auto_headers.hpp"
+
+# write user options to headers
+echo "// auto generated by configure" > $SRS_AUTO_HEADERS_H
+echo "#ifndef SRS_AUTO_HEADER_HPP" >> $SRS_AUTO_HEADERS_H
+echo "#define SRS_AUTO_HEADER_HPP" >> $SRS_AUTO_HEADERS_H
+echo "" >> $SRS_AUTO_HEADERS_H
+
+echo "#define SRS_AUTO_BUILD_TS \"`date +%s`\"" >> $SRS_AUTO_HEADERS_H
+echo "#define SRS_AUTO_BUILD_DATE \"`date \"+%Y-%m-%d %H:%M:%S\"`\"" >> $SRS_AUTO_HEADERS_H
+echo "#define SRS_AUTO_UNAME \"`uname -a`\"" >> $SRS_AUTO_HEADERS_H
+echo "#define SRS_AUTO_USER_CONFIGURE \"${SRS_AUTO_USER_CONFIGURE}\"" >> $SRS_AUTO_HEADERS_H
+echo "#define SRS_AUTO_CONFIGURE \"${SRS_AUTO_CONFIGURE}\"" >> $SRS_AUTO_HEADERS_H
+echo "" >> $SRS_AUTO_HEADERS_H
+
+# export the preset.
+if [ $SRS_OSX = YES ]; then
+    echo "#define SRS_OSX" >> $SRS_AUTO_HEADERS_H
+fi
+if [ $SRS_X86_X64 = YES ]; then
+    echo "#define SRS_X86_X64" >> $SRS_AUTO_HEADERS_H
+fi
+if [ $SRS_ARM_UBUNTU12 = YES ]; then
+    echo "#define SRS_ARM_UBUNTU12" >> $SRS_AUTO_HEADERS_H
+fi
+if [ $SRS_MIPS_UBUNTU12 = YES ]; then
+    echo "#define SRS_MIPS_UBUNTU12" >> $SRS_AUTO_HEADERS_H
+fi
+if [ $SRS_PI = YES ]; then
+    echo "#define SRS_PI" >> $SRS_AUTO_HEADERS_H
+fi
+if [ $SRS_CUBIE = YES ]; then
+    echo "#define SRS_CUBIE" >> $SRS_AUTO_HEADERS_H
+fi
+
+echo "" >> $SRS_AUTO_HEADERS_H
+
+#####################################################################################
+# generate auto headers file, depends on the finished of options.sh
+#####################################################################################
+if [ $SRS_ARM_UBUNTU12 = YES ]; then
+    __SrsArmCC="arm-linux-gnueabi-gcc";
+    __SrsArmGCC="arm-linux-gnueabi-gcc";
+    __SrsArmCXX="arm-linux-gnueabi-g++";
+    __SrsArmAR="arm-linux-gnueabi-ar";
+    __SrsArmLD="arm-linux-gnueabi-ld";
+    __SrsArmRANDLIB="arm-linux-gnueabi-ranlib";
+fi
+if [ $SRS_MIPS_UBUNTU12 = YES ]; then
+    __SrsArmCC="mipsel-openwrt-linux-gcc";
+    __SrsArmGCC="mipsel-openwrt-linux-gcc";
+    __SrsArmCXX="mipsel-openwrt-linux-g++";
+    __SrsArmAR="mipsel-openwrt-linux-ar";
+    __SrsArmLD="mipsel-openwrt-linux-ld";
+    __SrsArmRANDLIB="mipsel-openwrt-linux-ranlib";
+fi
+# the arm-ubuntu12 options for make for depends
+if [[ -z $SrsArmCC ]]; then SrsArmCC=$__SrsArmCC; fi
+if [[ -z $SrsArmGCC ]]; then SrsArmGCC=$__SrsArmGCC; fi
+if [[ -z $SrsArmCXX ]]; then SrsArmCXX=$__SrsArmCXX; fi
+if [[ -z $SrsArmAR ]]; then SrsArmAR=$__SrsArmAR; fi
+if [[ -z $SrsArmLD ]]; then SrsArmLD=$__SrsArmLD; fi
+if [[ -z $SrsArmRANDLIB ]]; then SrsArmRANDLIB=$__SrsArmRANDLIB; fi
+# write to source file
+if [ $SRS_EMBEDED_CPU = YES ]; then
+    echo "cc=$SrsArmCC gcc=$SrsArmGCC g++=$SrsArmCXX ar=$SrsArmAR ld=$SrsArmLD randlib=$SrsArmRANDLIB"
+    echo "#define SRS_AUTO_EMBEDED_TOOL_CHAIN \"cc=$SrsArmCC gcc=$SrsArmGCC g++=$SrsArmCXX ar=$SrsArmAR ld=$SrsArmLD randlib=$SrsArmRANDLIB\"" >> $SRS_AUTO_HEADERS_H
+else
+    echo "#define SRS_AUTO_EMBEDED_TOOL_CHAIN \"normal x86/x64 gcc\"" >> $SRS_AUTO_HEADERS_H
+fi
+echo "" >> $SRS_AUTO_HEADERS_H
+
+# auto headers in depends.
+if [ $SRS_HTTP_PARSER = YES ]; then
+    echo "#define SRS_AUTO_HTTP_PARSER" >> $SRS_AUTO_HEADERS_H
+else
+    echo "#undef SRS_AUTO_HTTP_PARSER" >> $SRS_AUTO_HEADERS_H
+fi
+
+if [ $SRS_HTTP_SERVER = YES ]; then
+    echo "#define SRS_AUTO_HTTP_SERVER" >> $SRS_AUTO_HEADERS_H
+else
+    echo "#undef SRS_AUTO_HTTP_SERVER" >> $SRS_AUTO_HEADERS_H
+fi
+
+if [ $SRS_STREAM_CASTER = YES ]; then
+    echo "#define SRS_AUTO_STREAM_CASTER" >> $SRS_AUTO_HEADERS_H
+else
+    echo "#undef SRS_AUTO_STREAM_CASTER" >> $SRS_AUTO_HEADERS_H
+fi
+
+if [ $SRS_HTTP_API = YES ]; then
+    echo "#define SRS_AUTO_HTTP_API" >> $SRS_AUTO_HEADERS_H
+else
+    echo "#undef SRS_AUTO_HTTP_API" >> $SRS_AUTO_HEADERS_H
+fi
+
+if [ $SRS_NGINX = YES ]; then
+    echo "#define SRS_AUTO_NGINX" >> $SRS_AUTO_HEADERS_H
+else
+    echo "#undef SRS_AUTO_NGINX" >> $SRS_AUTO_HEADERS_H
+fi
+
+if [ $SRS_DVR = YES ]; then
+    echo "#define SRS_AUTO_DVR" >> $SRS_AUTO_HEADERS_H
+else
+    echo "#undef SRS_AUTO_DVR" >> $SRS_AUTO_HEADERS_H
+fi
+
+if [ $SRS_HLS = YES ]; then
+    echo "#define SRS_AUTO_HLS" >> $SRS_AUTO_HEADERS_H
+else
+    echo "#undef SRS_AUTO_HLS" >> $SRS_AUTO_HEADERS_H
+fi
+
+if [ $SRS_HDS = YES ]; then
+    echo "#define SRS_AUTO_HDS" >> $SRS_AUTO_HEADERS_H
+else
+    echo "#undef SRS_AUTO_HDS" >> $SRS_AUTO_HEADERS_H
+fi
+
+if [ $SRS_HTTP_CALLBACK = YES ]; then
+    echo "#define SRS_AUTO_HTTP_CALLBACK" >> $SRS_AUTO_HEADERS_H
+else
+    echo "#undef SRS_AUTO_HTTP_CALLBACK" >> $SRS_AUTO_HEADERS_H
+fi
+
+if [ $SRS_SSL = YES ]; then
+    echo "#define SRS_AUTO_SSL" >> $SRS_AUTO_HEADERS_H
+else
+    echo "#undef SRS_AUTO_SSL" >> $SRS_AUTO_HEADERS_H
+fi
+
+# whether compile ffmpeg tool
+if [ $SRS_FFMPEG_TOOL = YES ]; then
+    echo "#define SRS_AUTO_FFMPEG_TOOL" >> $SRS_AUTO_HEADERS_H
+else
+    echo "#undef SRS_AUTO_FFMPEG_TOOL" >> $SRS_AUTO_HEADERS_H
+fi
+
+# whatever the FFMPEG tools, if transcode and ingest specified,
+# srs always compile the FFMPEG tool stub which used to start the FFMPEG process.
+if [ $SRS_FFMPEG_STUB = YES ]; then
+    echo "#define SRS_AUTO_FFMPEG_STUB" >> $SRS_AUTO_HEADERS_H
+else
+    echo "#undef SRS_AUTO_FFMPEG_STUB" >> $SRS_AUTO_HEADERS_H
+fi
+
+if [ $SRS_TRANSCODE = YES ]; then
+    echo "#define SRS_AUTO_TRANSCODE" >> $SRS_AUTO_HEADERS_H
+else
+    echo "#undef SRS_AUTO_TRANSCODE" >> $SRS_AUTO_HEADERS_H
+fi
+
+if [ $SRS_INGEST = YES ]; then
+    echo "#define SRS_AUTO_INGEST" >> $SRS_AUTO_HEADERS_H
+else
+    echo "#undef SRS_AUTO_INGEST" >> $SRS_AUTO_HEADERS_H
+fi
+
+# for statistic.
+if [ $SRS_STAT = YES ]; then
+    echo "#define SRS_AUTO_STAT" >> $SRS_AUTO_HEADERS_H
+else
+    echo "#undef SRS_AUTO_STAT" >> $SRS_AUTO_HEADERS_H
+fi
+
+if [ $SRS_GPERF = YES ]; then
+    echo "#define SRS_AUTO_GPERF" >> $SRS_AUTO_HEADERS_H
+else
+    echo "#undef SRS_AUTO_GPERF" >> $SRS_AUTO_HEADERS_H
+fi
+if [ $SRS_GPERF_MC = YES ]; then
+    echo "#define SRS_AUTO_GPERF_MC" >> $SRS_AUTO_HEADERS_H
+else
+    echo "#undef SRS_AUTO_GPERF_MC" >> $SRS_AUTO_HEADERS_H
+fi
+if [ $SRS_GPERF_MP = YES ]; then
+    echo "#define SRS_AUTO_GPERF_MP" >> $SRS_AUTO_HEADERS_H
+else
+    echo "#undef SRS_AUTO_GPERF_MP" >> $SRS_AUTO_HEADERS_H
+fi
+if [ $SRS_GPERF_CP = YES ]; then
+    echo "#define SRS_AUTO_GPERF_CP" >> $SRS_AUTO_HEADERS_H
+else
+    echo "#undef SRS_AUTO_GPERF_CP" >> $SRS_AUTO_HEADERS_H
+fi
+
+#####################################################################################
+# for embeded.
+#####################################################################################
+if [ $SRS_EMBEDED_CPU = YES ]; then
+    echo "#define SRS_AUTO_EMBEDED_CPU" >> $SRS_AUTO_HEADERS_H
+else
+    echo "#undef SRS_AUTO_EMBEDED_CPU" >> $SRS_AUTO_HEADERS_H
+fi
+
+# arm
+if [ $SRS_ARM_UBUNTU12 = YES ]; then
+    echo "#define SRS_AUTO_ARM_UBUNTU12" >> $SRS_AUTO_HEADERS_H
+else
+    echo "#undef SRS_AUTO_ARM_UBUNTU12" >> $SRS_AUTO_HEADERS_H
+fi
+
+# mips
+if [ $SRS_MIPS_UBUNTU12 = YES ]; then
+    echo "#define SRS_AUTO_MIPS_UBUNTU12" >> $SRS_AUTO_HEADERS_H
+else
+    echo "#undef SRS_AUTO_MIPS_UBUNTU12" >> $SRS_AUTO_HEADERS_H
+fi
+
+echo "" >> $SRS_AUTO_HEADERS_H
+# for log level compile settings
+if [ $SRS_LOG_VERBOSE = YES ]; then
+    echo "#define SRS_AUTO_VERBOSE" >> $SRS_AUTO_HEADERS_H
+else
+    echo "#undef SRS_AUTO_VERBOSE" >> $SRS_AUTO_HEADERS_H
+fi
+if [ $SRS_LOG_INFO = YES ]; then
+    echo "#define SRS_AUTO_INFO" >> $SRS_AUTO_HEADERS_H
+else
+    echo "#undef SRS_AUTO_INFO" >> $SRS_AUTO_HEADERS_H
+fi
+if [ $SRS_LOG_TRACE = YES ]; then
+    echo "#define SRS_AUTO_TRACE" >> $SRS_AUTO_HEADERS_H
+else
+    echo "#undef SRS_AUTO_TRACE" >> $SRS_AUTO_HEADERS_H
+fi
+
+# prefix
+echo "" >> $SRS_AUTO_HEADERS_H
+echo "#define SRS_AUTO_PREFIX \"${SRS_PREFIX}\"" >> $SRS_AUTO_HEADERS_H
+
+echo "" >> $SRS_AUTO_HEADERS_H
+
+#####################################################################################
+# generated the contributors from AUTHORS.txt
+#####################################################################################
+SRS_CONSTRIBUTORS=`cat ../AUTHORS.txt|grep "*"|awk '{print $2}'`
+echo "#define SRS_AUTO_CONSTRIBUTORS \"\\" >> $SRS_AUTO_HEADERS_H
+for CONTRIBUTOR in $SRS_CONSTRIBUTORS; do
+    echo "${CONTRIBUTOR} \\" >> $SRS_AUTO_HEADERS_H
+done
+echo "\"" >> $SRS_AUTO_HEADERS_H
+
+# new empty line to auto headers file.
+echo "" >> $SRS_AUTO_HEADERS_H
+
+# auto header EOF.
+echo "#endif" >> $SRS_AUTO_HEADERS_H
+echo "" >> $SRS_AUTO_HEADERS_H
+
diff --git a/trunk/auto/build_ffmpeg.sh b/trunk/auto/build_ffmpeg.sh
index cc66eca4b5..8748224cd9 100755
--- a/trunk/auto/build_ffmpeg.sh
+++ b/trunk/auto/build_ffmpeg.sh
@@ -3,7 +3,7 @@
 ff_src_dir="../../3rdparty"
 
 # the jobs to make ffmpeg
-if [[ "" -eq SRS_JOBS ]]; then 
+if [[ "" == $SRS_JOBS ]]; then 
     export SRS_JOBS="--jobs=1" 
 fi
 
@@ -19,6 +19,9 @@ echo "SRS_JOBS: ${SRS_JOBS}"
 mkdir -p ${ff_build_dir}
 mkdir -p ${ff_release_dir}
 
+# for ubuntu14, donot compile libaacplus.
+UBUNTU14=NO;grep -in "Ubuntu 14." /etc/issue >/dev/null 2>&1; if [[ $? -eq 0 ]]; then UBUNTU14=YES; fi
+
 # yasm for libx264
 ff_yasm_bin=${ff_release_dir}/bin/yasm
 if [[ -f ${ff_yasm_bin} ]]; then 
@@ -35,16 +38,15 @@ fi
 # ffmpeg can specifies the yasm path when configure it.
 export PATH=${PATH}:${ff_release_dir}/bin
 
-# libaacplus
-if [[ -f ${ff_release_dir}/lib/libaacplus.a ]]; then
-    echo "libaacplus is ok"
+# libfdk-aac
+if [[ -f ${ff_release_dir}/lib/libfdk-aac.a ]]; then
+    echo "libfdk_aac is ok"
 else
-    echo "build yasm-1.2.0"
+    echo "build fdk-aac-0.1.3"
     cd $ff_current_dir &&
-    rm -rf libaacplus-2.0.2 && unzip -q ${ff_src_dir}/libaacplus-2.0.2.zip &&
-    cd libaacplus-2.0.2 && cp ../${ff_src_dir}/libaacplus-patch-26410-800.zip src/26410-800.zip &&
-    bash autogen.sh && ./configure --prefix=${ff_release_dir} --enable-static && make && make install
-    ret=$?; if [[ 0 -ne ${ret} ]]; then echo "build libaacplus-2.0.2 failed"; exit 1; fi
+    rm -rf fdk-aac-0.1.3 && unzip -q ${ff_src_dir}/fdk-aac-0.1.3.zip &&
+    cd fdk-aac-0.1.3 && bash autogen.sh && ./configure --prefix=${ff_release_dir} --enable-static && make ${SRS_JOBS} && make install &&
+    ret=$?; if [[ 0 -ne ${ret} ]]; then echo "build fdk-aac-0.1.3 failed"; exit 1; fi
 fi
 
 # lame-3.99
@@ -77,6 +79,7 @@ else
     cd $ff_current_dir &&
     rm -rf x264-snapshot-20131129-2245-stable && unzip -q ${ff_src_dir}/x264-snapshot-20131129-2245-stable.zip &&
     cd x264-snapshot-20131129-2245-stable && 
+    chmod +w configure && patch -p0 <../../../3rdparty/patches/5.x264.osx.gcc.patch &&
     ./configure --prefix=${ff_release_dir} --disable-opencl --bit-depth=8 \
         --enable-static --disable-avs  --disable-swscale  --disable-lavf \
         --disable-ffms  --disable-gpac && 
@@ -105,7 +108,7 @@ else
         --extra-ldflags='-L${ffmpeg_exported_release_dir}/lib -lm -ldl' \
         --disable-ffplay --disable-ffprobe --disable-ffserver --disable-doc \
         --enable-postproc --enable-bzlib --enable-zlib --enable-parsers \
-        --enable-libx264 --enable-libmp3lame --enable-libaacplus --enable-libspeex \
+        --enable-libx264 --enable-libmp3lame --enable-libfdk-aac --enable-libspeex \
         --enable-pthreads --extra-libs=-lpthread \
         --enable-encoders --enable-decoders --enable-avfilter --enable-muxers --enable-demuxers && 
     make ${SRS_JOBS} && make install
diff --git a/trunk/auto/depends.sh b/trunk/auto/depends.sh
index 05ccc196dd..a843868a3a 100755
--- a/trunk/auto/depends.sh
+++ b/trunk/auto/depends.sh
@@ -3,7 +3,6 @@
 # variables, parent script must set it:
 # SRS_JOBS: the build jobs.
 # SrsArmMakeOptions: the arm make options for ubuntu12(armhf, v7cpu)
-# SRS_AUTO_HEADERS_H: the auto generated header file.
 
 #####################################################################################
 #####################################################################################
@@ -42,6 +41,24 @@ function Ubuntu_prepare()
             return 0;
         fi
     fi
+    
+    # for arm, install the cross build tool chain.
+    if [ $SRS_ARM_UBUNTU12 = YES ]; then
+        $SrsArmCC --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then
+            echo "install gcc-arm-linux-gnueabi g++-arm-linux-gnueabi"
+            require_sudoer "sudo apt-get install -y --force-yes gcc-arm-linux-gnueabi g++-arm-linux-gnueabi"
+            sudo apt-get install -y --force-yes gcc-arm-linux-gnueabi g++-arm-linux-gnueabi; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi
+            echo "install gcc-arm-linux-gnueabi g++-arm-linux-gnueabi success"
+        fi
+    fi
+    
+    # for mips, user must installed the tool chain.
+    if [ $SRS_MIPS_UBUNTU12 = YES ]; then
+        $SrsArmCC --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then
+            echo "user must install the tool chain: $SrsArmCC"
+            return 2
+        fi
+    fi
 
     OS_IS_UBUNTU=YES
     echo "Ubuntu detected, install tools if needed"
@@ -74,6 +91,22 @@ function Ubuntu_prepare()
         echo "install patch success"
     fi
     
+    unzip --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then
+        echo "install unzip"
+        require_sudoer "sudo apt-get install -y --force-yes unzip"
+        sudo apt-get install -y --force-yes unzip; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi
+        echo "install unzip success"
+    fi
+
+    if [ $SRS_NGINX = YES ]; then
+        if [[ ! -f /usr/include/pcre.h ]]; then
+            echo "install libpcre3-dev"
+            require_sudoer "sudo apt-get install -y --force-yes libpcre3-dev"
+            sudo apt-get install -y --force-yes libpcre3-dev; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi
+            echo "install libpcre3-dev success"
+        fi
+    fi
+    
     if [ $SRS_FFMPEG_TOOL = YES ]; then
         autoconf --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then
             echo "install autoconf"
@@ -89,13 +122,6 @@ function Ubuntu_prepare()
             echo "install libtool success"
         fi
         
-        if [[ ! -f /usr/include/pcre.h ]]; then
-            echo "install libpcre3-dev"
-            require_sudoer "sudo apt-get install -y --force-yes libpcre3-dev"
-            sudo apt-get install -y --force-yes libpcre3-dev; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi
-            echo "install libpcre3-dev success"
-        fi
-        
         if [[ ! -f /usr/include/zlib.h ]]; then
             echo "install zlib1g-dev"
             require_sudoer "sudo apt-get install -y --force-yes zlib1g-dev"
@@ -104,28 +130,13 @@ function Ubuntu_prepare()
         fi
     fi
     
-    # for arm, install the cross build tool chain.
-    if [ $SRS_ARM_UBUNTU12 = YES ]; then
-        $SrsArmCC --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then
-            echo "install gcc-arm-linux-gnueabi g++-arm-linux-gnueabi"
-            require_sudoer "sudo apt-get install -y --force-yes gcc-arm-linux-gnueabi g++-arm-linux-gnueabi"
-            sudo apt-get install -y --force-yes gcc-arm-linux-gnueabi g++-arm-linux-gnueabi; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi
-            echo "install gcc-arm-linux-gnueabi g++-arm-linux-gnueabi success"
-        fi
-    fi
-    
-    # for mips, user must installed the tool chain.
-    if [ $SRS_MIPS_UBUNTU12 = YES ]; then
-        $SrsArmCC --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then
-            echo "user must install the tool chain: $SrsArmCC"
-            return 2
-        fi
-    fi
-    
     echo "Ubuntu install tools success"
     return 0
 }
-Ubuntu_prepare; ret=$?; if [[ 0 -ne $ret ]]; then echo "Ubuntu prepare failed, ret=$ret"; exit $ret; fi
+# donot prepare tools, for srs-librtmp depends only gcc and g++.
+if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then
+    Ubuntu_prepare; ret=$?; if [[ 0 -ne $ret ]]; then echo "Ubuntu prepare failed, ret=$ret"; exit $ret; fi
+fi
 #####################################################################################
 # for Centos, auto install tools by yum
 #####################################################################################
@@ -135,6 +146,12 @@ function Centos_prepare()
     if [[ ! -f /etc/redhat-release ]]; then
         return 0;
     fi
+    
+    # for arm, install the cross build tool chain.
+    if [ $SRS_EMBEDED_CPU = YES ]; then
+        echo "embeded(arm/mips) is invalid for CentOS"
+        return 1
+    fi
 
     OS_IS_CENTOS=YES
     echo "Centos detected, install tools if needed"
@@ -167,6 +184,22 @@ function Centos_prepare()
         echo "install patch success"
     fi
     
+    unzip --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then
+        echo "install unzip"
+        require_sudoer "sudo yum install -y unzip"
+        sudo yum install -y unzip; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi
+        echo "install unzip success"
+    fi
+
+    if [ $SRS_NGINX = YES ]; then
+        if [[ ! -f /usr/include/pcre.h ]]; then
+            echo "install pcre-devel"
+            require_sudoer "sudo yum install -y pcre-devel"
+            sudo yum install -y pcre-devel; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi
+            echo "install pcre-devel success"
+        fi
+    fi
+    
     if [ $SRS_FFMPEG_TOOL = YES ]; then
         automake --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then
             echo "install automake"
@@ -189,13 +222,6 @@ function Centos_prepare()
             echo "install libtool success"
         fi
         
-        if [[ ! -f /usr/include/pcre.h ]]; then
-            echo "install pcre-devel"
-            require_sudoer "sudo yum install -y pcre-devel"
-            sudo yum install -y pcre-devel; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi
-            echo "install pcre-devel success"
-        fi
-        
         if [[ ! -f /usr/include/zlib.h ]]; then
             echo "install zlib-devel"
             require_sudoer "sudo yum install -y zlib-devel"
@@ -204,168 +230,205 @@ function Centos_prepare()
         fi
     fi
     
-    # for arm, install the cross build tool chain.
-    if [ $SRS_EMBEDED_CPU = YES ]; then
-        echo "embeded(arm/mips) is invalid for CentOS"
-        return 1
-    fi
-    
     echo "Centos install tools success"
     return 0
 }
-Centos_prepare; ret=$?; if [[ 0 -ne $ret ]]; then echo "CentOS prepare failed, ret=$ret"; exit $ret; fi
+# donot prepare tools, for srs-librtmp depends only gcc and g++.
+if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then
+    Centos_prepare; ret=$?; if [[ 0 -ne $ret ]]; then echo "CentOS prepare failed, ret=$ret"; exit $ret; fi
+fi
 #####################################################################################
-# for OSX, auto install tools by brew
+# for Centos, auto install tools by yum
 #####################################################################################
 OS_IS_OSX=NO
 function OSX_prepare()
 {
-    SYS_NAME=`uname -s`
-    if [ $SYS_NAME != Darwin ]; then
-        echo "This is not Darwin OSX"
+    uname -s|grep Darwin >/dev/null 2>&1
+    ret=$?; if [[ 0 -ne $ret ]]; then
+        if [ $SRS_OSX = YES ]; then
+            echo "OSX check failed, actual is `uname -s`"
+            exit 1;
+        fi
         return 0;
     fi
+    
+    # for arm, install the cross build tool chain.
+    if [ $SRS_EMBEDED_CPU = YES ]; then
+        echo "embeded(arm/mips) is invalid for OSX"
+        return 1
+    fi
 
     OS_IS_OSX=YES
     echo "OSX detected, install tools if needed"
+    # requires the osx when os
+    if [ $OS_IS_OSX = YES ]; then
+        if [ $SRS_OSX = NO ]; then
+            echo "OSX detected, must specifies the --osx"
+            exit 1
+        fi
+    fi
+
+    brew --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then
+        echo "install brew"
+        echo "ruby -e \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)\""
+        ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi
+        echo "install brew success"
+    fi
     
     gcc --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then
         echo "install gcc"
-        require_sudoer "sudo brew install gcc"
-        sudo brew install gcc; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi
+        echo "brew install gcc"
+        brew install gcc; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi
         echo "install gcc success"
     fi
     
     g++ --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then
         echo "install gcc-c++"
-        require_sudoer "sudo brew install gcc-c++"
-        sudo brew install gcc-c++; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi
+        echo "brew install gcc-c++"
+        brew install gcc-c++; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi
         echo "install gcc-c++ success"
     fi
     
     make --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then
         echo "install make"
-        require_sudoer "sudo brew install make"
-        sudo brew install make; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi
+        echo "brew install make"
+        brew install make; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi
         echo "install make success"
     fi
     
     patch --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then
         echo "install patch"
-        require_sudoer "sudo brew install patch"
-        sudo brew install patch; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi
+        echo "brew install patch"
+        brew install patch; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi
         echo "install patch success"
     fi
     
+    unzip --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then
+        echo "install unzip"
+        echo "brew install unzip"
+        brew install unzip; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi
+        echo "install unzip success"
+    fi
+
+    if [ $SRS_NGINX = YES ]; then
+        if [[ ! -f /usr/local/include/pcre.h ]]; then
+        echo "install pcre"
+        echo "brew install pcre"
+        brew install pcre; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi
+        echo "install pcre success"
+        fi
+    fi
+
     if [ $SRS_FFMPEG_TOOL = YES ]; then
         automake --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then
             echo "install automake"
-            require_sudoer "sudo brew install automake"
-            sudo brew install automake; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi
+            echo "brew install automake"
+            brew install automake; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi
             echo "install automake success"
         fi
         
         autoconf --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then
             echo "install autoconf"
-            require_sudoer "sudo brew install autoconf"
-            sudo brew install autoconf; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi
+            echo "brew install autoconf"
+            brew install autoconf; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi
             echo "install autoconf success"
         fi
         
-        libtool --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then
+        which libtool >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then
             echo "install libtool"
-            require_sudoer "sudo brew install libtool"
-            sudo brew install libtool; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi
+            echo "brew install libtool"
+            brew install libtool; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi
             echo "install libtool success"
         fi
-        
-        if [[ ! -f /usr/include/pcre.h ]]; then
-            echo "install pcre-devel"
-            require_sudoer "sudo brew install pcre-devel"
-            sudo brew install pcre-devel; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi
-            echo "install pcre-devel success"
-        fi
-        
-        if [[ ! -f /usr/include/zlib.h ]]; then
-            echo "install zlib-devel"
-            require_sudoer "sudo brew install zlib-devel"
-            sudo brew install zlib-devel; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi
-            echo "install zlib-devel success"
+
+        brew info zlib >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then
+            echo "install zlib"
+            echo "brew install zlib"
+            brew install zlib; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi
+            echo "install zlib success"
         fi
     fi
     
     echo "OSX install tools success"
     return 0
 }
-OSX_prepare; ret=$?; if [[ 0 -ne $ret ]]; then echo "OSX prepare failed, ret=$ret"; exit $ret; fi
+# donot prepare tools, for srs-librtmp depends only gcc and g++.
+if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then
+    OSX_prepare; ret=$?; if [[ 0 -ne $ret ]]; then echo "OSX prepare failed, ret=$ret"; exit $ret; fi
+fi
 
+#####################################################################################
+# check the os.
+#####################################################################################
+# user must specifies something what a fuck, we suppport following os:
+#       centos/ubuntu/osx,
+#       embeded system, for example, mips or arm,
+#       export srs-librtmp
+# others is invalid.
+if [[ $OS_IS_UBUNTU = NO && $OS_IS_CENTOS = NO && $OS_IS_OSX = NO && $SRS_EMBEDED_CPU = NO && $SRS_EXPORT_LIBRTMP_PROJECT = NO ]]; then
+    echo "what a fuck, os not supported."
+    exit 1
+fi
 
 #####################################################################################
 # st-1.9
 #####################################################################################
-# check the arm flag file, if flag changed, need to rebuild the st.
-_ST_MAKE=linux-debug
-if [ $SRS_EMBEDED_CPU = YES ]; then
-    # ok, arm specified, if the flag filed does not exists, need to rebuild.
-    if [[ -f ${SRS_OBJS}/_flag.st.arm.tmp && -f ${SRS_OBJS}/st/libst.a ]]; then
-        echo "st-1.9t for arm is ok.";
-    else
-        # TODO: FIXME: patch the bug.
-        # patch st for arm, @see: https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SrsLinuxArm#st-arm-bug-fix
-        echo "build st-1.9t for arm"; 
-        (
-            rm -rf ${SRS_OBJS}/st-1.9 && cd ${SRS_OBJS} && 
-            unzip -q ../3rdparty/st-1.9.zip && cd st-1.9 && 
-            patch -p0 < ../../3rdparty/patches/1.st.arm.patch &&
-            make CC=${SrsArmCC} AR=${SrsArmAR} LD=${SrsArmLD} RANDLIB=${SrsArmRANDLIB} EXTRA_CFLAGS="-DMD_HAVE_EPOLL" ${_ST_MAKE} &&
-            cd .. && rm -rf st && ln -sf st-1.9/obj st &&
-            cd .. && touch ${SRS_OBJS}/_flag.st.arm.tmp
-        )
+if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then
+    # check the arm flag file, if flag changed, need to rebuild the st.
+    _ST_MAKE=linux-debug && _ST_EXTRA_CFLAGS="EXTRA_CFLAGS=-DMD_HAVE_EPOLL"
+    # for osx, use darwin for st, donot use epoll.
+    if [ $OS_IS_OSX = YES ]; then
+        _ST_MAKE=darwin-debug && _ST_EXTRA_CFLAGS="EXTRA_CFLAGS=-DMD_HAVE_KQUEUE"
     fi
-else
-    if [ $SRS_OSX = YES ]; then 
-        _ST_MAKE=darwin-debug
-    fi
-    if [[ ! -f ${SRS_OBJS}/_flag.st.arm.tmp && -f ${SRS_OBJS}/st/libst.a ]]; then
-        echo "st-1.9t is ok.";
+    # memory leak for linux-optimized
+    # @see: https://github.com/simple-rtmp-server/srs/issues/197
+    if [ $SRS_EMBEDED_CPU = YES ]; then
+        # ok, arm specified, if the flag filed does not exists, need to rebuild.
+        if [[ -f ${SRS_OBJS}/_flag.st.arm.tmp && -f ${SRS_OBJS}/st/libst.a ]]; then
+            echo "st-1.9t for arm is ok.";
+        else
+            # TODO: FIXME: patch the bug.
+            # patch st for arm, @see: https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SrsLinuxArm#st-arm-bug-fix
+            echo "build st-1.9t for arm"; 
+            (
+                rm -rf ${SRS_OBJS}/st-1.9 && cd ${SRS_OBJS} && 
+                unzip -q ../3rdparty/st-1.9.zip && cd st-1.9 && chmod +w * &&
+                patch -p0 < ../../3rdparty/patches/1.st.arm.patch &&
+                patch -p0 < ../../3rdparty/patches/3.st.osx.kqueue.patch &&
+                patch -p0 < ../../3rdparty/patches/4.st.disable.examples.patch &&
+                make ${_ST_MAKE} CC=${SrsArmCC} AR=${SrsArmAR} LD=${SrsArmLD} RANDLIB=${SrsArmRANDLIB} ${_ST_EXTRA_CFLAGS} &&
+                cd .. && rm -rf st && ln -sf st-1.9/obj st &&
+                cd .. && touch ${SRS_OBJS}/_flag.st.arm.tmp
+            )
+        fi
     else
-        echo "build st-1.9t"; 
-        (
-            rm -rf ${SRS_OBJS}/st-1.9 && cd ${SRS_OBJS} && 
-            unzip -q ../3rdparty/st-1.9.zip && cd st-1.9 && 
-            echo "we alaways patch the st, for we may build srs under arm directly" &&
-            echo "the 1.st.arm.patch is ok for x86 because it's only modify code under macro linux arm" &&
-            patch -p0 < ../../3rdparty/patches/1.st.arm.patch &&
-            make ${_ST_MAKE} &&
-            cd .. && rm -rf st && ln -sf st-1.9/obj st &&
-            cd .. && rm -f ${SRS_OBJS}/_flag.st.arm.tmp
-        )
+        if [[ ! -f ${SRS_OBJS}/_flag.st.arm.tmp && -f ${SRS_OBJS}/st/libst.a ]]; then
+            echo "st-1.9t is ok.";
+        else
+            echo "build st-1.9t"; 
+            (
+                rm -rf ${SRS_OBJS}/st-1.9 && cd ${SRS_OBJS} && 
+                unzip -q ../3rdparty/st-1.9.zip && cd st-1.9 && chmod +w * &&
+                patch -p0 < ../../3rdparty/patches/3.st.osx.kqueue.patch &&
+                patch -p0 < ../../3rdparty/patches/4.st.disable.examples.patch &&
+                make ${_ST_MAKE} ${_ST_EXTRA_CFLAGS} &&
+                cd .. && rm -rf st && ln -sf st-1.9/obj st &&
+                cd .. && rm -f ${SRS_OBJS}/_flag.st.arm.tmp
+            )
+        fi
     fi
+    # check status
+    ret=$?; if [[ $ret -ne 0 ]]; then echo "build st-1.9 failed, ret=$ret"; exit $ret; fi
+    if [ ! -f ${SRS_OBJS}/st/libst.a ]; then echo "build st-1.9 static lib failed."; exit -1; fi
 fi
-# check status
-ret=$?; if [[ $ret -ne 0 ]]; then echo "build st-1.9 failed, ret=$ret"; exit $ret; fi
-if [ ! -f ${SRS_OBJS}/st/libst.a ]; then echo "build st-1.9 static lib failed."; exit -1; fi
 
 #####################################################################################
 # http-parser-2.1
 #####################################################################################
 # check the arm flag file, if flag changed, need to rebuild the st.
 if [ $SRS_HTTP_PARSER = YES ]; then
-    # for osx(darwin), donot use sed.
-    if [ $SRS_OSX = YES ]; then 
-        if [[ -f ${SRS_OBJS}/hp/http_parser.h && -f ${SRS_OBJS}/hp/libhttp_parser.a ]]; then
-            echo "http-parser-2.1 is ok.";
-        else
-            echo "build http-parser-2.1 for osx(darwin)";
-            (
-                rm -rf ${SRS_OBJS}/http-parser-2.1 && cd ${SRS_OBJS} && unzip -q ../3rdparty/http-parser-2.1.zip && 
-                cd http-parser-2.1 && 
-                make package &&
-                cd .. && rm -rf hp && ln -sf http-parser-2.1 hp
-            )
-        fi
     # ok, arm specified, if the flag filed does not exists, need to rebuild.
-    elif [ $SRS_EMBEDED_CPU = YES ]; then
+    if [ $SRS_EMBEDED_CPU = YES ]; then
         if [[ -f ${SRS_OBJS}/_flag.st.hp.tmp && -f ${SRS_OBJS}/hp/http_parser.h && -f ${SRS_OBJS}/hp/libhttp_parser.a ]]; then
             echo "http-parser-2.1 for arm is ok.";
         else
@@ -373,8 +436,7 @@ if [ $SRS_HTTP_PARSER = YES ]; then
             (
                 rm -rf ${SRS_OBJS}/http-parser-2.1 && cd ${SRS_OBJS} && unzip -q ../3rdparty/http-parser-2.1.zip && 
                 cd http-parser-2.1 && 
-                sed -i "s/CPPFLAGS_FAST +=.*$/CPPFLAGS_FAST = \$\(CPPFLAGS_DEBUG\)/g" Makefile &&
-                sed -i "s/CFLAGS_FAST =.*$/CFLAGS_FAST = \$\(CFLAGS_DEBUG\)/g" Makefile &&
+                patch -p0 < ../../3rdparty/patches/2.http.parser.patch &&
                 make CC=${SrsArmCC} AR=${SrsArmAR} package &&
                 cd .. && rm -rf hp && ln -sf http-parser-2.1 hp &&
                 cd .. && touch ${SRS_OBJS}/_flag.st.hp.tmp
@@ -389,8 +451,7 @@ if [ $SRS_HTTP_PARSER = YES ]; then
             (
                 rm -rf ${SRS_OBJS}/http-parser-2.1 && cd ${SRS_OBJS} && unzip -q ../3rdparty/http-parser-2.1.zip && 
                 cd http-parser-2.1 && 
-                sed -i "s/CPPFLAGS_FAST +=.*$/CPPFLAGS_FAST = \$\(CPPFLAGS_DEBUG\)/g" Makefile &&
-                sed -i "s/CFLAGS_FAST =.*$/CFLAGS_FAST = \$\(CFLAGS_DEBUG\)/g" Makefile &&
+                patch -p0 < ../../3rdparty/patches/2.http.parser.patch &&
                 make package &&
                 cd .. && rm -rf hp && ln -sf http-parser-2.1 hp &&
                 cd .. && rm -f ${SRS_OBJS}/_flag.st.hp.tmp
@@ -404,24 +465,6 @@ if [ $SRS_HTTP_PARSER = YES ]; then
     if [[ ! -f ${SRS_OBJS}/hp/libhttp_parser.a ]]; then echo "build http-parser-2.1 failed"; exit -1; fi
 fi
 
-if [ $SRS_HTTP_PARSER = YES ]; then
-    echo "#define SRS_AUTO_HTTP_PARSER" >> $SRS_AUTO_HEADERS_H
-else
-    echo "#undef SRS_AUTO_HTTP_PARSER" >> $SRS_AUTO_HEADERS_H
-fi
-
-if [ $SRS_HTTP_SERVER = YES ]; then
-    echo "#define SRS_AUTO_HTTP_SERVER" >> $SRS_AUTO_HEADERS_H
-else
-    echo "#undef SRS_AUTO_HTTP_SERVER" >> $SRS_AUTO_HEADERS_H
-fi
-
-if [ $SRS_HTTP_API = YES ]; then
-    echo "#define SRS_AUTO_HTTP_API" >> $SRS_AUTO_HEADERS_H
-else
-    echo "#undef SRS_AUTO_HTTP_API" >> $SRS_AUTO_HEADERS_H
-fi
-
 #####################################################################################
 # nginx for HLS, nginx-1.5.0
 #####################################################################################
@@ -436,7 +479,9 @@ function write_nginx_html5()
 END
 }
 # create the nginx dir, for http-server if not build nginx
-mkdir -p ${SRS_OBJS}/nginx
+if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then
+    mkdir -p ${SRS_OBJS}/nginx
+fi
 # make nginx
 __SRS_BUILD_NGINX=NO; if [ $SRS_EMBEDED_CPU = NO ]; then if [ $SRS_NGINX = YES ]; then __SRS_BUILD_NGINX=YES; fi fi
 if [ $__SRS_BUILD_NGINX = YES ]; then
@@ -459,51 +504,40 @@ if [ $__SRS_BUILD_NGINX = YES ]; then
     # srs will write ts/m3u8 file use current user,
     # nginx default use nobody, so cannot read the ts/m3u8 created by srs.
     cp ${SRS_OBJS}/nginx/conf/nginx.conf ${SRS_OBJS}/nginx/conf/nginx.conf.bk
-    sed -i "s/^.user  nobody;/user `whoami`;/g" ${SRS_OBJS}/nginx/conf/nginx.conf
+    if [ $OS_IS_OSX = YES ]; then
+        sed -i '' "s/^.user  nobody;/user `whoami`;/g" ${SRS_OBJS}/nginx/conf/nginx.conf
+    else
+        sed -i "s/^.user  nobody;/user `whoami`;/g" ${SRS_OBJS}/nginx/conf/nginx.conf
+    fi
 fi
 
-# create forward dir
-mkdir -p ${SRS_OBJS}/nginx/html/live &&
-mkdir -p ${SRS_OBJS}/nginx/html/forward/live
-
-# generate default html pages for android.
-html_file=${SRS_OBJS}/nginx/html/live/demo.html && hls_stream=demo.m3u8 && write_nginx_html5
-html_file=${SRS_OBJS}/nginx/html/live/livestream.html && hls_stream=livestream.m3u8 && write_nginx_html5
-html_file=${SRS_OBJS}/nginx/html/live/livestream_ld.html && hls_stream=livestream_ld.m3u8 && write_nginx_html5
-html_file=${SRS_OBJS}/nginx/html/live/livestream_sd.html && hls_stream=livestream_sd.m3u8 && write_nginx_html5
-html_file=${SRS_OBJS}/nginx/html/forward/live/livestream.html && hls_stream=livestream.m3u8 && write_nginx_html5
-html_file=${SRS_OBJS}/nginx/html/forward/live/livestream_ld.html && hls_stream=livestream_ld.m3u8 && write_nginx_html5
-html_file=${SRS_OBJS}/nginx/html/forward/live/livestream_sd.html && hls_stream=livestream_sd.m3u8 && write_nginx_html5
-
-# copy players to nginx html dir.
-rm -rf ${SRS_OBJS}/nginx/html/players &&
-ln -sf `pwd`/research/players ${SRS_OBJS}/nginx/html/players &&
-rm -f ${SRS_OBJS}/nginx/crossdomain.xml &&
-ln -sf `pwd`/research/players/crossdomain.xml ${SRS_OBJS}/nginx/html/crossdomain.xml
-
-# for favicon.ico
-rm -rf ${SRS_OBJS}/nginx/html/favicon.ico &&
-ln -sf `pwd`/research/api-server/static-dir/favicon.ico ${SRS_OBJS}/nginx/html/favicon.ico
-
-# nginx.html to detect whether nginx is alive
-echo "nginx is ok" > ${SRS_OBJS}/nginx/html/nginx.html
-
-if [ $SRS_NGINX = YES ]; then
-    echo "#define SRS_AUTO_NGINX" >> $SRS_AUTO_HEADERS_H
-else
-    echo "#undef SRS_AUTO_NGINX" >> $SRS_AUTO_HEADERS_H
-fi
+# the demo dir.
+if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then
+    # create forward dir
+    mkdir -p ${SRS_OBJS}/nginx/html/live &&
+    mkdir -p ${SRS_OBJS}/nginx/html/forward/live
 
-if [ $SRS_DVR = YES ]; then
-    echo "#define SRS_AUTO_DVR" >> $SRS_AUTO_HEADERS_H
-else
-    echo "#undef SRS_AUTO_DVR" >> $SRS_AUTO_HEADERS_H
-fi
+    # generate default html pages for android.
+    html_file=${SRS_OBJS}/nginx/html/live/demo.html && hls_stream=demo.m3u8 && write_nginx_html5
+    html_file=${SRS_OBJS}/nginx/html/live/livestream.html && hls_stream=livestream.m3u8 && write_nginx_html5
+    html_file=${SRS_OBJS}/nginx/html/live/livestream_ld.html && hls_stream=livestream_ld.m3u8 && write_nginx_html5
+    html_file=${SRS_OBJS}/nginx/html/live/livestream_sd.html && hls_stream=livestream_sd.m3u8 && write_nginx_html5
+    html_file=${SRS_OBJS}/nginx/html/forward/live/livestream.html && hls_stream=livestream.m3u8 && write_nginx_html5
+    html_file=${SRS_OBJS}/nginx/html/forward/live/livestream_ld.html && hls_stream=livestream_ld.m3u8 && write_nginx_html5
+    html_file=${SRS_OBJS}/nginx/html/forward/live/livestream_sd.html && hls_stream=livestream_sd.m3u8 && write_nginx_html5
+
+    # copy players to nginx html dir.
+    rm -rf ${SRS_OBJS}/nginx/html/players &&
+    ln -sf `pwd`/research/players ${SRS_OBJS}/nginx/html/players &&
+    rm -f ${SRS_OBJS}/nginx/crossdomain.xml &&
+    ln -sf `pwd`/research/players/crossdomain.xml ${SRS_OBJS}/nginx/html/crossdomain.xml
+
+    # for favicon.ico
+    rm -rf ${SRS_OBJS}/nginx/html/favicon.ico &&
+    ln -sf `pwd`/research/api-server/static-dir/favicon.ico ${SRS_OBJS}/nginx/html/favicon.ico
 
-if [ $SRS_HLS = YES ]; then
-    echo "#define SRS_AUTO_HLS" >> $SRS_AUTO_HEADERS_H
-else
-    echo "#undef SRS_AUTO_HLS" >> $SRS_AUTO_HEADERS_H
+    # nginx.html to detect whether nginx is alive
+    echo "nginx is ok" > ${SRS_OBJS}/nginx/html/nginx.html
 fi
 
 #####################################################################################
@@ -513,7 +547,7 @@ if [ $SRS_HTTP_CALLBACK = YES ]; then
     if [[ -f ${SRS_OBJS}/CherryPy-3.2.4/setup.py ]]; then
         echo "CherryPy-3.2.4 is ok.";
     else
-        require_sudoer "configure --with-http-callback"
+        require_sudoer "install CherryPy-3.2.4"
         echo "install CherryPy-3.2.4"; 
         (
             sudo rm -rf ${SRS_OBJS}/CherryPy-3.2.4 && cd ${SRS_OBJS} && 
@@ -526,25 +560,21 @@ if [ $SRS_HTTP_CALLBACK = YES ]; then
     if [ ! -f ${SRS_OBJS}/CherryPy-3.2.4/setup.py ]; then echo "build CherryPy-3.2.4 failed."; exit -1; fi
 fi
 
-if [ $SRS_HTTP_CALLBACK = YES ]; then
-    echo "#define SRS_AUTO_HTTP_CALLBACK" >> $SRS_AUTO_HEADERS_H
-else
-    echo "#undef SRS_AUTO_HTTP_CALLBACK" >> $SRS_AUTO_HEADERS_H
+if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then
+    echo "link players to cherrypy static-dir"
+    rm -rf research/api-server/static-dir/players &&
+    ln -sf `pwd`/research/players research/api-server/static-dir/players &&
+    rm -f research/api-server/static-dir/crossdomain.xml &&
+    ln -sf `pwd`/research/players/crossdomain.xml research/api-server/static-dir/crossdomain.xml &&
+    rm -rf research/api-server/static-dir/live && 
+    mkdir -p `pwd`/${SRS_OBJS}/nginx/html/live &&
+    ln -sf `pwd`/${SRS_OBJS}/nginx/html/live research/api-server/static-dir/live &&
+    rm -rf research/api-server/static-dir/forward && 
+    mkdir -p `pwd`/${SRS_OBJS}/nginx/html/forward &&
+    ln -sf `pwd`/${SRS_OBJS}/nginx/html/forward research/api-server/static-dir/forward
+    ret=$?; if [[ $ret -ne 0 ]]; then echo "link players to cherrypy static-dir failed, ret=$ret"; exit $ret; fi
 fi
 
-echo "link players to cherrypy static-dir"
-rm -rf research/api-server/static-dir/players &&
-ln -sf `pwd`/research/players research/api-server/static-dir/players &&
-rm -f research/api-server/static-dir/crossdomain.xml &&
-ln -sf `pwd`/research/players/crossdomain.xml research/api-server/static-dir/crossdomain.xml &&
-rm -rf research/api-server/static-dir/live && 
-mkdir -p `pwd`/${SRS_OBJS}/nginx/html/live &&
-ln -sf `pwd`/${SRS_OBJS}/nginx/html/live research/api-server/static-dir/live &&
-rm -rf research/api-server/static-dir/forward && 
-mkdir -p `pwd`/${SRS_OBJS}/nginx/html/forward &&
-ln -sf `pwd`/${SRS_OBJS}/nginx/html/forward research/api-server/static-dir/forward
-ret=$?; if [[ $ret -ne 0 ]]; then echo "link players to cherrypy static-dir failed, ret=$ret"; exit $ret; fi
-
 #####################################################################################
 # generate demo index.html
 #####################################################################################
@@ -569,21 +599,11 @@ fi
 #####################################################################################
 # extra configure options
 CONFIGURE_TOOL="./config"
-EXTRA_CONFIGURE=""
-if [ $SRS_OSX = YES ]; then
-    CONFIGURE_TOOL="./Configure"
-    arch=`uname -m` && echo "OSX $arch";
-    if [ $arch = x86_64 ]; then
-        echo "configure 64bit openssl";
-        EXTRA_CONFIGURE=darwin64-x86_64-cc
-    else
-        echo "configure 32bit openssl";
-        EXTRA_CONFIGURE=darwin-i386-cc
-    fi
-    echo "openssl extra config: $CONFIGURE_TOOL $EXTRA_CONFIGURE"
-fi
 if [ $SRS_EMBEDED_CPU = YES ]; then
-    CONFIGURE_TOOL="./Configure"
+    CONFIGURE_TOOL="./Configure linux-armv4"
+fi
+if [ $SRS_OSX = YES ]; then
+    CONFIGURE_TOOL="./Configure darwin64-`uname -m`-cc"
 fi
 # @see http://www.openssl.org/news/secadv_20140407.txt
 # Affected users should upgrade to OpenSSL 1.0.1g. Users unable to immediately
@@ -602,7 +622,7 @@ if [ $SRS_SSL = YES ]; then
                 (
                     rm -rf ${SRS_OBJS}/openssl-1.0.1f && cd ${SRS_OBJS} && 
                     unzip -q ../3rdparty/openssl-1.0.1f.zip && cd openssl-1.0.1f && 
-                    $CONFIGURE_TOOL --prefix=`pwd`/_release -no-shared no-asm linux-armv4 -DOPENSSL_NO_HEARTBEATS ${EXTRA_CONFIGURE} && 
+                    $CONFIGURE_TOOL --prefix=`pwd`/_release -no-shared no-asm &&
                     make CC=${SrsArmCC} GCC=${SrsArmGCC} AR="${SrsArmAR} r" \
                         LD=${SrsArmLD} LINK=${SrsArmGCC} RANDLIB=${SrsArmRANDLIB} && 
                     make install_sw &&
@@ -619,7 +639,7 @@ if [ $SRS_SSL = YES ]; then
                 (
                     rm -rf ${SRS_OBJS}/openssl-1.0.1f && cd ${SRS_OBJS} && 
                     unzip -q ../3rdparty/openssl-1.0.1f.zip && cd openssl-1.0.1f && 
-                    $CONFIGURE_TOOL --prefix=`pwd`/_release -no-shared -DOPENSSL_NO_HEARTBEATS ${EXTRA_CONFIGURE} && 
+                    $CONFIGURE_TOOL --prefix=`pwd`/_release -no-shared &&
                     make && make install_sw &&
                     cd .. && rm -rf openssl && ln -sf openssl-1.0.1f/_release openssl &&
                     cd .. && rm -f ${SRS_OBJS}/_flag.ssl.arm.tmp
@@ -632,12 +652,6 @@ if [ $SRS_SSL = YES ]; then
     fi
 fi
 
-if [ $SRS_SSL = YES ]; then
-    echo "#define SRS_AUTO_SSL" >> $SRS_AUTO_HEADERS_H
-else
-    echo "#undef SRS_AUTO_SSL" >> $SRS_AUTO_HEADERS_H
-fi
-
 #####################################################################################
 # live transcoding, ffmpeg-2.1, x264-core138, lame-3.99.5, libaacplus-2.0.2.
 #####################################################################################
@@ -658,58 +672,26 @@ if [ $SRS_FFMPEG_TOOL = YES ]; then
     if [ ! -f ${SRS_OBJS}/ffmpeg/bin/ffmpeg ]; then echo "build ffmpeg-2.1 failed."; exit -1; fi
 fi
 
-# whether compile ffmpeg tool
-if [ $SRS_FFMPEG_TOOL = YES ]; then
-    echo "#define SRS_AUTO_FFMPEG_TOOL" >> $SRS_AUTO_HEADERS_H
-else
-    echo "#undef SRS_AUTO_FFMPEG_TOOL" >> $SRS_AUTO_HEADERS_H
-fi
-
-# whatever the FFMPEG tools, if transcode and ingest specified,
-# srs always compile the FFMPEG tool stub which used to start the FFMPEG process.
-if [ $SRS_FFMPEG_STUB = YES ]; then
-    echo "#define SRS_AUTO_FFMPEG_STUB" >> $SRS_AUTO_HEADERS_H
-else
-    echo "#undef SRS_AUTO_FFMPEG_STUB" >> $SRS_AUTO_HEADERS_H
-fi
-
-if [ $SRS_TRANSCODE = YES ]; then
-    echo "#define SRS_AUTO_TRANSCODE" >> $SRS_AUTO_HEADERS_H
-else
-    echo "#undef SRS_AUTO_TRANSCODE" >> $SRS_AUTO_HEADERS_H
-fi
-
-if [ $SRS_INGEST = YES ]; then
-    echo "#define SRS_AUTO_INGEST" >> $SRS_AUTO_HEADERS_H
-else
-    echo "#undef SRS_AUTO_INGEST" >> $SRS_AUTO_HEADERS_H
-fi
-
-# for statistic.
-if [ $SRS_STAT = YES ]; then
-    echo "#define SRS_AUTO_STAT" >> $SRS_AUTO_HEADERS_H
-else
-    echo "#undef SRS_AUTO_STAT" >> $SRS_AUTO_HEADERS_H
-fi
-
 #####################################################################################
 # build research code, librtmp
 #####################################################################################
-if [ $SRS_RESEARCH = YES ]; then
-    mkdir -p ${SRS_OBJS}/research
+if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then
+    if [ $SRS_RESEARCH = YES ]; then
+        mkdir -p ${SRS_OBJS}/research
 
-    (cd research/hls && make ${SRS_JOBS} && mv ts_info ../../${SRS_OBJS}/research)
-    ret=$?; if [[ $ret -ne 0 ]]; then echo "build research/hls failed, ret=$ret"; exit $ret; fi
+        (cd ${SRS_WORKDIR}/research/hls && make ${SRS_JOBS} && mv ts_info ../../${SRS_OBJS_DIR}/research)
+        ret=$?; if [[ $ret -ne 0 ]]; then echo "build research/hls failed, ret=$ret"; exit $ret; fi
 
-    (cd research/ffempty && make ${SRS_JOBS} && mv ffempty ../../${SRS_OBJS}/research)
-    ret=$?; if [[ $ret -ne 0 ]]; then echo "build research/ffempty failed, ret=$ret"; exit $ret; fi
+        (cd research/ffempty && make ${SRS_JOBS} && mv ffempty ../../${SRS_OBJS_DIR}/research)
+        ret=$?; if [[ $ret -ne 0 ]]; then echo "build research/ffempty failed, ret=$ret"; exit $ret; fi
+    fi
 fi
 
 if [ $SRS_LIBRTMP = YES ]; then
     mkdir -p ${SRS_OBJS}/research
     
     # librtmp
-    (cd research/librtmp && mkdir -p objs && ln -sf `pwd`/objs ../../${SRS_OBJS}/research/librtmp)
+    (cd ${SRS_WORKDIR}/research/librtmp && mkdir -p objs && ln -sf `pwd`/objs ../../${SRS_OBJS_DIR}/research/librtmp)
     ret=$?; if [[ $ret -ne 0 ]]; then echo "link research/librtmp failed, ret=$ret"; exit $ret; fi
 fi
 
@@ -753,89 +735,8 @@ if [ $SRS_GPERF = YES ]; then
     if [ ! -f ${SRS_OBJS}/gperf/bin/pprof ]; then echo "build gperftools-2.1 failed."; exit -1; fi
 fi
 
-if [ $SRS_GPERF = YES ]; then
-    echo "#define SRS_AUTO_GPERF" >> $SRS_AUTO_HEADERS_H
-else
-    echo "#undef SRS_AUTO_GPERF" >> $SRS_AUTO_HEADERS_H
-fi
-if [ $SRS_GPERF_MC = YES ]; then
-    echo "#define SRS_AUTO_GPERF_MC" >> $SRS_AUTO_HEADERS_H
-else
-    echo "#undef SRS_AUTO_GPERF_MC" >> $SRS_AUTO_HEADERS_H
-fi
-if [ $SRS_GPERF_MP = YES ]; then
-    echo "#define SRS_AUTO_GPERF_MP" >> $SRS_AUTO_HEADERS_H
-else
-    echo "#undef SRS_AUTO_GPERF_MP" >> $SRS_AUTO_HEADERS_H
-fi
-if [ $SRS_GPERF_CP = YES ]; then
-    echo "#define SRS_AUTO_GPERF_CP" >> $SRS_AUTO_HEADERS_H
-else
-    echo "#undef SRS_AUTO_GPERF_CP" >> $SRS_AUTO_HEADERS_H
-fi
-
-#####################################################################################
-# for embeded.
-#####################################################################################
-if [ $SRS_EMBEDED_CPU = YES ]; then
-    echo "#define SRS_AUTO_EMBEDED_CPU" >> $SRS_AUTO_HEADERS_H
-else
-    echo "#undef SRS_AUTO_EMBEDED_CPU" >> $SRS_AUTO_HEADERS_H
-fi
-
-# arm
-if [ $SRS_ARM_UBUNTU12 = YES ]; then
-    echo "#define SRS_AUTO_ARM_UBUNTU12" >> $SRS_AUTO_HEADERS_H
-else
-    echo "#undef SRS_AUTO_ARM_UBUNTU12" >> $SRS_AUTO_HEADERS_H
-fi
-
-# mips
-if [ $SRS_MIPS_UBUNTU12 = YES ]; then
-    echo "#define SRS_AUTO_MIPS_UBUNTU12" >> $SRS_AUTO_HEADERS_H
-else
-    echo "#undef SRS_AUTO_MIPS_UBUNTU12" >> $SRS_AUTO_HEADERS_H
-fi
-
-echo "" >> $SRS_AUTO_HEADERS_H
-
-# for log level compile settings
-if [ $SRS_LOG_VERBOSE = YES ]; then
-    echo "#define SRS_AUTO_VERBOSE" >> $SRS_AUTO_HEADERS_H
-else
-    echo "#undef SRS_AUTO_VERBOSE" >> $SRS_AUTO_HEADERS_H
-fi
-if [ $SRS_LOG_INFO = YES ]; then
-    echo "#define SRS_AUTO_INFO" >> $SRS_AUTO_HEADERS_H
-else
-    echo "#undef SRS_AUTO_INFO" >> $SRS_AUTO_HEADERS_H
-fi
-if [ $SRS_LOG_TRACE = YES ]; then
-    echo "#define SRS_AUTO_TRACE" >> $SRS_AUTO_HEADERS_H
-else
-    echo "#undef SRS_AUTO_TRACE" >> $SRS_AUTO_HEADERS_H
-fi
-
-# prefix
-echo "" >> $SRS_AUTO_HEADERS_H
-echo "#define SRS_AUTO_PREFIX \"${SRS_PREFIX}\"" >> $SRS_AUTO_HEADERS_H
-
-echo "" >> $SRS_AUTO_HEADERS_H
-
-#####################################################################################
-# generated the contributors from AUTHORS.txt
-#####################################################################################
-SRS_CONSTRIBUTORS=`cat ../AUTHORS.txt|grep "*"|awk '{print $2}'`
-echo "#define SRS_AUTO_CONSTRIBUTORS \"\\" >> $SRS_AUTO_HEADERS_H
-for CONTRIBUTOR in $SRS_CONSTRIBUTORS; do
-    echo "${CONTRIBUTOR} \\" >> $SRS_AUTO_HEADERS_H
-done
-echo "\"" >> $SRS_AUTO_HEADERS_H
-
-# new empty line to auto headers file.
-echo "" >> $SRS_AUTO_HEADERS_H
-
 #####################################################################################
 # generated the test script
 #####################################################################################
 rm -rf ${SRS_OBJS}/srs.test && ln -sf `pwd`/scripts/srs.test objs/srs.test
+
diff --git a/trunk/auto/generate-srs-librtmp-project.sh b/trunk/auto/generate-srs-librtmp-project.sh
new file mode 100755
index 0000000000..d2baf04666
--- /dev/null
+++ b/trunk/auto/generate-srs-librtmp-project.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+# when export srs-librtmp project
+# set the SRS_WORKDIR and SRS_OBJS, 
+# then copy the srs-librtmp needed files.
+#
+# params:
+#     $SRS_WORKDIR the work dir. ie. .
+#     $SRS_OBJS the objs directory to store the Makefile. ie. ./objs
+#     $SRS_OBJS_DIR the objs directory for Makefile. ie. objs
+#     $SRS_EXPORT_LIBRTMP_PROJECT the export srs-librtmp project path. ie. srs-librtmp
+#
+
+if [ $SRS_EXPORT_LIBRTMP_PROJECT != NO ]; then
+    if [[ -d ${SRS_EXPORT_LIBRTMP_PROJECT} ]]; then
+        echo -e "${RED}srs-librtmp target dir exists: ${SRS_EXPORT_LIBRTMP_PROJECT}. ${BLACK}"
+        exit 1
+    fi
+    # create target
+    SRS_WORKDIR=${SRS_EXPORT_LIBRTMP_PROJECT} && SRS_OBJS=${SRS_WORKDIR}/${SRS_OBJS_DIR} && mkdir -p ${SRS_OBJS} &&
+    # copy src to target
+    _CPT=${SRS_EXPORT_LIBRTMP_PROJECT}/research/librtmp && mkdir -p ${_CPT} && cp research/librtmp/*.c research/librtmp/Makefile ${_CPT} &&
+    _CPT=${SRS_EXPORT_LIBRTMP_PROJECT}/auto && mkdir -p ${_CPT} && cp auto/generate_header.sh auto/generate-srs-librtmp-single.sh ${_CPT} &&
+    _CPT=${SRS_EXPORT_LIBRTMP_PROJECT}/src/core && mkdir -p ${_CPT} && cp src/core/* ${_CPT} &&
+    _CPT=${SRS_EXPORT_LIBRTMP_PROJECT}/src/kernel && mkdir -p ${_CPT} && cp src/kernel/* ${_CPT} &&
+    _CPT=${SRS_EXPORT_LIBRTMP_PROJECT}/src/protocol && mkdir -p ${_CPT} && cp src/protocol/* ${_CPT} &&
+    _CPT=${SRS_EXPORT_LIBRTMP_PROJECT}/src/libs && mkdir -p ${_CPT} && cp src/libs/* ${_CPT}
+    # check ret
+    ret=$?; if [[ $ret -ne 0 ]]; then echo "export src failed, ret=$ret"; exit $ret; fi
+fi
\ No newline at end of file
diff --git a/trunk/auto/generate-srs-librtmp-single.sh b/trunk/auto/generate-srs-librtmp-single.sh
new file mode 100755
index 0000000000..3ca5dee79a
--- /dev/null
+++ b/trunk/auto/generate-srs-librtmp-single.sh
@@ -0,0 +1,133 @@
+#!/bin/bash
+
+# when export srs-librtmp single files
+# package the whole project to srs_librtmp.h and srs_librtmp.cpp
+#
+# params:
+#     $SRS_OBJS_DIR the objs directory for Makefile. ie. objs
+#     $SRS_EXPORT_LIBRTMP_SINGLE the export srs-librtmp single path. ie. srs-librtmp
+#
+
+# the target dir must created
+if [[ ! -d $SRS_EXPORT_LIBRTMP_SINGLE ]]; then
+    echo -e "${RED}error, target dir not created: $SRS_EXPORT_LIBRTMP_SINGLE${BLACK}"
+    exit -1
+fi
+
+# generate the srs_librtmp.h
+cp $SRS_EXPORT_LIBRTMP_SINGLE/src/libs/srs_librtmp.hpp $SRS_EXPORT_LIBRTMP_SINGLE/srs_librtmp.h
+
+# create srs_librtmp.cpp
+FILE=$SRS_EXPORT_LIBRTMP_SINGLE/srs_librtmp.cpp
+cat << END >$FILE
+/*
+The MIT License (MIT)
+
+Copyright (c) 2013-2015 SRS(simple-rtmp-server)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include "srs_librtmp.h"
+
+END
+# build objs auto files to cpp
+cat $SRS_EXPORT_LIBRTMP_SINGLE/$SRS_OBJS_DIR/srs_auto_headers.hpp >>$FILE
+ret=$?; if [[ $ret -ne 0 ]]; then 
+    echo -e "${RED}failed to generate the srs_librtmp.cpp${BLACK}"
+    exit $ret
+fi
+# module to cpp files.
+function build_module_hpp()
+{
+    echo "build files ${SRS_LIBRTMP_OBJS} to $FILE"
+    for item in ${SRS_LIBRTMP_OBJS[*]}; do
+        FILE_NAME="${item%.*}"
+        echo "// following is generated by ${FILE_NAME}.hpp" >> $FILE &&
+        sed -i "s|#include >$FILE
+        ret=$?; if [[ $ret -ne 0 ]]; then 
+            echo -e "${RED}failed to generate the srs_librtmp.cpp by ${FILE_NAME}.hpp. {${BLACK}"
+            exit $ret
+        fi
+    done
+}
+SRS_LIBRTMP_OBJS="${CORE_OBJS[@]}" && build_module_hpp
+SRS_LIBRTMP_OBJS="${KERNEL_OBJS[@]}" && build_module_hpp
+SRS_LIBRTMP_OBJS="${RTMP_OBJS[@]}" && build_module_hpp
+SRS_LIBRTMP_OBJS="${LIBS_OBJS[@]}" && build_module_hpp
+# module to cpp files.
+function build_module_cpp()
+{
+    echo "build files ${SRS_LIBRTMP_OBJS} to $FILE"
+    for item in ${SRS_LIBRTMP_OBJS[*]}; do
+        FILE_NAME="${item%.*}"
+        echo "// following is generated by ${FILE_NAME}.cpp" >> $FILE &&
+        sed -i "s|#include >$FILE
+        ret=$?; if [[ $ret -ne 0 ]]; then 
+            echo -e "${RED}failed to generate the srs_librtmp.cpp by ${FILE_NAME}.cpp. {${BLACK}"
+            exit $ret
+        fi
+    done
+}
+SRS_LIBRTMP_OBJS="${CORE_OBJS[@]}" && build_module_cpp
+SRS_LIBRTMP_OBJS="${KERNEL_OBJS[@]}" && build_module_cpp
+SRS_LIBRTMP_OBJS="${RTMP_OBJS[@]}" && build_module_cpp
+SRS_LIBRTMP_OBJS="${LIBS_OBJS[@]}" && build_module_cpp
+
+# create example.cpp
+FILE=$SRS_EXPORT_LIBRTMP_SINGLE/example.c
+SRS_SINGLE_LIBRTMP_COMPILE='gcc example.c srs_librtmp.cpp -g -O0 -lstdc++ -o example'
+cat << END >$FILE
+/**
+# Example to use srs-librtmp
+# see: https://github.com/simple-rtmp-server/srs/wiki/v2_CN_SrsLibrtmp
+    ${SRS_SINGLE_LIBRTMP_COMPILE}
+*/
+#include 
+#include "srs_librtmp.h"
+
+int main(int argc, char** argv) 
+{
+    srs_rtmp_t rtmp;
+    
+    printf("Example for srs-librtmp\n");
+    printf("SRS(simple-rtmp-server) client librtmp library.\n");
+    printf("version: %d.%d.%d\n", srs_version_major(), srs_version_minor(), srs_version_revision());
+    
+    rtmp = srs_rtmp_create("rtmp://ossrs.net/live/livestream");
+    srs_human_trace("create rtmp success");
+    srs_rtmp_destroy(rtmp);
+    
+    return 0;
+}
+
+END
+
+# compile the example
+(cd $SRS_EXPORT_LIBRTMP_SINGLE && echo "${SRS_SINGLE_LIBRTMP_COMPILE}" && 
+`${SRS_SINGLE_LIBRTMP_COMPILE}` && ./example && rm -f example)
+ret=$?; if [[ $ret -ne 0 ]]; then 
+    echo "(cd $SRS_EXPORT_LIBRTMP_SINGLE && ${SRS_SINGLE_LIBRTMP_COMPILE} && ./example && rm -f example)"
+    echo -e "${RED}failed to compile example.${BLACK}"
+    exit $ret
+fi
+
+# clear the files for srs-librtmp project, generated by generate-srs-librtmp-project.sh
+(cd $SRS_EXPORT_LIBRTMP_SINGLE && rm -rf auto $SRS_OBJS_DIR research src Makefile)
diff --git a/trunk/auto/generate_header.sh b/trunk/auto/generate_header.sh
old mode 100644
new mode 100755
diff --git a/trunk/auto/libs.sh b/trunk/auto/libs.sh
old mode 100644
new mode 100755
index fcfeabc4b1..2795aed7e0
--- a/trunk/auto/libs.sh
+++ b/trunk/auto/libs.sh
@@ -1,7 +1,8 @@
 # generate the library for static link.
 #
 # params:
-#     $SRS_OBJS the objs directory. ie. objs
+#     $SRS_OBJS the objs directory to store the Makefile. ie. ./objs
+#     $SRS_OBJS_DIR the objs directory for Makefile. ie. objs
 #     $SRS_MAKEFILE the makefile name. ie. Makefile
 #
 #     $BUILD_KEY a string indicates the build key for Makefile. ie. dump
@@ -10,7 +11,7 @@
 
 FILE=${SRS_OBJS}/${SRS_MAKEFILE}
 
-LIB_TARGET="${SRS_OBJS}/${LIB_NAME}"
+LIB_TARGET="${SRS_OBJS_DIR}/${LIB_NAME}"
 LIB_TAGET_STATIC="${LIB_TARGET}.a"
 
 echo "generate lib ${LIB_NAME} depends..."
@@ -29,14 +30,14 @@ for item in ${MODULE_OBJS[*]}; do
         continue;
     fi
     
-    OBJ_FILE=${SRS_OBJS}/$item
+    OBJ_FILE=${SRS_OBJS_DIR}/$item
     OBJ_FILE="${OBJ_FILE%.*}.o"
     echo -n "${OBJ_FILE} " >> ${FILE}
 done
 echo "" >> ${FILE}
 
 # build header file
-echo -n "	@bash auto/generate_header.sh ${SRS_OBJS}" >> ${FILE}
+echo -n "	@bash auto/generate_header.sh ${SRS_OBJS_DIR}" >> ${FILE}
 echo "" >> ${FILE}
 
 # archive librtmp.a
@@ -49,14 +50,14 @@ for item in ${MODULE_OBJS[*]}; do
         continue;
     fi
     
-    OBJ_FILE=${SRS_OBJS}/$item
+    OBJ_FILE=${SRS_OBJS_DIR}/$item
     OBJ_FILE="${OBJ_FILE%.*}.o"
     echo -n "${OBJ_FILE} " >> ${FILE}
 done
 echo "" >> ${FILE}
 
 # parent Makefile, to create module output dir before compile it.
-echo "	mkdir -p ${SRS_OBJS}/include" >> ${SRS_MAKEFILE}
-echo "	mkdir -p ${SRS_OBJS}/lib" >> ${SRS_MAKEFILE}
+echo "	@mkdir -p ${SRS_OBJS_DIR}/include" >> ${SRS_WORKDIR}/${SRS_MAKEFILE}
+echo "	@mkdir -p ${SRS_OBJS_DIR}/lib" >> ${SRS_WORKDIR}/${SRS_MAKEFILE}
 
 echo -n "generate lib ${LIB_NAME} ok"; echo '!';
diff --git a/trunk/auto/modules.sh b/trunk/auto/modules.sh
old mode 100644
new mode 100755
index 8c9a59c56f..1088e71b66
--- a/trunk/auto/modules.sh
+++ b/trunk/auto/modules.sh
@@ -1,7 +1,8 @@
 # generate the module info to Makefile
 #
 # params:
-#     $SRS_OBJS the objs directory. ie. objs
+#     $SRS_OBJS the objs directory to store the Makefile. ie. ./objs
+#     $SRS_OBJS_DIR the objs directory for Makefile. ie. objs
 #     $SRS_MAKEFILE the makefile name. ie. Makefile
 #
 #     $MODULE_DIR the module dir. ie. src/os/linux
@@ -9,6 +10,7 @@
 #     $MODULE_DEPENDS array, the denpend MODULEs id. ie. (CORE OS)
 #     $ModuleLibIncs array, the depend 3rdpart library includes. ie. (objs/st-1.9/obj objs/libx264/obj)
 #     $MODULE_FILES array, the head/cpp files of modules. ie. (public log)
+#     $DEFINES string, the build macro defines. ie. "-DMY_SRS"
 #     
 # returns:
 #     $MODULE_OBJS array, the objects of the modules, used for link the binary
@@ -73,17 +75,17 @@ echo "# OBJ for ${MODULE_ID}, each object file" >> ${FILE}
 MODULE_OBJS=()
 for item in ${MODULE_FILES[*]}; do
     CPP_FILE="${MODULE_DIR}/${item}.cpp"
-    OBJ_FILE="${SRS_OBJS}/${MODULE_DIR}/${item}.o"
+    OBJ_FILE="${SRS_OBJS_DIR}/${MODULE_DIR}/${item}.o"
     MODULE_OBJS="${MODULE_OBJS[@]} ${CPP_FILE}"
     if [ -f ${CPP_FILE} ]; then
         echo "${OBJ_FILE}: \$(${DEPS_NAME}) ${CPP_FILE} " >> ${FILE}
-        echo "	\$(CXX) -c \$(CXXFLAGS) \$(${INCS_NAME})\\" >> ${FILE}
+        echo "	\$(CXX) -c \$(CXXFLAGS) ${DEFINES} \$(${INCS_NAME})\\" >> ${FILE}
         echo "          -o ${OBJ_FILE} ${CPP_FILE}" >> ${FILE}
     fi
 done
 echo "" >> ${FILE}
 
 # parent Makefile, to create module output dir before compile it.
-echo "	mkdir -p ${SRS_OBJS}/${MODULE_DIR}" >> ${SRS_MAKEFILE}
+echo "	@mkdir -p ${SRS_OBJS_DIR}/${MODULE_DIR}" >> ${SRS_WORKDIR}/${SRS_MAKEFILE}
 
 echo -n "generate module ${MODULE_ID} ok"; echo '!';
diff --git a/trunk/auto/options.sh b/trunk/auto/options.sh
old mode 100644
new mode 100755
index 66d358edd1..5864e9cf45
--- a/trunk/auto/options.sh
+++ b/trunk/auto/options.sh
@@ -16,6 +16,7 @@ help=no
 ################################################################
 # feature options
 SRS_HLS=RESERVED
+SRS_HDS=RESERVED
 SRS_DVR=RESERVED
 SRS_NGINX=RESERVED
 SRS_SSL=RESERVED
@@ -25,6 +26,7 @@ SRS_INGEST=RESERVED
 SRS_STAT=RESERVED
 SRS_HTTP_CALLBACK=RESERVED
 SRS_HTTP_SERVER=RESERVED
+SRS_STREAM_CASTER=RESERVED
 SRS_HTTP_API=RESERVED
 SRS_LIBRTMP=RESERVED
 SRS_RESEARCH=RESERVED
@@ -58,12 +60,16 @@ SRS_LOG_TRACE=RESERVED
 # experts
 # donot compile ssl, use system ssl(-lssl) if required.
 SRS_USE_SYS_SSL=NO
+# export the srs-librtmp to specified project, NO to disable it.
+SRS_EXPORT_LIBRTMP_PROJECT=NO
+# export the srs-librtmp to a single .h and .c, NO to disable it.
+SRS_EXPORT_LIBRTMP_SINGLE=NO
 #
 ################################################################
 # presets
 # for x86/x64 pc/servers
 SRS_X86_X64=NO
-# for ios(darwin)
+# for osx system
 SRS_OSX=NO
 # armhf(v7cpu) built on ubuntu12
 SRS_ARM_UBUNTU12=NO
@@ -73,7 +79,7 @@ SRS_MIPS_UBUNTU12=NO
 SRS_DEV=NO
 # dev, open main server feature for dev, no utest/research/librtmp
 SRS_FAST_DEV=NO
-# demo, for the demo of srs, @see: https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleDemo
+# demo, for the demo of srs, @see: https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleDemo
 SRS_DEMO=NO
 # raspberry-pi, open hls/ssl/static
 SRS_PI=NO
@@ -107,11 +113,13 @@ Options:
   --with-ssl                enable rtmp complex handshake, requires openssl-devel installed.
                             to delivery h264 video and aac audio to flash player.
   --with-hls                enable hls streaming, mux RTMP to m3u8/ts files.
+  --with-hds                enable hds streaming, mux RTMP to f4m/f4v files.
   --with-dvr                enable dvr, mux RTMP to flv files.
   --with-nginx              enable delivery HTTP stream with nginx.
                             build nginx at: ./objs/nginx/sbin/nginx
   --with-http-callback      enable http hooks, build cherrypy as demo api server.
   --with-http-server        enable http server to delivery http stream.
+  --with-stream-caster      enable stream caster to serve other stream over other protocol.
   --with-http-api           enable http api, to manage SRS by http api.
   --with-ffmpeg             enable transcoding tool ffmpeg.
                             build ffmpeg at: ./objs/ffmpeg/bin/ffmpeg
@@ -131,11 +139,13 @@ Options:
   --with-arm-ubuntu12       build SRS on ubuntu12 for armhf(v7cpu).
                           
   --without-ssl             disable rtmp complex handshake.
-  --without-hls             disable hls, rtmp streaming only.
+  --without-hls             disable hls, the apple http live streaming.
+  --without-hds             disable hds, the adobe http dynamic streaming.
   --without-dvr             disable dvr, donot support record RTMP stream to flv.
   --without-nginx           disable delivery HTTP stream with nginx.
   --without-http-callback   disable http, http hooks callback.
   --without-http-server     disable http server, use external server to delivery http stream.
+  --without-stream-caster   disable stream caster, only listen and serve RTMP/HTTP.
   --without-http-api        disable http api, only use console to manage SRS process.
   --without-ffmpeg          disable the ffmpeg transcode tool feature.
   --without-transcode       disable the transcoding feature.
@@ -161,7 +171,7 @@ Options:
 
 Presets:
   --x86-x64                 [default] for x86/x64 cpu, common pc and servers.
-  --osx                     for IOS(darwin) to build SRS.
+  --osx                     for osx(darwin) system to build SRS.
   --pi                      for raspberry-pi(directly build), open features hls/ssl/static.
   --cubie                   for cubieboard(directly build), open features except ffmpeg/nginx.
   --arm                     alias for --with-arm-ubuntu12, for ubuntu12, arm crossbuild
@@ -172,7 +182,7 @@ Presets:
   --disable-all             disable all features, only support vp6 RTMP.
   --dev                     for dev, open all features, no nginx/gperf/gprof/arm.
   --fast-dev                for dev fast compile, the RTMP server, without librtmp/utest/research.
-  --demo                    for srs demo, @see: https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleDemo
+  --demo                    for srs demo, @see: https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleDemo
   --full                    enable all features, no gperf/gprof/arm.
   
 Conflicts:
@@ -184,7 +194,9 @@ Conflicts:
         the complex tools not available for arm.
 
 Experts:
-  --use-sys-ssl             donot compile ssl, use system ssl(-lssl) if required.
+  --use-sys-ssl                     donot compile ssl, use system ssl(-lssl) if required.
+  --export-librtmp-project=   export srs-librtmp to specified project in path.
+  --export-librtmp-single=    export srs-librtmp to a single file(.h+.cpp) in path.
 
 Workflow:
   1. apply "Presets". if not specified, use default preset.
@@ -202,6 +214,7 @@ function parse_user_option() {
         
         --with-ssl)                     SRS_SSL=YES                 ;;
         --with-hls)                     SRS_HLS=YES                 ;;
+        --with-hds)                     SRS_HDS=YES                 ;;
         --with-dvr)                     SRS_DVR=YES                 ;;
         --with-nginx)                   SRS_NGINX=YES               ;;
         --with-ffmpeg)                  SRS_FFMPEG_TOOL=YES         ;;
@@ -210,6 +223,7 @@ function parse_user_option() {
         --with-stat)                    SRS_STAT=YES                ;;
         --with-http-callback)           SRS_HTTP_CALLBACK=YES       ;;
         --with-http-server)             SRS_HTTP_SERVER=YES         ;;
+        --with-stream-caster)           SRS_STREAM_CASTER=YES       ;;
         --with-http-api)                SRS_HTTP_API=YES            ;;
         --with-librtmp)                 SRS_LIBRTMP=YES             ;;
         --with-research)                SRS_RESEARCH=YES            ;;
@@ -224,6 +238,7 @@ function parse_user_option() {
                                                                  
         --without-ssl)                  SRS_SSL=NO                  ;;
         --without-hls)                  SRS_HLS=NO                  ;;
+        --without-hds)                  SRS_HDS=NO                  ;;
         --without-dvr)                  SRS_DVR=NO                  ;;
         --without-nginx)                SRS_NGINX=NO                ;;
         --without-ffmpeg)               SRS_FFMPEG_TOOL=NO          ;;
@@ -232,6 +247,7 @@ function parse_user_option() {
         --without-stat)                 SRS_STAT=NO                 ;;
         --without-http-callback)        SRS_HTTP_CALLBACK=NO        ;;
         --without-http-server)          SRS_HTTP_SERVER=NO          ;;
+        --without-stream-caster)        SRS_STREAM_CASTER=NO        ;;
         --without-http-api)             SRS_HTTP_API=NO             ;;
         --without-librtmp)              SRS_LIBRTMP=NO              ;;
         --without-research)             SRS_RESEARCH=NO             ;;
@@ -267,6 +283,8 @@ function parse_user_option() {
         --full)                         SRS_ENABLE_ALL=YES          ;;
         
         --use-sys-ssl)                  SRS_USE_SYS_SSL=YES         ;;
+        --export-librtmp-project)       SRS_EXPORT_LIBRTMP_PROJECT=${value}     ;;
+        --export-librtmp-single)        SRS_EXPORT_LIBRTMP_SINGLE=${value}      ;;
 
         *)
             echo "$0: error: invalid option \"$option\""
@@ -350,6 +368,7 @@ function apply_user_presets() {
     # all disabled.
     if [ $SRS_DISABLE_ALL = YES ]; then
         SRS_HLS=NO
+        SRS_HDS=NO
         SRS_DVR=NO
         SRS_NGINX=NO
         SRS_SSL=NO
@@ -360,6 +379,7 @@ function apply_user_presets() {
         SRS_HTTP_PARSER=NO
         SRS_HTTP_CALLBACK=NO
         SRS_HTTP_SERVER=NO
+        SRS_STREAM_CASTER=NO
         SRS_HTTP_API=NO
         SRS_LIBRTMP=NO
         SRS_RESEARCH=NO
@@ -375,6 +395,7 @@ function apply_user_presets() {
     # all enabled.
     if [ $SRS_ENABLE_ALL = YES ]; then
         SRS_HLS=YES
+        SRS_HDS=YES
         SRS_DVR=YES
         SRS_NGINX=YES
         SRS_SSL=YES
@@ -385,6 +406,7 @@ function apply_user_presets() {
         SRS_HTTP_PARSER=YES
         SRS_HTTP_CALLBACK=YES
         SRS_HTTP_SERVER=YES
+        SRS_STREAM_CASTER=YES
         SRS_HTTP_API=YES
         SRS_LIBRTMP=YES
         SRS_RESEARCH=YES
@@ -400,6 +422,7 @@ function apply_user_presets() {
     # only rtmp vp6
     if [ $SRS_FAST = YES ]; then
         SRS_HLS=NO
+        SRS_HDS=NO
         SRS_DVR=NO
         SRS_NGINX=NO
         SRS_SSL=NO
@@ -410,6 +433,7 @@ function apply_user_presets() {
         SRS_HTTP_PARSER=NO
         SRS_HTTP_CALLBACK=NO
         SRS_HTTP_SERVER=NO
+        SRS_STREAM_CASTER=NO
         SRS_HTTP_API=NO
         SRS_LIBRTMP=NO
         SRS_RESEARCH=NO
@@ -425,6 +449,7 @@ function apply_user_presets() {
     # all disabled.
     if [ $SRS_RTMP_HLS = YES ]; then
         SRS_HLS=YES
+        SRS_HDS=YES
         SRS_DVR=NO
         SRS_NGINX=NO
         SRS_SSL=YES
@@ -435,6 +460,7 @@ function apply_user_presets() {
         SRS_HTTP_PARSER=NO
         SRS_HTTP_CALLBACK=NO
         SRS_HTTP_SERVER=NO
+        SRS_STREAM_CASTER=NO
         SRS_HTTP_API=NO
         SRS_LIBRTMP=NO
         SRS_RESEARCH=NO
@@ -450,6 +476,7 @@ function apply_user_presets() {
     # only ssl for RTMP with complex handshake.
     if [ $SRS_PURE_RTMP = YES ]; then
         SRS_HLS=NO
+        SRS_HDS=NO
         SRS_DVR=NO
         SRS_NGINX=NO
         SRS_SSL=YES
@@ -460,6 +487,7 @@ function apply_user_presets() {
         SRS_HTTP_PARSER=NO
         SRS_HTTP_CALLBACK=NO
         SRS_HTTP_SERVER=NO
+        SRS_STREAM_CASTER=NO
         SRS_HTTP_API=NO
         SRS_LIBRTMP=NO
         SRS_RESEARCH=NO
@@ -475,6 +503,7 @@ function apply_user_presets() {
     # if arm specified, set some default to disabled.
     if [ $SRS_ARM_UBUNTU12 = YES ]; then
         SRS_HLS=YES
+        SRS_HDS=YES
         SRS_DVR=YES
         SRS_NGINX=NO
         SRS_SSL=YES
@@ -485,6 +514,7 @@ function apply_user_presets() {
         SRS_HTTP_PARSER=YES
         SRS_HTTP_CALLBACK=YES
         SRS_HTTP_SERVER=YES
+        SRS_STREAM_CASTER=NO
         SRS_HTTP_API=YES
         SRS_LIBRTMP=YES
         SRS_RESEARCH=NO
@@ -501,6 +531,7 @@ function apply_user_presets() {
     # if mips specified, set some default to disabled.
     if [ $SRS_MIPS_UBUNTU12 = YES ]; then
         SRS_HLS=YES
+        SRS_HDS=YES
         SRS_DVR=YES
         SRS_NGINX=NO
         SRS_SSL=YES
@@ -511,6 +542,7 @@ function apply_user_presets() {
         SRS_HTTP_PARSER=YES
         SRS_HTTP_CALLBACK=YES
         SRS_HTTP_SERVER=YES
+        SRS_STREAM_CASTER=NO
         SRS_HTTP_API=YES
         SRS_LIBRTMP=YES
         SRS_RESEARCH=NO
@@ -526,6 +558,7 @@ function apply_user_presets() {
     # defaults for x86/x64
     if [ $SRS_X86_X64 = YES ]; then
         SRS_HLS=YES
+        SRS_HDS=YES
         SRS_DVR=YES
         SRS_NGINX=NO
         SRS_SSL=YES
@@ -536,6 +569,7 @@ function apply_user_presets() {
         SRS_HTTP_PARSER=YES
         SRS_HTTP_CALLBACK=YES
         SRS_HTTP_SERVER=YES
+        SRS_STREAM_CASTER=NO
         SRS_HTTP_API=YES
         SRS_LIBRTMP=YES
         SRS_RESEARCH=NO
@@ -548,23 +582,25 @@ function apply_user_presets() {
         SRS_STATIC=NO
     fi
 
-    # if dev specified, open features if possible.
-    if [ $SRS_DEV = YES ]; then
+    # for osx(darwin)
+    if [ $SRS_OSX = YES ]; then
         SRS_HLS=YES
+        SRS_HDS=YES
         SRS_DVR=YES
         SRS_NGINX=NO
         SRS_SSL=YES
-        SRS_FFMPEG_TOOL=YES
+        SRS_FFMPEG_TOOL=NO
         SRS_TRANSCODE=YES
         SRS_INGEST=YES
         SRS_STAT=YES
         SRS_HTTP_PARSER=YES
         SRS_HTTP_CALLBACK=YES
         SRS_HTTP_SERVER=YES
+        SRS_STREAM_CASTER=NO
         SRS_HTTP_API=YES
         SRS_LIBRTMP=YES
-        SRS_RESEARCH=YES
-        SRS_UTEST=YES
+        SRS_RESEARCH=NO
+        SRS_UTEST=NO
         SRS_GPERF=NO
         SRS_GPERF_MC=NO
         SRS_GPERF_MP=NO
@@ -573,23 +609,25 @@ function apply_user_presets() {
         SRS_STATIC=NO
     fi
 
-    # if fast dev specified, open main server features.
-    if [ $SRS_FAST_DEV = YES ]; then
+    # if dev specified, open features if possible.
+    if [ $SRS_DEV = YES ]; then
         SRS_HLS=YES
+        SRS_HDS=YES
         SRS_DVR=YES
         SRS_NGINX=NO
         SRS_SSL=YES
-        SRS_FFMPEG_TOOL=NO
+        SRS_FFMPEG_TOOL=YES
         SRS_TRANSCODE=YES
         SRS_INGEST=YES
         SRS_STAT=YES
         SRS_HTTP_PARSER=YES
         SRS_HTTP_CALLBACK=YES
         SRS_HTTP_SERVER=YES
+        SRS_STREAM_CASTER=NO
         SRS_HTTP_API=YES
-        SRS_LIBRTMP=NO
-        SRS_RESEARCH=NO
-        SRS_UTEST=NO
+        SRS_LIBRTMP=YES
+        SRS_RESEARCH=YES
+        SRS_UTEST=YES
         SRS_GPERF=NO
         SRS_GPERF_MC=NO
         SRS_GPERF_MP=NO
@@ -598,19 +636,21 @@ function apply_user_presets() {
         SRS_STATIC=NO
     fi
 
-    # if osx dev specified, open main server features.
-    if [ $SRS_OSX = YES ]; then
+    # if fast dev specified, open main server features.
+    if [ $SRS_FAST_DEV = YES ]; then
         SRS_HLS=YES
+        SRS_HDS=YES
         SRS_DVR=YES
         SRS_NGINX=NO
         SRS_SSL=YES
         SRS_FFMPEG_TOOL=NO
         SRS_TRANSCODE=YES
         SRS_INGEST=YES
-        SRS_STAT=NO
+        SRS_STAT=YES
         SRS_HTTP_PARSER=YES
         SRS_HTTP_CALLBACK=YES
         SRS_HTTP_SERVER=YES
+        SRS_STREAM_CASTER=NO
         SRS_HTTP_API=YES
         SRS_LIBRTMP=NO
         SRS_RESEARCH=NO
@@ -622,12 +662,11 @@ function apply_user_presets() {
         SRS_GPROF=NO
         SRS_STATIC=NO
     fi
-
-
-
+	
     # for srs demo
     if [ $SRS_DEMO = YES ]; then
         SRS_HLS=YES
+        SRS_HDS=YES
         SRS_DVR=YES
         SRS_NGINX=NO
         SRS_SSL=YES
@@ -638,6 +677,7 @@ function apply_user_presets() {
         SRS_HTTP_PARSER=YES
         SRS_HTTP_CALLBACK=YES
         SRS_HTTP_SERVER=YES
+        SRS_STREAM_CASTER=NO
         SRS_HTTP_API=YES
         SRS_LIBRTMP=YES
         SRS_RESEARCH=NO
@@ -653,6 +693,7 @@ function apply_user_presets() {
     # if raspberry-pi specified, open ssl/hls/static features
     if [ $SRS_PI = YES ]; then
         SRS_HLS=YES
+        SRS_HDS=YES
         SRS_DVR=YES
         SRS_NGINX=NO
         SRS_SSL=YES
@@ -663,6 +704,7 @@ function apply_user_presets() {
         SRS_HTTP_PARSER=YES
         SRS_HTTP_CALLBACK=YES
         SRS_HTTP_SERVER=YES
+        SRS_STREAM_CASTER=NO
         SRS_HTTP_API=YES
         SRS_LIBRTMP=YES
         SRS_RESEARCH=NO
@@ -678,6 +720,7 @@ function apply_user_presets() {
     # if cubieboard specified, open features except ffmpeg/nginx.
     if [ $SRS_CUBIE = YES ]; then
         SRS_HLS=YES
+        SRS_HDS=YES
         SRS_DVR=YES
         SRS_NGINX=NO
         SRS_SSL=YES
@@ -688,6 +731,7 @@ function apply_user_presets() {
         SRS_HTTP_PARSER=YES
         SRS_HTTP_CALLBACK=YES
         SRS_HTTP_SERVER=YES
+        SRS_STREAM_CASTER=NO
         SRS_HTTP_API=YES
         SRS_LIBRTMP=YES
         SRS_RESEARCH=NO
@@ -729,6 +773,38 @@ function apply_user_detail_options() {
     else
         export SRS_JOBS="--jobs=${SRS_JOBS}"
     fi
+    
+    # if specified export single file, export project first.
+    if [ $SRS_EXPORT_LIBRTMP_SINGLE != NO ]; then
+        SRS_EXPORT_LIBRTMP_PROJECT=$SRS_EXPORT_LIBRTMP_SINGLE
+    fi
+    
+    # disable almost all features for export srs-librtmp.
+    if [ $SRS_EXPORT_LIBRTMP_PROJECT != NO ]; then
+        SRS_HLS=NO
+        SRS_HDS=NO
+        SRS_DVR=NO
+        SRS_NGINX=NO
+        SRS_SSL=NO
+        SRS_FFMPEG_TOOL=NO
+        SRS_TRANSCODE=NO
+        SRS_INGEST=NO
+        SRS_STAT=NO
+        SRS_HTTP_PARSER=NO
+        SRS_HTTP_CALLBACK=NO
+        SRS_HTTP_SERVER=NO
+        SRS_STREAM_CASTER=NO
+        SRS_HTTP_API=NO
+        SRS_LIBRTMP=YES
+        SRS_RESEARCH=YES
+        SRS_UTEST=NO
+        SRS_GPERF=NO
+        SRS_GPERF_MC=NO
+        SRS_GPERF_MP=NO
+        SRS_GPERF_CP=NO
+        SRS_GPROF=NO
+        SRS_STATIC=NO
+    fi
 }
 apply_user_detail_options
 
@@ -736,8 +812,9 @@ function regenerate_options() {
     # save all config options to macro to write to auto headers file
     SRS_AUTO_USER_CONFIGURE="$opt"
     # regenerate the options for default values.
-    SRS_AUTO_CONFIGURE="--prefix=${SRS_PREFIX}"
+SRS_AUTO_CONFIGURE="--prefix=${SRS_PREFIX}"
     if [ $SRS_HLS = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-hls"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-hls"; fi
+    if [ $SRS_HDS = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-hds"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-hds"; fi
     if [ $SRS_DVR = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-dvr"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-dvr"; fi
     if [ $SRS_NGINX = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-nginx"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-nginx"; fi
     if [ $SRS_SSL = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-ssl"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-ssl"; fi
@@ -747,6 +824,7 @@ function regenerate_options() {
     if [ $SRS_STAT = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-stat"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-stat"; fi
     if [ $SRS_HTTP_CALLBACK = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-http-callback"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-http-callback"; fi
     if [ $SRS_HTTP_SERVER = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-http-server"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-http-server"; fi
+    if [ $SRS_STREAM_CASTER = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-stream-caster"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-stream-caster"; fi
     if [ $SRS_HTTP_API = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-http-api"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-http-api"; fi
     if [ $SRS_LIBRTMP = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-librtmp"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-librtmp"; fi
     if [ $SRS_RESEARCH = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-research"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-research"; fi
@@ -812,35 +890,19 @@ function check_option_conflicts() {
             echo "x86/x64 should never use static, see: ./configure --help"; __check_ok=NO;
         fi
     fi
-
-    # for darwin, not support stat yet.
-    if [ $SRS_OSX = YES ]; then
-        if [ $SRS_STAT = YES ]; then
-            echo "osx should never use stat, see: ./configure --help"; __check_ok=NO;
-        fi
-    fi
-    
-    # for darwin, must use --osx, vice versa
-    if [ $SRS_OSX = YES ]; then 
-        if [ `uname -s` != Darwin ]; then
-            echo "--osx is for darwin(your os is not), see: ./configure --help"; __check_ok=NO;
-        fi
-    else
-        if [ `uname -s` = Darwin ]; then
-            echo "use --osx for darwin, see: ./configure --help"; __check_ok=NO;
-        fi
-    fi
     
     # TODO: FIXME: check more os.
 
     # check variable neccessary
     if [ $SRS_HLS = RESERVED ]; then echo "you must specifies the hls, see: ./configure --help"; __check_ok=NO; fi
+    if [ $SRS_HDS = RESERVED ]; then echo "you must specifies the hds, see: ./configure --help"; __check_ok=NO; fi
     if [ $SRS_DVR = RESERVED ]; then echo "you must specifies the dvr, see: ./configure --help"; __check_ok=NO; fi
     if [ $SRS_NGINX = RESERVED ]; then echo "you must specifies the nginx, see: ./configure --help"; __check_ok=NO; fi
     if [ $SRS_SSL = RESERVED ]; then echo "you must specifies the ssl, see: ./configure --help"; __check_ok=NO; fi
     if [ $SRS_FFMPEG_TOOL = RESERVED ]; then echo "you must specifies the ffmpeg, see: ./configure --help"; __check_ok=NO; fi
     if [ $SRS_HTTP_CALLBACK = RESERVED ]; then echo "you must specifies the http-callback, see: ./configure --help"; __check_ok=NO; fi
     if [ $SRS_HTTP_SERVER = RESERVED ]; then echo "you must specifies the http-server, see: ./configure --help"; __check_ok=NO; fi
+    if [ $SRS_STREAM_CASTER = RESERVED ]; then echo "you must specifies the stream-caster, see: ./configure --help"; __check_ok=NO; fi
     if [ $SRS_HTTP_API = RESERVED ]; then echo "you must specifies the http-api, see: ./configure --help"; __check_ok=NO; fi
     if [ $SRS_LIBRTMP = RESERVED ]; then echo "you must specifies the librtmp, see: ./configure --help"; __check_ok=NO; fi
     if [ $SRS_RESEARCH = RESERVED ]; then echo "you must specifies the research, see: ./configure --help"; __check_ok=NO; fi
diff --git a/trunk/auto/summary.sh b/trunk/auto/summary.sh
new file mode 100755
index 0000000000..cbe5cc0750
--- /dev/null
+++ b/trunk/auto/summary.sh
@@ -0,0 +1,128 @@
+#!/bin/bash
+
+# colorful summary
+SrsHlsSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_HLS = YES ]; then SrsHlsSummaryColor="\${GREEN}"; fi
+SrsDvrSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_DVR = YES ]; then SrsDvrSummaryColor="\${GREEN}"; fi
+SrsNginxSummaryColor="\${GREEN}{disabled} "; if [ $SRS_NGINX = YES ]; then SrsNginxSummaryColor="\${GREEN}"; fi
+SrsSslSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_SSL = YES ]; then SrsSslSummaryColor="\${GREEN}"; fi
+SrsFfmpegSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_FFMPEG_TOOL = YES ]; then SrsFfmpegSummaryColor="\${GREEN}"; fi
+SrsTranscodeSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_TRANSCODE = YES ]; then SrsTranscodeSummaryColor="\${GREEN}"; fi
+SrsIngestSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_INGEST = YES ]; then SrsIngestSummaryColor="\${GREEN}"; fi
+SrsHttpCallbackSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_HTTP_CALLBACK = YES ]; then SrsHttpCallbackSummaryColor="\${GREEN}"; fi
+SrsHttpServerSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_HTTP_SERVER = YES ]; then SrsHttpServerSummaryColor="\${GREEN}"; fi
+SrsHttpApiSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_HTTP_API = YES ]; then SrsHttpApiSummaryColor="\${GREEN}"; fi
+SrsStreamCasterSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_STREAM_CASTER = YES ]; then SrsStreamCasterSummaryColor="\${GREEN}"; fi
+SrsLibrtmpSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_LIBRTMP = YES ]; then SrsLibrtmpSummaryColor="\${GREEN}"; fi
+SrsLibrtmpSSLSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_LIBRTMP = YES ]; then if [ $SRS_SSL = YES ]; then SrsLibrtmpSSLSummaryColor="\${GREEN}"; fi fi
+SrsResearchSummaryColor="\${GREEN}{disabled} "; if [ $SRS_RESEARCH = YES ]; then SrsResearchSummaryColor="\${GREEN}"; fi
+SrsUtestSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_UTEST = YES ]; then SrsUtestSummaryColor="\${GREEN}"; fi
+SrsGperfSummaryColor="\${GREEN}{disabled} "; if [ $SRS_GPERF = YES ]; then SrsGperfSummaryColor="\${GREEN}"; fi
+SrsGperfMCSummaryColor="\${GREEN}{disabled} "; if [ $SRS_GPERF_MC = YES ]; then SrsGperfMCSummaryColor="\${YELLOW}"; fi
+SrsGperfMPSummaryColor="\${GREEN}{disabled} "; if [ $SRS_GPERF_MP = YES ]; then SrsGperfMPSummaryColor="\${YELLOW}"; fi
+SrsGperfCPSummaryColor="\${GREEN}{disabled} "; if [ $SRS_GPERF_CP = YES ]; then SrsGperfCPSummaryColor="\${YELLOW}"; fi
+SrsGprofSummaryColor="\${GREEN}{disabled} "; if [ $SRS_GPROF = YES ]; then SrsGprofSummaryColor="\${YELLOW}"; fi
+
+if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then
+    cat < ${SRS_OBJS}/${SRS_BUILD_SUMMARY}
+#!/bin/bash
+
+#####################################################################################
+# linux shell color support.
+RED="\\${RED}"
+GREEN="\\${GREEN}"
+YELLOW="\\${YELLOW}"
+BLACK="\\${BLACK}"
+
+echo -e "\${GREEN}build summary:\${BLACK}"
+echo -e "     \${BLACK}+------------------------------------------------------------------------------------\${BLACK}"
+echo -e "     |${SrsGperfSummaryColor}gperf @see: https://github.com/simple-rtmp-server/srs/wiki/v1_CN_GPERF\${BLACK}"
+echo -e "     |     ${SrsGperfMCSummaryColor}gmc @see: http://google-perftools.googlecode.com/svn/trunk/doc/heap_checker.html\${BLACK}"
+echo -e "     |     ${SrsGperfMCSummaryColor}gmc: gperf memory check\${BLACK}"
+echo -e "     |             ${SrsGperfMCSummaryColor}env PPROF_PATH=./objs/pprof HEAPCHECK=normal ./objs/srs -c conf/console.conf # start gmc\${BLACK}"
+echo -e "     |             ${SrsGperfMCSummaryColor}killall -2 srs # or CTRL+C to stop gmc\${BLACK}"
+echo -e "     |     ${SrsGperfMPSummaryColor}gmp @see: http://google-perftools.googlecode.com/svn/trunk/doc/heapprofile.html\${BLACK}"
+echo -e "     |     ${SrsGperfMPSummaryColor}gmp: gperf memory profile\${BLACK}"
+echo -e "     |             ${SrsGperfMPSummaryColor}rm -f gperf.srs.gmp*; ./objs/srs -c conf/console.conf # start gmp\${BLACK}"
+echo -e "     |             ${SrsGperfMPSummaryColor}killall -2 srs # or CTRL+C to stop gmp\${BLACK}"
+echo -e "     |             ${SrsGperfMPSummaryColor}./objs/pprof --text objs/srs gperf.srs.gmp* # to analysis memory profile\${BLACK}"
+echo -e "     |     ${SrsGperfCPSummaryColor}gcp @see: http://google-perftools.googlecode.com/svn/trunk/doc/cpuprofile.html\${BLACK}"
+echo -e "     |     ${SrsGperfCPSummaryColor}gcp: gperf cpu profile\${BLACK}"
+echo -e "     |             ${SrsGperfCPSummaryColor}rm -f gperf.srs.gcp*; ./objs/srs -c conf/console.conf # start gcp\${BLACK}"
+echo -e "     |             ${SrsGperfCPSummaryColor}killall -2 srs # or CTRL+C to stop gcp\${BLACK}"
+echo -e "     |             ${SrsGperfCPSummaryColor}./objs/pprof --text objs/srs gperf.srs.gcp* # to analysis cpu profile\${BLACK}"
+echo -e "     \${BLACK}+------------------------------------------------------------------------------------\${BLACK}"
+echo -e "     |${SrsGprofSummaryColor}gprof @see: https://github.com/simple-rtmp-server/srs/wiki/v1_CN_GPROF\${BLACK}"
+echo -e "     |${SrsGprofSummaryColor}gprof: GNU profile tool, @see: http://www.cs.utah.edu/dept/old/texinfo/as/gprof.html\${BLACK}"
+echo -e "     |     ${SrsGprofSummaryColor}rm -f gmon.out; ./objs/srs -c conf/console.conf # start gprof\${BLACK}"
+echo -e "     |     ${SrsGprofSummaryColor}killall -2 srs # or CTRL+C to stop gprof\${BLACK}"
+echo -e "     |     ${SrsGprofSummaryColor}gprof -b ./objs/srs gmon.out > gprof.srs.log && rm -f gmon.out # gprof report to gprof.srs.log\${BLACK}"
+echo -e "     \${BLACK}+------------------------------------------------------------------------------------\${BLACK}"
+echo -e "     |${SrsUtestSummaryColor}utest: ./objs/srs_utest, the utest for srs\${BLACK}"
+echo -e "     \${BLACK}+------------------------------------------------------------------------------------\${BLACK}"
+echo -e "     |${SrsLibrtmpSummaryColor}librtmp @see: https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SrsLibrtmp\${BLACK}"
+echo -e "     |${SrsLibrtmpSummaryColor}librtmp: ./objs/include, ./objs/lib, the srs-librtmp library\${BLACK}"
+echo -e "     |     ${SrsLibrtmpSummaryColor}simple handshake: publish/play stream with simple handshake to server\${BLACK}"
+echo -e "     |     ${SrsLibrtmpSSLSummaryColor}complex handshake: it's not required for client, recommend disable it\${BLACK}"
+echo -e "     |     ${SrsLibrtmpSummaryColor}librtmp-sample: ./research/librtmp, the srs-librtmp client sample\${BLACK}"
+echo -e "     |     ${SrsLibrtmpSummaryColor}librtmp-sample: ./research/librtmp/objs/srs_ingest_flv\${BLACK}"
+echo -e "     |     ${SrsLibrtmpSummaryColor}librtmp-sample: ./research/librtmp/objs/srs_ingest_rtmp\${BLACK}"
+echo -e "     |     ${SrsLibrtmpSummaryColor}librtmp-sample: ./research/librtmp/objs/srs_detect_rtmp\${BLACK}"
+echo -e "     |     ${SrsLibrtmpSummaryColor}librtmp-sample: ./research/librtmp/objs/srs_bandwidth_check\${BLACK}"
+echo -e "     \${BLACK}+------------------------------------------------------------------------------------\${BLACK}"
+echo -e "     |${SrsResearchSummaryColor}research: ./objs/research, api server, players, ts info, librtmp.\${BLACK}"
+echo -e "     |     ${SrsResearchSummaryColor} @see https://github.com/simple-rtmp-server/srs/wiki/v2_CN_SrsLibrtmp#srs-librtmp-examples\${BLACK}"
+echo -e "     \${BLACK}+------------------------------------------------------------------------------------\${BLACK}"
+echo -e "     |\${GREEN}tools: important tool, others @see https://github.com/simple-rtmp-server/srs/wiki/v2_CN_SrsLibrtmp#srs-librtmp-examples\${BLACK}"
+echo -e "     |     \${GREEN}./objs/srs_ingest_hls -i http://ossrs.net/live/livestream.m3u8 -y rtmp://127.0.0.1/live/livestream\${BLACK}"
+echo -e "     \${BLACK}+------------------------------------------------------------------------------------\${BLACK}"
+echo -e "     |\${GREEN}server: ./objs/srs -c conf/srs.conf, start the srs server\${BLACK}"
+echo -e "     |     ${SrsHlsSummaryColor}hls @see: https://github.com/simple-rtmp-server/srs/wiki/v2_CN_DeliveryHLS\${BLACK}"
+echo -e "     |     ${SrsHlsSummaryColor}hls: generate m3u8 and ts from rtmp stream\${BLACK}"
+echo -e "     |     ${SrsDvrSummaryColor}dvr @see: https://github.com/simple-rtmp-server/srs/wiki/v2_CN_DVR\${BLACK}"
+echo -e "     |     ${SrsDvrSummaryColor}dvr: record RTMP stream to flv files.\${BLACK}"
+echo -e "     |     ${SrsNginxSummaryColor}nginx @see: https://github.com/simple-rtmp-server/srs/wiki/v2_CN_DeliveryHLS\${BLACK}"
+echo -e "     |     ${SrsNginxSummaryColor}nginx: delivery HLS stream by nginx\${BLACK}"
+echo -e "     |     ${SrsNginxSummaryColor}nginx: sudo ./objs/nginx/sbin/nginx\${BLACK}"
+echo -e "     |     ${SrsSslSummaryColor}ssl @see: https://github.com/simple-rtmp-server/srs/wiki/v1_CN_RTMPHandshake\${BLACK}"
+echo -e "     |     ${SrsSslSummaryColor}ssl: support RTMP complex handshake for client required, for instance, flash\${BLACK}"
+echo -e "     |     ${SrsFfmpegSummaryColor}ffmpeg @see: https://github.com/simple-rtmp-server/srs/wiki/v1_CN_FFMPEG\${BLACK}"
+echo -e "     |     ${SrsFfmpegSummaryColor}ffmpeg: transcode, mux, ingest tool\${BLACK}"
+echo -e "     |     ${SrsFfmpegSummaryColor}ffmpeg: ./objs/ffmpeg/bin/ffmpeg\${BLACK}"
+echo -e "     |     ${SrsTranscodeSummaryColor}transcode @see: https://github.com/simple-rtmp-server/srs/wiki/v1_CN_FFMPEG\${BLACK}"
+echo -e "     |     ${SrsTranscodeSummaryColor}transcode: support transcoding RTMP stream\${BLACK}"
+echo -e "     |     ${SrsIngestSummaryColor}ingest @see: https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Ingest\${BLACK}"
+echo -e "     |     ${SrsIngestSummaryColor}ingest: support ingest file/stream/device then push to SRS by RTMP stream\${BLACK}"
+echo -e "     |     ${SrsHttpCallbackSummaryColor}http-callback @see: https://github.com/simple-rtmp-server/srs/wiki/v2_CN_HTTPCallback\${BLACK}"
+echo -e "     |     ${SrsHttpCallbackSummaryColor}http-callback: support http callback for authentication and event injection\${BLACK}"
+echo -e "     |     ${SrsHttpServerSummaryColor}http-server @see: https://github.com/simple-rtmp-server/srs/wiki/v2_CN_HTTPServer\${BLACK}"
+echo -e "     |     ${SrsHttpServerSummaryColor}http-server: support http server to delivery http stream\${BLACK}"
+echo -e "     |     ${SrsHttpApiSummaryColor}http-api @see: https://github.com/simple-rtmp-server/srs/wiki/v2_CN_HTTPApi\${BLACK}"
+echo -e "     |     ${SrsHttpApiSummaryColor}http-api: support http api to manage server\${BLACK}"
+echo -e "     |     ${SrsStreamCasterSummaryColor}stream-caster @see: https://github.com/simple-rtmp-server/srs/wiki/v2_CN_Streamer\${BLACK}"
+echo -e "     |     ${SrsStreamCasterSummaryColor}stream-caster: start server to cast stream over other protocols.\${BLACK}"
+echo -e "     \${BLACK}+------------------------------------------------------------------------------------\${BLACK}"
+echo -e "\${GREEN}binaries @see: https://github.com/simple-rtmp-server/srs/wiki/v2_CN_Build\${BLACK}"
+
+echo "You can:"
+echo "      ./objs/srs -c conf/srs.conf"
+echo "                  to start the srs server, with config conf/srs.conf."
+END
+else
+    cat < ${SRS_OBJS}/${SRS_BUILD_SUMMARY}
+#!/bin/bash
+
+#####################################################################################
+# linux shell color support.
+RED="\\${RED}"
+GREEN="\\${GREEN}"
+YELLOW="\\${YELLOW}"
+BLACK="\\${BLACK}"
+
+echo -e "\${BLACK}You can use srs-librtmp at:\${BLACK}"
+echo -e "\${GREEN}      objs/include/srs_librtmp.h\${BLACK}"
+echo -e "\${GREEN}      objs/lib/srs_librtmp.a\${BLACK}"
+echo -e "\${BLACK}Examples for srs-librtmp at:\${BLACK}"
+echo -e "\${GREEN}      objs/research/librtmp\${BLACK}"
+echo -e "\${GREEN}      Examples: https://github.com/simple-rtmp-server/srs/wiki/v2_CN_SrsLibrtmp#srs-librtmp-examples\${BLACK}"
+END
+fi
diff --git a/trunk/auto/utest.sh b/trunk/auto/utest.sh
index 45b4a15bcb..f789e8a846 100755
--- a/trunk/auto/utest.sh
+++ b/trunk/auto/utest.sh
@@ -1,7 +1,8 @@
 # generate utest Makefile
 #
 # params:
-#     $SRS_OBJS the objs directory. ie. objs
+#     $SRS_OBJS the objs directory to store the Makefile. ie. ./objs
+#     $SRS_OBJS_DIR the objs directory for Makefile. ie. objs
 #     $SRS_MAKEFILE the makefile name. ie. Makefile
 #
 #     $APP_NAME the app name to output. ie. srs_utest
@@ -17,10 +18,10 @@ mkdir -p ${SRS_OBJS}/utest
 # trunk of srs, which contains the src dir, relative to objs/utest, it's trunk
 SRS_TRUNK_PREFIX=../..
 # gest dir, relative to objs/utest, it's trunk/objs/gtest
-GTEST_DIR=${SRS_TRUNK_PREFIX}/${SRS_OBJS}/gtest
+GTEST_DIR=${SRS_TRUNK_PREFIX}/${SRS_OBJS_DIR}/gtest
 
 cat << END > ${FILE}
-# user must run make the ${SRS_OBJS}/utest dir
+# user must run make the ${SRS_OBJS_DIR}/utest dir
 # at the same dir of Makefile.
 
 # A sample Makefile for building Google Test and using it in user
@@ -52,7 +53,7 @@ CXXFLAGS += -g -Wall -Wextra -O0
 
 # All tests produced by this Makefile.  Remember to add new tests you
 # created to the list.
-TESTS = ${SRS_TRUNK_PREFIX}/${SRS_OBJS}/${APP_NAME}
+TESTS = ${SRS_TRUNK_PREFIX}/${SRS_OBJS_DIR}/${APP_NAME}
 
 # All Google Test headers.  Usually you shouldn't change this
 # definition.
@@ -129,7 +130,7 @@ echo "# Depends, the depends objects" >> ${FILE}
 echo -n "SRS_UTEST_DEPS = " >> ${FILE}
 for item in ${MODULE_OBJS[*]}; do
     FILE_NAME=${item%.*}
-    echo -n "${SRS_TRUNK_PREFIX}/${SRS_OBJS}/${FILE_NAME}.o " >> ${FILE}
+    echo -n "${SRS_TRUNK_PREFIX}/${SRS_OBJS_DIR}/${FILE_NAME}.o " >> ${FILE}
 done
 echo "" >> ${FILE}; echo "" >> ${FILE}
 #
@@ -169,12 +170,12 @@ echo "" >> ${FILE}; echo "" >> ${FILE}
 #
 echo "# generate the utest binary" >> ${FILE}
 cat << END >> ${FILE}
-${SRS_TRUNK_PREFIX}/${SRS_OBJS}/${APP_NAME} : \$(SRS_UTEST_DEPS) ${MODULE_OBJS} gtest_main.a
+${SRS_TRUNK_PREFIX}/${SRS_OBJS_DIR}/${APP_NAME} : \$(SRS_UTEST_DEPS) ${MODULE_OBJS} gtest_main.a
 	\$(CXX) -o \$@ \$(CPPFLAGS) \$(CXXFLAGS) \$^ \$(DEPS_LIBRARIES_FILES) ${LINK_OPTIONS}
 END
 
 #####################################################################################
 # parent Makefile, to create module output dir before compile it.
-echo "	mkdir -p ${SRS_OBJS}/utest" >> ${SRS_MAKEFILE}
+echo "	@mkdir -p ${SRS_OBJS_DIR}/utest" >> ${SRS_WORKDIR}/${SRS_MAKEFILE}
 
 echo -n "generate utest ok"; echo '!';
diff --git a/trunk/conf/console.conf b/trunk/conf/console.conf
index e53d87c264..6814346328 100644
--- a/trunk/conf/console.conf
+++ b/trunk/conf/console.conf
@@ -9,7 +9,7 @@ http_api {
     enabled         on;
     listen          1985;
 }
-http_stream {
+http_server {
     enabled         on;
     listen          8080;
 }
diff --git a/trunk/conf/demo.19350.conf b/trunk/conf/demo.19350.conf
index 0aada8e339..e85579c7ea 100644
--- a/trunk/conf/demo.19350.conf
+++ b/trunk/conf/demo.19350.conf
@@ -1,5 +1,5 @@
 # the config for srs demo
-# @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleDemo
+# @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleDemo
 # @see full.conf for detail config.
 
 listen              19350;
diff --git a/trunk/conf/demo.conf b/trunk/conf/demo.conf
index 191ff87f02..60b80f5b6e 100644
--- a/trunk/conf/demo.conf
+++ b/trunk/conf/demo.conf
@@ -1,5 +1,5 @@
 # the config for srs demo
-# @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleDemo
+# @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleDemo
 # @see full.conf for detail config.
 
 listen              1935;
@@ -17,7 +17,7 @@ http_api {
     listen          1985;
 }
 
-http_stream {
+http_server {
     enabled         on;
     listen          8080;
     dir             ./objs/nginx/html;
@@ -82,7 +82,7 @@ vhost demo.srs.com {
             vpreset         superfast;
             vparams {
             }
-            acodec          libaacplus;
+            acodec          libfdk_aac;
             abitrate        45;
             asample_rate    44100;
             achannels       2;
@@ -105,7 +105,7 @@ vhost demo.srs.com {
             vpreset         fast;
             vparams {
             }
-            acodec          libaacplus;
+            acodec          libfdk_aac;
             abitrate        40;
             asample_rate    44100;
             achannels       2;
@@ -151,7 +151,7 @@ vhost players {
             vparams {
                 g           100;
             }
-            acodec          libaacplus;
+            acodec          libfdk_aac;
             abitrate        30;
             asample_rate    44100;
             achannels       2;
diff --git a/trunk/conf/dvr.path.conf b/trunk/conf/dvr.path.conf
new file mode 100644
index 0000000000..9dcbb20ec1
--- /dev/null
+++ b/trunk/conf/dvr.path.conf
@@ -0,0 +1,16 @@
+# the config for srs to dvr in custom path.
+# @see https://github.com/simple-rtmp-server/srs/wiki/v2_CN_DVR#custom-path
+# @see https://github.com/simple-rtmp-server/srs/wiki/v2_EN_DVR#custom-path
+# @see full.conf for detail config.
+
+listen              1935;
+max_connections     1000;
+vhost __defaultVhost__ {
+    dvr {
+        enabled             on;
+        dvr_path            ./objs/nginx/html/[app]/[stream]/[2006]/[01]/[02]/[15].[04].[05].[999].flv;
+        dvr_plan            segment;
+        dvr_duration        30;
+        dvr_wait_keyframe   on;
+    }
+}
diff --git a/trunk/conf/dvr.segment.conf b/trunk/conf/dvr.segment.conf
index f29c28d30b..44af9dd5ea 100644
--- a/trunk/conf/dvr.segment.conf
+++ b/trunk/conf/dvr.segment.conf
@@ -1,5 +1,5 @@
 # the config for srs to dvr in segment mode
-# @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_DVR
+# @see https://github.com/simple-rtmp-server/srs/wiki/v2_CN_DVR
 # @see full.conf for detail config.
 
 listen              1935;
@@ -7,7 +7,7 @@ max_connections     1000;
 vhost __defaultVhost__ {
     dvr {
         enabled      on;
-        dvr_path     ./objs/nginx/html;
+        dvr_path     ./objs/nginx/html/[app]/[stream].[timestamp].flv;
         dvr_plan     segment;
         dvr_duration    30;
         dvr_wait_keyframe       on;
diff --git a/trunk/conf/dvr.session.conf b/trunk/conf/dvr.session.conf
index 8b16b32eb7..1eb5dad4d5 100644
--- a/trunk/conf/dvr.session.conf
+++ b/trunk/conf/dvr.session.conf
@@ -1,5 +1,5 @@
 # the config for srs to dvr in session mode
-# @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_DVR
+# @see https://github.com/simple-rtmp-server/srs/wiki/v2_CN_DVR
 # @see full.conf for detail config.
 
 listen              1935;
@@ -7,7 +7,7 @@ max_connections     1000;
 vhost __defaultVhost__ {
     dvr {
         enabled      on;
-        dvr_path     ./objs/nginx/html;
+        dvr_path     dvr_path     ./objs/nginx/html/[app]/[stream].[timestamp].flv;
         dvr_plan     session;
     }
 }
diff --git a/trunk/conf/edge.conf b/trunk/conf/edge.conf
index a77986b6e5..c018dfc118 100644
--- a/trunk/conf/edge.conf
+++ b/trunk/conf/edge.conf
@@ -1,5 +1,5 @@
 # the config for srs origin-edge cluster
-# @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Edge
+# @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Edge
 # @see full.conf for detail config.
 
 listen              1935;
diff --git a/trunk/conf/edge.token.traverse.conf b/trunk/conf/edge.token.traverse.conf
index bff8749f68..f173240132 100644
--- a/trunk/conf/edge.token.traverse.conf
+++ b/trunk/conf/edge.token.traverse.conf
@@ -1,5 +1,5 @@
 # the config for srs for token traverse authentication
-# @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_DRM
+# @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_DRM
 # @see full.conf for detail config.
 
 listen              1935
diff --git a/trunk/conf/ffmpeg.transcode.conf b/trunk/conf/ffmpeg.transcode.conf
index 04c434982c..e2de8d960d 100644
--- a/trunk/conf/ffmpeg.transcode.conf
+++ b/trunk/conf/ffmpeg.transcode.conf
@@ -1,5 +1,5 @@
 # the config for srs use ffmpeg to transcode
-# @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleFFMPEG
+# @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleFFMPEG
 # @see full.conf for detail config.
 
 listen              1935;
@@ -22,7 +22,7 @@ vhost __defaultVhost__ {
             vpreset         medium;
             vparams {
             }
-            acodec          libaacplus;
+            acodec          libfdk_aac;
             abitrate        70;
             asample_rate    44100;
             achannels       2;
diff --git a/trunk/conf/forward.master.conf b/trunk/conf/forward.master.conf
index 8200b1c5fa..18d9bc16ff 100644
--- a/trunk/conf/forward.master.conf
+++ b/trunk/conf/forward.master.conf
@@ -1,5 +1,5 @@
 # the config for srs to forward
-# @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleForward
+# @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleForward
 # @see full.conf for detail config.
 
 listen              1935;
diff --git a/trunk/conf/forward.slave.conf b/trunk/conf/forward.slave.conf
index ff2dd53ed5..67a7462144 100644
--- a/trunk/conf/forward.slave.conf
+++ b/trunk/conf/forward.slave.conf
@@ -1,5 +1,5 @@
 # the config for srs to forward
-# @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleForward
+# @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleForward
 # @see full.conf for detail config.
 
 listen              19350;
diff --git a/trunk/conf/full.conf b/trunk/conf/full.conf
old mode 100755
new mode 100644
index 74de204fbe..bab1433ded
--- a/trunk/conf/full.conf
+++ b/trunk/conf/full.conf
@@ -3,7 +3,9 @@
 #############################################################################################
 # RTMP sections
 #############################################################################################
-# the rtmp listen ports, split by space.
+# the rtmp listen ports, split by space, each listen entry is <[ip:]port>
+# for example, 192.168.1.100:1935 10.10.10.100:1935
+# where the ip is optional, default to 0.0.0.0, that is 1935 equals to 0.0.0.0:1935
 listen              1935;
 # the pid file
 # to ensure only one process can use a pid file
@@ -42,6 +44,11 @@ max_connections     1000;
 # @remark: donot support reload.
 # default: on
 daemon              on;
+# whether use utc_time to generate the time struct,
+# if off, use localtime() to generate it,
+# if on, use gmtime() instead, which use UTC time.
+# default: off
+utc_time            off;
 
 #############################################################################################
 # heartbeat/stats sections
@@ -68,7 +75,7 @@ heartbeat {
     # the id of devide.
     device_id       "my-srs-device";
     # whether report with summaries
-    # if true, put /api/v1/summaries to the request data:
+    # if on, put /api/v1/summaries to the request data:
     #   {
     #       "summaries": summaries object.
     #   }
@@ -106,9 +113,14 @@ http_api {
     # whether http api is enabled.
     # default: off
     enabled         on;
-    # the http api port
+    # the http api listen entry is <[ip:]port>
+    # for example, 192.168.1.100:1985
+    # where the ip is optional, default to 0.0.0.0, that is 1985 equals to 0.0.0.0:1985
     # default: 1985
     listen          1985;
+    # whether enable crossdomain request.
+    # default: on
+    crossdomain     on;
 }
 # embeded http server in srs.
 # the http streaming config, for HLS/HDS/DASH/HTTPProgressive
@@ -120,11 +132,13 @@ http_api {
 # which will show srs version and welcome to srs.
 # @remark, the http embeded stream need to config the vhost, for instance, the __defaultVhost__
 # need to open the feature http of vhost.
-http_stream {
+http_server {
     # whether http streaming service is enabled.
     # default: off
     enabled         on;
-    # the http streaming port
+    # the http streaming listen entry is <[ip:]port>
+    # for example, 192.168.1.100:8080
+    # where the ip is optional, default to 0.0.0.0, that is 8080 equals to 0.0.0.0:8080
     # @remark, if use lower port, for instance 80, user must start srs by root.
     # default: 8080
     listen          8080;
@@ -133,6 +147,63 @@ http_stream {
     dir             ./objs/nginx/html;
 }
 
+#############################################################################################
+# Streamer sections
+#############################################################################################
+# the streamer cast stream from other protocol to SRS over RTMP.
+# @see https://github.com/simple-rtmp-server/srs/tree/develop#stream-architecture
+stream_caster {
+    # whether stream caster is enabled.
+    # default: off
+    enabled         off;
+    # the caster type of stream, the casters:
+    #       mpegts_over_udp, MPEG-TS over UDP caster.
+    #       rtsp, Real Time Streaming Protocol (RTSP).
+    #       flv, FLV over HTTP POST.
+    caster          mpegts_over_udp;
+    # the output rtmp url.
+    # for mpegts_over_udp caster, the typically output url:
+    #       rtmp://127.0.0.1/live/livestream
+    # for rtsp caster, the typically output url:
+    #       rtmp://127.0.0.1/[app]/[stream]
+    #       for example, the rtsp url:
+    #           rtsp://192.168.1.173:8544/live/livestream.sdp
+    #           where the [app] is "live" and [stream] is "livestream", output is:
+    #           rtmp://127.0.0.1/live/livestream
+    output          rtmp://127.0.0.1/live/livestream;
+    # the listen port for stream caster.
+    #       for mpegts_over_udp caster, listen at udp port. for example, 8935.
+    #       for rtsp caster, listen at tcp port. for example, 554.
+    #       for flv caster, listen at tcp port. for example, 8936.
+    # TODO: support listen at <[ip:]port>
+    listen          8935;
+    # for the rtsp caster, the rtp server local port over udp,
+    # which reply the rtsp setup request message, the port will be used:
+    #       [rtp_port_min, rtp_port_max)
+    rtp_port_min    57200;
+    rtp_port_max    57300;
+}
+stream_caster {
+    enabled         off;
+    caster          mpegts_over_udp;
+    output          rtmp://127.0.0.1/live/livestream;
+    listen          8935;
+}
+stream_caster {
+    enabled         off;
+    caster          rtsp;
+    output          rtmp://127.0.0.1/[app]/[stream];
+    listen          554;
+    rtp_port_min    57200;
+    rtp_port_max    57300;
+}
+stream_caster {
+    enabled         off;
+    caster          flv;
+    output          rtmp://127.0.0.1/[app]/[stream];
+    listen          8936;
+}
+
 #############################################################################################
 # RTMP/HTTP VHOST sections
 #############################################################################################
@@ -142,6 +213,69 @@ http_stream {
 vhost __defaultVhost__ {
 }
 
+# the security to allow or deny clients.
+vhost security.srs.com {
+    # security for host to allow or deny clients.
+    # @see https://github.com/simple-rtmp-server/srs/issues/211   
+    security {
+        # whether enable the security for vhost.
+        # default: off
+        enabled         on;
+        # the security list, each item format as:
+        #       allow|deny    publish|play    all|
+        # for example:
+        #       allow           publish     all;
+        #       deny            publish     all;
+        #       allow           publish     127.0.0.1;
+        #       deny            publish     127.0.0.1;
+        #       allow           play        all;
+        #       deny            play        all;
+        #       allow           play        127.0.0.1;
+        #       deny            play        127.0.0.1;
+        # SRS apply the following simple strategies one by one:
+        #       1. allow all if security disabled.
+        #       2. default to deny all when security enabled.
+        #       3. allow if matches allow strategy.
+        #       4. deny if matches deny strategy.
+        allow           play        all;
+        allow           publish     all;
+    }
+}
+
+# the MR(merged-read) setting for publisher.
+# the MW(merged-write) settings for player.
+vhost mrw.srs.com {
+    # whether enable min delay mode for vhost.
+    # for min latence mode:
+    # 1. disable the mr for vhost.
+    # 2. use timeout for cond wait for consumer queue.
+    # @see https://github.com/simple-rtmp-server/srs/issues/257
+    # default: off
+    min_latency     off;
+    # about MR, read https://github.com/simple-rtmp-server/srs/issues/241
+    mr {
+        # whether enable the MR(merged-read)
+        # default: off
+        enabled     on;
+        # the latency in ms for MR(merged-read),
+        # the performance+ when latency+, and memory+,
+        #       memory(buffer) = latency * kbps / 8
+        # for example, latency=500ms, kbps=3000kbps, each publish connection will consume
+        #       memory = 500 * 3000 / 8 = 187500B = 183KB
+        # when there are 2500 publisher, the total memory of SRS atleast:
+        #       183KB * 2500 = 446MB
+        # the value recomment is [300, 2000]
+        # default: 350
+        latency     350;
+    }
+    # set the MW(merged-write) latency in ms. 
+    # SRS always set mw on, so we just set the latency value.
+    # the latency of stream >= mw_latency + mr_latency
+    # the value recomment is [300, 1800]
+    # default: 350
+    mw_latency      350;
+}
+
 # vhost for edge, edge and origin is the same vhost
 vhost same.edge.srs.com {
     # the mode of vhost, local or remote.
@@ -163,6 +297,17 @@ vhost same.edge.srs.com {
     token_traverse  off;
 }
 
+# vhost for edge, edge transform vhost to fetch from another vhost.
+vhost transform.edge.srs.com {
+    mode            remote;
+    origin          127.0.0.1:1935;
+    # the vhost to transform for edge,
+    # to fetch from the specified vhost at origin,
+    # if not specified, use the current vhost of edge in origin, the variable [vhost].
+    # default: [vhost]
+    vhost           same.edge.srs.com;
+}
+
 # vhost for dvr
 vhost dvr.srs.com {
     # dvr RTMP stream to file,
@@ -172,30 +317,58 @@ vhost dvr.srs.com {
         # whether enabled dvr features
         # default: off
         enabled         on;
-        # the dvr output path.
-        # the app dir is auto created under the dvr_path.
-        # for example, for rtmp stream:
-        #   rtmp://127.0.0.1/live/livestream
-        #   http://127.0.0.1/live/livestream.m3u8
-        # where dvr_path is /dvr, srs will create the following files:
-        #   /dvr/live       the app dir for all streams.
-        #   /dvr/live/livestream.{time}.flv   the dvr flv file.
-        # @remark, the time use system timestamp in ms, user can use http callback to rename it.
-        # in a word, the dvr_path is for vhost.
-        # default: ./objs/nginx/html
-        dvr_path        ./objs/nginx/html;
         # the dvr plan. canbe:
-        #   session reap flv when session end(unpublish).
-        #   segment reap flv when flv duration exceed the specified dvr_duration.
+        #       session reap flv when session end(unpublish).
+        #       segment reap flv when flv duration exceed the specified dvr_duration.
+        #       append always append to flv file, never reap it.
         # default: session
         dvr_plan        session;
-        # the param for plan(segment), in seconds.
+        # the dvr output path.
+        # we supports some variables to generate the filename.
+        #       [vhost], the vhost of stream.
+        #       [app], the app of stream.
+        #       [stream], the stream name of stream.
+        #       [2006], replace this const to current year.
+        #       [01], replace this const to current month.
+        #       [02], replace this const to current date.
+        #       [15], replace this const to current hour.
+        #       [04], repleace this const to current minute.
+        #       [05], repleace this const to current second.
+        #       [999], repleace this const to current millisecond.
+        #       [timestamp],replace this const to current UNIX timestamp in ms.
+        # @remark we use golang time format "2006-01-02 15:04:05.999" as "[2006]-[01]-[02]_[15].[04].[05]_[999]"
+        # for example, for url rtmp://ossrs.net/live/livestream and time 2015-01-03 10:57:30.776
+        # 1. No variables, the rule of SRS1.0(auto add [stream].[timestamp].flv as filename):
+        #       dvr_path ./objs/nginx/html;
+        #       =>
+        #       dvr_path ./objs/nginx/html/live/livestream.1420254068776.flv;
+        # 2. Use stream and date as dir name, time as filename:
+        #       dvr_path /data/[vhost]/[app]/[stream]/[2006]/[01]/[02]/[15].[04].[05].[999].flv;
+        #       =>
+        #       dvr_path /data/ossrs.net/live/livestream/2015/01/03/10.57.30.776.flv;
+        # 3. Use stream and year/month as dir name, date and time as filename:
+        #       dvr_path /data/[vhost]/[app]/[stream]/[2006]/[01]/[02]-[15].[04].[05].[999].flv;
+        #       =>
+        #       dvr_path /data/ossrs.net/live/livestream/2015/01/03-10.57.30.776.flv;
+        # 4. Use vhost/app and year/month as dir name, stream/date/time as filename:
+        #       dvr_path /data/[vhost]/[app]/[2006]/[01]/[stream]-[02]-[15].[04].[05].[999].flv;
+        #       =>
+        #       dvr_path /data/ossrs.net/live/2015/01/livestream-03-10.57.30.776.flv;
+        # @see https://github.com/simple-rtmp-server/srs/wiki/v2_CN_DVR#custom-path
+        # @see https://github.com/simple-rtmp-server/srs/wiki/v2_EN_DVR#custom-path
+        #       segment,session apply it.
+        # default: ./objs/nginx/html/[app]/[stream].[timestamp].flv
+        dvr_path        ./objs/nginx/html/[app]/[stream].[timestamp].flv;
+        # the duration for dvr file, reap if exeed, in seconds.
+        #       segment apply it.
+        #       session,append ignore.
         # default: 30
         dvr_duration    30;
-        # the param for plan(segment),
         # whether wait keyframe to reap segment,
         # if off, reap segment when duration exceed the dvr_duration,
         # if on, reap segment when duration exceed and got keyframe.
+        #       segment apply it.
+        #       session,append ignore.
         # default: on
         dvr_wait_keyframe       on;
         # about the stream monotonically increasing:
@@ -208,8 +381,14 @@ vhost dvr.srs.com {
         #   1. full, to ensure stream start at zero, and ensure stream monotonically increasing.
         #   2. zero, only ensure sttream start at zero, ignore timestamp jitter.
         #   3. off, disable the time jitter algorithm, like atc.
+        # apply for all dvr plan.
         # default: full
         time_jitter             full;
+        
+        # on_dvr, never config in here, should config in http_hooks.
+        # for the dvr http callback, @see http_hooks.on_dvr of vhost hooks.callback.srs.com
+        # @read https://github.com/simple-rtmp-server/srs/wiki/v2_CN_DVR#http-callback
+        # @read https://github.com/simple-rtmp-server/srs/wiki/v2_EN_DVR#http-callback
     }
 }
 
@@ -245,24 +424,40 @@ vhost ingest.srs.com {
             # default: off.
             enabled          off;
             # output stream. variables:
-            # [vhost] current vhost which start the ingest.
-            # [port] system RTMP stream port.
+            #       [vhost] current vhost which start the ingest.
+            #       [port] system RTMP stream port.
             output          rtmp://127.0.0.1:[port]/live?vhost=[vhost]/livestream;
         }
     }
 }
 
-# vhost for http
-vhost http.srs.com {
-    # http vhost specified config
+# vhost for http static and flv vod stream for each vhost.
+vhost http.static.srs.com {
+    # http static vhost specified config
     http {
-        # whether enabled the http streaming service for vhost.
+        # whether enabled the http static service for vhost.
         # default: off
         enabled     on;
-        # the virtual directory root for this vhost to mount at
-        # for example, if mount to /hls, user access by http://server/hls
-        # default: /
-        mount       /hls;
+        # the url to mount to, 
+        # typical mount to [vhost]/
+        # the variables:
+        #       [vhost] current vhost for http server.
+        # @remark the [vhost] is optional, used to mount at specified vhost.
+        # @remark the http of __defaultVhost__ will override the http_server section.
+        # for example:
+        #       mount to [vhost]/
+        #           access by http://ossrs.net:8080/xxx.html
+        #       mount to [vhost]/hls
+        #           access by http://ossrs.net:8080/hls/xxx.html
+        #       mount to /
+        #           access by http://ossrs.net:8080/xxx.html
+        #           or by http://192.168.1.173:8080/xxx.html
+        #       mount to /hls
+        #           access by http://ossrs.net:8080/hls/xxx.html
+        #           or by http://192.168.1.173:8080/hls/xxx.html
+        # @remark the port of http is specified by http_server section.
+        # default: [vhost]/
+        mount       [vhost]/hls;
         # main dir of vhost,
         # to delivery HTTP stream of this vhost.
         # default: ./objs/nginx/html
@@ -270,6 +465,54 @@ vhost http.srs.com {
     }
 }
 
+# vhost for http flv/aac/mp3 live stream for each vhost.
+vhost http.remux.srs.com {
+    # http flv/mp3/aac/ts stream vhost specified config
+    http_remux {
+        # whether enable the http live streaming service for vhost.
+        # default: off
+        enabled     on;
+        # the fast cache for audio stream(mp3/aac),
+        # to cache more audio and send to client in a time to make android(weixin) happy.
+        # @remark the flv/ts stream ignore it
+        # @remark 0 to disable fast cache for http audio stream.
+        # default: 0
+        fast_cache  30;
+        # the stream mout for rtmp to remux to live streaming.
+        # typical mount to [vhost]/[app]/[stream].flv
+        # the variables:
+        #       [vhost] current vhost for http live stream.
+        #       [app] current app for http live stream.
+        #       [stream] current stream for http live stream.
+        # @remark the [vhost] is optional, used to mount at specified vhost.
+        # the extension:
+        #       .flv mount http live flv stream, use default gop cache.
+        #       .ts mount http live ts stream, use default gop cache.
+        #       .mp3 mount http live mp3 stream, ignore video and audio mp3 codec required.
+        #       .aac mount http live aac stream, ignore video and audio aac codec required.
+        # for example:
+        #       mount to [vhost]/[app]/[stream].flv
+        #           access by http://ossrs.net:8080/live/livestream.flv
+        #       mount to /[app]/[stream].flv
+        #           access by http://ossrs.net:8080/live/livestream.flv
+        #           or by http://192.168.1.173:8080/live/livestream.flv
+        #       mount to [vhost]/[app]/[stream].mp3
+        #           access by http://ossrs.net:8080/live/livestream.mp3
+        #       mount to [vhost]/[app]/[stream].aac
+        #           access by http://ossrs.net:8080/live/livestream.aac
+        #       mount to [vhost]/[app]/[stream].ts
+        #           access by http://ossrs.net:8080/live/livestream.ts
+        # @remark the port of http is specified by http_server section.
+        # default: [vhost]/[app]/[stream].flv
+        mount       [vhost]/[app]/[stream].flv;
+        # whether http stream trigger rtmp stream source when no stream available,
+        # for example, when encoder has not publish stream yet,
+        # user can play the http flv stream and wait for stream.
+        # default: on
+        hstrs       on;
+    }
+}
+
 # the vhost with hls specified.
 vhost with-hls.srs.com {
     hls {
@@ -277,31 +520,131 @@ vhost with-hls.srs.com {
         # if off, donot write hls(ts and m3u8) when publish.
         # default: off
         enabled         on;
-        # the hls output path.
-        # the app dir is auto created under the hls_path.
-        # for example, for rtmp stream:
-        #   rtmp://127.0.0.1/live/livestream
-        #   http://127.0.0.1/live/livestream.m3u8
-        # where hls_path is /hls, srs will create the following files:
-        #   /hls/live       the app dir for all streams.
-        #   /hls/live/livestream.m3u8   the HLS m3u8 file.
-        #   /hls/live/livestream-1.ts   the HLS media/ts file.
-        # in a word, the hls_path is for vhost.
-        # default: ./objs/nginx/html
-        hls_path        ./objs/nginx/html;
         # the hls fragment in seconds, the duration of a piece of ts.
         # default: 10
         hls_fragment    10;
+        # the hls m3u8 target duration ratio,
+        #   EXT-X-TARGETDURATION = hls_td_ratio * hls_fragment // init
+        #   EXT-X-TARGETDURATION = max(ts_duration, EXT-X-TARGETDURATION) // for each ts
+        # @see https://github.com/simple-rtmp-server/srs/issues/304#issuecomment-74000081
+        # default: 1.5
+        hls_td_ratio    1.5;
+        # the audio overflow ratio.
+        # for pure audio, the duration to reap the segment.
+        # for example, the hls_fragment is 10s, hsl_aof_ratio is 2.0,
+        # the segemnt will reap to 20s for pure audio.
+        # default: 2.0
+        hls_aof_ratio   2.0;
         # the hls window in seconds, the number of ts in m3u8.
         # default: 60
         hls_window      60;
         # the error strategy. canbe:
-        #   ignore, when error ignore and disable hls.
-        #   disconnect, when error disconnect the publish connection.
-        #   continue, when error ignore and continue output hls.
-        # @see https://github.com/winlinvip/simple-rtmp-server/issues/264
+        #       ignore, when error ignore and disable hls.
+        #       disconnect, when error disconnect the publish connection.
+        #       continue, when error ignore and continue output hls.
+        # @see https://github.com/simple-rtmp-server/srs/issues/264
         # default: ignore
         hls_on_error    ignore;
+        # the hls storage: disk, ram or both.
+        #       disk, to write hls m3u8/ts to disk.
+        #       ram, serve m3u8/ts in memory, which use embeded http server to delivery.
+        #       both, disk and ram.
+        # default: disk
+        hls_storage     disk;
+        # the hls output path.
+        # the m3u8 file is configed by hls_path/hls_m3u8_file, the default is:
+        #       ./objs/nginx/html/[app]/[stream].m3u8
+        # the ts file is configed by hls_path/hls_ts_file, the default is:
+        #       ./objs/nginx/html/[app]/[stream]-[seq].ts
+        # @remark the hls_path is compatible with srs v1 config.
+        # default: ./objs/nginx/html
+        hls_path        ./objs/nginx/html;
+        # the hls m3u8 file name.
+        # we supports some variables to generate the filename.
+        #       [vhost], the vhost of stream.
+        #       [app], the app of stream.
+        #       [stream], the stream name of stream.
+        # default: [app]/[stream].m3u8
+        hls_m3u8_file   [app]/[stream].m3u8;
+        # the hls ts file name.
+        # we supports some variables to generate the filename.
+        #       [vhost], the vhost of stream.
+        #       [app], the app of stream.
+        #       [stream], the stream name of stream.
+        #       [2006], replace this const to current year.
+        #       [01], replace this const to current month.
+        #       [02], replace this const to current date.
+        #       [15], replace this const to current hour.
+        #       [04], repleace this const to current minute.
+        #       [05], repleace this const to current second.
+        #       [999], repleace this const to current millisecond.
+        #       [timestamp],replace this const to current UNIX timestamp in ms.
+        #       [seq], the sequence number of ts.
+        # @see https://github.com/simple-rtmp-server/srs/wiki/v2_CN_DVR#custom-path
+        # @see https://github.com/simple-rtmp-server/srs/wiki/v2_CN_DeliveryHLS#hls-config
+        # default: [app]/[stream]-[seq].ts
+        hls_ts_file     [app]/[stream]-[seq].ts;
+        # whether use floor for the hls_ts_file path generation.
+        # if on, use floor(timestamp/hls_fragment) as the variable [timestamp],
+        #       and use enahanced algorithm to calc deviation for segment.
+        # @remark when floor on, recommend the hls_segment>=2*gop.
+        # default: off
+        hls_ts_floor    off;
+        # the hls entry prefix, which is base url of ts url.
+        # if specified, the ts path in m3u8 will be like:
+        #         http://your-server/live/livestream-0.ts
+        #         http://your-server/live/livestream-1.ts
+        #         ...
+        # optional, default to empty string.
+        hls_entry_prefix http://your-server;
+        # the hls mount for hls_storage ram,
+        # which use srs embeded http server to delivery HLS,
+        # where the mount specifies the HTTP url to mount.
+        # @see the mount of http_remux.
+        # @remark the hls_mount must endswith .m3u8.
+        # default: [vhost]/[app]/[stream].m3u8
+        hls_mount       [vhost]/[app]/[stream].m3u8;
+        # the default audio codec of hls.
+        # when codec changed, write the PAT/PMT table, but maybe ok util next ts.
+        # so user can set the default codec for mp3.
+        # the available audio codec: 
+        #       aac, mp3
+        # default: aac
+        hls_acodec      aac;
+        # the default video codec of hls.
+        # when codec changed, write the PAT/PMT table, but maybe ok util next ts.
+        # so user can set the default codec for pure audio(without video) to vn.
+        # the available video codec:
+        #       h264, vn
+        # default: h264
+        hls_vcodec      h264;
+        # whether cleanup the old ts files.
+        # default: on
+        hls_cleanup     on;
+        # the max size to notify hls,
+        # to read max bytes from ts of specified cdn network,
+        # @remark only used when on_hls_notify is config.
+        # default: 64
+        hls_nb_notify   64;
+        # whether wait keyframe to reap segment,
+        # if off, reap segment when duration exceed the fragment,
+        # if on, reap segment when duration exceed and got keyframe.
+        # default: on
+        hls_wait_keyframe       on;
+
+        # on_hls, never config in here, should config in http_hooks.
+        # for the hls http callback, @see http_hooks.on_hls of vhost hooks.callback.srs.com
+        # @read https://github.com/simple-rtmp-server/srs/wiki/v2_CN_DeliveryHLS#http-callback
+        # @read https://github.com/simple-rtmp-server/srs/wiki/v2_EN_DeliveryHLS#http-callback
+        
+        # on_hls_notify, never config in here, should config in http_hooks.
+        # we support the variables to generate the notify url:
+        #       [app], replace with the app.
+        #       [stream], replace with the stream.
+        #       [ts_url], replace with the ts url.
+        # for the hls http callback, @see http_hooks.on_hls_notify of vhost hooks.callback.srs.com
+        # @read https://github.com/simple-rtmp-server/srs/wiki/v2_CN_DeliveryHLS#on-hls-notify
+        # @read https://github.com/simple-rtmp-server/srs/wiki/v2_EN_DeliveryHLS#on-hls-notify
     }
 }
 # the vhost with hls disabled.
@@ -314,6 +657,24 @@ vhost no-hls.srs.com {
     }
 }
 
+# the vhost with adobe hds
+vhost hds.srs.com {
+    hds {
+        # whether hds enabled
+        # default: off
+        enabled         on;
+        # the hds fragment in seconds.
+        # default: 10
+        hds_fragment    10;
+        # the hds window in seconds, erase the segment when exceed the window.
+        # default: 60
+        hds_window      60;
+        # the path to store the hds files.
+        # default: ./objs/nginx/html
+        hds_path        ./objs/nginx/html;
+    }
+}
+
 # the http hook callback vhost, srs will invoke the hooks for specified events.
 vhost hooks.callback.srs.com {
     http_hooks {
@@ -404,6 +765,48 @@ vhost hooks.callback.srs.com {
         # support multiple api hooks, format:
         #       on_stop http://xxx/api0 http://xxx/api1 http://xxx/apiN
         on_stop         http://127.0.0.1:8085/api/v1/sessions http://localhost:8085/api/v1/sessions;
+        # when srs reap a dvr file, call the hook,
+        # the request in the POST data string is a object encode by json:
+        #       {
+        #           "action": "on_dvr",
+        #           "client_id": 1985,
+        #           "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live",
+        #           "stream": "livestream",
+        #           "cwd": "/usr/local/srs",
+        #           "file": "./objs/nginx/html/live/livestream.1420254068776.flv"
+        #       }
+        # if valid, the hook must return HTTP code 200(Stauts OK) and response
+        # an int value specifies the error code(0 corresponding to success):
+        #       0
+        on_dvr          http://127.0.0.1:8085/api/v1/dvrs http://localhost:8085/api/v1/dvrs;
+        # when srs reap a ts file of hls, call the hook,
+        # the request in the POST data string is a object encode by json:
+        #       {
+        #           "action": "on_hls",
+        #           "client_id": 1985,
+        #           "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live",
+        #           "stream": "livestream",
+        #           "duration": 9.36, // in seconds
+        #           "cwd": "/usr/local/srs",
+        #           "file": "./objs/nginx/html/live/livestream/2015-04-23/01/476584165.ts",
+        #           "url": "live/livestream/2015-04-23/01/476584165.ts",
+        #           "m3u8": "./objs/nginx/html/live/livestream/live.m3u8",
+        #           "m3u8_url": "live/livestream/live.m3u8",
+        #           "seq_no": 100
+        #       }
+        # if valid, the hook must return HTTP code 200(Stauts OK) and response
+        # an int value specifies the error code(0 corresponding to success):
+        #       0
+        on_hls          http://127.0.0.1:8085/api/v1/hls http://localhost:8085/api/v1/hls;
+        # when srs reap a ts file of hls, call this hook,
+        # used to push file to cdn network, by get the ts file from cdn network.
+        # so we use HTTP GET and use the variable following:
+        #       [app], replace with the app.
+        #       [stream], replace with the stream.
+        #       [ts_url], replace with the ts url.
+        # ignore any return data of server.
+        # @remark random select a url to report, not report all.
+        on_hls_notify   http://127.0.0.1:8085/api/v1/hls/[app]/[stream][ts_url];
     }
 }
 
@@ -413,13 +816,19 @@ vhost debug.srs.com {
     # it's strongly recommend to open the debug_srs_upnode,
     # when connect to upnode, it will take the debug info, 
     # for example, the id, source id, pid.
-    # please see: https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SrsLog
+    # please see: https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SrsLog
     # default: on
     debug_srs_upnode    on;
 }
 
 # the vhost for min delay, donot cache any stream.
 vhost min.delay.com {
+    # @see vhost mrw.srs.com for detail.
+    min_latency     on;
+    mr {
+        enabled     off;
+    }
+    mw_latency      100;
     # whether cache the last gop.
     # if on, cache the last gop and dispatch to client,
     #   to enabled fast startup for client, client play immediately.
@@ -461,11 +870,106 @@ vhost same.vhost.forward.srs.com {
     # this used to split/forward the current stream for cluster active-standby,
     # active-active for cdn to build high available fault tolerance system.
     # format: {ip}:{port} {ip_N}:{port_N}
-    # or specify the vhost by params, @see: change.vhost.forward.srs.com
-    # if vhost not specified, use the request vhost instead.
     forward         127.0.0.1:1936 127.0.0.1:1937;
 }
 
+# the main comments for transcode
+vhost example.transcode.srs.com {
+    # the streaming transcode configs.
+    transcode {
+        # whether the transcode enabled.
+        # if off, donot transcode.
+        # default: off.
+        enabled     on;
+        # the ffmpeg 
+        ffmpeg      ./objs/ffmpeg/bin/ffmpeg;
+        # the transcode engine for matched stream.
+        # all matched stream will transcoded to the following stream.
+        # the transcode set name(ie. hd) is optional and not used.
+        engine example {
+            # whether the engine is enabled
+            # default: off.
+            enabled         on;
+            # input format, can be:
+            # off, do not specifies the format, ffmpeg will guess it.
+            # flv, for flv or RTMP stream.
+            # other format, for example, mp4/aac whatever.
+            # default: flv
+            iformat         flv;
+            # ffmpeg filters, follows the main input.
+            vfilter {
+                # the logo input file.
+                i               ./doc/ffmpeg-logo.png;
+                # the ffmpeg complex filter.
+                # for filters, @see: http://ffmpeg.org/ffmpeg-filters.html
+                filter_complex  'overlay=10:10';
+            }
+            # video encoder name. can be:
+            #       libx264: use h.264(libx264) video encoder.
+            #       copy: donot encoder the video stream, copy it.
+            #       vn: disable video output.
+            vcodec          libx264;
+            # video bitrate, in kbps
+            vbitrate        1500;
+            # video framerate.
+            vfps            25;
+            # video width, must be even numbers.
+            vwidth          768;
+            # video height, must be even numbers.
+            vheight         320;
+            # the max threads for ffmpeg to used.
+            vthreads        12;
+            # x264 profile, @see x264 -help, can be:
+            # high,main,baseline
+            vprofile        main;
+            # x264 preset, @see x264 -help, can be: 
+            #       ultrafast,superfast,veryfast,faster,fast
+            #       medium,slow,slower,veryslow,placebo
+            vpreset         medium;
+            # other x264 or ffmpeg video params
+            vparams {
+                # ffmpeg options, @see: http://ffmpeg.org/ffmpeg.html
+                t               100;
+                # 264 params, @see: http://ffmpeg.org/ffmpeg-codecs.html#libx264
+                coder           1;
+                b_strategy      2;
+                bf              3;
+                refs            10;
+            }
+            # audio encoder name. can be:
+            #       libfdk_aac: use aac(libfdk_aac) audio encoder.
+            #       copy: donot encoder the audio stream, copy it.
+            #       an: disable audio output.
+            acodec          libfdk_aac;
+            # audio bitrate, in kbps. [16, 72] for libfdk_aac.
+            abitrate        70;
+            # audio sample rate. for flv/rtmp, it must be:
+            #       44100,22050,11025,5512
+            asample_rate    44100;
+            # audio channel, 1 for mono, 2 for stereo.
+            achannels       2;
+            # other ffmpeg audio params
+            aparams {
+                # audio params, @see: http://ffmpeg.org/ffmpeg-codecs.html#Audio-Encoders
+                # @remark SRS supported aac profile for HLS is: aac_low, aac_he, aac_he_v2
+                profile:a   aac_low;
+            }
+            # output format, can be:
+            #       off, do not specifies the format, ffmpeg will guess it.
+            #       flv, for flv or RTMP stream.
+            #       other format, for example, mp4/aac whatever.
+            # default: flv
+            oformat         flv;
+            # output stream. variables:
+            #       [vhost] the input stream vhost.
+            #       [port] the intput stream port.
+            #       [app] the input stream app.
+            #       [stream] the input stream name.
+            #       [engine] the tanscode engine name.
+            output          rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine];
+        }
+    }
+}
 # the mirror filter of ffmpeg, @see: http://ffmpeg.org/ffmpeg-filters.html#Filtering-Introduction
 vhost mirror.transcode.srs.com {
     transcode {
@@ -486,7 +990,7 @@ vhost mirror.transcode.srs.com {
             vpreset         superfast;
             vparams {
             }
-            acodec          libaacplus;
+            acodec          libfdk_aac;
             abitrate        45;
             asample_rate    44100;
             achannels       2;
@@ -496,10 +1000,8 @@ vhost mirror.transcode.srs.com {
         }
     }
 }
-#
 # the drawtext filter of ffmpeg, @see: http://ffmpeg.org/ffmpeg-filters.html#drawtext-1
 # remark: we remove the libfreetype which always cause build failed, you must add it manual if needed.
-#
 #######################################################################################################
 # the crop filter of ffmpeg, @see: http://ffmpeg.org/ffmpeg-filters.html#crop
 vhost crop.transcode.srs.com {
@@ -521,7 +1023,7 @@ vhost crop.transcode.srs.com {
             vpreset         superfast;
             vparams {
             }
-            acodec          libaacplus;
+            acodec          libfdk_aac;
             abitrate        45;
             asample_rate    44100;
             achannels       2;
@@ -552,7 +1054,7 @@ vhost logo.transcode.srs.com {
             vpreset         superfast;
             vparams {
             }
-            acodec          libaacplus;
+            acodec          libfdk_aac;
             abitrate        45;
             asample_rate    44100;
             achannels       2;
@@ -572,7 +1074,7 @@ vhost audio.transcode.srs.com {
         engine acodec {
             enabled         on;
             vcodec          copy;
-            acodec          libaacplus;
+            acodec          libfdk_aac;
             abitrate        45;
             asample_rate    44100;
             achannels       2;
@@ -591,7 +1093,7 @@ vhost vn.transcode.srs.com {
         engine vn {
             enabled         on;
             vcodec          vn;
-            acodec          libaacplus;
+            acodec          libfdk_aac;
             abitrate        45;
             asample_rate    44100;
             achannels       2;
@@ -616,97 +1118,41 @@ vhost copy.transcode.srs.com {
     }
 }
 # transcode all app and stream of vhost
+# the comments, read example.transcode.srs.com
 vhost all.transcode.srs.com {
-    # the streaming transcode configs.
     transcode {
-        # whether the transcode enabled.
-        # if off, donot transcode.
-        # default: off.
         enabled     on;
-        # the ffmpeg 
         ffmpeg      ./objs/ffmpeg/bin/ffmpeg;
-        # the transcode engine for matched stream.
-        # all matched stream will transcoded to the following stream.
-        # the transcode set name(ie. hd) is optional and not used.
         engine ffsuper {
-            # whether the engine is enabled
-            # default: off.
             enabled         on;
-            # input format, can be:
-            # off, do not specifies the format, ffmpeg will guess it.
-            # flv, for flv or RTMP stream.
-            # other format, for example, mp4/aac whatever.
-            # default: flv
             iformat         flv;
-            # ffmpeg filters, follows the main input.
             vfilter {
-                # the logo input file.
                 i               ./doc/ffmpeg-logo.png;
-                # the ffmpeg complex filter.
-                # for filters, @see: http://ffmpeg.org/ffmpeg-filters.html
                 filter_complex  'overlay=10:10';
             }
-            # video encoder name. can be:
-            # libx264: use h.264(libx264) video encoder.
-            # copy: donot encoder the video stream, copy it.
-            # vn: disable video output.
             vcodec          libx264;
-            # video bitrate, in kbps
             vbitrate        1500;
-            # video framerate.
             vfps            25;
-            # video width, must be even numbers.
             vwidth          768;
-            # video height, must be even numbers.
             vheight         320;
-            # the max threads for ffmpeg to used.
             vthreads        12;
-            # x264 profile, @see x264 -help, can be:
-            # high,main,baseline
             vprofile        main;
-            # x264 preset, @see x264 -help, can be: 
-            # ultrafast,superfast,veryfast,faster,fast
-            # medium,slow,slower,veryslow,placebo
             vpreset         medium;
-            # other x264 or ffmpeg video params
             vparams {
-                # ffmpeg options, @see: http://ffmpeg.org/ffmpeg.html
                 t               100;
-                # 264 params, @see: http://ffmpeg.org/ffmpeg-codecs.html#libx264
                 coder           1;
                 b_strategy      2;
                 bf              3;
                 refs            10;
             }
-            # audio encoder name. can be:
-            # libaacplus: use aac(libaacplus) audio encoder.
-            # copy: donot encoder the audio stream, copy it.
-            # an: disable audio output.
-            acodec          libaacplus;
-            # audio bitrate, in kbps. [16, 72] for libaacplus.
+            acodec          libfdk_aac;
             abitrate        70;
-            # audio sample rate. for flv/rtmp, it must be:
-            # 44100,22050,11025,5512
             asample_rate    44100;
-            # audio channel, 1 for mono, 2 for stereo.
             achannels       2;
-            # other ffmpeg audio params
             aparams {
-                # audio params, @see: http://ffmpeg.org/ffmpeg-codecs.html#Audio-Encoders
                 profile:a   aac_low;
             }
-            # output format, can be:
-            # off, do not specifies the format, ffmpeg will guess it.
-            # flv, for flv or RTMP stream.
-            # other format, for example, mp4/aac whatever.
-            # default: flv
             oformat         flv;
-            # output stream. variables:
-            # [vhost] the input stream vhost.
-            # [port] the intput stream port.
-            # [app] the input stream app.
-            # [stream] the input stream name.
-            # [engine] the tanscode engine name.
             output          rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine];
         }
         engine ffhd {
@@ -721,7 +1167,7 @@ vhost all.transcode.srs.com {
             vpreset         medium;
             vparams {
             }
-            acodec          libaacplus;
+            acodec          libfdk_aac;
             abitrate        70;
             asample_rate    44100;
             achannels       2;
@@ -741,7 +1187,7 @@ vhost all.transcode.srs.com {
             vpreset         fast;
             vparams {
             }
-            acodec          libaacplus;
+            acodec          libfdk_aac;
             abitrate        60;
             asample_rate    44100;
             achannels       2;
@@ -761,7 +1207,7 @@ vhost all.transcode.srs.com {
             vpreset         superfast;
             vparams {
             }
-            acodec          libaacplus;
+            acodec          libfdk_aac;
             abitrate        45;
             asample_rate    44100;
             achannels       2;
@@ -772,7 +1218,7 @@ vhost all.transcode.srs.com {
         engine vcopy {
             enabled         on;
             vcodec          copy;
-            acodec          libaacplus;
+            acodec          libfdk_aac;
             abitrate        45;
             asample_rate    44100;
             achannels       2;
@@ -820,7 +1266,7 @@ vhost ffempty.transcode.srs.com {
             vpreset         superfast;
             vparams {
             }
-            acodec          libaacplus;
+            acodec          libfdk_aac;
             abitrate        45;
             asample_rate    44100;
             achannels       2;
@@ -894,7 +1340,7 @@ vhost jitter.srs.com {
     # about the stream monotonically increasing:
     #   1. video timestamp is monotonically increasing, 
     #   2. audio timestamp is monotonically increasing,
-    #   3. video and audio timestamp is interleaved monotonically increasing.
+    #   3. video and audio timestamp is interleaved/mixed monotonically increasing.
     # it's specified by RTMP specification, @see 3. Byte Order, Alignment, and Time Format
     # however, some encoder cannot provides this feature, please set this to off to ignore time jitter.
     # the time jitter algorithm:
@@ -903,6 +1349,11 @@ vhost jitter.srs.com {
     #   3. off, disable the time jitter algorithm, like atc.
     # default: full
     time_jitter             full;
+    # whether use the interleaved/mixed algorithm to correct the timestamp.
+    # if on, always ensure the timestamp of audio+video is interleaved/mixed monotonically increase.
+    # if off, use time_jitter to correct the timestamp if required.
+    # default: off
+    mix_correct             off;
 }
 
 # vhost for atc.
@@ -939,26 +1390,5 @@ vhost removed.srs.com {
 # config for the pithy print,
 # which always print constant message specified by interval,
 # whatever the clients in concurrency.
-pithy_print {
-    # shared print interval for all publish clients, in milliseconds.
-    # default: 10000
-    publish         10000;
-    # shared print interval for all play clients, in milliseconds.
-    # default: 10000
-    play            10000;
-    # shared print interval for all forwarders, in milliseconds.
-    # default: 10000
-    forwarder       10000;
-    # shared print interval for all encoders, in milliseconds.
-    # default: 10000
-    encoder         10000;
-    # shared print interval for all ingesters, in milliseconds.
-    # default: 10000
-    ingester        10000;
-    # shared print interval for all hls, in milliseconds.
-    # default: 10000
-    hls             10000;
-    # shared print interval for all edge, in milliseconds.
-    # default: 10000
-    edge            10000;
-}
+# default: 10000
+pithy_print_ms      10000;
diff --git a/trunk/conf/hds.conf b/trunk/conf/hds.conf
new file mode 100644
index 0000000000..cae629118e
--- /dev/null
+++ b/trunk/conf/hds.conf
@@ -0,0 +1,19 @@
+# the config for srs to delivery hds
+# @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleHDS
+# @see full.conf for detail config.
+
+listen              1935;
+max_connections     1000;
+
+daemon              off;
+srs_log_tank        console;
+srs_log_level       trace;
+
+vhost __defaultVhost__ {
+    hds {
+        enabled         on;
+        hds_fragment    10;
+        hds_window      60;
+        hds_path        ./objs/nginx/html;
+    }
+}
diff --git a/trunk/conf/hls.conf b/trunk/conf/hls.conf
index 375bc3629d..2787fe5238 100644
--- a/trunk/conf/hls.conf
+++ b/trunk/conf/hls.conf
@@ -1,5 +1,5 @@
 # the config for srs to delivery hls
-# @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleHLS
+# @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleHLS
 # @see full.conf for detail config.
 
 listen              1935;
@@ -7,8 +7,10 @@ max_connections     1000;
 vhost __defaultVhost__ {
     hls {
         enabled         on;
-        hls_path        ./objs/nginx/html;
         hls_fragment    10;
         hls_window      60;
+        hls_path        ./objs/nginx/html;
+        hls_m3u8_file   [app]/[stream].m3u8;
+        hls_ts_file     [app]/[stream]-[seq].ts;
     }
 }
diff --git a/trunk/conf/http.aac.live.conf b/trunk/conf/http.aac.live.conf
new file mode 100644
index 0000000000..f596690550
--- /dev/null
+++ b/trunk/conf/http.aac.live.conf
@@ -0,0 +1,20 @@
+# the config for srs to remux rtmp to aac live stream.
+# @see https://github.com/simple-rtmp-server/srs/wiki/v2_CN_DeliveryHttpStream
+# @see full.conf for detail config.
+
+listen              1935;
+max_connections     1000;
+http_server {
+    enabled         on;
+    listen          8080;
+    dir             ./objs/nginx/html;
+}
+vhost __defaultVhost__ {
+    http_remux {
+        enabled     on;
+        fast_cache  30;
+        mount       [vhost]/[app]/[stream].aac;
+        hstrs       on;
+
+    }
+}
diff --git a/trunk/conf/http.flv.live.conf b/trunk/conf/http.flv.live.conf
new file mode 100644
index 0000000000..f8c56b2f2e
--- /dev/null
+++ b/trunk/conf/http.flv.live.conf
@@ -0,0 +1,18 @@
+# the config for srs to remux rtmp to flv live stream.
+# @see https://github.com/simple-rtmp-server/srs/wiki/v2_CN_DeliveryHttpStream
+# @see full.conf for detail config.
+
+listen              1935;
+max_connections     1000;
+http_server {
+    enabled         on;
+    listen          8080;
+    dir             ./objs/nginx/html;
+}
+vhost __defaultVhost__ {
+    http_remux {
+        enabled     on;
+        mount       [vhost]/[app]/[stream].flv;
+        hstrs       on;
+    }
+}
diff --git a/trunk/conf/http.hls.conf b/trunk/conf/http.hls.conf
index 8c2c4cdc92..4cd6d8adf8 100644
--- a/trunk/conf/http.hls.conf
+++ b/trunk/conf/http.hls.conf
@@ -1,10 +1,10 @@
 # the config for srs to delivery hls
-# @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleHLS
+# @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleHLS
 # @see full.conf for detail config.
 
 listen              1935;
 max_connections     1000;
-http_stream {
+http_server {
     enabled         on;
     listen          8080;
     dir             ./objs/nginx/html;
@@ -12,8 +12,10 @@ http_stream {
 vhost __defaultVhost__ {
     hls {
         enabled         on;
-        hls_path        ./objs/nginx/html;
         hls_fragment    10;
         hls_window      60;
+        hls_path        ./objs/nginx/html;
+        hls_m3u8_file   [app]/[stream].m3u8;
+        hls_ts_file     [app]/[stream]-[seq].ts;
     }
 }
diff --git a/trunk/conf/http.mp3.live.conf b/trunk/conf/http.mp3.live.conf
new file mode 100644
index 0000000000..a8047f4f01
--- /dev/null
+++ b/trunk/conf/http.mp3.live.conf
@@ -0,0 +1,20 @@
+# the config for srs to remux rtmp to mp3 live stream.
+# @see https://github.com/simple-rtmp-server/srs/wiki/v2_CN_DeliveryHttpStream
+# @see full.conf for detail config.
+
+listen              1935;
+max_connections     1000;
+http_server {
+    enabled         on;
+    listen          8080;
+    dir             ./objs/nginx/html;
+}
+vhost __defaultVhost__ {
+    http_remux {
+        enabled     on;
+        fast_cache  30;
+        mount       [vhost]/[app]/[stream].mp3;
+        hstrs       on;
+
+    }
+}
diff --git a/trunk/conf/http.server.conf b/trunk/conf/http.server.conf
new file mode 100644
index 0000000000..69c94f39c7
--- /dev/null
+++ b/trunk/conf/http.server.conf
@@ -0,0 +1,17 @@
+# the config for srs to serve as http server
+# @see full.conf for detail config.
+
+listen              1935;
+max_connections     1000;
+http_server {
+    enabled         on;
+    listen          8080;
+    dir             ./objs/nginx/html;
+}
+vhost ossrs.net {
+    http {
+        enabled     on;
+        mount       [vhost]/;
+        dir         ./objs/nginx/html;
+    }
+}
diff --git a/trunk/conf/http.ts.live.conf b/trunk/conf/http.ts.live.conf
new file mode 100644
index 0000000000..e83800ec2c
--- /dev/null
+++ b/trunk/conf/http.ts.live.conf
@@ -0,0 +1,18 @@
+# the config for srs to remux rtmp to ts live stream.
+# @see https://github.com/simple-rtmp-server/srs/wiki/v2_CN_DeliveryHttpStream
+# @see full.conf for detail config.
+
+listen              1935;
+max_connections     1000;
+http_server {
+    enabled         on;
+    listen          8080;
+    dir             ./objs/nginx/html;
+}
+vhost __defaultVhost__ {
+    http_remux {
+        enabled     on;
+        mount       [vhost]/[app]/[stream].ts;
+        hstrs       on;
+    }
+}
diff --git a/trunk/conf/ingest.conf b/trunk/conf/ingest.conf
index 12fa2b8e53..c9c5ae4c1f 100644
--- a/trunk/conf/ingest.conf
+++ b/trunk/conf/ingest.conf
@@ -1,5 +1,5 @@
 # use ffmpeg to ingest file/stream/device to SRS
-# @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleIngest
+# @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleIngest
 # @see full.conf for detail config.
 
 listen              1935;
diff --git a/trunk/conf/mac.dev.conf b/trunk/conf/mac.dev.conf
new file mode 100644
index 0000000000..e624e165bb
--- /dev/null
+++ b/trunk/conf/mac.dev.conf
@@ -0,0 +1,36 @@
+# no-daemon and write log to console config for srs.
+# @see full.conf for detail config.
+
+listen              1935;
+max_connections     1000;
+daemon              off;
+srs_log_tank        console;
+http_api {
+    enabled         on;
+    listen          1985;
+}
+http_server {
+    enabled         on;
+    listen          8080;
+}
+vhost __defaultVhost__ {
+    hls {
+        enabled         on;
+        hls_fragment    10;
+        hls_window      60;
+        hls_path        ./objs/nginx/html;
+    }
+    ingest livestream {
+        enabled      on;
+        input {
+            type    file;
+            url     ./doc/source.200kbps.768x320.flv;
+        }
+        #ffmpeg      ./objs/ffmpeg/bin/ffmpeg;
+        ffmpeg      ./objs/research/librtmp/srs_ingest_flv;
+        engine {
+            enabled          off;
+            output          rtmp://127.0.0.1:[port]/live?vhost=[vhost]/livestream;
+        }
+    }
+}
diff --git a/trunk/conf/origin.conf b/trunk/conf/origin.conf
index 401c5cd258..268e947992 100644
--- a/trunk/conf/origin.conf
+++ b/trunk/conf/origin.conf
@@ -1,5 +1,5 @@
 # the config for srs origin-edge cluster
-# @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Edge
+# @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Edge
 # @see full.conf for detail config.
 
 listen              19350;
diff --git a/trunk/conf/push.flv.conf b/trunk/conf/push.flv.conf
new file mode 100644
index 0000000000..140ff6eab9
--- /dev/null
+++ b/trunk/conf/push.flv.conf
@@ -0,0 +1,14 @@
+# push HTTP FLV to SRS.
+# @see https://github.com/simple-rtmp-server/srs/wiki/v2_CN_Streamer#push-http-flv-to-srs
+# @see full.conf for detail config.
+
+listen              1935;
+max_connections     1000;
+stream_caster {
+    enabled         on;
+    caster          flv;
+    output          rtmp://127.0.0.1/[app]/[stream];
+    listen          8936;
+}
+vhost __defaultVhost__ {
+}
diff --git a/trunk/conf/push.mpegts.over.udp.conf b/trunk/conf/push.mpegts.over.udp.conf
new file mode 100644
index 0000000000..b3bcbc1d15
--- /dev/null
+++ b/trunk/conf/push.mpegts.over.udp.conf
@@ -0,0 +1,15 @@
+# push MPEG-TS over UDP to SRS.
+# @see https://github.com/simple-rtmp-server/srs/wiki/v2_CN_Streamer#push-mpeg-ts-over-udp
+# @see https://github.com/simple-rtmp-server/srs/issues/250#issuecomment-72321769
+# @see full.conf for detail config.
+
+listen              1935;
+max_connections     1000;
+stream_caster {
+    enabled         on;
+    caster          mpegts_over_udp;
+    output          rtmp://127.0.0.1/live/livestream;
+    listen          1935;
+}
+vhost __defaultVhost__ {
+}
diff --git a/trunk/conf/push.rtsp.conf b/trunk/conf/push.rtsp.conf
new file mode 100644
index 0000000000..b9f4976463
--- /dev/null
+++ b/trunk/conf/push.rtsp.conf
@@ -0,0 +1,17 @@
+# push MPEG-TS over UDP to SRS.
+# @see https://github.com/simple-rtmp-server/srs/wiki/v2_CN_Streamer#push-mpeg-ts-over-udp
+# @see https://github.com/simple-rtmp-server/srs/issues/250#issuecomment-72321769
+# @see full.conf for detail config.
+
+listen              1935;
+max_connections     1000;
+stream_caster {
+    enabled         on;
+    caster          rtsp;
+    output          rtmp://127.0.0.1/[app]/[stream];
+    listen          554;
+    rtp_port_min    57200;
+    rtp_port_max    57300;
+}
+vhost __defaultVhost__ {
+}
diff --git a/trunk/conf/ram.hls.conf b/trunk/conf/ram.hls.conf
new file mode 100644
index 0000000000..61f1e60759
--- /dev/null
+++ b/trunk/conf/ram.hls.conf
@@ -0,0 +1,20 @@
+# the config for srs to delivery hls
+# @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleHLS
+# @see full.conf for detail config.
+
+listen              1935;
+max_connections     1000;
+http_server {
+    enabled         on;
+    listen          8080;
+    dir             ./objs/nginx/html;
+}
+vhost __defaultVhost__ {
+    hls {
+        enabled         on;
+        hls_fragment    10;
+        hls_window      60;
+        hls_storage     ram;
+        hls_mount       /[app]/[stream].m3u8;
+    }
+}
diff --git a/trunk/conf/realtime.conf b/trunk/conf/realtime.conf
index 6fd74b60e4..4257019b30 100644
--- a/trunk/conf/realtime.conf
+++ b/trunk/conf/realtime.conf
@@ -1,5 +1,5 @@
 # the config for srs to delivery realtime RTMP stream
-# @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleRealtime
+# @see https://github.com/simple-rtmp-server/srs/wiki/v2_CN_SampleRealtime
 # @see full.conf for detail config.
 
 listen              1935;
@@ -7,4 +7,9 @@ max_connections     1000;
 vhost __defaultVhost__ {
     gop_cache       off;
     queue_length    10;
+    min_latency     on;
+    mr {
+        enabled     off;
+    }
+    mw_latency      100;
 }
diff --git a/trunk/conf/rtmp.conf b/trunk/conf/rtmp.conf
index 38212b79e5..934c65266c 100644
--- a/trunk/conf/rtmp.conf
+++ b/trunk/conf/rtmp.conf
@@ -1,5 +1,5 @@
 # the config for srs to delivery RTMP
-# @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleRTMP
+# @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleRTMP
 # @see full.conf for detail config.
 
 listen              1935;
diff --git a/trunk/conf/security.deny.publish.conf b/trunk/conf/security.deny.publish.conf
new file mode 100644
index 0000000000..abefd029c5
--- /dev/null
+++ b/trunk/conf/security.deny.publish.conf
@@ -0,0 +1,13 @@
+# security config for srs, allow play and deny publish.
+# @see https://github.com/simple-rtmp-server/srs/issues/211#issuecomment-68507035
+# @see full.conf for detail config.
+
+listen              1935;
+max_connections     1000;
+vhost __defaultVhost__ {
+    security {
+        enabled         on;
+        deny            publish     all;
+        allow           play        all;
+    }
+}
diff --git a/trunk/conf/srs.conf b/trunk/conf/srs.conf
index d5d836bdd1..da062849ab 100644
--- a/trunk/conf/srs.conf
+++ b/trunk/conf/srs.conf
@@ -9,7 +9,7 @@ http_api {
     enabled         on;
     listen          1985;
 }
-http_stream {
+http_server {
     enabled         on;
     listen          8080;
     dir             ./objs/nginx/html;
diff --git a/trunk/conf/transcode2hls.audio.only.conf b/trunk/conf/transcode2hls.audio.only.conf
index 69eb9525c1..719cfc8aed 100644
--- a/trunk/conf/transcode2hls.audio.only.conf
+++ b/trunk/conf/transcode2hls.audio.only.conf
@@ -1,5 +1,5 @@
 # the config for srs to delivery hls
-# @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleHLS
+# @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleHLS
 # @see full.conf for detail config.
 
 listen              1935;
@@ -7,9 +7,11 @@ max_connections     1000;
 vhost __defaultVhost__ {
     hls {
         enabled         on;
-        hls_path        ./objs/nginx/html;
         hls_fragment    10;
         hls_window      60;
+        hls_path        ./objs/nginx/html;
+        hls_m3u8_file   [app]/[stream].m3u8;
+        hls_ts_file     [app]/[stream]-[seq].ts;
     }
     transcode {
         enabled     on;
@@ -17,7 +19,7 @@ vhost __defaultVhost__ {
         engine ff {
             enabled         on;
             vcodec          copy;
-            acodec          libaacplus;
+            acodec          libfdk_aac;
             abitrate        45;
             asample_rate    44100;
             achannels       2;
diff --git a/trunk/conf/transform.edge.conf b/trunk/conf/transform.edge.conf
new file mode 100644
index 0000000000..964c796dde
--- /dev/null
+++ b/trunk/conf/transform.edge.conf
@@ -0,0 +1,27 @@
+# the config for srs origin-edge cluster
+# @see https://github.com/simple-rtmp-server/srs/wiki/v2_CN_Edge#transform-vhost
+# @see full.conf for detail config.
+
+listen              1935;
+max_connections     1000;
+vhost __defaultVhost__ {
+}
+vhost source.srs.com {
+    ingest livestream {
+        enabled      on;
+        input {
+            type    file;
+            url     ./doc/source.200kbps.768x320.flv;
+        }
+        ffmpeg      ./objs/ffmpeg/bin/ffmpeg;
+        engine {
+            enabled          off;
+            output          rtmp://127.0.0.1:[port]/live?vhost=[vhost]/livestream;
+        }
+    }
+}
+vhost transform.srs.edge.com {
+    mode            remote;
+    origin          127.0.0.1:1935;
+    vhost           source.srs.com;
+}
\ No newline at end of file
diff --git a/trunk/configure b/trunk/configure
index 632e2bf0ff..7f87274423 100755
--- a/trunk/configure
+++ b/trunk/configure
@@ -4,208 +4,279 @@
 # the main output dir, all configure and make output are in this dir.
 #####################################################################################
 # create the main objs
-SRS_OBJS="objs"
-mkdir -p ${SRS_OBJS}
+SRS_WORKDIR="."
+SRS_OBJS_DIR="objs"
+SRS_OBJS="${SRS_WORKDIR}/${SRS_OBJS_DIR}"
+SRS_MAKEFILE="Makefile"
 
-#####################################################################################
 # linux shell color support.
-RED="\\e[31m"
-GREEN="\\e[32m"
-YELLOW="\\e[33m"
-BLACK="\\e[0m"
+RED="\\033[31m"
+GREEN="\\033[32m"
+YELLOW="\\033[33m"
+BLACK="\\033[0m"
 
 #####################################################################################
 # parse user options, set the variables like:
 # srs features: SRS_SSL/SRS_HLS/SRS_NGINX/SRS_FFMPEG_TOOL/SRS_HTTP_CALLBACK/......
 # build options: SRS_JOBS
 #####################################################################################
-SRS_AUTO_HEADERS_H="${SRS_OBJS}/srs_auto_headers.hpp"
 # parse options, exit with error when parse options invalid.
 . auto/options.sh
 
-# clean the exists
+# clean the exists, when not export srs-librtmp.
 # do this only when the options is ok.
 if [[ -f Makefile ]]; then
-    make clean
+make clean
 fi
+# remove makefile
+rm -f ${SRS_WORKDIR}/${SRS_MAKEFILE}
 
-# write user options to headers
-echo "// auto generated by configure" > $SRS_AUTO_HEADERS_H
-echo "#ifndef SRS_AUTO_HEADER_HPP" >> $SRS_AUTO_HEADERS_H
-echo "#define SRS_AUTO_HEADER_HPP" >> $SRS_AUTO_HEADERS_H
-echo "" >> $SRS_AUTO_HEADERS_H
-
-echo "#define SRS_AUTO_BUILD_TS \"`date +%s`\"" >> $SRS_AUTO_HEADERS_H
-echo "#define SRS_AUTO_BUILD_DATE \"`date \"+%Y-%m-%d %H:%M:%S\"`\"" >> $SRS_AUTO_HEADERS_H
-echo "#define SRS_AUTO_UNAME \"`uname -a`\"" >> $SRS_AUTO_HEADERS_H
-echo "#define SRS_AUTO_USER_CONFIGURE \"${SRS_AUTO_USER_CONFIGURE}\"" >> $SRS_AUTO_HEADERS_H
-echo "#define SRS_AUTO_CONFIGURE \"${SRS_AUTO_CONFIGURE}\"" >> $SRS_AUTO_HEADERS_H
-
-# new empty line to auto headers file.
-echo "" >> $SRS_AUTO_HEADERS_H
+# create objs
+mkdir -p ${SRS_OBJS}
 
-#####################################################################################
-# generate auto headers file, depends on the finished of options.sh
-#####################################################################################
-if [ $SRS_ARM_UBUNTU12 = YES ]; then
-    __SrsArmCC="arm-linux-gnueabi-gcc";
-    __SrsArmGCC="arm-linux-gnueabi-gcc";
-    __SrsArmCXX="arm-linux-gnueabi-g++";
-    __SrsArmAR="arm-linux-gnueabi-ar";
-    __SrsArmLD="arm-linux-gnueabi-ld";
-    __SrsArmRANDLIB="arm-linux-gnueabi-ranlib";
-fi
-if [ $SRS_MIPS_UBUNTU12 = YES ]; then
-    __SrsArmCC="mipsel-openwrt-linux-gcc";
-    __SrsArmGCC="mipsel-openwrt-linux-gcc";
-    __SrsArmCXX="mipsel-openwrt-linux-g++";
-    __SrsArmAR="mipsel-openwrt-linux-ar";
-    __SrsArmLD="mipsel-openwrt-linux-ld";
-    __SrsArmRANDLIB="mipsel-openwrt-linux-ranlib";
-fi
-# the arm-ubuntu12 options for make for depends
-if [[ -z $SrsArmCC ]]; then SrsArmCC=$__SrsArmCC; fi
-if [[ -z $SrsArmGCC ]]; then SrsArmGCC=$__SrsArmGCC; fi
-if [[ -z $SrsArmCXX ]]; then SrsArmCXX=$__SrsArmCXX; fi
-if [[ -z $SrsArmAR ]]; then SrsArmAR=$__SrsArmAR; fi
-if [[ -z $SrsArmLD ]]; then SrsArmLD=$__SrsArmLD; fi
-if [[ -z $SrsArmRANDLIB ]]; then SrsArmRANDLIB=$__SrsArmRANDLIB; fi
-# write to source file
-if [ $SRS_EMBEDED_CPU = YES ]; then
-    echo "cc=$SrsArmCC gcc=$SrsArmGCC g++=$SrsArmCXX ar=$SrsArmAR ld=$SrsArmLD randlib=$SrsArmRANDLIB"
-    echo "#define SRS_AUTO_EMBEDED_TOOL_CHAIN \"cc=$SrsArmCC gcc=$SrsArmGCC g++=$SrsArmCXX ar=$SrsArmAR ld=$SrsArmLD randlib=$SrsArmRANDLIB\"" >> $SRS_AUTO_HEADERS_H
-else
-    echo "#define SRS_AUTO_EMBEDED_TOOL_CHAIN \"normal x86/x64 gcc\"" >> $SRS_AUTO_HEADERS_H
-fi
-echo "" >> $SRS_AUTO_HEADERS_H
+# for export srs-librtmp, change target to it.
+. auto/generate-srs-librtmp-project.sh
 
 # apply user options.
 . auto/depends.sh
 
-# auto header EOF.
-echo "#endif" >> $SRS_AUTO_HEADERS_H
-echo "" >> $SRS_AUTO_HEADERS_H
+# the auto generated variables.
+. auto/auto_headers.sh
 
 #####################################################################################
 # generate Makefile.
 #####################################################################################
-SRS_MAKEFILE="Makefile"
 # ubuntu echo in Makefile cannot display color, use bash instead
 SRS_BUILD_SUMMARY="_srs_build_summary.sh"
 
-#####################################################################################
 # srs-librtmp sample entry
 SrsLibrtmpSampleEntry="nossl"
 if [ $SRS_SSL = YES ]; then SrsLibrtmpSampleEntry="ssl";fi
 # utest make entry, (cd utest; make)
 SrsUtestMakeEntry="@echo -e \"ignore utest for it's disabled\""
-if [ $SRS_UTEST = YES ]; then SrsUtestMakeEntry="(cd ${SRS_OBJS}/utest; \$(MAKE))"; fi
+if [ $SRS_UTEST = YES ]; then SrsUtestMakeEntry="(cd ${SRS_OBJS_DIR}/utest; \$(MAKE))"; fi
 
 #####################################################################################
-# colorful summary
-SrsHlsSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_HLS = YES ]; then SrsHlsSummaryColor="\${GREEN}"; fi
-SrsDvrSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_DVR = YES ]; then SrsDvrSummaryColor="\${GREEN}"; fi
-SrsNginxSummaryColor="\${GREEN}{disabled} "; if [ $SRS_NGINX = YES ]; then SrsNginxSummaryColor="\${GREEN}"; fi
-SrsSslSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_SSL = YES ]; then SrsSslSummaryColor="\${GREEN}"; fi
-SrsFfmpegSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_FFMPEG_TOOL = YES ]; then SrsFfmpegSummaryColor="\${GREEN}"; fi
-SrsTranscodeSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_TRANSCODE = YES ]; then SrsTranscodeSummaryColor="\${GREEN}"; fi
-SrsIngestSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_INGEST = YES ]; then SrsIngestSummaryColor="\${GREEN}"; fi
-SrsHttpCallbackSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_HTTP_CALLBACK = YES ]; then SrsHttpCallbackSummaryColor="\${GREEN}"; fi
-SrsHttpServerSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_HTTP_SERVER = YES ]; then SrsHttpServerSummaryColor="\${GREEN}"; fi
-SrsHttpApiSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_HTTP_API = YES ]; then SrsHttpApiSummaryColor="\${GREEN}"; fi
-SrsLibrtmpSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_LIBRTMP = YES ]; then SrsLibrtmpSummaryColor="\${GREEN}"; fi
-SrsLibrtmpSSLSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_LIBRTMP = YES ]; then if [ $SRS_SSL = YES ]; then SrsLibrtmpSSLSummaryColor="\${GREEN}"; fi fi
-SrsResearchSummaryColor="\${GREEN}{disabled} "; if [ $SRS_RESEARCH = YES ]; then SrsResearchSummaryColor="\${GREEN}"; fi
-SrsUtestSummaryColor="\${YELLOW}{disabled} "; if [ $SRS_UTEST = YES ]; then SrsUtestSummaryColor="\${GREEN}"; fi
-SrsGperfSummaryColor="\${GREEN}{disabled} "; if [ $SRS_GPERF = YES ]; then SrsGperfSummaryColor="\${GREEN}"; fi
-SrsGperfMCSummaryColor="\${GREEN}{disabled} "; if [ $SRS_GPERF_MC = YES ]; then SrsGperfMCSummaryColor="\${YELLOW}"; fi
-SrsGperfMPSummaryColor="\${GREEN}{disabled} "; if [ $SRS_GPERF_MP = YES ]; then SrsGperfMPSummaryColor="\${YELLOW}"; fi
-SrsGperfCPSummaryColor="\${GREEN}{disabled} "; if [ $SRS_GPERF_CP = YES ]; then SrsGperfCPSummaryColor="\${YELLOW}"; fi
-SrsGprofSummaryColor="\${GREEN}{disabled} "; if [ $SRS_GPROF = YES ]; then SrsGprofSummaryColor="\${YELLOW}"; fi
-cat < ${SRS_OBJS}/${SRS_BUILD_SUMMARY}
-#!/bin/bash
+# finger out modules to install.
+# where srs module is a dir which contains a config file.
+SRS_MODULES=()
+__mfiles=`find modules -name "config"` && for __mfile in $__mfiles; do
+    SRS_MODULES+=("`dirname $__mfile`")
+done
+
+# variables for makefile for all modules.
+__mphonys="" && __mdefaults="" && __mcleanups=""
+# add each modules for application
+for SRS_MODULE in ${SRS_MODULES[*]}; do
+    echo "install module at: $SRS_MODULE"
+    . $SRS_MODULE/config
+    if [[ 0 -eq ${#SRS_MODULE_MAIN[@]} ]]; then continue; fi
+    __mphonys="$__mphonys $SRS_MODULE_NAME"
+    __mdefaults="$__mdefaults $SRS_MODULE_NAME"
+    __mcleanups="$__mcleanups $SRS_MODULE_NAME"
+done
 
 #####################################################################################
-# linux shell color support.
-RED="\\${RED}"
-GREEN="\\${GREEN}"
-YELLOW="\\${YELLOW}"
-BLACK="\\${BLACK}"
-
-echo -e "\${GREEN}build summary:\${BLACK}"
-echo -e "     \${BLACK}+------------------------------------------------------------------------------------\${BLACK}"
-echo -e "     |${SrsGperfSummaryColor}gperf @see: https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_GPERF\${BLACK}"
-echo -e "     |     ${SrsGperfMCSummaryColor}gmc @see: http://google-perftools.googlecode.com/svn/trunk/doc/heap_checker.html\${BLACK}"
-echo -e "     |     ${SrsGperfMCSummaryColor}gmc: gperf memory check\${BLACK}"
-echo -e "     |             ${SrsGperfMCSummaryColor}env PPROF_PATH=./objs/pprof HEAPCHECK=normal ./objs/srs -c conf/console.conf # start gmc\${BLACK}"
-echo -e "     |             ${SrsGperfMCSummaryColor}killall -2 srs # or CTRL+C to stop gmc\${BLACK}"
-echo -e "     |     ${SrsGperfMPSummaryColor}gmp @see: http://google-perftools.googlecode.com/svn/trunk/doc/heapprofile.html\${BLACK}"
-echo -e "     |     ${SrsGperfMPSummaryColor}gmp: gperf memory profile\${BLACK}"
-echo -e "     |             ${SrsGperfMPSummaryColor}rm -f gperf.srs.gmp*; ./objs/srs -c conf/console.conf # start gmp\${BLACK}"
-echo -e "     |             ${SrsGperfMPSummaryColor}killall -2 srs # or CTRL+C to stop gmp\${BLACK}"
-echo -e "     |             ${SrsGperfMPSummaryColor}./objs/pprof --text objs/srs gperf.srs.gmp* # to analysis memory profile\${BLACK}"
-echo -e "     |     ${SrsGperfCPSummaryColor}gcp @see: http://google-perftools.googlecode.com/svn/trunk/doc/cpuprofile.html\${BLACK}"
-echo -e "     |     ${SrsGperfCPSummaryColor}gcp: gperf cpu profile\${BLACK}"
-echo -e "     |             ${SrsGperfCPSummaryColor}rm -f gperf.srs.gcp*; ./objs/srs -c conf/console.conf # start gcp\${BLACK}"
-echo -e "     |             ${SrsGperfCPSummaryColor}killall -2 srs # or CTRL+C to stop gcp\${BLACK}"
-echo -e "     |             ${SrsGperfCPSummaryColor}./objs/pprof --text objs/srs gperf.srs.gcp* # to analysis cpu profile\${BLACK}"
-echo -e "     \${BLACK}+------------------------------------------------------------------------------------\${BLACK}"
-echo -e "     |${SrsGprofSummaryColor}gprof @see: https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_GPROF\${BLACK}"
-echo -e "     |${SrsGprofSummaryColor}gprof: GNU profile tool, @see: http://www.cs.utah.edu/dept/old/texinfo/as/gprof.html\${BLACK}"
-echo -e "     |     ${SrsGprofSummaryColor}rm -f gmon.out; ./objs/srs -c conf/console.conf # start gprof\${BLACK}"
-echo -e "     |     ${SrsGprofSummaryColor}killall -2 srs # or CTRL+C to stop gprof\${BLACK}"
-echo -e "     |     ${SrsGprofSummaryColor}gprof -b ./objs/srs gmon.out > gprof.srs.log && rm -f gmon.out # gprof report to gprof.srs.log\${BLACK}"
-echo -e "     \${BLACK}+------------------------------------------------------------------------------------\${BLACK}"
-echo -e "     |${SrsResearchSummaryColor}research: ./objs/research, api server, players, ts info, librtmp.\${BLACK}"
-echo -e "     \${BLACK}+------------------------------------------------------------------------------------\${BLACK}"
-echo -e "     |${SrsUtestSummaryColor}utest: ./objs/srs_utest, the utest for srs\${BLACK}"
-echo -e "     \${BLACK}+------------------------------------------------------------------------------------\${BLACK}"
-echo -e "     |${SrsLibrtmpSummaryColor}librtmp @see: https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SrsLibrtmp\${BLACK}"
-echo -e "     |${SrsLibrtmpSummaryColor}librtmp: ./objs/include, ./objs/lib, the srs-librtmp library\${BLACK}"
-echo -e "     |     ${SrsLibrtmpSummaryColor}simple handshake: publish/play stream with simple handshake to server\${BLACK}"
-echo -e "     |     ${SrsLibrtmpSSLSummaryColor}complex handshake: it's not required for client, recommend disable it\${BLACK}"
-echo -e "     |     ${SrsLibrtmpSummaryColor}librtmp-sample: ./research/librtmp, the srs-librtmp client sample\${BLACK}"
-echo -e "     |     ${SrsLibrtmpSummaryColor}librtmp-sample: ./research/librtmp/objs/srs_ingest_flv\${BLACK}"
-echo -e "     |     ${SrsLibrtmpSummaryColor}librtmp-sample: ./research/librtmp/objs/srs_ingest_rtmp\${BLACK}"
-echo -e "     |     ${SrsLibrtmpSummaryColor}librtmp-sample: ./research/librtmp/objs/srs_detect_rtmp\${BLACK}"
-echo -e "     |     ${SrsLibrtmpSummaryColor}librtmp-sample: ./research/librtmp/objs/srs_bandwidth_check\${BLACK}"
-echo -e "     \${BLACK}+------------------------------------------------------------------------------------\${BLACK}"
-echo -e "     |\${GREEN}server: ./objs/srs -c conf/srs.conf, start the srs server\${BLACK}"
-echo -e "     |     ${SrsHlsSummaryColor}hls @see: https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_DeliveryHLS\${BLACK}"
-echo -e "     |     ${SrsHlsSummaryColor}hls: generate m3u8 and ts from rtmp stream\${BLACK}"
-echo -e "     |     ${SrsDvrSummaryColor}dvr @see: https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_DVR\${BLACK}"
-echo -e "     |     ${SrsDvrSummaryColor}dvr: record RTMP stream to flv files.\${BLACK}"
-echo -e "     |     ${SrsNginxSummaryColor}nginx @see: https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_DeliveryHLS\${BLACK}"
-echo -e "     |     ${SrsNginxSummaryColor}nginx: delivery HLS stream by nginx\${BLACK}"
-echo -e "     |     ${SrsNginxSummaryColor}nginx: sudo ./objs/nginx/sbin/nginx\${BLACK}"
-echo -e "     |     ${SrsSslSummaryColor}ssl @see: https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_RTMPHandshake\${BLACK}"
-echo -e "     |     ${SrsSslSummaryColor}ssl: support RTMP complex handshake for client required, for instance, flash\${BLACK}"
-echo -e "     |     ${SrsFfmpegSummaryColor}ffmpeg @see: https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_FFMPEG\${BLACK}"
-echo -e "     |     ${SrsFfmpegSummaryColor}ffmpeg: transcode, mux, ingest tool\${BLACK}"
-echo -e "     |     ${SrsFfmpegSummaryColor}ffmpeg: ./objs/ffmpeg/bin/ffmpeg\${BLACK}"
-echo -e "     |     ${SrsTranscodeSummaryColor}transcode @see: https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_FFMPEG\${BLACK}"
-echo -e "     |     ${SrsTranscodeSummaryColor}transcode: support transcoding RTMP stream\${BLACK}"
-echo -e "     |     ${SrsIngestSummaryColor}ingest @see: https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Ingest\${BLACK}"
-echo -e "     |     ${SrsIngestSummaryColor}ingest: support ingest file/stream/device then push to SRS by RTMP stream\${BLACK}"
-echo -e "     |     ${SrsHttpCallbackSummaryColor}http-callback @see: https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_HTTPCallback\${BLACK}"
-echo -e "     |     ${SrsHttpCallbackSummaryColor}http-callback: support http callback for authentication and event injection\${BLACK}"
-echo -e "     |     ${SrsHttpServerSummaryColor}http-server @see: https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_HTTPServer\${BLACK}"
-echo -e "     |     ${SrsHttpServerSummaryColor}http-server: support http server to delivery http stream\${BLACK}"
-echo -e "     |     ${SrsHttpApiSummaryColor}http-api @see: https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_HTTPApi\${BLACK}"
-echo -e "     |     ${SrsHttpApiSummaryColor}http-api: support http api to manage server\${BLACK}"
-echo -e "     \${BLACK}+------------------------------------------------------------------------------------\${BLACK}"
-echo -e "\${GREEN}binaries @see: https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Build\${BLACK}"
-
-echo "you can:"
-echo "      ./objs/srs -c conf/srs.conf"
-echo "                  to start the srs server, with config conf/srs.conf."
+# build tools or compiler args.
+# enable gdb debug
+GDBDebug=" -g -O0"
+# the warning level.
+WarnLevel=" -Wall"
+# the compile standard.
+CppStd="-ansi"
+# for library compile
+LibraryCompile=" -fPIC"
+# performance of gprof
+SrsGprof=""; SrsGprofLink=""; if [ $SRS_GPROF = YES ]; then SrsGprof=" -pg -lc_p"; SrsGprofLink=" -pg"; fi
+# performance of gperf
+SrsGperf=""; SrsGperfLink=""; if [ $SRS_GPERF = YES ]; then SrsGperfLink=" -lpthread"; fi
+# the cxx flag generated.
+CXXFLAGS="${CppStd}${WarnLevel}${GDBDebug}${LibraryCompile}${SrsGprof}"
+if [ $SRS_GPERF = YES ]; then CXXFLAGS="${CXXFLAGS} -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-free"; fi
+cat << END > ${SRS_OBJS}/${SRS_MAKEFILE}
+GCC = gcc
+CXX = g++
+AR = ar
+LINK = g++
+CXXFLAGS = ${CXXFLAGS}
+
+.PHONY: default srs srs_ingest_hls librtmp
+
+default:
+
 END
 
+#####################################################################################
+# Libraries, external library to build in srs,
+# header(.h): add to ModuleLibIncs if need the specified library. for example, LibSTRoot
+# library(.a): add to ModuleLibFiles if binary need the specifeid library. for example, LibSTfile
+#
+# st(state-threads) the basic network library for SRS.
+LibSTRoot="${SRS_OBJS_DIR}/st"; LibSTfile="${LibSTRoot}/libst.a"
+# hp(http-parser) the http request/url parser, for SRS to support HTTP callback.
+LibHttpParserRoot=""; LibHttpParserfile=""
+if [ $SRS_HTTP_PARSER = YES ]; then LibHttpParserRoot="${SRS_OBJS_DIR}/hp"; LibHttpParserfile="${LibHttpParserRoot}/libhttp_parser.a"; fi
+# openssl-1.0.1f, for the RTMP complex handshake.
+LibSSLRoot="";LibSSLfile=""
+if [ $SRS_SSL = YES ]; then if [ $SRS_USE_SYS_SSL = NO ]; then LibSSLRoot="${SRS_OBJS_DIR}/openssl/include"; LibSSLfile="${SRS_OBJS_DIR}/openssl/lib/libssl.a ${SRS_OBJS_DIR}/openssl/lib/libcrypto.a"; fi fi
+# gperftools-2.1, for mem check and mem/cpu profile
+LibGperfRoot=""; LibGperfFile=""
+if [ $SRS_GPERF = YES ]; then LibGperfRoot="${SRS_OBJS_DIR}/gperf/include"; LibGperfFile="${SRS_OBJS_DIR}/gperf/lib/libtcmalloc_and_profiler.a"; fi
+# the link options, always use static link
+SrsLinkOptions="-ldl"; 
+if [ $SRS_SSL = YES ]; then if [ $SRS_USE_SYS_SSL = YES ]; then SrsLinkOptions="${SrsLinkOptions} -lssl"; fi fi
+# if static specified, add static
+# TODO: FIXME: remove static.
+if [ $SRS_STATIC = YES ]; then SrsLinkOptions="${SrsLinkOptions} -static"; fi
+# if mips, add -lgcc_eh, or stl compile failed.
+if [ $SRS_MIPS_UBUNTU12 = YES ]; then SrsLinkOptions="${SrsLinkOptions} -lgcc_eh"; fi
+
+#####################################################################################
+# Modules, compile each module, then link to binary
+#
+#Core, depends only on system apis.
+MODULE_ID="CORE"
+MODULE_DEPENDS=()
+ModuleLibIncs=(${SRS_OBJS_DIR})
+MODULE_FILES=("srs_core" "srs_core_autofree" "srs_core_performance")
+CORE_INCS="src/core"; MODULE_DIR=${CORE_INCS} . auto/modules.sh
+CORE_OBJS="${MODULE_OBJS[@]}"
+#
+#Kernel, depends on core, provides error/log/config, nothing about stream information.
+MODULE_ID="KERNEL" 
+MODULE_DEPENDS=("CORE") 
+ModuleLibIncs=(${SRS_OBJS_DIR})
+MODULE_FILES=("srs_kernel_error" "srs_kernel_log" "srs_kernel_stream"
+        "srs_kernel_utility" "srs_kernel_flv" "srs_kernel_codec" "srs_kernel_file" 
+        "srs_kernel_consts" "srs_kernel_aac" "srs_kernel_mp3" "srs_kernel_ts"
+        "srs_kernel_buffer")
+KERNEL_INCS="src/kernel"; MODULE_DIR=${KERNEL_INCS} . auto/modules.sh
+KERNEL_OBJS="${MODULE_OBJS[@]}"
+#
+#RTMP Protocol, depends on core/kernel, provides rtmp/htttp protocol features.
+MODULE_ID="RTMP" 
+MODULE_DEPENDS=("CORE" "KERNEL") 
+ModuleLibIncs=(${SRS_OBJS_DIR} ${LibSSLRoot})
+MODULE_FILES=("srs_rtmp_amf0" "srs_rtmp_io" "srs_rtmp_stack" "srs_rtmp_sdk"
+        "srs_rtmp_handshake" "srs_rtmp_utility" "srs_rtmp_msg_array" "srs_rtmp_buffer"
+        "srs_raw_avc" "srs_rtsp_stack")
+RTMP_INCS="src/protocol"; MODULE_DIR=${RTMP_INCS} . auto/modules.sh
+RTMP_OBJS="${MODULE_OBJS[@]}"
+#
+#App Module
+if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then
+    MODULE_ID="APP" 
+    MODULE_DEPENDS=("CORE" "KERNEL" "RTMP") 
+    ModuleLibIncs=(${LibSTRoot} ${LibHttpParserRoot} ${SRS_OBJS_DIR})
+    MODULE_FILES=("srs_app_server" "srs_app_conn" "srs_app_rtmp_conn" "srs_app_st_socket" "srs_app_source" 
+            "srs_app_refer" "srs_app_hls" "srs_app_forward" "srs_app_encoder" "srs_app_http" 
+            "srs_app_thread" "srs_app_bandwidth" "srs_app_st" "srs_app_log" "srs_app_config" 
+            "srs_app_pithy_print" "srs_app_reload" "srs_app_http_api" "srs_app_http_conn" "srs_app_http_hooks" 
+            "srs_app_json" "srs_app_ingest" "srs_app_ffmpeg" "srs_app_utility" "srs_app_dvr" "srs_app_edge"
+            "srs_app_kbps" "srs_app_heartbeat" "srs_app_empty" "srs_app_http_client"
+            "srs_app_recv_thread" "srs_app_security" "srs_app_statistic" "srs_app_hds"
+            "srs_app_mpegts_udp" "srs_app_rtsp" "srs_app_listener" "srs_app_async_call"
+            "srs_app_caster_flv")
+    DEFINES=""
+    # add each modules for app
+    for SRS_MODULE in ${SRS_MODULES[*]}; do
+        . $SRS_MODULE/config
+        MODULE_FILES+=("${SRS_MODULE_APP[*]}")
+        DEFINES="${DEFINES} ${SRS_MODULE_DEFINES}"
+    done
+    APP_INCS="src/app"; MODULE_DIR=${APP_INCS} . auto/modules.sh
+    APP_OBJS="${MODULE_OBJS[@]}"
+fi
+#
+#LIBS Module, build libsrs.a for static link.
+MODULE_ID="LIBS" 
+MODULE_DEPENDS=("CORE" "KERNEL" "RTMP") 
+ModuleLibIncs=(${SRS_OBJS_DIR})
+MODULE_FILES=("srs_librtmp" "srs_lib_simple_socket" "srs_lib_bandwidth")
+LIBS_INCS="src/libs"; MODULE_DIR=${LIBS_INCS} . auto/modules.sh
+LIBS_OBJS="${MODULE_OBJS[@]}"
+#
+#Main Module
+if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then
+    MODULE_ID="MAIN" 
+    MODULE_DEPENDS=("CORE" "KERNEL" "RTMP" "APP")
+    ModuleLibIncs=(${LibSTRoot} ${SRS_OBJS_DIR} ${LibGperfRoot} ${LibHttpParserRoot})
+    MODULE_FILES=("srs_main_server" "srs_main_ingest_hls")
+    # add each modules for main
+    for SRS_MODULE in ${SRS_MODULES[*]}; do
+        . $SRS_MODULE/config
+        MODULE_FILES+=("${SRS_MODULE_MAIN[*]}")
+    done
+    MAIN_INCS="src/main"; MODULE_DIR=${MAIN_INCS} . auto/modules.sh
+    MAIN_OBJS="${MODULE_OBJS[@]}"
+fi
+
+#####################################################################################
+# Binaries, main entrances, link the module and its depends modules,
+# then link to a binary, for example, objs/srs
+#
+# disable all app when export librtmp
+if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then
+    # all main entrances
+    MAIN_ENTRANCES=("srs_main_server" "srs_main_ingest_hls")
+    # add each modules for main
+    for SRS_MODULE in ${SRS_MODULES[*]}; do
+        . $SRS_MODULE/config
+        MAIN_ENTRANCES+=("${SRS_MODULE_MAIN[*]}")
+    done
+    # 
+    # all depends libraries
+    ModuleLibFiles=(${LibSTfile} ${LibHttpParserfile} ${LibSSLfile} ${LibGperfFile})
+    # all depends objects
+    MODULE_OBJS="${CORE_OBJS[@]} ${KERNEL_OBJS[@]} ${RTMP_OBJS[@]} ${APP_OBJS[@]} ${MAIN_OBJS[@]}"
+    LINK_OPTIONS="${SrsLinkOptions}${SrsGprofLink}${SrsGperfLink}"
+    #
+    # srs: srs(simple rtmp server) over st(state-threads)
+    BUILD_KEY="srs" APP_MAIN="srs_main_server" APP_NAME="srs" . auto/apps.sh
+    # 
+    # srs_ingest_hls: to ingest hls stream to srs. 
+    BUILD_KEY="srs_ingest_hls" APP_MAIN="srs_main_ingest_hls" APP_NAME="srs_ingest_hls" . auto/apps.sh
+    # add each modules for application
+    for SRS_MODULE in ${SRS_MODULES[*]}; do
+        . $SRS_MODULE/config
+        # no SRS_MODULE_MAIN 
+        if [[ 0 -eq ${#SRS_MODULE_MAIN[@]} ]]; then continue; fi
+        BUILD_KEY="$SRS_MODULE_NAME" APP_MAIN="$SRS_MODULE_MAIN" APP_NAME="$SRS_MODULE_NAME" . auto/apps.sh
+    done
+fi
+# srs librtmp
+if [ $SRS_LIBRTMP = YES ]; then
+    MODULE_OBJS="${CORE_OBJS[@]} ${KERNEL_OBJS[@]} ${RTMP_OBJS[@]} ${LIBS_OBJS[@]}"
+    BUILD_KEY="librtmp" LIB_NAME="lib/srs_librtmp" . auto/libs.sh
+fi
+#
+# utest, the unit-test cases of srs, base on gtest1.6
+if [ $SRS_UTEST = YES ]; then
+    MODULE_FILES=("srs_utest" "srs_utest_amf0" "srs_utest_protocol" 
+            "srs_utest_kernel" "srs_utest_core" "srs_utest_config" 
+            "srs_utest_reload")
+    ModuleLibIncs=(${SRS_OBJS_DIR} ${LibSTRoot} ${LibSSLRoot})
+    ModuleLibFiles=(${LibSTfile} ${LibHttpParserfile} ${LibSSLfile})
+    MODULE_DEPENDS=("CORE" "KERNEL" "RTMP" "APP")
+    MODULE_OBJS="${CORE_OBJS[@]} ${KERNEL_OBJS[@]} ${RTMP_OBJS[@]} ${APP_OBJS[@]}"
+    LINK_OPTIONS="-lpthread ${SrsLinkOptions}" MODULE_DIR="src/utest" APP_NAME="srs_utest" . auto/utest.sh
+fi
+
+#####################################################################################
+# generate colorful summary script
+. auto/summary.sh
+
 #####################################################################################
 # makefile
 echo "generate Makefile"
-cat << END > ${SRS_MAKEFILE}
-.PHONY: default _default install install-api help clean server librtmp utest _prepare_dir
+
+# backup old makefile.
+rm -f ${SRS_WORKDIR}/${SRS_MAKEFILE}.bk &&
+mv ${SRS_WORKDIR}/${SRS_MAKEFILE} ${SRS_WORKDIR}/${SRS_MAKEFILE}.bk
+
+# generate phony header
+cat << END > ${SRS_WORKDIR}/${SRS_MAKEFILE}
+.PHONY: default _default install install-api help clean server srs_ingest_hls librtmp utest _prepare_dir $__mphonys
 
 # install prefix.
 SRS_PREFIX=${SRS_PREFIX}
@@ -215,51 +286,107 @@ END
 
 # embeded, ubuntu12, use embeded tool chain.
 if [ $SRS_EMBEDED_CPU = YES ]; then
-    cat << END >> ${SRS_MAKEFILE}
+    cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE}
 default:
 	\$(MAKE) GCC=${SrsArmGCC} CXX=${SrsArmCXX} AR=${SrsArmAR} LINK=${SrsArmCXX} _default
 
 END
 # x86/x64, use gnu-gcc/g++ tool chain.
 else
-    cat << END >> ${SRS_MAKEFILE}
+    cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE}
 default:
 	\$(MAKE) _default
 
 END
 fi
 
-# the real entry for all platform.
-cat << END >> ${SRS_MAKEFILE}
-_default: server librtmp utest
+# the real entry for all platform:
+#       the server, librtmp and utest
+# where the bellow will check and disable some entry by only echo.
+cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE}
+_default: server srs_ingest_hls librtmp utest $__mdefaults
 	@bash objs/_srs_build_summary.sh
 
 help:
-	@echo "Usage: make |||||||"
-	@echo "  help               display this help menu"
-	@echo "  clean              cleanup project"
-	@echo "  server             build the srs(simple rtmp server) over st(state-threads)"
-	@echo "  librtmp            build the client publish/play library, and samples"
-	@echo "  utest              build the utest for srs"
-	@echo "  install            install srs to the prefix path"
-	@echo "  install-api        install srs and api-server to the prefix path"
-	@echo "  uninstall          uninstall srs from prefix path"
+	@echo "Usage: make ||||||||"
+	@echo "     help               display this help menu"
+	@echo "     clean              cleanup project"
+	@echo "     server             build the srs(simple rtmp server) over st(state-threads)"
+	@echo "     srs_ingest_hls     build the hls ingest tool of srs."
+	@echo "     librtmp            build the client publish/play library, and samples"
+	@echo "     utest              build the utest for srs"
+	@echo "     install            install srs to the prefix path"
+	@echo "     install-api        install srs and api-server to the prefix path"
+	@echo "     uninstall          uninstall srs from prefix path"
+	@echo "@remark all modules will auto genearted and build"
+	@echo "For example:"
+	@echo "     make"
+	@echo "     make help"
 
 clean: 
-	(cd ${SRS_OBJS}; rm -rf srs srs_utest)
-	(cd ${SRS_OBJS}; rm -rf src research include lib)
-	(cd ${SRS_OBJS}/utest; rm -rf *.o *.a)
+	(cd ${SRS_OBJS_DIR}; rm -rf srs srs_utest $__mcleanups)
+	(cd ${SRS_OBJS_DIR}; rm -rf src include lib)
+	(cd ${SRS_OBJS_DIR}/utest; rm -rf *.o *.a)
 	(cd research/librtmp; make clean)
 	(cd research/api-server/static-dir; rm -rf crossdomain.xml forward live players)
 
+END
+
+# if export librtmp, donot build the srs server.
+if [ $SRS_EXPORT_LIBRTMP_PROJECT != NO ]; then
+    cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE}
+server: _prepare_dir
+	@echo "donot build the srs(simple rtmp server) for srs-librtmp"
+srs_ingest_hls: _prepare_dir
+	@echo "donot build the srs_ingest_hls for srs-librtmp"
+
+END
+else
+    cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE}
 server: _prepare_dir
 	@echo "build the srs(simple rtmp server) over st(state-threads)"
-	\$(MAKE) -f ${SRS_OBJS}/${SRS_MAKEFILE} srs
+	\$(MAKE) -f ${SRS_OBJS_DIR}/${SRS_MAKEFILE} srs
+srs_ingest_hls: _prepare_dir
+	@echo "build the srs_ingest_hls for srs"
+	\$(MAKE) -f ${SRS_OBJS_DIR}/${SRS_MAKEFILE} srs_ingest_hls
 
 END
+fi
+# generate all modules entry
+for SRS_MODULE in ${SRS_MODULES[*]}; do
+    . $SRS_MODULE/config
+    # if export librtmp, donot build the bravo-ingest.
+    if [ $SRS_EXPORT_LIBRTMP_PROJECT != NO ]; then
+        cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE}
+$SRS_MODULE_NAME: _prepare_dir
+	@echo "donot build the $SRS_MODULE_NAME for srs-librtmp"
+
+END
+    else
+        cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE}
+$SRS_MODULE_NAME: _prepare_dir
+	@echo "build the $SRS_MODULE_NAME over SRS"
+	\$(MAKE) -f ${SRS_OBJS_DIR}/${SRS_MAKEFILE} $SRS_MODULE_NAME
+
+END
+    fi
+done
+
+# disable install entry for srs-librtmp
+if [ $SRS_EXPORT_LIBRTMP_PROJECT != NO ]; then
+    cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE}
+uninstall: 
+	@echo "disable uninstall for srs-librtmp"
 
-# install entry
-cat << END >> ${SRS_MAKEFILE}
+install-api: 
+	@echo "disable install-api for srs-librtmp"
+
+install: 
+	@echo "disable install for srs-librtmp"
+
+END
+else
+    cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE}
 uninstall:
 	@echo "rmdir \$(SRS_PREFIX)"
 	@rm -rf \$(SRS_PREFIX)
@@ -286,7 +413,7 @@ install-api: install
 	@echo "     sudo ln -sf \$(SRS_PREFIX)/etc/init.d/srs-api /etc/init.d/srs-api"
 	@echo "     /etc/init.d/srs-api start"
 	@echo "     http://\$(shell bash auto/local_ip.sh):8085"
-	@echo "@see: https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_LinuxService"
+	@echo "@see: https://github.com/simple-rtmp-server/srs/wiki/v1_CN_LinuxService"
 
 install:
 	@echo "mkdir \$(__REAL_INSTALL)"
@@ -308,21 +435,23 @@ install:
 	@echo "srs installed, to link and start srs:"
 	@echo "     sudo ln -sf \$(SRS_PREFIX)/etc/init.d/srs /etc/init.d/srs"
 	@echo "     /etc/init.d/srs start"
-	@echo "@see: https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_LinuxService"
+	@echo "@see: https://github.com/simple-rtmp-server/srs/wiki/v1_CN_LinuxService"
 
 END
+fi
 
+# generate srs-librtmp entry
 if [ $SRS_LIBRTMP = YES ]; then
-    cat << END >> ${SRS_MAKEFILE}
+    cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE}
 librtmp: server
 	@echo "build the client publish/play library."
-	\$(MAKE) -f ${SRS_OBJS}/${SRS_MAKEFILE} librtmp
+	\$(MAKE) -f ${SRS_OBJS_DIR}/${SRS_MAKEFILE} librtmp
 	@echo "build the srs-librtmp sample"
 	(cd research/librtmp; \$(MAKE) ${SrsLibrtmpSampleEntry})
 
 END
 else
-    cat << END >> ${SRS_MAKEFILE}
+    cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE}
 librtmp: server
 	@echo "srs-librtmp is disabled, ignore."
 
@@ -330,7 +459,7 @@ END
 fi
 
 if [ $SRS_UTEST = YES ]; then
-    cat << END >> ${SRS_MAKEFILE}
+    cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE}
 utest: server
 	@echo "build the utest for srs"
 	${SrsUtestMakeEntry}
@@ -338,164 +467,22 @@ utest: server
 
 END
 else
-    cat << END >> ${SRS_MAKEFILE}
+    cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE}
 utest: server
 	@echo "utest is disabled, ignore"
 
 END
 fi
 
-cat << END >> ${SRS_MAKEFILE}
+cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE}
 # the ./configure will generate it.
 _prepare_dir:
-	@mkdir -p ${SRS_OBJS}
-END
-
-#####################################################################################
-# build tools or compiler args.
-# enable gdb debug
-GDBDebug=" -g -O0"
-# the warning level.
-WarnLevel=" -Wall"
-# the compile standard.
-CppStd="-ansi"
-# for library compile
-LibraryCompile=" -fPIC"
-# performance of gprof
-SrsGprof=""; SrsGprofLink=""; if [ $SRS_GPROF = YES ]; then SrsGprof=" -pg -lc_p"; SrsGprofLink=" -pg"; fi
-# performance of gperf
-SrsGperf=""; SrsGperfLink=""; if [ $SRS_GPERF = YES ]; then SrsGperfLink=" -lpthread"; fi
-# the cxx flag generated.
-CXXFLAGS="${CppStd}${WarnLevel}${GDBDebug}${LibraryCompile}${SrsGprof}"
-if [ $SRS_GPERF = YES ]; then CXXFLAGS="${CXXFLAGS} -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-free"; fi
-cat << END > ${SRS_OBJS}/${SRS_MAKEFILE}
-GCC = gcc
-CXX = g++
-AR = ar
-LINK = g++
-CXXFLAGS = ${CXXFLAGS}
-
-.PHONY: default srs librtmp
-
-default:
-
+	@mkdir -p ${SRS_OBJS_DIR}
 END
 
-#####################################################################################
-# Libraries, external library to build in srs,
-# header(.h): add to ModuleLibIncs if need the specified library. for example, LibSTRoot
-# library(.a): add to ModuleLibFiles if binary need the specifeid library. for example, LibSTfile
-#
-# st(state-threads) the basic network library for SRS.
-LibSTRoot="${SRS_OBJS}/st"; LibSTfile="${LibSTRoot}/libst.a"
-# hp(http-parser) the http request/url parser, for SRS to support HTTP callback.
-LibHttpParserRoot=""; LibHttpParserfile=""
-if [ $SRS_HTTP_PARSER = YES ]; then LibHttpParserRoot="${SRS_OBJS}/hp"; LibHttpParserfile="${LibHttpParserRoot}/libhttp_parser.a"; fi
-# openssl-1.0.1f, for the RTMP complex handshake.
-LibSSLRoot="";LibSSLfile=""
-if [ $SRS_SSL = YES ]; then if [ $SRS_USE_SYS_SSL = NO ]; then LibSSLRoot="${SRS_OBJS}/openssl/include"; LibSSLfile="${SRS_OBJS}/openssl/lib/libssl.a ${SRS_OBJS}/openssl/lib/libcrypto.a"; fi fi
-# gperftools-2.1, for mem check and mem/cpu profile
-LibGperfRoot=""; LibGperfFile=""
-if [ $SRS_GPERF = YES ]; then LibGperfRoot="${SRS_OBJS}/gperf/include"; LibGperfFile="${SRS_OBJS}/gperf/lib/libtcmalloc_and_profiler.a"; fi
-# the link options, always use static link
-SrsLinkOptions="-ldl"; 
-if [ $SRS_SSL = YES ]; then if [ $SRS_USE_SYS_SSL = YES ]; then SrsLinkOptions="${SrsLinkOptions} -lssl"; fi fi
-# if static specified, add static
-# TODO: FIXME: remove static.
-if [ $SRS_STATIC = YES ]; then SrsLinkOptions="${SrsLinkOptions} -static"; fi
-# if mips, add -lgcc_eh, or stl compile failed.
-if [ $SRS_MIPS_UBUNTU12 = YES ]; then SrsLinkOptions="${SrsLinkOptions} -lgcc_eh"; fi
-
-#####################################################################################
-# Modules, compile each module, then link to binary
-#
-#Core, depends only on system apis.
-MODULE_ID="CORE"
-MODULE_DEPENDS=()
-ModuleLibIncs=(${SRS_OBJS})
-MODULE_FILES=("srs_core" "srs_core_autofree")
-CORE_INCS="src/core"; MODULE_DIR=${CORE_INCS} . auto/modules.sh
-CORE_OBJS="${MODULE_OBJS[@]}"
-#
-#Kernel, depends on core, provides error/log/config, nothing about stream information.
-MODULE_ID="KERNEL" 
-MODULE_DEPENDS=("CORE") 
-ModuleLibIncs=(${SRS_OBJS})
-MODULE_FILES=("srs_kernel_error" "srs_kernel_log" "srs_kernel_stream" "srs_kernel_buffer" 
-        "srs_kernel_utility" "srs_kernel_flv" "srs_kernel_codec" "srs_kernel_file" 
-        "srs_kernel_consts")
-KERNEL_INCS="src/kernel"; MODULE_DIR=${KERNEL_INCS} . auto/modules.sh
-KERNEL_OBJS="${MODULE_OBJS[@]}"
-#
-#RTMP Protocol, depends on core/kernel, provides rtmp/htttp protocol features.
-MODULE_ID="RTMP" 
-MODULE_DEPENDS=("CORE" "KERNEL") 
-ModuleLibIncs=(${SRS_OBJS} ${LibSSLRoot})
-MODULE_FILES=("srs_protocol_amf0" "srs_protocol_io" "srs_protocol_stack" "srs_protocol_rtmp"
-        "srs_protocol_handshake" "srs_protocol_utility" "srs_protocol_msg_array")
-RTMP_INCS="src/rtmp"; MODULE_DIR=${RTMP_INCS} . auto/modules.sh
-RTMP_OBJS="${MODULE_OBJS[@]}"
-#
-#App Module
-MODULE_ID="APP" 
-MODULE_DEPENDS=("CORE" "KERNEL" "RTMP") 
-ModuleLibIncs=(${LibSTRoot} ${LibHttpParserRoot} ${SRS_OBJS})
-MODULE_FILES=("srs_app_server" "srs_app_conn" "srs_app_rtmp_conn" "srs_app_st_socket" "srs_app_source" 
-        "srs_app_refer" "srs_app_hls" "srs_app_forward" "srs_app_encoder" "srs_app_http" 
-        "srs_app_thread" "srs_app_bandwidth" "srs_app_st" "srs_app_log" "srs_app_config" 
-        "srs_app_pithy_print" "srs_app_reload" "srs_app_http_api" "srs_app_http_conn" "srs_app_http_hooks" 
-        "srs_app_json" "srs_app_ingest" "srs_app_ffmpeg" "srs_app_utility" "srs_app_dvr" "srs_app_edge"
-        "srs_app_kbps" "srs_app_heartbeat" "srs_app_empty" "srs_app_http_client" "srs_app_avc_aac")
-APP_INCS="src/app"; MODULE_DIR=${APP_INCS} . auto/modules.sh
-APP_OBJS="${MODULE_OBJS[@]}"
-#
-#LIBS Module, build libsrs.a for static link.
-MODULE_ID="LIBS" 
-MODULE_DEPENDS=("CORE" "KERNEL" "RTMP") 
-ModuleLibIncs=(${SRS_OBJS})
-MODULE_FILES=("srs_librtmp" "srs_lib_simple_socket" "srs_lib_bandwidth")
-LIBS_INCS="src/libs"; MODULE_DIR=${LIBS_INCS} . auto/modules.sh
-LIBS_OBJS="${MODULE_OBJS[@]}"
-#
-#Main Module
-MODULE_ID="MAIN" 
-MODULE_DEPENDS=("CORE" "KERNEL" "RTMP" "APP")
-ModuleLibIncs=(${LibSTRoot} ${SRS_OBJS} ${LibGperfRoot})
-MODULE_FILES=("srs_main_server")
-MAIN_INCS="src/main"; MODULE_DIR=${MAIN_INCS} . auto/modules.sh
-MAIN_OBJS="${MODULE_OBJS[@]}"
-
-#####################################################################################
-# Binaries, main entrances, link the module and its depends modules,
-# then link to a binary, for example, objs/srs
-#
-# all main entrances
-MAIN_ENTRANCES=("srs_main_server")
-# 
-# all depends libraries
-ModuleLibFiles=(${LibSTfile} ${LibHttpParserfile} ${LibSSLfile} ${LibGperfFile})
-# all depends objects
-MODULE_OBJS="${CORE_OBJS[@]} ${KERNEL_OBJS[@]} ${RTMP_OBJS[@]} ${APP_OBJS[@]} ${MAIN_OBJS[@]}"
-LINK_OPTIONS="${SrsLinkOptions}${SrsGprofLink}${SrsGperfLink}"
-#
-# srs:
-# srs(simple rtmp server) over st(state-threads)
-BUILD_KEY="srs" APP_MAIN="srs_main_server" APP_NAME="srs" . auto/apps.sh
-# srs librtmp
-if [ $SRS_LIBRTMP = YES ]; then
-    MODULE_OBJS="${CORE_OBJS[@]} ${KERNEL_OBJS[@]} ${RTMP_OBJS[@]} ${LIBS_OBJS[@]}"
-    BUILD_KEY="librtmp" LIB_NAME="lib/srs_librtmp" . auto/libs.sh
-fi
-#
-# utest, the unit-test cases of srs, base on gtest1.6
-MODULE_FILES=("srs_utest" "srs_utest_amf0" "srs_utest_protocol" 
-        "srs_utest_kernel" "srs_utest_core" "srs_utest_config" 
-        "srs_utest_reload")
-ModuleLibIncs=(${SRS_OBJS} ${LibSTRoot} ${LibSSLRoot})
-ModuleLibFiles=(${LibSTfile} ${LibHttpParserfile} ${LibSSLfile})
-MODULE_DEPENDS=("CORE" "KERNEL" "RTMP" "APP")
-MODULE_OBJS="${CORE_OBJS[@]} ${KERNEL_OBJS[@]} ${RTMP_OBJS[@]} ${APP_OBJS[@]}"
-LINK_OPTIONS="-lpthread ${SrsLinkOptions}" MODULE_DIR="src/utest" APP_NAME="srs_utest" . auto/utest.sh
+# generate makefile ok, append the tails.
+cat ${SRS_WORKDIR}/${SRS_MAKEFILE}.bk >> ${SRS_WORKDIR}/${SRS_MAKEFILE} &&
+rm -f ${SRS_WORKDIR}/${SRS_MAKEFILE}.bk
 
 echo 'configure ok! '
 
@@ -503,133 +490,165 @@ echo 'configure ok! '
 # when configure success, prepare build
 #####################################################################################
 # create objs/logs for ffmpeg to write log.
-mkdir -p ${SRS_OBJS}/logs
+if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then
+    mkdir -p ${SRS_OBJS}/logs
+fi
 
 #####################################################################################
 # configure summary
 #####################################################################################
 # summary
-echo ""
-echo "configure summary:"
-echo "      ${SRS_AUTO_USER_CONFIGURE}"
-echo "      ${SRS_AUTO_CONFIGURE}"
-if [ $SRS_HLS = YES ]; then
-    echo -e "${GREEN}HLS is enabled${BLACK}"
-else
-    echo -e "${YELLOW}warning: without HLS support${BLACK}"
-fi
-if [ $SRS_NGINX = YES ]; then
-    echo -e "${GREEN}Nginx http server is enabled${BLACK}"
-else
-    echo -e "${GREEN}note: Nginx http server is disabled${BLACK}"
-fi
-if [ $SRS_DVR = YES ]; then
-    echo -e "${GREEN}DVR is enabled${BLACK}"
-else
-    echo -e "${YELLOW}warning: without DVR support${BLACK}"
-fi
-if [ $SRS_SSL = YES ]; then
-    echo -e "${GREEN}rtmp complex handshake is enabled${BLACK}"
-else
-    echo -e "${YELLOW}warning: without rtmp complex handshake support, donot support h264/aac to adobe flash player${BLACK}"
-fi
-if [ $SRS_FFMPEG_TOOL = YES ]; then
-    echo -e "${GREEN}transcode/mux/ingest tool FFMPEG is enabled${BLACK}"
-else
-    echo -e "${YELLOW}warning: without transcode/mux/ingest tool FFMPEG support${BLACK}"
-fi
-if [ $SRS_TRANSCODE = YES ]; then
-    echo -e "${GREEN}transcoding RTMP stream is enabled${BLACK}"
-else
-    echo -e "${YELLOW}warning: without transcoding RTMP stream support${BLACK}"
-fi
-if [ $SRS_INGEST = YES ]; then
-    echo -e "${GREEN}ingest file/stream/device is enabled${BLACK}"
-else
-    echo -e "${YELLOW}warning: without ingest file/stream/device support${BLACK}"
-fi
-if [ $SRS_HTTP_CALLBACK = YES ]; then
-    echo -e "${GREEN}http hooks callback over CherryPy is enabled${BLACK}"
-else
-    echo -e "${YELLOW}warning: without http hooks callback over CherryPy support${BLACK}"
-fi
-if [ $SRS_HTTP_SERVER = YES ]; then
-    echo -e "${GREEN}http server to delivery http stream is enabled${BLACK}"
-else
-    echo -e "${YELLOW}warning: without http server to delivery http stream support${BLACK}"
-fi
-if [ $SRS_HTTP_API = YES ]; then
-    echo -e "${GREEN}http api to manage server is enabled${BLACK}"
-else
-    echo -e "${YELLOW}warning: without http api to manage server support${BLACK}"
-fi
-if [ $SRS_LIBRTMP = YES ]; then
-    echo -e "${GREEN}srs-librtmp for client is enabled${BLACK}"
-else
-    echo -e "${YELLOW}note: srs-librtmp for client is disabled${BLACK}"
-fi
-if [ $SRS_RESEARCH = YES ]; then
-    echo -e "${GREEN}research tools are builded${BLACK}"
-else
-    echo -e "${GREEN}note: research tools are not builded${BLACK}"
-fi
-if [ $SRS_UTEST = YES ]; then
-    echo -e "${GREEN}utest for srs are builded${BLACK}"
-else
-    echo -e "${YELLOW}note: utest for srs are not builded${BLACK}"
-fi
-if [ $SRS_GPERF = YES ]; then
-    echo -e "${GREEN}gperf(tcmalloc) for srs are builded${BLACK}"
-else
-    echo -e "${GREEN}note: gperf(tcmalloc) for srs are not builded${BLACK}"
-fi
-if [ $SRS_GPERF_MC = YES ]; then
-    echo -e "${YELLOW}gmc(gperf memory check) for srs are builded -- Performance may suffer${BLACK}"
-else
-    echo -e "${GREEN}note: gmc(gperf memory check) for srs are not builded${BLACK}"
-fi
-if [ $SRS_GPERF_MP = YES ]; then
-    echo -e "${YELLOW}gmp(gperf memory profile) for srs are builded -- Performance may suffer${BLACK}"
-else
-    echo -e "${GREEN}note: gmp(gperf memory profile) for srs are not builded${BLACK}"
-fi
-if [ $SRS_GPERF_CP = YES ]; then
-    echo -e "${YELLOW}gcp(gperf cpu profile) for srs are builded -- Performance may suffer${BLACK}"
-else
-    echo -e "${GREEN}note: gcp(gperf cpu profile) for srs are not builded${BLACK}"
-fi
-if [ $SRS_GPROF = YES ]; then
-    echo -e "${YELLOW}gprof(GNU profile tool) for srs are builded -- Performance may suffer${BLACK}"
-else
-    echo -e "${GREEN}note: gprof(GNU profile tool) for srs are not builded${BLACK}"
-fi
-if [ $SRS_ARM_UBUNTU12 = YES ]; then
-    echo -e "${GREEN}arm-ubuntu12(armhf, v7cpu) for srs are builded${BLACK}"
-else
-    echo -e "${GREEN}note: arm-ubuntu12(armhf, v7cpu) for srs are not builded${BLACK}"
-fi
-if [ $SRS_MIPS_UBUNTU12 = YES ]; then
-    echo -e "${GREEN}mips-ubuntu12 for srs are builded${BLACK}"
-else
-    echo -e "${GREEN}note: mips-ubuntu12 for srs are not builded${BLACK}"
+if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then
+    echo ""
+    echo "configure summary:"
+    echo "      ${SRS_AUTO_USER_CONFIGURE}"
+    echo "      ${SRS_AUTO_CONFIGURE}"
+    if [ $SRS_HLS = YES ]; then
+        echo -e "${GREEN}HLS is enabled${BLACK}"
+    else
+        echo -e "${YELLOW}warning: without HLS support${BLACK}"
+    fi
+    if [ $SRS_HDS = YES ]; then
+        echo -e "${GREEN}HDS is enabled${BLACK}"
+    else
+        echo -e "${YELLOW}warning: without HDS support${BLACK}"
+    fi
+    if [ $SRS_NGINX = YES ]; then
+        echo -e "${GREEN}Nginx http server is enabled${BLACK}"
+    else
+        echo -e "${GREEN}note: Nginx http server is disabled${BLACK}"
+    fi
+    if [ $SRS_DVR = YES ]; then
+        echo -e "${GREEN}DVR is enabled${BLACK}"
+    else
+        echo -e "${YELLOW}warning: without DVR support${BLACK}"
+    fi
+    if [ $SRS_SSL = YES ]; then
+        echo -e "${GREEN}rtmp complex handshake is enabled${BLACK}"
+    else
+        echo -e "${YELLOW}warning: without rtmp complex handshake support, donot support h264/aac to adobe flash player${BLACK}"
+    fi
+    if [ $SRS_FFMPEG_TOOL = YES ]; then
+        echo -e "${GREEN}transcode/mux/ingest tool FFMPEG is enabled${BLACK}"
+    else
+        echo -e "${YELLOW}warning: without transcode/mux/ingest tool FFMPEG support${BLACK}"
+    fi
+    if [ $SRS_TRANSCODE = YES ]; then
+        echo -e "${GREEN}transcoding RTMP stream is enabled${BLACK}"
+    else
+        echo -e "${YELLOW}warning: without transcoding RTMP stream support${BLACK}"
+    fi
+    if [ $SRS_INGEST = YES ]; then
+        echo -e "${GREEN}ingest file/stream/device is enabled${BLACK}"
+    else
+        echo -e "${YELLOW}warning: without ingest file/stream/device support${BLACK}"
+    fi
+    if [ $SRS_HTTP_CALLBACK = YES ]; then
+        echo -e "${GREEN}http hooks callback over CherryPy is enabled${BLACK}"
+    else
+        echo -e "${YELLOW}warning: without http hooks callback over CherryPy support${BLACK}"
+    fi
+    if [ $SRS_HTTP_SERVER = YES ]; then
+        echo -e "${GREEN}http server to delivery http stream is enabled${BLACK}"
+    else
+        echo -e "${YELLOW}warning: without http server to delivery http stream support${BLACK}"
+    fi
+    if [ $SRS_HTTP_API = YES ]; then
+        echo -e "${GREEN}http api to manage server is enabled${BLACK}"
+    else
+        echo -e "${YELLOW}warning: without http api to manage server support${BLACK}"
+    fi
+    if [ $SRS_LIBRTMP = YES ]; then
+        echo -e "${GREEN}srs-librtmp for client is enabled${BLACK}"
+    else
+        echo -e "${YELLOW}note: srs-librtmp for client is disabled${BLACK}"
+    fi
+    if [ $SRS_RESEARCH = YES ]; then
+        echo -e "${GREEN}research tools are builded${BLACK}"
+    else
+        echo -e "${GREEN}note: research tools are not builded${BLACK}"
+    fi
+    if [ $SRS_UTEST = YES ]; then
+        echo -e "${GREEN}utest for srs are builded${BLACK}"
+    else
+        echo -e "${YELLOW}note: utest for srs are not builded${BLACK}"
+    fi
+    if [ $SRS_GPERF = YES ]; then
+        echo -e "${GREEN}gperf(tcmalloc) for srs are builded${BLACK}"
+    else
+        echo -e "${GREEN}note: gperf(tcmalloc) for srs are not builded${BLACK}"
+    fi
+    if [ $SRS_GPERF_MC = YES ]; then
+        echo -e "${YELLOW}gmc(gperf memory check) for srs are builded -- Performance may suffer${BLACK}"
+    else
+        echo -e "${GREEN}note: gmc(gperf memory check) for srs are not builded${BLACK}"
+    fi
+    if [ $SRS_GPERF_MP = YES ]; then
+        echo -e "${YELLOW}gmp(gperf memory profile) for srs are builded -- Performance may suffer${BLACK}"
+    else
+        echo -e "${GREEN}note: gmp(gperf memory profile) for srs are not builded${BLACK}"
+    fi
+    if [ $SRS_GPERF_CP = YES ]; then
+        echo -e "${YELLOW}gcp(gperf cpu profile) for srs are builded -- Performance may suffer${BLACK}"
+    else
+        echo -e "${GREEN}note: gcp(gperf cpu profile) for srs are not builded${BLACK}"
+    fi
+    if [ $SRS_GPROF = YES ]; then
+        echo -e "${YELLOW}gprof(GNU profile tool) for srs are builded -- Performance may suffer${BLACK}"
+    else
+        echo -e "${GREEN}note: gprof(GNU profile tool) for srs are not builded${BLACK}"
+    fi
+    if [ $SRS_ARM_UBUNTU12 = YES ]; then
+        echo -e "${GREEN}arm-ubuntu12(armhf, v7cpu) for srs are builded${BLACK}"
+    else
+        echo -e "${GREEN}note: arm-ubuntu12(armhf, v7cpu) for srs are not builded${BLACK}"
+    fi
+    if [ $SRS_MIPS_UBUNTU12 = YES ]; then
+        echo -e "${GREEN}mips-ubuntu12 for srs are builded${BLACK}"
+    else
+        echo -e "${GREEN}note: mips-ubuntu12 for srs are not builded${BLACK}"
+    fi
+    # add each modules for application
+    for SRS_MODULE in ${SRS_MODULES[*]}; do
+        echo -e "${GREEN}module: $SRS_MODULE${BLACK}"
+    done
 fi
 
 #####################################################################################
 # next step
 #####################################################################################
-ip=`ifconfig|grep "inet addr"| grep -v "127.0.0.1"|awk '{print $2}'|awk -F ':' 'NR==1 {print $2}'`
-echo ""
-echo "to run 3rdparty application:"
-if [ $SRS_NGINX = YES ]; then
-    echo "\" sudo ./objs/nginx/sbin/nginx  \" to start the nginx http server for hls"
-fi
-if [ $SRS_FFMPEG_TOOL = YES ]; then
-    echo -e "\" ./objs/ffmpeg/bin/ffmpeg  \" is used for live stream transcoding"
-fi
-if [ $SRS_HTTP_CALLBACK = YES ]; then
-    echo -e "\" python ./research/api-server/server.py 8085  \" to start the api-server"
+if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then
+    ip=`ifconfig|grep "inet addr"| grep -v "127.0.0.1"|awk '{print $2}'|awk -F ':' 'NR==1 {print $2}'`
+    echo ""
+    echo "to run 3rdparty application:"
+    if [ $SRS_NGINX = YES ]; then
+        echo "\" sudo ./objs/nginx/sbin/nginx  \" to start the nginx http server for hls"
+    fi
+    if [ $SRS_FFMPEG_TOOL = YES ]; then
+        echo -e "\" ./objs/ffmpeg/bin/ffmpeg  \" is used for live stream transcoding"
+    fi
+    if [ $SRS_HTTP_CALLBACK = YES ]; then
+        echo -e "\" python ./research/api-server/server.py 8085  \" to start the api-server"
+    fi
+    echo ""
+    echo "to build:"
+    echo "\" make \" to build the srs(simple rtmp server)."
+    echo "\" make help \" to get the usage of make"
+else
+    # for srs-librtmp single file, 
+    # package the whole project to srs_librtmp.h and srs_librtmp.cpp
+    if [ $SRS_EXPORT_LIBRTMP_SINGLE != NO ]; then
+        echo "package the whole project to srs_librtmp.h and srs_librtmp.cpp"
+        . $SRS_EXPORT_LIBRTMP_SINGLE/auto/generate-srs-librtmp-single.sh
+        echo -e "${GREEN}Please use the srs-librtmp files: ${BLACK}"
+        echo -e "${GREEN}       $SRS_EXPORT_LIBRTMP_PROJECT/srs_librtmp.h ${BLACK}"
+        echo -e "${GREEN}       $SRS_EXPORT_LIBRTMP_PROJECT/srs_librtmp.cpp ${BLACK}"
+        echo -e "${GREEN}       $SRS_EXPORT_LIBRTMP_PROJECT/example.c ${BLACK}"
+        echo -e "${GREEN}To compile the example: ${BLACK}"
+        echo -e "${GREEN}       cd $SRS_EXPORT_LIBRTMP_PROJECT && $SRS_SINGLE_LIBRTMP_COMPILE ${BLACK}"
+    # for srs-librtmp project.
+    else
+        echo -e "${GREEN}Please use the srs-librtmp project: ${BLACK}"
+        echo -e "${GREEN}       cd $SRS_EXPORT_LIBRTMP_PROJECT && make ${BLACK}"
+    fi
 fi
-echo ""
-echo "to build:"
-echo "\" make \" to build the srs(simple rtmp server)."
-echo "\" make help \" to get the usage of make"
diff --git a/trunk/doc/FlashMediaManifestFormatSpecificationErrataMay2014.pdf b/trunk/doc/FlashMediaManifestFormatSpecificationErrataMay2014.pdf
new file mode 100644
index 0000000000..9a56b47420
Binary files /dev/null and b/trunk/doc/FlashMediaManifestFormatSpecificationErrataMay2014.pdf differ
diff --git a/trunk/doc/H.264-AVC-ISO_IEC_14496-10-2012.pdf b/trunk/doc/H.264-AVC-ISO_IEC_14496-10-2012.pdf
new file mode 100644
index 0000000000..99f7e5eddc
Binary files /dev/null and b/trunk/doc/H.264-AVC-ISO_IEC_14496-10-2012.pdf differ
diff --git a/trunk/doc/HTTPDynamicStreamingSpecificationErrataMay2014.pdf b/trunk/doc/HTTPDynamicStreamingSpecificationErrataMay2014.pdf
new file mode 100644
index 0000000000..70deaf48b0
Binary files /dev/null and b/trunk/doc/HTTPDynamicStreamingSpecificationErrataMay2014.pdf differ
diff --git a/trunk/doc/adobe-hds-specification.pdf b/trunk/doc/adobe-hds-specification.pdf
new file mode 100644
index 0000000000..a3afdc2fb8
Binary files /dev/null and b/trunk/doc/adobe-hds-specification.pdf differ
diff --git a/trunk/doc/adobe-media-manifest-specification.pdf b/trunk/doc/adobe-media-manifest-specification.pdf
new file mode 100644
index 0000000000..be2f6652ce
Binary files /dev/null and b/trunk/doc/adobe-media-manifest-specification.pdf differ
diff --git a/trunk/doc/hls-m3u8-draft-pantos-http-live-streaming-12.pdf b/trunk/doc/hls-m3u8-draft-pantos-http-live-streaming-12.pdf
new file mode 100644
index 0000000000..f1290a8919
Binary files /dev/null and b/trunk/doc/hls-m3u8-draft-pantos-http-live-streaming-12.pdf differ
diff --git a/trunk/doc/mp3.id3v2.3.0.pdf b/trunk/doc/mp3.id3v2.3.0.pdf
new file mode 100644
index 0000000000..0e919b5064
Binary files /dev/null and b/trunk/doc/mp3.id3v2.3.0.pdf differ
diff --git a/trunk/doc/readme.txt b/trunk/doc/readme.txt
index 42cc71eb07..6de58c5007 100644
--- a/trunk/doc/readme.txt
+++ b/trunk/doc/readme.txt
@@ -19,6 +19,11 @@ amf3_spec_121207.pdf
 H.264-AVC-ISO_IEC_14496-10.pdf
     avc标准,编码部分。
     
+H.264-AVC-ISO_IEC_14496-10-2012.pdf
+    avc标准,编码部分。
+    上面的标准是2003年的,和下面的15是2010年的对不上。
+    http://www.itu.int/ITU-T/recommendations/rec.aspx?rec=11466
+    
 H.264-AVC-ISO_IEC_14496-15.pdf
     avc标准,封装部分。
     
@@ -58,4 +63,8 @@ http1.1-rfc2616.txt
 arpa-internet-text-messages-rfc822.txt
     http://www.rfc-editor.org/rfc/rfc822.txt
 
+mp3规范:
+mp3.id3v2.3.0.pdf
+    http://id3.org/id3v2.3.0
+
 Winlin
diff --git a/trunk/etc/init.d/srs b/trunk/etc/init.d/srs
index f490396495..9cfb7abda7 100755
--- a/trunk/etc/init.d/srs
+++ b/trunk/etc/init.d/srs
@@ -7,7 +7,7 @@
 # Default-Start:     2 3 4 5
 # Default-Stop:      0 1 6
 # Short-Description: simple-rtmp-server(srs)
-# Description:       https://github.com/winlinvip/simple-rtmp-server
+# Description:       https://github.com/simple-rtmp-server/srs
 ### END INIT INFO
 
 # the config of ROOT, user must modify it when start srs from other directory,
@@ -21,11 +21,11 @@ DEFAULT_LOG_FILE='./objs/srs.log'
 ########################################################################
 # utility functions
 ########################################################################
-RED="\\e[31m"
-GREEN="\\e[32m"
-YELLOW="\\e[33m"
-BLACK="\\e[0m"
-POS="\\e[60G"
+RED="\\033[31m"
+GREEN="\\033[32m"
+YELLOW="\\033[33m"
+BLACK="\\033[0m"
+POS="\\033[60G"
 
 ok_msg() {
     echo -e "${1}${POS}${BLACK}[${GREEN}  OK  ${BLACK}]"
diff --git a/trunk/etc/init.d/srs-api b/trunk/etc/init.d/srs-api
index dba4dc5244..94e0a57d50 100755
--- a/trunk/etc/init.d/srs-api
+++ b/trunk/etc/init.d/srs-api
@@ -7,7 +7,7 @@
 # Default-Start:     2 3 4 5
 # Default-Stop:      0 1 6
 # Short-Description: simple-rtmp-server-api(srs-api)
-# Description:       https://github.com/winlinvip/simple-rtmp-server
+# Description:       https://github.com/simple-rtmp-server/srs
 ### END INIT INFO
 
 # the config of ROOT, user must modify it when start srs from other directory,
@@ -19,11 +19,11 @@ CONFIG="8085"
 ########################################################################
 # utility functions
 ########################################################################
-RED="\\e[31m"
-GREEN="\\e[32m"
-YELLOW="\\e[33m"
-BLACK="\\e[0m"
-POS="\\e[60G"
+RED="\\033[31m"
+GREEN="\\033[32m"
+YELLOW="\\033[33m"
+BLACK="\\033[0m"
+POS="\\033[60G"
 
 ok_msg(){
     echo -e "${1}${POS}${BLACK}[${GREEN}  OK  ${BLACK}]"
diff --git a/trunk/etc/init.d/srs-demo b/trunk/etc/init.d/srs-demo
index e7e2bd5eb1..d8ebd8c38d 100755
--- a/trunk/etc/init.d/srs-demo
+++ b/trunk/etc/init.d/srs-demo
@@ -7,7 +7,7 @@
 # Default-Start:     2 3 4 5
 # Default-Stop:      0 1 6
 # Short-Description: simple-rtmp-server(srs)
-# Description:       https://github.com/winlinvip/simple-rtmp-server
+# Description:       https://github.com/simple-rtmp-server/srs
 ### END INIT INFO
 
 # the config of ROOT, user must modify it when start srs from other directory,
@@ -21,11 +21,11 @@ DEFAULT_LOG_FILE='./objs/srs.demo.log'
 ########################################################################
 # utility functions
 ########################################################################
-RED="\\e[31m"
-GREEN="\\e[32m"
-YELLOW="\\e[33m"
-BLACK="\\e[0m"
-POS="\\e[60G"
+RED="\\033[31m"
+GREEN="\\033[32m"
+YELLOW="\\033[33m"
+BLACK="\\033[0m"
+POS="\\033[60G"
 
 ok_msg() {
     echo -e "${1}${POS}${BLACK}[${GREEN}  OK  ${BLACK}]"
diff --git a/trunk/etc/init.d/srs-demo-19350 b/trunk/etc/init.d/srs-demo-19350
index 704b89bdc9..3500cd0418 100755
--- a/trunk/etc/init.d/srs-demo-19350
+++ b/trunk/etc/init.d/srs-demo-19350
@@ -7,7 +7,7 @@
 # Default-Start:     2 3 4 5
 # Default-Stop:      0 1 6
 # Short-Description: simple-rtmp-server(srs)
-# Description:       https://github.com/winlinvip/simple-rtmp-server
+# Description:       https://github.com/simple-rtmp-server/srs
 ### END INIT INFO
 
 # the config of ROOT, user must modify it when start srs from other directory,
@@ -21,11 +21,11 @@ DEFAULT_LOG_FILE='./objs/srs.demo.19350.log'
 ########################################################################
 # utility functions
 ########################################################################
-RED="\\e[31m"
-GREEN="\\e[32m"
-YELLOW="\\e[33m"
-BLACK="\\e[0m"
-POS="\\e[60G"
+RED="\\033[31m"
+GREEN="\\033[32m"
+YELLOW="\\033[33m"
+BLACK="\\033[0m"
+POS="\\033[60G"
 
 ok_msg() {
     echo -e "${1}${POS}${BLACK}[${GREEN}  OK  ${BLACK}]"
diff --git a/trunk/ide/readme.txt b/trunk/ide/readme.txt
new file mode 100644
index 0000000000..a979100719
--- /dev/null
+++ b/trunk/ide/readme.txt
@@ -0,0 +1,5 @@
+提供了各种ide。
+
+1. UPP添加Assembly时,路径指向ide即可,譬如:\\dev\winlin\srs\ide
+
+2015.3 winlin
diff --git a/trunk/ide/srs_clion/CMakeLists.txt b/trunk/ide/srs_clion/CMakeLists.txt
new file mode 100755
index 0000000000..80f9c6d784
--- /dev/null
+++ b/trunk/ide/srs_clion/CMakeLists.txt
@@ -0,0 +1,58 @@
+cmake_minimum_required(VERSION 2.6.4)
+project(srs CXX)
+
+INCLUDE_DIRECTORIES(../../objs 
+    ../../objs/st ../../objs/hp ../../objs/openssl/include 
+    ../../src/core ../../src/kernel ../../src/protocol ../../src/app)
+
+set(SOURCE_FILES ../../src/main/srs_main_server.cpp)
+AUX_SOURCE_DIRECTORY(../../src/core SOURCE_FILES)
+AUX_SOURCE_DIRECTORY(../../src/kernel SOURCE_FILES)
+AUX_SOURCE_DIRECTORY(../../src/protocol SOURCE_FILES)
+AUX_SOURCE_DIRECTORY(../../src/app SOURCE_FILES)
+
+ADD_DEFINITIONS("-g -O0")
+
+ADD_EXECUTABLE(srs ${SOURCE_FILES})
+TARGET_LINK_LIBRARIES(srs dl)
+TARGET_LINK_LIBRARIES(srs ${PROJECT_SOURCE_DIR}/../../objs/st/libst.a)
+TARGET_LINK_LIBRARIES(srs ${PROJECT_SOURCE_DIR}/../../objs/openssl/lib/libssl.a)
+TARGET_LINK_LIBRARIES(srs ${PROJECT_SOURCE_DIR}/../../objs/openssl/lib/libcrypto.a)
+TARGET_LINK_LIBRARIES(srs ${PROJECT_SOURCE_DIR}/../../objs/hp/libhttp_parser.a)
+TARGET_LINK_LIBRARIES(srs -ldl)
+
+IF(NOT EXISTS ${PROJECT_SOURCE_DIR}/../../objs/st/libst.a)
+    MESSAGE("srs_libs not found")
+    EXEC_PROGRAM("cd .. && ./configure")
+ELSE(NOT EXISTS ${PROJECT_SOURCE_DIR}/../../objs/st/libst.a)
+    MESSAGE("srs_libs is ok")
+ENDIF(NOT EXISTS ${PROJECT_SOURCE_DIR}/../../objs/st/libst.a)
+
+MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_IDE#jetbrains")
+MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_IDE#jetbrains")
+MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_IDE#jetbrains")
+MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_IDE#jetbrains")
+MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_IDE#jetbrains")
+MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_IDE#jetbrains")
+MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_IDE#jetbrains")
+MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_IDE#jetbrains")
+MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_IDE#jetbrains")
+MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_IDE#jetbrains")
+MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_IDE#jetbrains")
+MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_IDE#jetbrains")
+MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_IDE#jetbrains")
+MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_IDE#jetbrains")
+MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_IDE#jetbrains")
+MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_IDE#jetbrains")
+MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_IDE#jetbrains")
+MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_IDE#jetbrains")
+MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_IDE#jetbrains")
+MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_IDE#jetbrains")
+MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_IDE#jetbrains")
+MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_IDE#jetbrains")
+MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_IDE#jetbrains")
+MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_IDE#jetbrains")
+MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_IDE#jetbrains")
+MESSAGE(STATUS "only for jetbrains IDE, @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_IDE#jetbrains")
+MESSAGE(STATUS "use ./configure && make, @see https://github.com/simple-rtmp-server/srs#usage")
+
diff --git a/trunk/ide/srs_qt/srs-qt.pro b/trunk/ide/srs_qt/srs-qt.pro
new file mode 100755
index 0000000000..a1933712d6
--- /dev/null
+++ b/trunk/ide/srs_qt/srs-qt.pro
@@ -0,0 +1,35 @@
+TEMPLATE = app
+CONFIG += console
+CONFIG -= app_bundle
+CONFIG -= qt
+
+HEADERS += \
+    ../../src/core/*.hpp \
+    ../../src/kernel/*.hpp \
+    ../../src/app/*.hpp \
+    ../../src/protocol/*.hpp
+
+SOURCES += \
+    ../../src/core/*.cpp \
+    ../../src/kernel/*.cpp \
+    ../../src/app/*.cpp \
+    ../../src/protocol/*.cpp \
+    ../../src/main/*.cpp
+
+INCLUDEPATH += \
+    ../../src/core \
+    ../../src/kernel \
+    ../../src/app \
+    ../../src/protocol \
+    ../../objs \
+    ../../objs/st \
+    ../../objs/hp \
+    ../../objs/openssl/include
+
+LIBS += \
+    ../../objs/st/libst.a \
+    ../../objs/hp/libhttp_parser.a \
+    ../../objs/openssl/lib/libssl.a \
+    ../../objs/openssl/lib/libcrypto.a \
+    -ldl
+
diff --git a/trunk/ide/srs_qt/srs-qt.pro.user b/trunk/ide/srs_qt/srs-qt.pro.user
new file mode 100755
index 0000000000..5d891ca10e
--- /dev/null
+++ b/trunk/ide/srs_qt/srs-qt.pro.user
@@ -0,0 +1,263 @@
+
+
+
+
+ 
+  ProjectExplorer.Project.ActiveTarget
+  0
+ 
+ 
+  ProjectExplorer.Project.EditorSettings
+  
+   true
+   false
+   true
+   
+    Cpp
+    
+     CppGlobal
+    
+   
+   
+    QmlJS
+    
+     QmlJSGlobal
+    
+   
+   2
+   UTF-8
+   false
+   4
+   false
+   80
+   true
+   true
+   1
+   true
+   false
+   0
+   true
+   0
+   8
+   true
+   1
+   true
+   true
+   true
+   false
+  
+ 
+ 
+  ProjectExplorer.Project.PluginSettings
+  
+ 
+ 
+  ProjectExplorer.Project.Target.0
+  
+   Desktop
+   Desktop
+   {fdda5a74-8ef6-4e67-b28c-c5be6c667578}
+   0
+   0
+   0
+   
+    /home/winlin/git/simple-rtmp-server/trunk/ide/build-srs-qt-Desktop-Debug
+    
+     
+      true
+      qmake
+      
+      QtProjectManager.QMakeBuildStep
+      false
+      true
+      
+      false
+     
+     
+      true
+      Make
+      
+      Qt4ProjectManager.MakeStep
+      
+       -w
+       -r
+      
+      false
+      
+      
+     
+     2
+     Build
+     
+     ProjectExplorer.BuildSteps.Build
+    
+    
+     
+      true
+      Make
+      
+      Qt4ProjectManager.MakeStep
+      
+       -w
+       -r
+      
+      true
+      clean
+      
+     
+     1
+     Clean
+     
+     ProjectExplorer.BuildSteps.Clean
+    
+    2
+    false
+    
+    Debug
+    
+    Qt4ProjectManager.Qt4BuildConfiguration
+    2
+    true
+   
+   
+    /home/winlin/git/simple-rtmp-server/trunk/ide/build-srs-qt-Desktop-Release
+    
+     
+      true
+      qmake
+      
+      QtProjectManager.QMakeBuildStep
+      false
+      true
+      
+      false
+     
+     
+      true
+      Make
+      
+      Qt4ProjectManager.MakeStep
+      
+       -w
+       -r
+      
+      false
+      
+      
+     
+     2
+     Build
+     
+     ProjectExplorer.BuildSteps.Build
+    
+    
+     
+      true
+      Make
+      
+      Qt4ProjectManager.MakeStep
+      
+       -w
+       -r
+      
+      true
+      clean
+      
+     
+     1
+     Clean
+     
+     ProjectExplorer.BuildSteps.Clean
+    
+    2
+    false
+    
+    Release
+    
+    Qt4ProjectManager.Qt4BuildConfiguration
+    0
+    true
+   
+   2
+   
+    
+     0
+     Deploy
+     
+     ProjectExplorer.BuildSteps.Deploy
+    
+    1
+    Deploy locally
+    
+    ProjectExplorer.DefaultDeployConfiguration
+   
+   1
+   
+   
+    
+    false
+    false
+    false
+    false
+    true
+    0.01
+    10
+    true
+    1
+    25
+    
+    1
+    true
+    false
+    true
+    valgrind
+    
+     0
+     1
+     2
+     3
+     4
+     5
+     6
+     7
+     8
+     9
+     10
+     11
+     12
+     13
+     14
+    
+    2
+    
+    srs-qt
+    
+    Qt4ProjectManager.Qt4RunConfiguration:/home/winlin/git/simple-rtmp-server/trunk/ide/srs_qt/srs-qt.pro
+    
+    srs-qt.pro
+    false
+    true
+    
+    3768
+    false
+    true
+    false
+    false
+    true
+   
+   1
+  
+ 
+ 
+  ProjectExplorer.Project.TargetCount
+  1
+ 
+ 
+  ProjectExplorer.Project.Updater.EnvironmentId
+  {67c55164-ec8b-40af-90ff-d8b0b02de43a}
+ 
+ 
+  ProjectExplorer.Project.Updater.FileVersion
+  15
+ 
+
diff --git a/trunk/ide/srs_upp/init b/trunk/ide/srs_upp/init
new file mode 100755
index 0000000000..cf8175cb41
--- /dev/null
+++ b/trunk/ide/srs_upp/init
@@ -0,0 +1,3 @@
+#ifndef _ide_srs_upp_icpp_init_stub
+#define _ide_srs_upp_icpp_init_stub
+#endif
diff --git a/trunk/ide/srs_upp/srs_upp.upp b/trunk/ide/srs_upp/srs_upp.upp
new file mode 100755
index 0000000000..e302bb98b5
--- /dev/null
+++ b/trunk/ide/srs_upp/srs_upp.upp
@@ -0,0 +1,176 @@
+file
+	main readonly separator,
+	../../src/main/srs_main_server.cpp,
+        ../../src/main/srs_main_ingest_hls.cpp,
+	auto readonly separator,
+	../../objs/srs_auto_headers.hpp,
+	libs readonly separator,
+	../../src/libs/srs_librtmp.hpp,
+	../../src/libs/srs_librtmp.cpp,
+	../../src/libs/srs_lib_bandwidth.hpp,
+	../../src/libs/srs_lib_bandwidth.cpp,
+	../../src/libs/srs_lib_simple_socket.hpp,
+	../../src/libs/srs_lib_simple_socket.cpp,
+	core readonly separator,
+	../../src/core/srs_core.hpp,
+	../../src/core/srs_core.cpp,
+	../../src/core/srs_core_autofree.hpp,
+	../../src/core/srs_core_autofree.cpp,
+	../../src/core/srs_core_performance.hpp,
+	../../src/core/srs_core_performance.cpp,
+	kernel readonly separator,
+	../../src/kernel/srs_kernel_aac.hpp,
+	../../src/kernel/srs_kernel_aac.cpp,
+	../../src/kernel/srs_kernel_buffer.hpp,
+	../../src/kernel/srs_kernel_buffer.cpp,
+	../../src/kernel/srs_kernel_codec.hpp,
+	../../src/kernel/srs_kernel_codec.cpp,
+	../../src/kernel/srs_kernel_consts.hpp,
+	../../src/kernel/srs_kernel_consts.cpp,
+	../../src/kernel/srs_kernel_error.hpp,
+	../../src/kernel/srs_kernel_error.cpp,
+	../../src/kernel/srs_kernel_file.hpp,
+	../../src/kernel/srs_kernel_file.cpp,
+	../../src/kernel/srs_kernel_flv.hpp,
+	../../src/kernel/srs_kernel_flv.cpp,
+	../../src/kernel/srs_kernel_log.hpp,
+	../../src/kernel/srs_kernel_log.cpp,
+	../../src/kernel/srs_kernel_mp3.hpp,
+	../../src/kernel/srs_kernel_mp3.cpp,
+	../../src/kernel/srs_rtsp_stack.hpp,
+	../../src/kernel/srs_rtsp_stack.cpp,
+	../../src/kernel/srs_kernel_stream.hpp,
+	../../src/kernel/srs_kernel_stream.cpp,
+	../../src/kernel/srs_kernel_ts.cpp,
+	../../src/kernel/srs_kernel_ts.hpp,
+	../../src/kernel/srs_kernel_utility.hpp,
+	../../src/kernel/srs_kernel_utility.cpp,
+	protocol readonly separator,
+	../../src/protocol/srs_raw_avc.hpp,
+	../../src/protocol/srs_raw_avc.cpp,
+	../../src/protocol/srs_rtmp_amf0.hpp,
+	../../src/protocol/srs_rtmp_amf0.cpp,
+	../../src/protocol/srs_rtmp_buffer.hpp,
+	../../src/protocol/srs_rtmp_buffer.cpp,
+	../../src/protocol/srs_rtmp_handshake.hpp,
+	../../src/protocol/srs_rtmp_handshake.cpp,
+	../../src/protocol/srs_rtmp_io.hpp,
+	../../src/protocol/srs_rtmp_io.cpp,
+	../../src/protocol/srs_rtmp_msg_array.hpp,
+	../../src/protocol/srs_rtmp_msg_array.cpp,
+	../../src/protocol/srs_rtmp_sdk.hpp,
+	../../src/protocol/srs_rtmp_sdk.cpp,
+	../../src/protocol/srs_rtmp_stack.hpp,
+	../../src/protocol/srs_rtmp_stack.cpp,
+	../../src/protocol/srs_rtmp_utility.hpp,
+	../../src/protocol/srs_rtmp_utility.cpp,
+	app readonly separator,
+        ../../src/app/srs_app_async_call.hpp,
+        ../../src/app/srs_app_async_call.cpp,
+	../../src/app/srs_app_bandwidth.hpp,
+	../../src/app/srs_app_bandwidth.cpp,
+        ../../src/app/srs_app_caster_flv.hpp,
+        ../../src/app/srs_app_caster_flv.cpp,
+	../../src/app/srs_app_conn.hpp,
+	../../src/app/srs_app_conn.cpp,
+	../../src/app/srs_app_config.hpp,
+	../../src/app/srs_app_config.cpp,
+	../../src/app/srs_app_dvr.hpp,
+	../../src/app/srs_app_dvr.cpp,
+	../../src/app/srs_app_edge.hpp,
+	../../src/app/srs_app_edge.cpp,
+	../../src/app/srs_app_empty.hpp,
+	../../src/app/srs_app_empty.cpp,
+	../../src/app/srs_app_encoder.hpp,
+	../../src/app/srs_app_encoder.cpp,
+	../../src/app/srs_app_ffmpeg.hpp,
+	../../src/app/srs_app_ffmpeg.cpp,
+	../../src/app/srs_app_forward.hpp,
+	../../src/app/srs_app_forward.cpp,
+	../../src/app/srs_app_heartbeat.hpp,
+	../../src/app/srs_app_heartbeat.cpp,
+	../../src/app/srs_app_hls.hpp,
+	../../src/app/srs_app_hls.cpp,
+	../../src/app/srs_app_http.hpp,
+	../../src/app/srs_app_http.cpp,
+	../../src/app/srs_app_http_api.hpp,
+	../../src/app/srs_app_http_api.cpp,
+	../../src/app/srs_app_http_client.hpp,
+	../../src/app/srs_app_http_client.cpp,
+	../../src/app/srs_app_http_conn.hpp,
+	../../src/app/srs_app_http_conn.cpp,
+	../../src/app/srs_app_http_hooks.hpp,
+	../../src/app/srs_app_http_hooks.cpp,
+	../../src/app/srs_app_ingest.hpp,
+	../../src/app/srs_app_ingest.cpp,
+	../../src/app/srs_app_json.hpp,
+	../../src/app/srs_app_json.cpp,
+	../../src/app/srs_app_kbps.hpp,
+	../../src/app/srs_app_kbps.cpp,
+	../../src/app/srs_app_listener.hpp,
+	../../src/app/srs_app_listener.cpp,
+	../../src/app/srs_app_log.hpp,
+	../../src/app/srs_app_log.cpp,
+	../../src/app/srs_app_mpegts_udp.hpp,
+	../../src/app/srs_app_mpegts_udp.cpp,
+	../../src/app/srs_app_recv_thread.hpp,
+	../../src/app/srs_app_recv_thread.cpp,
+	../../src/app/srs_app_refer.hpp,
+	../../src/app/srs_app_refer.cpp,
+	../../src/app/srs_app_reload.hpp,
+	../../src/app/srs_app_reload.cpp,
+	../../src/app/srs_app_rtmp_conn.hpp,
+	../../src/app/srs_app_rtmp_conn.cpp,
+	../../src/app/srs_app_rtsp.hpp,
+	../../src/app/srs_app_rtsp.cpp,
+	../../src/app/srs_app_pithy_print.hpp,
+	../../src/app/srs_app_pithy_print.cpp,
+	../../src/app/srs_app_security.hpp,
+	../../src/app/srs_app_security.cpp,
+	../../src/app/srs_app_server.hpp,
+	../../src/app/srs_app_server.cpp,
+	../../src/app/srs_app_st.hpp,
+	../../src/app/srs_app_st.cpp,
+	../../src/app/srs_app_st_socket.hpp,
+	../../src/app/srs_app_st_socket.cpp,
+	../../src/app/srs_app_statistic.hpp,
+	../../src/app/srs_app_statistic.cpp,
+	../../src/app/srs_app_source.hpp,
+	../../src/app/srs_app_source.cpp,
+	../../src/app/srs_app_thread.hpp,
+	../../src/app/srs_app_thread.cpp,
+	../../src/app/srs_app_utility.hpp,
+	../../src/app/srs_app_utility.cpp,
+	utest readonly separator,
+	../../src/utest/srs_utest.hpp,
+	../../src/utest/srs_utest.cpp,
+	../../src/utest/srs_utest_amf0.hpp,
+	../../src/utest/srs_utest_amf0.cpp,
+	../../src/utest/srs_utest_config.hpp,
+	../../src/utest/srs_utest_config.cpp,
+	../../src/utest/srs_utest_core.hpp,
+	../../src/utest/srs_utest_core.cpp,
+	../../src/utest/srs_utest_kernel.hpp,
+	../../src/utest/srs_utest_kernel.cpp,
+	../../src/utest/srs_utest_protocol.hpp,
+	../../src/utest/srs_utest_protocol.cpp,
+	../../src/utest/srs_utest_reload.hpp,
+	../../src/utest/srs_utest_reload.cpp,
+	research readonly separator,
+	../../research/librtmp/srs_aac_raw_publish.c,
+	../../research/librtmp/srs_audio_raw_publish.c,
+	../../research/librtmp/srs_bandwidth_check.c,
+	../../research/librtmp/srs_detect_rtmp.c,
+	../../research/librtmp/srs_flv_injecter.c,
+	../../research/librtmp/srs_flv_parser.c,
+	../../research/librtmp/srs_h264_raw_publish.c,
+	../../research/librtmp/srs_ingest_flv.c,
+	../../research/librtmp/srs_ingest_rtmp.c,
+	../../research/librtmp/srs_play.c,
+	../../research/librtmp/srs_publish.c,
+	../../research/librtmp/srs_rtmp_dump.c,
+	../../research/hls/ts_info.cc;
+
+mainconfig
+	"" = "MAIN";
+
diff --git a/trunk/ide/srs_vs2010/srs.sln b/trunk/ide/srs_vs2010/srs.sln
new file mode 100755
index 0000000000..4834750859
--- /dev/null
+++ b/trunk/ide/srs_vs2010/srs.sln
@@ -0,0 +1,18 @@
+
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual Studio 2010
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "srs", "srs.vcxproj", "{74591348-C63A-4E00-B85E-62A9D8544F6F}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Win32 = Debug|Win32
+		Release|Win32 = Release|Win32
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{74591348-C63A-4E00-B85E-62A9D8544F6F}.Debug|Win32.ActiveCfg = Debug|Win32
+		{74591348-C63A-4E00-B85E-62A9D8544F6F}.Release|Win32.ActiveCfg = Release|Win32
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal
diff --git a/trunk/ide/srs_vs2010/srs.vcxproj b/trunk/ide/srs_vs2010/srs.vcxproj
new file mode 100755
index 0000000000..f7d0a9ff10
--- /dev/null
+++ b/trunk/ide/srs_vs2010/srs.vcxproj
@@ -0,0 +1,218 @@
+
+
+  
+    
+      Debug
+      Win32
+    
+    
+      Release
+      Win32
+    
+  
+  
+    {74591348-C63A-4E00-B85E-62A9D8544F6F}
+    srs
+  
+  
+  
+    Application
+    true
+    MultiByte
+  
+  
+    Application
+    false
+    true
+    MultiByte
+  
+  
+  
+  
+  
+    
+  
+  
+    
+  
+  
+  
+    $(ProjectDir)/../../src/core;$(ProjectDir)/../../src/kernel;$(ProjectDir)/../../src/protocol;$(ProjectDir)/../../src/app;$(ProjectDir)/../../src/libs;$(ProjectDir)/../../objs;$(IncludePath)
+  
+  
+    
+      Level3
+      Disabled
+    
+    
+      true
+    
+  
+  
+    
+      Level3
+      MaxSpeed
+      true
+      true
+    
+    
+      true
+      true
+      true
+    
+  
+  
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+  
+  
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+  
+  
+  
+  
+
diff --git a/trunk/ide/srs_vs2010/srs.vcxproj.filters b/trunk/ide/srs_vs2010/srs.vcxproj.filters
new file mode 100755
index 0000000000..5a60d8b869
--- /dev/null
+++ b/trunk/ide/srs_vs2010/srs.vcxproj.filters
@@ -0,0 +1,453 @@
+
+
+  
+    
+      research
+    
+    
+      research
+    
+    
+      research
+    
+    
+      research
+    
+    
+      research
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      research
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      research
+    
+    
+      research
+    
+    
+      research
+    
+    
+      research
+    
+    
+      research
+    
+    
+      research
+    
+    
+      librtmp
+    
+    
+      librtmp
+    
+    
+      librtmp
+    
+    
+      librtmp
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+  
+  
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      librtmp
+    
+    
+      librtmp
+    
+    
+      librtmp
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+    
+      srs
+    
+  
+  
+    
+      {7e9069c1-f19c-4ab0-b47f-36d7903cfa8b}
+    
+    
+      {0919b792-be75-49ed-b15a-2dc521e8394f}
+    
+    
+      {d45a9ecb-fcbe-4400-abe3-792cddecb47e}
+    
+  
+
\ No newline at end of file
diff --git a/trunk/ide/srs_xcode/srs_xcode.xcodeproj/project.pbxproj b/trunk/ide/srs_xcode/srs_xcode.xcodeproj/project.pbxproj
new file mode 100644
index 0000000000..94b678670b
--- /dev/null
+++ b/trunk/ide/srs_xcode/srs_xcode.xcodeproj/project.pbxproj
@@ -0,0 +1,1074 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 46;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		3C1231F61AAE652D00CE8F6C /* srs_core_autofree.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1231F01AAE652C00CE8F6C /* srs_core_autofree.cpp */; };
+		3C1231F71AAE652D00CE8F6C /* srs_core_performance.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1231F21AAE652C00CE8F6C /* srs_core_performance.cpp */; };
+		3C1231F81AAE652D00CE8F6C /* srs_core.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1231F41AAE652D00CE8F6C /* srs_core.cpp */; };
+		3C1232061AAE812C00CE8F6C /* srs_main_server.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232051AAE812C00CE8F6C /* srs_main_server.cpp */; };
+		3C1232201AAE814D00CE8F6C /* srs_kernel_aac.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232081AAE814D00CE8F6C /* srs_kernel_aac.cpp */; };
+		3C1232211AAE814D00CE8F6C /* srs_kernel_buffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12320A1AAE814D00CE8F6C /* srs_kernel_buffer.cpp */; };
+		3C1232221AAE814D00CE8F6C /* srs_kernel_codec.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12320C1AAE814D00CE8F6C /* srs_kernel_codec.cpp */; };
+		3C1232231AAE814D00CE8F6C /* srs_kernel_consts.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12320E1AAE814D00CE8F6C /* srs_kernel_consts.cpp */; };
+		3C1232241AAE814D00CE8F6C /* srs_kernel_error.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232101AAE814D00CE8F6C /* srs_kernel_error.cpp */; };
+		3C1232251AAE814D00CE8F6C /* srs_kernel_file.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232121AAE814D00CE8F6C /* srs_kernel_file.cpp */; };
+		3C1232261AAE814D00CE8F6C /* srs_kernel_flv.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232141AAE814D00CE8F6C /* srs_kernel_flv.cpp */; };
+		3C1232271AAE814D00CE8F6C /* srs_kernel_log.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232161AAE814D00CE8F6C /* srs_kernel_log.cpp */; };
+		3C1232281AAE814D00CE8F6C /* srs_kernel_mp3.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232181AAE814D00CE8F6C /* srs_kernel_mp3.cpp */; };
+		3C1232291AAE814D00CE8F6C /* srs_kernel_stream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12321A1AAE814D00CE8F6C /* srs_kernel_stream.cpp */; };
+		3C12322A1AAE814D00CE8F6C /* srs_kernel_ts.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12321C1AAE814D00CE8F6C /* srs_kernel_ts.cpp */; };
+		3C12322B1AAE814D00CE8F6C /* srs_kernel_utility.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12321E1AAE814D00CE8F6C /* srs_kernel_utility.cpp */; };
+		3C1232411AAE81A400CE8F6C /* srs_raw_avc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12322D1AAE81A400CE8F6C /* srs_raw_avc.cpp */; };
+		3C1232421AAE81A400CE8F6C /* srs_rtmp_amf0.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12322F1AAE81A400CE8F6C /* srs_rtmp_amf0.cpp */; };
+		3C1232431AAE81A400CE8F6C /* srs_rtmp_buffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232311AAE81A400CE8F6C /* srs_rtmp_buffer.cpp */; };
+		3C1232441AAE81A400CE8F6C /* srs_rtmp_handshake.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232331AAE81A400CE8F6C /* srs_rtmp_handshake.cpp */; };
+		3C1232451AAE81A400CE8F6C /* srs_rtmp_io.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232351AAE81A400CE8F6C /* srs_rtmp_io.cpp */; };
+		3C1232461AAE81A400CE8F6C /* srs_rtmp_msg_array.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232371AAE81A400CE8F6C /* srs_rtmp_msg_array.cpp */; };
+		3C1232471AAE81A400CE8F6C /* srs_rtmp_sdk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232391AAE81A400CE8F6C /* srs_rtmp_sdk.cpp */; };
+		3C1232481AAE81A400CE8F6C /* srs_rtmp_stack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12323B1AAE81A400CE8F6C /* srs_rtmp_stack.cpp */; };
+		3C1232491AAE81A400CE8F6C /* srs_rtmp_utility.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12323D1AAE81A400CE8F6C /* srs_rtmp_utility.cpp */; };
+		3C12324A1AAE81A400CE8F6C /* srs_rtsp_stack.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12323F1AAE81A400CE8F6C /* srs_rtsp_stack.cpp */; };
+		3C1232941AAE81D900CE8F6C /* srs_app_bandwidth.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12324C1AAE81D900CE8F6C /* srs_app_bandwidth.cpp */; };
+		3C1232951AAE81D900CE8F6C /* srs_app_config.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12324E1AAE81D900CE8F6C /* srs_app_config.cpp */; };
+		3C1232961AAE81D900CE8F6C /* srs_app_conn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232501AAE81D900CE8F6C /* srs_app_conn.cpp */; };
+		3C1232971AAE81D900CE8F6C /* srs_app_dvr.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232521AAE81D900CE8F6C /* srs_app_dvr.cpp */; };
+		3C1232981AAE81D900CE8F6C /* srs_app_edge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232541AAE81D900CE8F6C /* srs_app_edge.cpp */; };
+		3C1232991AAE81D900CE8F6C /* srs_app_empty.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232561AAE81D900CE8F6C /* srs_app_empty.cpp */; };
+		3C12329A1AAE81D900CE8F6C /* srs_app_encoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232581AAE81D900CE8F6C /* srs_app_encoder.cpp */; };
+		3C12329B1AAE81D900CE8F6C /* srs_app_ffmpeg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12325A1AAE81D900CE8F6C /* srs_app_ffmpeg.cpp */; };
+		3C12329C1AAE81D900CE8F6C /* srs_app_forward.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12325C1AAE81D900CE8F6C /* srs_app_forward.cpp */; };
+		3C12329D1AAE81D900CE8F6C /* srs_app_heartbeat.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12325E1AAE81D900CE8F6C /* srs_app_heartbeat.cpp */; };
+		3C12329E1AAE81D900CE8F6C /* srs_app_hls.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232601AAE81D900CE8F6C /* srs_app_hls.cpp */; };
+		3C12329F1AAE81D900CE8F6C /* srs_app_http_api.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232621AAE81D900CE8F6C /* srs_app_http_api.cpp */; };
+		3C1232A01AAE81D900CE8F6C /* srs_app_http_client.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232641AAE81D900CE8F6C /* srs_app_http_client.cpp */; };
+		3C1232A11AAE81D900CE8F6C /* srs_app_http_conn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232661AAE81D900CE8F6C /* srs_app_http_conn.cpp */; };
+		3C1232A21AAE81D900CE8F6C /* srs_app_http_hooks.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232681AAE81D900CE8F6C /* srs_app_http_hooks.cpp */; };
+		3C1232A31AAE81D900CE8F6C /* srs_app_http.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12326A1AAE81D900CE8F6C /* srs_app_http.cpp */; };
+		3C1232A41AAE81D900CE8F6C /* srs_app_ingest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12326C1AAE81D900CE8F6C /* srs_app_ingest.cpp */; };
+		3C1232A51AAE81D900CE8F6C /* srs_app_json.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12326E1AAE81D900CE8F6C /* srs_app_json.cpp */; };
+		3C1232A61AAE81D900CE8F6C /* srs_app_kbps.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232701AAE81D900CE8F6C /* srs_app_kbps.cpp */; };
+		3C1232A71AAE81D900CE8F6C /* srs_app_listener.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232721AAE81D900CE8F6C /* srs_app_listener.cpp */; };
+		3C1232A81AAE81D900CE8F6C /* srs_app_log.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232741AAE81D900CE8F6C /* srs_app_log.cpp */; };
+		3C1232A91AAE81D900CE8F6C /* srs_app_mpegts_udp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232761AAE81D900CE8F6C /* srs_app_mpegts_udp.cpp */; };
+		3C1232AA1AAE81D900CE8F6C /* srs_app_pithy_print.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232781AAE81D900CE8F6C /* srs_app_pithy_print.cpp */; };
+		3C1232AB1AAE81D900CE8F6C /* srs_app_recv_thread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12327A1AAE81D900CE8F6C /* srs_app_recv_thread.cpp */; };
+		3C1232AC1AAE81D900CE8F6C /* srs_app_refer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12327C1AAE81D900CE8F6C /* srs_app_refer.cpp */; };
+		3C1232AD1AAE81D900CE8F6C /* srs_app_reload.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12327E1AAE81D900CE8F6C /* srs_app_reload.cpp */; };
+		3C1232AE1AAE81D900CE8F6C /* srs_app_rtmp_conn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232801AAE81D900CE8F6C /* srs_app_rtmp_conn.cpp */; };
+		3C1232AF1AAE81D900CE8F6C /* srs_app_rtsp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232821AAE81D900CE8F6C /* srs_app_rtsp.cpp */; };
+		3C1232B01AAE81D900CE8F6C /* srs_app_security.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232841AAE81D900CE8F6C /* srs_app_security.cpp */; };
+		3C1232B11AAE81D900CE8F6C /* srs_app_server.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232861AAE81D900CE8F6C /* srs_app_server.cpp */; };
+		3C1232B21AAE81D900CE8F6C /* srs_app_source.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232881AAE81D900CE8F6C /* srs_app_source.cpp */; };
+		3C1232B31AAE81D900CE8F6C /* srs_app_st_socket.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12328A1AAE81D900CE8F6C /* srs_app_st_socket.cpp */; };
+		3C1232B41AAE81D900CE8F6C /* srs_app_st.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12328C1AAE81D900CE8F6C /* srs_app_st.cpp */; };
+		3C1232B51AAE81D900CE8F6C /* srs_app_statistic.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C12328E1AAE81D900CE8F6C /* srs_app_statistic.cpp */; };
+		3C1232B61AAE81D900CE8F6C /* srs_app_thread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232901AAE81D900CE8F6C /* srs_app_thread.cpp */; };
+		3C1232B71AAE81D900CE8F6C /* srs_app_utility.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1232921AAE81D900CE8F6C /* srs_app_utility.cpp */; };
+		3C1232D31AAEA56B00CE8F6C /* libst.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C1232D21AAEA56B00CE8F6C /* libst.a */; };
+		3C1232E91AAEA5D000CE8F6C /* libcrypto.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C1232E71AAEA5D000CE8F6C /* libcrypto.a */; };
+		3C1232EA1AAEA5D000CE8F6C /* libssl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C1232E81AAEA5D000CE8F6C /* libssl.a */; };
+		3C1232ED1AAEA70F00CE8F6C /* libhttp_parser.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C1232EC1AAEA70F00CE8F6C /* libhttp_parser.a */; };
+		3C1EE6AE1AB1055800576EE9 /* srs_app_hds.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1EE6AC1AB1055800576EE9 /* srs_app_hds.cpp */; };
+		3C1EE6D71AB1367D00576EE9 /* README.md in Sources */ = {isa = PBXBuildFile; fileRef = 3C1EE6D61AB1367D00576EE9 /* README.md */; };
+		3C28EDDF1AF5C43F00A3AEAC /* srs_app_caster_flv.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C28EDDD1AF5C43F00A3AEAC /* srs_app_caster_flv.cpp */; };
+		3C36DB5B1ABD1CB90066CCAF /* srs_lib_bandwidth.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C36DB551ABD1CB90066CCAF /* srs_lib_bandwidth.cpp */; };
+		3C36DB5C1ABD1CB90066CCAF /* srs_lib_simple_socket.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C36DB571ABD1CB90066CCAF /* srs_lib_simple_socket.cpp */; };
+		3C36DB5D1ABD1CB90066CCAF /* srs_librtmp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C36DB591ABD1CB90066CCAF /* srs_librtmp.cpp */; };
+		3C663F0F1AB0155100286D8B /* srs_aac_raw_publish.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C663F021AB0155100286D8B /* srs_aac_raw_publish.c */; };
+		3C663F101AB0155100286D8B /* srs_audio_raw_publish.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C663F031AB0155100286D8B /* srs_audio_raw_publish.c */; };
+		3C663F111AB0155100286D8B /* srs_bandwidth_check.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C663F041AB0155100286D8B /* srs_bandwidth_check.c */; };
+		3C663F121AB0155100286D8B /* srs_detect_rtmp.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C663F051AB0155100286D8B /* srs_detect_rtmp.c */; };
+		3C663F131AB0155100286D8B /* srs_flv_injecter.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C663F061AB0155100286D8B /* srs_flv_injecter.c */; };
+		3C663F141AB0155100286D8B /* srs_flv_parser.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C663F071AB0155100286D8B /* srs_flv_parser.c */; };
+		3C663F151AB0155100286D8B /* srs_h264_raw_publish.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C663F081AB0155100286D8B /* srs_h264_raw_publish.c */; };
+		3C663F161AB0155100286D8B /* srs_ingest_flv.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C663F091AB0155100286D8B /* srs_ingest_flv.c */; };
+		3C663F171AB0155100286D8B /* srs_ingest_rtmp.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C663F0A1AB0155100286D8B /* srs_ingest_rtmp.c */; };
+		3C663F181AB0155100286D8B /* srs_play.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C663F0B1AB0155100286D8B /* srs_play.c */; };
+		3C663F191AB0155100286D8B /* srs_publish.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C663F0C1AB0155100286D8B /* srs_publish.c */; };
+		3C663F1A1AB0155100286D8B /* srs_rtmp_dump.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C663F0D1AB0155100286D8B /* srs_rtmp_dump.c */; };
+		3C689F961AB6AAAC00C9CEEE /* event.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C689F921AB6AAAC00C9CEEE /* event.c */; };
+		3C689F971AB6AAAC00C9CEEE /* io.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C689F931AB6AAAC00C9CEEE /* io.c */; };
+		3C689F981AB6AAAC00C9CEEE /* key.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C689F941AB6AAAC00C9CEEE /* key.c */; };
+		3C689F9E1AB6AAC800C9CEEE /* md.S in Sources */ = {isa = PBXBuildFile; fileRef = 3C689F991AB6AAC800C9CEEE /* md.S */; };
+		3C689F9F1AB6AAC800C9CEEE /* sched.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C689F9B1AB6AAC800C9CEEE /* sched.c */; };
+		3C689FA01AB6AAC800C9CEEE /* stk.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C689F9C1AB6AAC800C9CEEE /* stk.c */; };
+		3C689FA11AB6AAC800C9CEEE /* sync.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C689F9D1AB6AAC800C9CEEE /* sync.c */; };
+		3CC52DD81ACE4023006FEB01 /* srs_utest_amf0.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3CC52DCA1ACE4023006FEB01 /* srs_utest_amf0.cpp */; };
+		3CC52DD91ACE4023006FEB01 /* srs_utest_config.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3CC52DCC1ACE4023006FEB01 /* srs_utest_config.cpp */; };
+		3CC52DDA1ACE4023006FEB01 /* srs_utest_core.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3CC52DCE1ACE4023006FEB01 /* srs_utest_core.cpp */; };
+		3CC52DDB1ACE4023006FEB01 /* srs_utest_kernel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3CC52DD01ACE4023006FEB01 /* srs_utest_kernel.cpp */; };
+		3CC52DDC1ACE4023006FEB01 /* srs_utest_protocol.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3CC52DD21ACE4023006FEB01 /* srs_utest_protocol.cpp */; };
+		3CC52DDD1ACE4023006FEB01 /* srs_utest_reload.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3CC52DD41ACE4023006FEB01 /* srs_utest_reload.cpp */; };
+		3CC52DDE1ACE4023006FEB01 /* srs_utest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3CC52DD61ACE4023006FEB01 /* srs_utest.cpp */; };
+		3CD88B3F1ACA9C58000359E0 /* srs_app_async_call.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3CD88B3D1ACA9C58000359E0 /* srs_app_async_call.cpp */; };
+		3CE6CD311AE4AFB800706E07 /* srs_main_ingest_hls.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3CE6CD301AE4AFB800706E07 /* srs_main_ingest_hls.cpp */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+		3C1231E31AAE64A400CE8F6C /* CopyFiles */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 2147483647;
+			dstPath = /usr/share/man/man1/;
+			dstSubfolderSpec = 0;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 1;
+		};
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+		3C1231E51AAE64A400CE8F6C /* srs_xcode */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = srs_xcode; sourceTree = BUILT_PRODUCTS_DIR; };
+		3C1231F01AAE652C00CE8F6C /* srs_core_autofree.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_core_autofree.cpp; path = ../../../src/core/srs_core_autofree.cpp; sourceTree = ""; };
+		3C1231F11AAE652C00CE8F6C /* srs_core_autofree.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_core_autofree.hpp; path = ../../../src/core/srs_core_autofree.hpp; sourceTree = ""; };
+		3C1231F21AAE652C00CE8F6C /* srs_core_performance.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_core_performance.cpp; path = ../../../src/core/srs_core_performance.cpp; sourceTree = ""; };
+		3C1231F31AAE652C00CE8F6C /* srs_core_performance.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_core_performance.hpp; path = ../../../src/core/srs_core_performance.hpp; sourceTree = ""; };
+		3C1231F41AAE652D00CE8F6C /* srs_core.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_core.cpp; path = ../../../src/core/srs_core.cpp; sourceTree = ""; };
+		3C1231F51AAE652D00CE8F6C /* srs_core.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_core.hpp; path = ../../../src/core/srs_core.hpp; sourceTree = ""; };
+		3C1231FB1AAE673100CE8F6C /* srs_auto_headers.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_auto_headers.hpp; path = ../../../objs/srs_auto_headers.hpp; sourceTree = ""; };
+		3C1232051AAE812C00CE8F6C /* srs_main_server.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_main_server.cpp; path = ../../../src/main/srs_main_server.cpp; sourceTree = ""; };
+		3C1232081AAE814D00CE8F6C /* srs_kernel_aac.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_kernel_aac.cpp; path = ../../../src/kernel/srs_kernel_aac.cpp; sourceTree = ""; };
+		3C1232091AAE814D00CE8F6C /* srs_kernel_aac.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_kernel_aac.hpp; path = ../../../src/kernel/srs_kernel_aac.hpp; sourceTree = ""; };
+		3C12320A1AAE814D00CE8F6C /* srs_kernel_buffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_kernel_buffer.cpp; path = ../../../src/kernel/srs_kernel_buffer.cpp; sourceTree = ""; };
+		3C12320B1AAE814D00CE8F6C /* srs_kernel_buffer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_kernel_buffer.hpp; path = ../../../src/kernel/srs_kernel_buffer.hpp; sourceTree = ""; };
+		3C12320C1AAE814D00CE8F6C /* srs_kernel_codec.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_kernel_codec.cpp; path = ../../../src/kernel/srs_kernel_codec.cpp; sourceTree = ""; };
+		3C12320D1AAE814D00CE8F6C /* srs_kernel_codec.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_kernel_codec.hpp; path = ../../../src/kernel/srs_kernel_codec.hpp; sourceTree = ""; };
+		3C12320E1AAE814D00CE8F6C /* srs_kernel_consts.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_kernel_consts.cpp; path = ../../../src/kernel/srs_kernel_consts.cpp; sourceTree = ""; };
+		3C12320F1AAE814D00CE8F6C /* srs_kernel_consts.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_kernel_consts.hpp; path = ../../../src/kernel/srs_kernel_consts.hpp; sourceTree = ""; };
+		3C1232101AAE814D00CE8F6C /* srs_kernel_error.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_kernel_error.cpp; path = ../../../src/kernel/srs_kernel_error.cpp; sourceTree = ""; };
+		3C1232111AAE814D00CE8F6C /* srs_kernel_error.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_kernel_error.hpp; path = ../../../src/kernel/srs_kernel_error.hpp; sourceTree = ""; };
+		3C1232121AAE814D00CE8F6C /* srs_kernel_file.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_kernel_file.cpp; path = ../../../src/kernel/srs_kernel_file.cpp; sourceTree = ""; };
+		3C1232131AAE814D00CE8F6C /* srs_kernel_file.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_kernel_file.hpp; path = ../../../src/kernel/srs_kernel_file.hpp; sourceTree = ""; };
+		3C1232141AAE814D00CE8F6C /* srs_kernel_flv.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_kernel_flv.cpp; path = ../../../src/kernel/srs_kernel_flv.cpp; sourceTree = ""; };
+		3C1232151AAE814D00CE8F6C /* srs_kernel_flv.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_kernel_flv.hpp; path = ../../../src/kernel/srs_kernel_flv.hpp; sourceTree = ""; };
+		3C1232161AAE814D00CE8F6C /* srs_kernel_log.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_kernel_log.cpp; path = ../../../src/kernel/srs_kernel_log.cpp; sourceTree = ""; };
+		3C1232171AAE814D00CE8F6C /* srs_kernel_log.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_kernel_log.hpp; path = ../../../src/kernel/srs_kernel_log.hpp; sourceTree = ""; };
+		3C1232181AAE814D00CE8F6C /* srs_kernel_mp3.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_kernel_mp3.cpp; path = ../../../src/kernel/srs_kernel_mp3.cpp; sourceTree = ""; };
+		3C1232191AAE814D00CE8F6C /* srs_kernel_mp3.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_kernel_mp3.hpp; path = ../../../src/kernel/srs_kernel_mp3.hpp; sourceTree = ""; };
+		3C12321A1AAE814D00CE8F6C /* srs_kernel_stream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_kernel_stream.cpp; path = ../../../src/kernel/srs_kernel_stream.cpp; sourceTree = ""; };
+		3C12321B1AAE814D00CE8F6C /* srs_kernel_stream.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_kernel_stream.hpp; path = ../../../src/kernel/srs_kernel_stream.hpp; sourceTree = ""; };
+		3C12321C1AAE814D00CE8F6C /* srs_kernel_ts.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_kernel_ts.cpp; path = ../../../src/kernel/srs_kernel_ts.cpp; sourceTree = ""; };
+		3C12321D1AAE814D00CE8F6C /* srs_kernel_ts.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_kernel_ts.hpp; path = ../../../src/kernel/srs_kernel_ts.hpp; sourceTree = ""; };
+		3C12321E1AAE814D00CE8F6C /* srs_kernel_utility.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_kernel_utility.cpp; path = ../../../src/kernel/srs_kernel_utility.cpp; sourceTree = ""; };
+		3C12321F1AAE814D00CE8F6C /* srs_kernel_utility.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_kernel_utility.hpp; path = ../../../src/kernel/srs_kernel_utility.hpp; sourceTree = ""; };
+		3C12322D1AAE81A400CE8F6C /* srs_raw_avc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_raw_avc.cpp; path = ../../../src/protocol/srs_raw_avc.cpp; sourceTree = ""; };
+		3C12322E1AAE81A400CE8F6C /* srs_raw_avc.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_raw_avc.hpp; path = ../../../src/protocol/srs_raw_avc.hpp; sourceTree = ""; };
+		3C12322F1AAE81A400CE8F6C /* srs_rtmp_amf0.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_rtmp_amf0.cpp; path = ../../../src/protocol/srs_rtmp_amf0.cpp; sourceTree = ""; };
+		3C1232301AAE81A400CE8F6C /* srs_rtmp_amf0.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_rtmp_amf0.hpp; path = ../../../src/protocol/srs_rtmp_amf0.hpp; sourceTree = ""; };
+		3C1232311AAE81A400CE8F6C /* srs_rtmp_buffer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_rtmp_buffer.cpp; path = ../../../src/protocol/srs_rtmp_buffer.cpp; sourceTree = ""; };
+		3C1232321AAE81A400CE8F6C /* srs_rtmp_buffer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_rtmp_buffer.hpp; path = ../../../src/protocol/srs_rtmp_buffer.hpp; sourceTree = ""; };
+		3C1232331AAE81A400CE8F6C /* srs_rtmp_handshake.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_rtmp_handshake.cpp; path = ../../../src/protocol/srs_rtmp_handshake.cpp; sourceTree = ""; };
+		3C1232341AAE81A400CE8F6C /* srs_rtmp_handshake.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_rtmp_handshake.hpp; path = ../../../src/protocol/srs_rtmp_handshake.hpp; sourceTree = ""; };
+		3C1232351AAE81A400CE8F6C /* srs_rtmp_io.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_rtmp_io.cpp; path = ../../../src/protocol/srs_rtmp_io.cpp; sourceTree = ""; };
+		3C1232361AAE81A400CE8F6C /* srs_rtmp_io.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_rtmp_io.hpp; path = ../../../src/protocol/srs_rtmp_io.hpp; sourceTree = ""; };
+		3C1232371AAE81A400CE8F6C /* srs_rtmp_msg_array.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_rtmp_msg_array.cpp; path = ../../../src/protocol/srs_rtmp_msg_array.cpp; sourceTree = ""; };
+		3C1232381AAE81A400CE8F6C /* srs_rtmp_msg_array.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_rtmp_msg_array.hpp; path = ../../../src/protocol/srs_rtmp_msg_array.hpp; sourceTree = ""; };
+		3C1232391AAE81A400CE8F6C /* srs_rtmp_sdk.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_rtmp_sdk.cpp; path = ../../../src/protocol/srs_rtmp_sdk.cpp; sourceTree = ""; };
+		3C12323A1AAE81A400CE8F6C /* srs_rtmp_sdk.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_rtmp_sdk.hpp; path = ../../../src/protocol/srs_rtmp_sdk.hpp; sourceTree = ""; };
+		3C12323B1AAE81A400CE8F6C /* srs_rtmp_stack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_rtmp_stack.cpp; path = ../../../src/protocol/srs_rtmp_stack.cpp; sourceTree = ""; };
+		3C12323C1AAE81A400CE8F6C /* srs_rtmp_stack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_rtmp_stack.hpp; path = ../../../src/protocol/srs_rtmp_stack.hpp; sourceTree = ""; };
+		3C12323D1AAE81A400CE8F6C /* srs_rtmp_utility.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_rtmp_utility.cpp; path = ../../../src/protocol/srs_rtmp_utility.cpp; sourceTree = ""; };
+		3C12323E1AAE81A400CE8F6C /* srs_rtmp_utility.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_rtmp_utility.hpp; path = ../../../src/protocol/srs_rtmp_utility.hpp; sourceTree = ""; };
+		3C12323F1AAE81A400CE8F6C /* srs_rtsp_stack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_rtsp_stack.cpp; path = ../../../src/protocol/srs_rtsp_stack.cpp; sourceTree = ""; };
+		3C1232401AAE81A400CE8F6C /* srs_rtsp_stack.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_rtsp_stack.hpp; path = ../../../src/protocol/srs_rtsp_stack.hpp; sourceTree = ""; };
+		3C12324C1AAE81D900CE8F6C /* srs_app_bandwidth.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_bandwidth.cpp; path = ../../../src/app/srs_app_bandwidth.cpp; sourceTree = ""; };
+		3C12324D1AAE81D900CE8F6C /* srs_app_bandwidth.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_bandwidth.hpp; path = ../../../src/app/srs_app_bandwidth.hpp; sourceTree = ""; };
+		3C12324E1AAE81D900CE8F6C /* srs_app_config.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_config.cpp; path = ../../../src/app/srs_app_config.cpp; sourceTree = ""; };
+		3C12324F1AAE81D900CE8F6C /* srs_app_config.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_config.hpp; path = ../../../src/app/srs_app_config.hpp; sourceTree = ""; };
+		3C1232501AAE81D900CE8F6C /* srs_app_conn.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_conn.cpp; path = ../../../src/app/srs_app_conn.cpp; sourceTree = ""; };
+		3C1232511AAE81D900CE8F6C /* srs_app_conn.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_conn.hpp; path = ../../../src/app/srs_app_conn.hpp; sourceTree = ""; };
+		3C1232521AAE81D900CE8F6C /* srs_app_dvr.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_dvr.cpp; path = ../../../src/app/srs_app_dvr.cpp; sourceTree = ""; };
+		3C1232531AAE81D900CE8F6C /* srs_app_dvr.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_dvr.hpp; path = ../../../src/app/srs_app_dvr.hpp; sourceTree = ""; };
+		3C1232541AAE81D900CE8F6C /* srs_app_edge.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_edge.cpp; path = ../../../src/app/srs_app_edge.cpp; sourceTree = ""; };
+		3C1232551AAE81D900CE8F6C /* srs_app_edge.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_edge.hpp; path = ../../../src/app/srs_app_edge.hpp; sourceTree = ""; };
+		3C1232561AAE81D900CE8F6C /* srs_app_empty.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_empty.cpp; path = ../../../src/app/srs_app_empty.cpp; sourceTree = ""; };
+		3C1232571AAE81D900CE8F6C /* srs_app_empty.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_empty.hpp; path = ../../../src/app/srs_app_empty.hpp; sourceTree = ""; };
+		3C1232581AAE81D900CE8F6C /* srs_app_encoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_encoder.cpp; path = ../../../src/app/srs_app_encoder.cpp; sourceTree = ""; };
+		3C1232591AAE81D900CE8F6C /* srs_app_encoder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_encoder.hpp; path = ../../../src/app/srs_app_encoder.hpp; sourceTree = ""; };
+		3C12325A1AAE81D900CE8F6C /* srs_app_ffmpeg.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_ffmpeg.cpp; path = ../../../src/app/srs_app_ffmpeg.cpp; sourceTree = ""; };
+		3C12325B1AAE81D900CE8F6C /* srs_app_ffmpeg.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_ffmpeg.hpp; path = ../../../src/app/srs_app_ffmpeg.hpp; sourceTree = ""; };
+		3C12325C1AAE81D900CE8F6C /* srs_app_forward.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_forward.cpp; path = ../../../src/app/srs_app_forward.cpp; sourceTree = ""; };
+		3C12325D1AAE81D900CE8F6C /* srs_app_forward.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_forward.hpp; path = ../../../src/app/srs_app_forward.hpp; sourceTree = ""; };
+		3C12325E1AAE81D900CE8F6C /* srs_app_heartbeat.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_heartbeat.cpp; path = ../../../src/app/srs_app_heartbeat.cpp; sourceTree = ""; };
+		3C12325F1AAE81D900CE8F6C /* srs_app_heartbeat.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_heartbeat.hpp; path = ../../../src/app/srs_app_heartbeat.hpp; sourceTree = ""; };
+		3C1232601AAE81D900CE8F6C /* srs_app_hls.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_hls.cpp; path = ../../../src/app/srs_app_hls.cpp; sourceTree = ""; };
+		3C1232611AAE81D900CE8F6C /* srs_app_hls.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_hls.hpp; path = ../../../src/app/srs_app_hls.hpp; sourceTree = ""; };
+		3C1232621AAE81D900CE8F6C /* srs_app_http_api.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_http_api.cpp; path = ../../../src/app/srs_app_http_api.cpp; sourceTree = ""; };
+		3C1232631AAE81D900CE8F6C /* srs_app_http_api.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_http_api.hpp; path = ../../../src/app/srs_app_http_api.hpp; sourceTree = ""; };
+		3C1232641AAE81D900CE8F6C /* srs_app_http_client.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_http_client.cpp; path = ../../../src/app/srs_app_http_client.cpp; sourceTree = ""; };
+		3C1232651AAE81D900CE8F6C /* srs_app_http_client.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_http_client.hpp; path = ../../../src/app/srs_app_http_client.hpp; sourceTree = ""; };
+		3C1232661AAE81D900CE8F6C /* srs_app_http_conn.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_http_conn.cpp; path = ../../../src/app/srs_app_http_conn.cpp; sourceTree = ""; };
+		3C1232671AAE81D900CE8F6C /* srs_app_http_conn.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_http_conn.hpp; path = ../../../src/app/srs_app_http_conn.hpp; sourceTree = ""; };
+		3C1232681AAE81D900CE8F6C /* srs_app_http_hooks.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_http_hooks.cpp; path = ../../../src/app/srs_app_http_hooks.cpp; sourceTree = ""; };
+		3C1232691AAE81D900CE8F6C /* srs_app_http_hooks.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_http_hooks.hpp; path = ../../../src/app/srs_app_http_hooks.hpp; sourceTree = ""; };
+		3C12326A1AAE81D900CE8F6C /* srs_app_http.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_http.cpp; path = ../../../src/app/srs_app_http.cpp; sourceTree = ""; };
+		3C12326B1AAE81D900CE8F6C /* srs_app_http.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_http.hpp; path = ../../../src/app/srs_app_http.hpp; sourceTree = ""; };
+		3C12326C1AAE81D900CE8F6C /* srs_app_ingest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_ingest.cpp; path = ../../../src/app/srs_app_ingest.cpp; sourceTree = ""; };
+		3C12326D1AAE81D900CE8F6C /* srs_app_ingest.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_ingest.hpp; path = ../../../src/app/srs_app_ingest.hpp; sourceTree = ""; };
+		3C12326E1AAE81D900CE8F6C /* srs_app_json.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_json.cpp; path = ../../../src/app/srs_app_json.cpp; sourceTree = ""; };
+		3C12326F1AAE81D900CE8F6C /* srs_app_json.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_json.hpp; path = ../../../src/app/srs_app_json.hpp; sourceTree = ""; };
+		3C1232701AAE81D900CE8F6C /* srs_app_kbps.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_kbps.cpp; path = ../../../src/app/srs_app_kbps.cpp; sourceTree = ""; };
+		3C1232711AAE81D900CE8F6C /* srs_app_kbps.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_kbps.hpp; path = ../../../src/app/srs_app_kbps.hpp; sourceTree = ""; };
+		3C1232721AAE81D900CE8F6C /* srs_app_listener.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_listener.cpp; path = ../../../src/app/srs_app_listener.cpp; sourceTree = ""; };
+		3C1232731AAE81D900CE8F6C /* srs_app_listener.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_listener.hpp; path = ../../../src/app/srs_app_listener.hpp; sourceTree = ""; };
+		3C1232741AAE81D900CE8F6C /* srs_app_log.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_log.cpp; path = ../../../src/app/srs_app_log.cpp; sourceTree = ""; };
+		3C1232751AAE81D900CE8F6C /* srs_app_log.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_log.hpp; path = ../../../src/app/srs_app_log.hpp; sourceTree = ""; };
+		3C1232761AAE81D900CE8F6C /* srs_app_mpegts_udp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_mpegts_udp.cpp; path = ../../../src/app/srs_app_mpegts_udp.cpp; sourceTree = ""; };
+		3C1232771AAE81D900CE8F6C /* srs_app_mpegts_udp.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_mpegts_udp.hpp; path = ../../../src/app/srs_app_mpegts_udp.hpp; sourceTree = ""; };
+		3C1232781AAE81D900CE8F6C /* srs_app_pithy_print.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_pithy_print.cpp; path = ../../../src/app/srs_app_pithy_print.cpp; sourceTree = ""; };
+		3C1232791AAE81D900CE8F6C /* srs_app_pithy_print.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_pithy_print.hpp; path = ../../../src/app/srs_app_pithy_print.hpp; sourceTree = ""; };
+		3C12327A1AAE81D900CE8F6C /* srs_app_recv_thread.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_recv_thread.cpp; path = ../../../src/app/srs_app_recv_thread.cpp; sourceTree = ""; };
+		3C12327B1AAE81D900CE8F6C /* srs_app_recv_thread.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_recv_thread.hpp; path = ../../../src/app/srs_app_recv_thread.hpp; sourceTree = ""; };
+		3C12327C1AAE81D900CE8F6C /* srs_app_refer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_refer.cpp; path = ../../../src/app/srs_app_refer.cpp; sourceTree = ""; };
+		3C12327D1AAE81D900CE8F6C /* srs_app_refer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_refer.hpp; path = ../../../src/app/srs_app_refer.hpp; sourceTree = ""; };
+		3C12327E1AAE81D900CE8F6C /* srs_app_reload.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_reload.cpp; path = ../../../src/app/srs_app_reload.cpp; sourceTree = ""; };
+		3C12327F1AAE81D900CE8F6C /* srs_app_reload.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_reload.hpp; path = ../../../src/app/srs_app_reload.hpp; sourceTree = ""; };
+		3C1232801AAE81D900CE8F6C /* srs_app_rtmp_conn.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_rtmp_conn.cpp; path = ../../../src/app/srs_app_rtmp_conn.cpp; sourceTree = ""; };
+		3C1232811AAE81D900CE8F6C /* srs_app_rtmp_conn.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_rtmp_conn.hpp; path = ../../../src/app/srs_app_rtmp_conn.hpp; sourceTree = ""; };
+		3C1232821AAE81D900CE8F6C /* srs_app_rtsp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_rtsp.cpp; path = ../../../src/app/srs_app_rtsp.cpp; sourceTree = ""; };
+		3C1232831AAE81D900CE8F6C /* srs_app_rtsp.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_rtsp.hpp; path = ../../../src/app/srs_app_rtsp.hpp; sourceTree = ""; };
+		3C1232841AAE81D900CE8F6C /* srs_app_security.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_security.cpp; path = ../../../src/app/srs_app_security.cpp; sourceTree = ""; };
+		3C1232851AAE81D900CE8F6C /* srs_app_security.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_security.hpp; path = ../../../src/app/srs_app_security.hpp; sourceTree = ""; };
+		3C1232861AAE81D900CE8F6C /* srs_app_server.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_server.cpp; path = ../../../src/app/srs_app_server.cpp; sourceTree = ""; };
+		3C1232871AAE81D900CE8F6C /* srs_app_server.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_server.hpp; path = ../../../src/app/srs_app_server.hpp; sourceTree = ""; };
+		3C1232881AAE81D900CE8F6C /* srs_app_source.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_source.cpp; path = ../../../src/app/srs_app_source.cpp; sourceTree = ""; };
+		3C1232891AAE81D900CE8F6C /* srs_app_source.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_source.hpp; path = ../../../src/app/srs_app_source.hpp; sourceTree = ""; };
+		3C12328A1AAE81D900CE8F6C /* srs_app_st_socket.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_st_socket.cpp; path = ../../../src/app/srs_app_st_socket.cpp; sourceTree = ""; };
+		3C12328B1AAE81D900CE8F6C /* srs_app_st_socket.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_st_socket.hpp; path = ../../../src/app/srs_app_st_socket.hpp; sourceTree = ""; };
+		3C12328C1AAE81D900CE8F6C /* srs_app_st.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_st.cpp; path = ../../../src/app/srs_app_st.cpp; sourceTree = ""; };
+		3C12328D1AAE81D900CE8F6C /* srs_app_st.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_st.hpp; path = ../../../src/app/srs_app_st.hpp; sourceTree = ""; };
+		3C12328E1AAE81D900CE8F6C /* srs_app_statistic.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_statistic.cpp; path = ../../../src/app/srs_app_statistic.cpp; sourceTree = ""; };
+		3C12328F1AAE81D900CE8F6C /* srs_app_statistic.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_statistic.hpp; path = ../../../src/app/srs_app_statistic.hpp; sourceTree = ""; };
+		3C1232901AAE81D900CE8F6C /* srs_app_thread.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_thread.cpp; path = ../../../src/app/srs_app_thread.cpp; sourceTree = ""; };
+		3C1232911AAE81D900CE8F6C /* srs_app_thread.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_thread.hpp; path = ../../../src/app/srs_app_thread.hpp; sourceTree = ""; };
+		3C1232921AAE81D900CE8F6C /* srs_app_utility.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_utility.cpp; path = ../../../src/app/srs_app_utility.cpp; sourceTree = ""; };
+		3C1232931AAE81D900CE8F6C /* srs_app_utility.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_utility.hpp; path = ../../../src/app/srs_app_utility.hpp; sourceTree = ""; };
+		3C1232B81AAE824500CE8F6C /* configure */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = configure; path = ../../../configure; sourceTree = ""; };
+		3C1232BB1AAE827E00CE8F6C /* apps.sh */ = {isa = PBXFileReference; explicitFileType = text.script.sh; fileEncoding = 4; name = apps.sh; path = ../../../auto/apps.sh; sourceTree = ""; };
+		3C1232BC1AAE827E00CE8F6C /* auto_headers.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = auto_headers.sh; path = ../../../auto/auto_headers.sh; sourceTree = ""; };
+		3C1232BD1AAE827E00CE8F6C /* build_ffmpeg.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = build_ffmpeg.sh; path = ../../../auto/build_ffmpeg.sh; sourceTree = ""; };
+		3C1232BE1AAE827E00CE8F6C /* depends.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = depends.sh; path = ../../../auto/depends.sh; sourceTree = ""; };
+		3C1232BF1AAE827E00CE8F6C /* generate_header.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = generate_header.sh; path = ../../../auto/generate_header.sh; sourceTree = ""; };
+		3C1232C01AAE827E00CE8F6C /* generate-srs-librtmp-project.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = "generate-srs-librtmp-project.sh"; path = "../../../auto/generate-srs-librtmp-project.sh"; sourceTree = ""; };
+		3C1232C11AAE827E00CE8F6C /* generate-srs-librtmp-single.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = "generate-srs-librtmp-single.sh"; path = "../../../auto/generate-srs-librtmp-single.sh"; sourceTree = ""; };
+		3C1232C21AAE827E00CE8F6C /* libs.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = libs.sh; path = ../../../auto/libs.sh; sourceTree = ""; };
+		3C1232C31AAE827E00CE8F6C /* local_ip.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = local_ip.sh; path = ../../../auto/local_ip.sh; sourceTree = ""; };
+		3C1232C41AAE827E00CE8F6C /* modules.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = modules.sh; path = ../../../auto/modules.sh; sourceTree = ""; };
+		3C1232C51AAE827E00CE8F6C /* options.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = options.sh; path = ../../../auto/options.sh; sourceTree = ""; wrapsLines = 0; };
+		3C1232C61AAE827E00CE8F6C /* summary.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = summary.sh; path = ../../../auto/summary.sh; sourceTree = ""; };
+		3C1232C71AAE827E00CE8F6C /* utest.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = utest.sh; path = ../../../auto/utest.sh; sourceTree = ""; };
+		3C1232C81AAE833300CE8F6C /* _log.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = _log.sh; path = ../../../scripts/_log.sh; sourceTree = ""; };
+		3C1232C91AAE833300CE8F6C /* build.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = build.sh; path = ../../../scripts/build.sh; sourceTree = ""; };
+		3C1232CA1AAE833300CE8F6C /* git.commit.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = git.commit.sh; path = ../../../scripts/git.commit.sh; sourceTree = ""; };
+		3C1232CB1AAE833300CE8F6C /* git2unix.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = git2unix.sh; path = ../../../scripts/git2unix.sh; sourceTree = ""; };
+		3C1232CC1AAE833300CE8F6C /* install.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = install.sh; path = ../../../scripts/install.sh; sourceTree = ""; };
+		3C1232CD1AAE833300CE8F6C /* package.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = package.sh; path = ../../../scripts/package.sh; sourceTree = ""; };
+		3C1232CE1AAE833300CE8F6C /* run.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = run.sh; path = ../../../scripts/run.sh; sourceTree = ""; };
+		3C1232CF1AAE833300CE8F6C /* srs.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = srs.test; path = ../../../scripts/srs.test; sourceTree = ""; };
+		3C1232D01AAE833300CE8F6C /* stop.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = stop.sh; path = ../../../scripts/stop.sh; sourceTree = ""; };
+		3C1232D11AAE833300CE8F6C /* test_configure.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = test_configure.sh; path = ../../../scripts/test_configure.sh; sourceTree = ""; };
+		3C1232D21AAEA56B00CE8F6C /* libst.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libst.a; path = "../../objs/st-1.9/DARWIN_14.0.0_DBG/libst.a"; sourceTree = ""; };
+		3C1232E71AAEA5D000CE8F6C /* libcrypto.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libcrypto.a; path = "../../objs/openssl-1.0.1f/_release/lib/libcrypto.a"; sourceTree = ""; };
+		3C1232E81AAEA5D000CE8F6C /* libssl.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libssl.a; path = "../../objs/openssl-1.0.1f/_release/lib/libssl.a"; sourceTree = ""; };
+		3C1232EC1AAEA70F00CE8F6C /* libhttp_parser.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libhttp_parser.a; path = "../../objs/http-parser-2.1/libhttp_parser.a"; sourceTree = ""; };
+		3C1232F11AAEAC7000CE8F6C /* srs */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = srs; path = ../../../etc/init.d/srs; sourceTree = ""; };
+		3C1232F21AAEAC7000CE8F6C /* srs-api */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = "srs-api"; path = "../../../etc/init.d/srs-api"; sourceTree = ""; };
+		3C1232F31AAEAC7000CE8F6C /* srs-demo */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = "srs-demo"; path = "../../../etc/init.d/srs-demo"; sourceTree = ""; };
+		3C1232F41AAEAC7000CE8F6C /* srs-demo-19350 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = "srs-demo-19350"; path = "../../../etc/init.d/srs-demo-19350"; sourceTree = ""; };
+		3C1EE6AC1AB1055800576EE9 /* srs_app_hds.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_hds.cpp; path = ../../../src/app/srs_app_hds.cpp; sourceTree = ""; };
+		3C1EE6AD1AB1055800576EE9 /* srs_app_hds.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_hds.hpp; path = ../../../src/app/srs_app_hds.hpp; sourceTree = ""; };
+		3C1EE6B01AB1080900576EE9 /* bandwidth.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = bandwidth.conf; path = ../../../conf/bandwidth.conf; sourceTree = ""; };
+		3C1EE6B11AB1080900576EE9 /* console.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = console.conf; path = ../../../conf/console.conf; sourceTree = ""; };
+		3C1EE6B21AB1080900576EE9 /* demo.19350.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = demo.19350.conf; path = ../../../conf/demo.19350.conf; sourceTree = ""; };
+		3C1EE6B31AB1080900576EE9 /* demo.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = demo.conf; path = ../../../conf/demo.conf; sourceTree = ""; };
+		3C1EE6B41AB1080900576EE9 /* dvr.path.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = dvr.path.conf; path = ../../../conf/dvr.path.conf; sourceTree = ""; };
+		3C1EE6B51AB1080900576EE9 /* dvr.segment.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = dvr.segment.conf; path = ../../../conf/dvr.segment.conf; sourceTree = ""; };
+		3C1EE6B61AB1080900576EE9 /* dvr.session.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = dvr.session.conf; path = ../../../conf/dvr.session.conf; sourceTree = ""; };
+		3C1EE6B71AB1080900576EE9 /* edge.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = edge.conf; path = ../../../conf/edge.conf; sourceTree = ""; };
+		3C1EE6B81AB1080900576EE9 /* edge.token.traverse.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = edge.token.traverse.conf; path = ../../../conf/edge.token.traverse.conf; sourceTree = ""; };
+		3C1EE6B91AB1080900576EE9 /* ffmpeg.transcode.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = ffmpeg.transcode.conf; path = ../../../conf/ffmpeg.transcode.conf; sourceTree = ""; };
+		3C1EE6BA1AB1080900576EE9 /* forward.master.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = forward.master.conf; path = ../../../conf/forward.master.conf; sourceTree = ""; };
+		3C1EE6BB1AB1080900576EE9 /* forward.slave.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = forward.slave.conf; path = ../../../conf/forward.slave.conf; sourceTree = ""; };
+		3C1EE6BC1AB1080900576EE9 /* full.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = full.conf; path = ../../../conf/full.conf; sourceTree = ""; };
+		3C1EE6BD1AB1080900576EE9 /* hds.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = hds.conf; path = ../../../conf/hds.conf; sourceTree = ""; };
+		3C1EE6BE1AB1080900576EE9 /* hls.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = hls.conf; path = ../../../conf/hls.conf; sourceTree = ""; };
+		3C1EE6BF1AB1080900576EE9 /* http.aac.live.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = http.aac.live.conf; path = ../../../conf/http.aac.live.conf; sourceTree = ""; };
+		3C1EE6C01AB1080900576EE9 /* http.flv.live.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = http.flv.live.conf; path = ../../../conf/http.flv.live.conf; sourceTree = ""; };
+		3C1EE6C11AB1080900576EE9 /* http.heartbeat.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = http.heartbeat.conf; path = ../../../conf/http.heartbeat.conf; sourceTree = ""; };
+		3C1EE6C21AB1080900576EE9 /* http.hls.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = http.hls.conf; path = ../../../conf/http.hls.conf; sourceTree = ""; };
+		3C1EE6C31AB1080900576EE9 /* http.hooks.callback.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = http.hooks.callback.conf; path = ../../../conf/http.hooks.callback.conf; sourceTree = ""; };
+		3C1EE6C41AB1080900576EE9 /* http.mp3.live.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = http.mp3.live.conf; path = ../../../conf/http.mp3.live.conf; sourceTree = ""; };
+		3C1EE6C51AB1080900576EE9 /* http.server.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = http.server.conf; path = ../../../conf/http.server.conf; sourceTree = ""; };
+		3C1EE6C61AB1080900576EE9 /* http.ts.live.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = http.ts.live.conf; path = ../../../conf/http.ts.live.conf; sourceTree = ""; };
+		3C1EE6C71AB1080900576EE9 /* ingest.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = ingest.conf; path = ../../../conf/ingest.conf; sourceTree = ""; };
+		3C1EE6C81AB1080900576EE9 /* mac.dev.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = mac.dev.conf; path = ../../../conf/mac.dev.conf; sourceTree = ""; };
+		3C1EE6C91AB1080900576EE9 /* origin.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = origin.conf; path = ../../../conf/origin.conf; sourceTree = ""; };
+		3C1EE6CA1AB1080900576EE9 /* push.mpegts.over.udp.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = push.mpegts.over.udp.conf; path = ../../../conf/push.mpegts.over.udp.conf; sourceTree = ""; };
+		3C1EE6CB1AB1080900576EE9 /* push.rtsp.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = push.rtsp.conf; path = ../../../conf/push.rtsp.conf; sourceTree = ""; };
+		3C1EE6CC1AB1080900576EE9 /* ram.hls.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = ram.hls.conf; path = ../../../conf/ram.hls.conf; sourceTree = ""; };
+		3C1EE6CD1AB1080900576EE9 /* realtime.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = realtime.conf; path = ../../../conf/realtime.conf; sourceTree = ""; };
+		3C1EE6CE1AB1080900576EE9 /* rtmp.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = rtmp.conf; path = ../../../conf/rtmp.conf; sourceTree = ""; };
+		3C1EE6CF1AB1080900576EE9 /* security.deny.publish.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = security.deny.publish.conf; path = ../../../conf/security.deny.publish.conf; sourceTree = ""; };
+		3C1EE6D01AB1080900576EE9 /* srs.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = srs.conf; path = ../../../conf/srs.conf; sourceTree = ""; };
+		3C1EE6D11AB1080900576EE9 /* transcode2hls.audio.only.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = transcode2hls.audio.only.conf; path = ../../../conf/transcode2hls.audio.only.conf; sourceTree = ""; };
+		3C1EE6D31AB1367D00576EE9 /* AUTHORS.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = AUTHORS.txt; path = ../../../AUTHORS.txt; sourceTree = ""; };
+		3C1EE6D41AB1367D00576EE9 /* DONATIONS.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = DONATIONS.txt; path = ../../../DONATIONS.txt; sourceTree = ""; };
+		3C1EE6D51AB1367D00576EE9 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = LICENSE; path = ../../../LICENSE; sourceTree = ""; };
+		3C1EE6D61AB1367D00576EE9 /* README.md */ = {isa = PBXFileReference; explicitFileType = net.daringfireball.markdown; fileEncoding = 4; name = README.md; path = ../../../README.md; sourceTree = ""; wrapsLines = 0; };
+		3C28EDDD1AF5C43F00A3AEAC /* srs_app_caster_flv.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_caster_flv.cpp; path = ../../../src/app/srs_app_caster_flv.cpp; sourceTree = ""; };
+		3C28EDDE1AF5C43F00A3AEAC /* srs_app_caster_flv.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_caster_flv.hpp; path = ../../../src/app/srs_app_caster_flv.hpp; sourceTree = ""; };
+		3C36DB551ABD1CB90066CCAF /* srs_lib_bandwidth.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_lib_bandwidth.cpp; path = ../../../src/libs/srs_lib_bandwidth.cpp; sourceTree = ""; };
+		3C36DB561ABD1CB90066CCAF /* srs_lib_bandwidth.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_lib_bandwidth.hpp; path = ../../../src/libs/srs_lib_bandwidth.hpp; sourceTree = ""; };
+		3C36DB571ABD1CB90066CCAF /* srs_lib_simple_socket.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_lib_simple_socket.cpp; path = ../../../src/libs/srs_lib_simple_socket.cpp; sourceTree = ""; };
+		3C36DB581ABD1CB90066CCAF /* srs_lib_simple_socket.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_lib_simple_socket.hpp; path = ../../../src/libs/srs_lib_simple_socket.hpp; sourceTree = ""; };
+		3C36DB591ABD1CB90066CCAF /* srs_librtmp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_librtmp.cpp; path = ../../../src/libs/srs_librtmp.cpp; sourceTree = ""; };
+		3C36DB5A1ABD1CB90066CCAF /* srs_librtmp.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_librtmp.hpp; path = ../../../src/libs/srs_librtmp.hpp; sourceTree = ""; };
+		3C663F021AB0155100286D8B /* srs_aac_raw_publish.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = srs_aac_raw_publish.c; path = ../../../research/librtmp/srs_aac_raw_publish.c; sourceTree = ""; };
+		3C663F031AB0155100286D8B /* srs_audio_raw_publish.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = srs_audio_raw_publish.c; path = ../../../research/librtmp/srs_audio_raw_publish.c; sourceTree = ""; };
+		3C663F041AB0155100286D8B /* srs_bandwidth_check.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = srs_bandwidth_check.c; path = ../../../research/librtmp/srs_bandwidth_check.c; sourceTree = ""; };
+		3C663F051AB0155100286D8B /* srs_detect_rtmp.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = srs_detect_rtmp.c; path = ../../../research/librtmp/srs_detect_rtmp.c; sourceTree = ""; };
+		3C663F061AB0155100286D8B /* srs_flv_injecter.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = srs_flv_injecter.c; path = ../../../research/librtmp/srs_flv_injecter.c; sourceTree = ""; };
+		3C663F071AB0155100286D8B /* srs_flv_parser.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = srs_flv_parser.c; path = ../../../research/librtmp/srs_flv_parser.c; sourceTree = ""; };
+		3C663F081AB0155100286D8B /* srs_h264_raw_publish.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = srs_h264_raw_publish.c; path = ../../../research/librtmp/srs_h264_raw_publish.c; sourceTree = ""; };
+		3C663F091AB0155100286D8B /* srs_ingest_flv.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = srs_ingest_flv.c; path = ../../../research/librtmp/srs_ingest_flv.c; sourceTree = ""; };
+		3C663F0A1AB0155100286D8B /* srs_ingest_rtmp.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = srs_ingest_rtmp.c; path = ../../../research/librtmp/srs_ingest_rtmp.c; sourceTree = ""; };
+		3C663F0B1AB0155100286D8B /* srs_play.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = srs_play.c; path = ../../../research/librtmp/srs_play.c; sourceTree = ""; };
+		3C663F0C1AB0155100286D8B /* srs_publish.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = srs_publish.c; path = ../../../research/librtmp/srs_publish.c; sourceTree = ""; };
+		3C663F0D1AB0155100286D8B /* srs_rtmp_dump.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = srs_rtmp_dump.c; path = ../../../research/librtmp/srs_rtmp_dump.c; sourceTree = ""; };
+		3C689F911AB6AAAC00C9CEEE /* common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = common.h; path = "../../objs/st-1.9/common.h"; sourceTree = ""; };
+		3C689F921AB6AAAC00C9CEEE /* event.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = event.c; path = "../../objs/st-1.9/event.c"; sourceTree = ""; };
+		3C689F931AB6AAAC00C9CEEE /* io.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = io.c; path = "../../objs/st-1.9/io.c"; sourceTree = ""; };
+		3C689F941AB6AAAC00C9CEEE /* key.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = key.c; path = "../../objs/st-1.9/key.c"; sourceTree = ""; };
+		3C689F951AB6AAAC00C9CEEE /* md.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = md.h; path = "../../objs/st-1.9/md.h"; sourceTree = ""; };
+		3C689F991AB6AAC800C9CEEE /* md.S */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.asm; name = md.S; path = "../../objs/st-1.9/md.S"; sourceTree = ""; };
+		3C689F9A1AB6AAC800C9CEEE /* public.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = public.h; path = "../../objs/st-1.9/public.h"; sourceTree = ""; };
+		3C689F9B1AB6AAC800C9CEEE /* sched.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = sched.c; path = "../../objs/st-1.9/sched.c"; sourceTree = ""; };
+		3C689F9C1AB6AAC800C9CEEE /* stk.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = stk.c; path = "../../objs/st-1.9/stk.c"; sourceTree = ""; };
+		3C689F9D1AB6AAC800C9CEEE /* sync.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = sync.c; path = "../../objs/st-1.9/sync.c"; sourceTree = ""; };
+		3CC52DCA1ACE4023006FEB01 /* srs_utest_amf0.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_utest_amf0.cpp; path = ../../src/utest/srs_utest_amf0.cpp; sourceTree = ""; };
+		3CC52DCB1ACE4023006FEB01 /* srs_utest_amf0.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_utest_amf0.hpp; path = ../../src/utest/srs_utest_amf0.hpp; sourceTree = ""; };
+		3CC52DCC1ACE4023006FEB01 /* srs_utest_config.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_utest_config.cpp; path = ../../src/utest/srs_utest_config.cpp; sourceTree = ""; };
+		3CC52DCD1ACE4023006FEB01 /* srs_utest_config.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_utest_config.hpp; path = ../../src/utest/srs_utest_config.hpp; sourceTree = ""; };
+		3CC52DCE1ACE4023006FEB01 /* srs_utest_core.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_utest_core.cpp; path = ../../src/utest/srs_utest_core.cpp; sourceTree = ""; };
+		3CC52DCF1ACE4023006FEB01 /* srs_utest_core.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_utest_core.hpp; path = ../../src/utest/srs_utest_core.hpp; sourceTree = ""; };
+		3CC52DD01ACE4023006FEB01 /* srs_utest_kernel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_utest_kernel.cpp; path = ../../src/utest/srs_utest_kernel.cpp; sourceTree = ""; };
+		3CC52DD11ACE4023006FEB01 /* srs_utest_kernel.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_utest_kernel.hpp; path = ../../src/utest/srs_utest_kernel.hpp; sourceTree = ""; };
+		3CC52DD21ACE4023006FEB01 /* srs_utest_protocol.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_utest_protocol.cpp; path = ../../src/utest/srs_utest_protocol.cpp; sourceTree = ""; };
+		3CC52DD31ACE4023006FEB01 /* srs_utest_protocol.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_utest_protocol.hpp; path = ../../src/utest/srs_utest_protocol.hpp; sourceTree = ""; };
+		3CC52DD41ACE4023006FEB01 /* srs_utest_reload.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_utest_reload.cpp; path = ../../src/utest/srs_utest_reload.cpp; sourceTree = ""; };
+		3CC52DD51ACE4023006FEB01 /* srs_utest_reload.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_utest_reload.hpp; path = ../../src/utest/srs_utest_reload.hpp; sourceTree = ""; };
+		3CC52DD61ACE4023006FEB01 /* srs_utest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_utest.cpp; path = ../../src/utest/srs_utest.cpp; sourceTree = ""; };
+		3CC52DD71ACE4023006FEB01 /* srs_utest.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_utest.hpp; path = ../../src/utest/srs_utest.hpp; sourceTree = ""; };
+		3CD88B3D1ACA9C58000359E0 /* srs_app_async_call.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_app_async_call.cpp; path = ../../../src/app/srs_app_async_call.cpp; sourceTree = ""; };
+		3CD88B3E1ACA9C58000359E0 /* srs_app_async_call.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = srs_app_async_call.hpp; path = ../../../src/app/srs_app_async_call.hpp; sourceTree = ""; };
+		3CE6CD301AE4AFB800706E07 /* srs_main_ingest_hls.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = srs_main_ingest_hls.cpp; path = ../../../src/main/srs_main_ingest_hls.cpp; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		3C1231E21AAE64A400CE8F6C /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				3C1232ED1AAEA70F00CE8F6C /* libhttp_parser.a in Frameworks */,
+				3C1232E91AAEA5D000CE8F6C /* libcrypto.a in Frameworks */,
+				3C1232EA1AAEA5D000CE8F6C /* libssl.a in Frameworks */,
+				3C1232D31AAEA56B00CE8F6C /* libst.a in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		3C1231DC1AAE64A400CE8F6C = {
+			isa = PBXGroup;
+			children = (
+				3C689F901AB6AA9100C9CEEE /* st-1.9 */,
+				3C1EE6D21AB1366500576EE9 /* doc */,
+				3C1231E61AAE64A400CE8F6C /* Products */,
+				3C1232EE1AAEA71C00CE8F6C /* links */,
+				3C1231E71AAE64A400CE8F6C /* srs_xcode */,
+				3CC52DC91ACE4006006FEB01 /* utest */,
+				3C663F001AB014B500286D8B /* research */,
+			);
+			sourceTree = "";
+		};
+		3C1231E61AAE64A400CE8F6C /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				3C1231E51AAE64A400CE8F6C /* srs_xcode */,
+			);
+			name = Products;
+			sourceTree = "";
+		};
+		3C1231E71AAE64A400CE8F6C /* srs_xcode */ = {
+			isa = PBXGroup;
+			children = (
+				3C1232B81AAE824500CE8F6C /* configure */,
+				3C36DB541ABD1CA70066CCAF /* libs */,
+				3C1EE6AF1AB107EE00576EE9 /* conf */,
+				3C1232EF1AAEAC5800CE8F6C /* etc */,
+				3C1232BA1AAE826F00CE8F6C /* auto */,
+				3C1232B91AAE825100CE8F6C /* scripts */,
+				3C12324B1AAE81CE00CE8F6C /* app */,
+				3C12322C1AAE819900CE8F6C /* protocol */,
+				3C1232071AAE814200CE8F6C /* kernel */,
+				3C1232041AAE80CB00CE8F6C /* main */,
+				3C1231F91AAE670E00CE8F6C /* objs */,
+				3C1231EF1AAE651100CE8F6C /* core */,
+			);
+			path = srs_xcode;
+			sourceTree = "";
+		};
+		3C1231EF1AAE651100CE8F6C /* core */ = {
+			isa = PBXGroup;
+			children = (
+				3C1231F01AAE652C00CE8F6C /* srs_core_autofree.cpp */,
+				3C1231F11AAE652C00CE8F6C /* srs_core_autofree.hpp */,
+				3C1231F21AAE652C00CE8F6C /* srs_core_performance.cpp */,
+				3C1231F31AAE652C00CE8F6C /* srs_core_performance.hpp */,
+				3C1231F41AAE652D00CE8F6C /* srs_core.cpp */,
+				3C1231F51AAE652D00CE8F6C /* srs_core.hpp */,
+			);
+			name = core;
+			sourceTree = "";
+		};
+		3C1231F91AAE670E00CE8F6C /* objs */ = {
+			isa = PBXGroup;
+			children = (
+				3C1231FB1AAE673100CE8F6C /* srs_auto_headers.hpp */,
+			);
+			name = objs;
+			sourceTree = "";
+		};
+		3C1232041AAE80CB00CE8F6C /* main */ = {
+			isa = PBXGroup;
+			children = (
+				3CE6CD301AE4AFB800706E07 /* srs_main_ingest_hls.cpp */,
+				3C1232051AAE812C00CE8F6C /* srs_main_server.cpp */,
+			);
+			name = main;
+			sourceTree = "";
+		};
+		3C1232071AAE814200CE8F6C /* kernel */ = {
+			isa = PBXGroup;
+			children = (
+				3C1232081AAE814D00CE8F6C /* srs_kernel_aac.cpp */,
+				3C1232091AAE814D00CE8F6C /* srs_kernel_aac.hpp */,
+				3C12320A1AAE814D00CE8F6C /* srs_kernel_buffer.cpp */,
+				3C12320B1AAE814D00CE8F6C /* srs_kernel_buffer.hpp */,
+				3C12320C1AAE814D00CE8F6C /* srs_kernel_codec.cpp */,
+				3C12320D1AAE814D00CE8F6C /* srs_kernel_codec.hpp */,
+				3C12320E1AAE814D00CE8F6C /* srs_kernel_consts.cpp */,
+				3C12320F1AAE814D00CE8F6C /* srs_kernel_consts.hpp */,
+				3C1232101AAE814D00CE8F6C /* srs_kernel_error.cpp */,
+				3C1232111AAE814D00CE8F6C /* srs_kernel_error.hpp */,
+				3C1232121AAE814D00CE8F6C /* srs_kernel_file.cpp */,
+				3C1232131AAE814D00CE8F6C /* srs_kernel_file.hpp */,
+				3C1232141AAE814D00CE8F6C /* srs_kernel_flv.cpp */,
+				3C1232151AAE814D00CE8F6C /* srs_kernel_flv.hpp */,
+				3C1232161AAE814D00CE8F6C /* srs_kernel_log.cpp */,
+				3C1232171AAE814D00CE8F6C /* srs_kernel_log.hpp */,
+				3C1232181AAE814D00CE8F6C /* srs_kernel_mp3.cpp */,
+				3C1232191AAE814D00CE8F6C /* srs_kernel_mp3.hpp */,
+				3C12321A1AAE814D00CE8F6C /* srs_kernel_stream.cpp */,
+				3C12321B1AAE814D00CE8F6C /* srs_kernel_stream.hpp */,
+				3C12321C1AAE814D00CE8F6C /* srs_kernel_ts.cpp */,
+				3C12321D1AAE814D00CE8F6C /* srs_kernel_ts.hpp */,
+				3C12321E1AAE814D00CE8F6C /* srs_kernel_utility.cpp */,
+				3C12321F1AAE814D00CE8F6C /* srs_kernel_utility.hpp */,
+			);
+			name = kernel;
+			sourceTree = "";
+		};
+		3C12322C1AAE819900CE8F6C /* protocol */ = {
+			isa = PBXGroup;
+			children = (
+				3C12322D1AAE81A400CE8F6C /* srs_raw_avc.cpp */,
+				3C12322E1AAE81A400CE8F6C /* srs_raw_avc.hpp */,
+				3C12322F1AAE81A400CE8F6C /* srs_rtmp_amf0.cpp */,
+				3C1232301AAE81A400CE8F6C /* srs_rtmp_amf0.hpp */,
+				3C1232311AAE81A400CE8F6C /* srs_rtmp_buffer.cpp */,
+				3C1232321AAE81A400CE8F6C /* srs_rtmp_buffer.hpp */,
+				3C1232331AAE81A400CE8F6C /* srs_rtmp_handshake.cpp */,
+				3C1232341AAE81A400CE8F6C /* srs_rtmp_handshake.hpp */,
+				3C1232351AAE81A400CE8F6C /* srs_rtmp_io.cpp */,
+				3C1232361AAE81A400CE8F6C /* srs_rtmp_io.hpp */,
+				3C1232371AAE81A400CE8F6C /* srs_rtmp_msg_array.cpp */,
+				3C1232381AAE81A400CE8F6C /* srs_rtmp_msg_array.hpp */,
+				3C1232391AAE81A400CE8F6C /* srs_rtmp_sdk.cpp */,
+				3C12323A1AAE81A400CE8F6C /* srs_rtmp_sdk.hpp */,
+				3C12323B1AAE81A400CE8F6C /* srs_rtmp_stack.cpp */,
+				3C12323C1AAE81A400CE8F6C /* srs_rtmp_stack.hpp */,
+				3C12323D1AAE81A400CE8F6C /* srs_rtmp_utility.cpp */,
+				3C12323E1AAE81A400CE8F6C /* srs_rtmp_utility.hpp */,
+				3C12323F1AAE81A400CE8F6C /* srs_rtsp_stack.cpp */,
+				3C1232401AAE81A400CE8F6C /* srs_rtsp_stack.hpp */,
+			);
+			name = protocol;
+			sourceTree = "";
+		};
+		3C12324B1AAE81CE00CE8F6C /* app */ = {
+			isa = PBXGroup;
+			children = (
+				3C28EDDD1AF5C43F00A3AEAC /* srs_app_caster_flv.cpp */,
+				3C28EDDE1AF5C43F00A3AEAC /* srs_app_caster_flv.hpp */,
+				3CD88B3D1ACA9C58000359E0 /* srs_app_async_call.cpp */,
+				3CD88B3E1ACA9C58000359E0 /* srs_app_async_call.hpp */,
+				3C12324C1AAE81D900CE8F6C /* srs_app_bandwidth.cpp */,
+				3C12324D1AAE81D900CE8F6C /* srs_app_bandwidth.hpp */,
+				3C12324E1AAE81D900CE8F6C /* srs_app_config.cpp */,
+				3C12324F1AAE81D900CE8F6C /* srs_app_config.hpp */,
+				3C1232501AAE81D900CE8F6C /* srs_app_conn.cpp */,
+				3C1232511AAE81D900CE8F6C /* srs_app_conn.hpp */,
+				3C1232521AAE81D900CE8F6C /* srs_app_dvr.cpp */,
+				3C1232531AAE81D900CE8F6C /* srs_app_dvr.hpp */,
+				3C1232541AAE81D900CE8F6C /* srs_app_edge.cpp */,
+				3C1232551AAE81D900CE8F6C /* srs_app_edge.hpp */,
+				3C1232561AAE81D900CE8F6C /* srs_app_empty.cpp */,
+				3C1232571AAE81D900CE8F6C /* srs_app_empty.hpp */,
+				3C1232581AAE81D900CE8F6C /* srs_app_encoder.cpp */,
+				3C1232591AAE81D900CE8F6C /* srs_app_encoder.hpp */,
+				3C12325A1AAE81D900CE8F6C /* srs_app_ffmpeg.cpp */,
+				3C12325B1AAE81D900CE8F6C /* srs_app_ffmpeg.hpp */,
+				3C12325C1AAE81D900CE8F6C /* srs_app_forward.cpp */,
+				3C12325D1AAE81D900CE8F6C /* srs_app_forward.hpp */,
+				3C12325E1AAE81D900CE8F6C /* srs_app_heartbeat.cpp */,
+				3C12325F1AAE81D900CE8F6C /* srs_app_heartbeat.hpp */,
+				3C1EE6AC1AB1055800576EE9 /* srs_app_hds.cpp */,
+				3C1EE6AD1AB1055800576EE9 /* srs_app_hds.hpp */,
+				3C1232601AAE81D900CE8F6C /* srs_app_hls.cpp */,
+				3C1232611AAE81D900CE8F6C /* srs_app_hls.hpp */,
+				3C1232621AAE81D900CE8F6C /* srs_app_http_api.cpp */,
+				3C1232631AAE81D900CE8F6C /* srs_app_http_api.hpp */,
+				3C1232641AAE81D900CE8F6C /* srs_app_http_client.cpp */,
+				3C1232651AAE81D900CE8F6C /* srs_app_http_client.hpp */,
+				3C1232661AAE81D900CE8F6C /* srs_app_http_conn.cpp */,
+				3C1232671AAE81D900CE8F6C /* srs_app_http_conn.hpp */,
+				3C1232681AAE81D900CE8F6C /* srs_app_http_hooks.cpp */,
+				3C1232691AAE81D900CE8F6C /* srs_app_http_hooks.hpp */,
+				3C12326A1AAE81D900CE8F6C /* srs_app_http.cpp */,
+				3C12326B1AAE81D900CE8F6C /* srs_app_http.hpp */,
+				3C12326C1AAE81D900CE8F6C /* srs_app_ingest.cpp */,
+				3C12326D1AAE81D900CE8F6C /* srs_app_ingest.hpp */,
+				3C12326E1AAE81D900CE8F6C /* srs_app_json.cpp */,
+				3C12326F1AAE81D900CE8F6C /* srs_app_json.hpp */,
+				3C1232701AAE81D900CE8F6C /* srs_app_kbps.cpp */,
+				3C1232711AAE81D900CE8F6C /* srs_app_kbps.hpp */,
+				3C1232721AAE81D900CE8F6C /* srs_app_listener.cpp */,
+				3C1232731AAE81D900CE8F6C /* srs_app_listener.hpp */,
+				3C1232741AAE81D900CE8F6C /* srs_app_log.cpp */,
+				3C1232751AAE81D900CE8F6C /* srs_app_log.hpp */,
+				3C1232761AAE81D900CE8F6C /* srs_app_mpegts_udp.cpp */,
+				3C1232771AAE81D900CE8F6C /* srs_app_mpegts_udp.hpp */,
+				3C1232781AAE81D900CE8F6C /* srs_app_pithy_print.cpp */,
+				3C1232791AAE81D900CE8F6C /* srs_app_pithy_print.hpp */,
+				3C12327A1AAE81D900CE8F6C /* srs_app_recv_thread.cpp */,
+				3C12327B1AAE81D900CE8F6C /* srs_app_recv_thread.hpp */,
+				3C12327C1AAE81D900CE8F6C /* srs_app_refer.cpp */,
+				3C12327D1AAE81D900CE8F6C /* srs_app_refer.hpp */,
+				3C12327E1AAE81D900CE8F6C /* srs_app_reload.cpp */,
+				3C12327F1AAE81D900CE8F6C /* srs_app_reload.hpp */,
+				3C1232801AAE81D900CE8F6C /* srs_app_rtmp_conn.cpp */,
+				3C1232811AAE81D900CE8F6C /* srs_app_rtmp_conn.hpp */,
+				3C1232821AAE81D900CE8F6C /* srs_app_rtsp.cpp */,
+				3C1232831AAE81D900CE8F6C /* srs_app_rtsp.hpp */,
+				3C1232841AAE81D900CE8F6C /* srs_app_security.cpp */,
+				3C1232851AAE81D900CE8F6C /* srs_app_security.hpp */,
+				3C1232861AAE81D900CE8F6C /* srs_app_server.cpp */,
+				3C1232871AAE81D900CE8F6C /* srs_app_server.hpp */,
+				3C1232881AAE81D900CE8F6C /* srs_app_source.cpp */,
+				3C1232891AAE81D900CE8F6C /* srs_app_source.hpp */,
+				3C12328A1AAE81D900CE8F6C /* srs_app_st_socket.cpp */,
+				3C12328B1AAE81D900CE8F6C /* srs_app_st_socket.hpp */,
+				3C12328C1AAE81D900CE8F6C /* srs_app_st.cpp */,
+				3C12328D1AAE81D900CE8F6C /* srs_app_st.hpp */,
+				3C12328E1AAE81D900CE8F6C /* srs_app_statistic.cpp */,
+				3C12328F1AAE81D900CE8F6C /* srs_app_statistic.hpp */,
+				3C1232901AAE81D900CE8F6C /* srs_app_thread.cpp */,
+				3C1232911AAE81D900CE8F6C /* srs_app_thread.hpp */,
+				3C1232921AAE81D900CE8F6C /* srs_app_utility.cpp */,
+				3C1232931AAE81D900CE8F6C /* srs_app_utility.hpp */,
+			);
+			name = app;
+			sourceTree = "";
+		};
+		3C1232B91AAE825100CE8F6C /* scripts */ = {
+			isa = PBXGroup;
+			children = (
+				3C1232C81AAE833300CE8F6C /* _log.sh */,
+				3C1232C91AAE833300CE8F6C /* build.sh */,
+				3C1232CA1AAE833300CE8F6C /* git.commit.sh */,
+				3C1232CB1AAE833300CE8F6C /* git2unix.sh */,
+				3C1232CC1AAE833300CE8F6C /* install.sh */,
+				3C1232CD1AAE833300CE8F6C /* package.sh */,
+				3C1232CE1AAE833300CE8F6C /* run.sh */,
+				3C1232CF1AAE833300CE8F6C /* srs.test */,
+				3C1232D01AAE833300CE8F6C /* stop.sh */,
+				3C1232D11AAE833300CE8F6C /* test_configure.sh */,
+			);
+			name = scripts;
+			sourceTree = "";
+		};
+		3C1232BA1AAE826F00CE8F6C /* auto */ = {
+			isa = PBXGroup;
+			children = (
+				3C1232BB1AAE827E00CE8F6C /* apps.sh */,
+				3C1232BC1AAE827E00CE8F6C /* auto_headers.sh */,
+				3C1232BD1AAE827E00CE8F6C /* build_ffmpeg.sh */,
+				3C1232BE1AAE827E00CE8F6C /* depends.sh */,
+				3C1232BF1AAE827E00CE8F6C /* generate_header.sh */,
+				3C1232C01AAE827E00CE8F6C /* generate-srs-librtmp-project.sh */,
+				3C1232C11AAE827E00CE8F6C /* generate-srs-librtmp-single.sh */,
+				3C1232C21AAE827E00CE8F6C /* libs.sh */,
+				3C1232C31AAE827E00CE8F6C /* local_ip.sh */,
+				3C1232C41AAE827E00CE8F6C /* modules.sh */,
+				3C1232C51AAE827E00CE8F6C /* options.sh */,
+				3C1232C61AAE827E00CE8F6C /* summary.sh */,
+				3C1232C71AAE827E00CE8F6C /* utest.sh */,
+			);
+			name = auto;
+			sourceTree = "";
+		};
+		3C1232EE1AAEA71C00CE8F6C /* links */ = {
+			isa = PBXGroup;
+			children = (
+				3C1232EC1AAEA70F00CE8F6C /* libhttp_parser.a */,
+				3C1232D21AAEA56B00CE8F6C /* libst.a */,
+				3C1232E81AAEA5D000CE8F6C /* libssl.a */,
+				3C1232E71AAEA5D000CE8F6C /* libcrypto.a */,
+			);
+			name = links;
+			sourceTree = "";
+		};
+		3C1232EF1AAEAC5800CE8F6C /* etc */ = {
+			isa = PBXGroup;
+			children = (
+				3C1232F01AAEAC5D00CE8F6C /* init.d */,
+			);
+			name = etc;
+			sourceTree = "";
+		};
+		3C1232F01AAEAC5D00CE8F6C /* init.d */ = {
+			isa = PBXGroup;
+			children = (
+				3C1232F11AAEAC7000CE8F6C /* srs */,
+				3C1232F21AAEAC7000CE8F6C /* srs-api */,
+				3C1232F31AAEAC7000CE8F6C /* srs-demo */,
+				3C1232F41AAEAC7000CE8F6C /* srs-demo-19350 */,
+			);
+			name = init.d;
+			sourceTree = "";
+		};
+		3C1EE6AF1AB107EE00576EE9 /* conf */ = {
+			isa = PBXGroup;
+			children = (
+				3C1EE6B01AB1080900576EE9 /* bandwidth.conf */,
+				3C1EE6B11AB1080900576EE9 /* console.conf */,
+				3C1EE6B21AB1080900576EE9 /* demo.19350.conf */,
+				3C1EE6B31AB1080900576EE9 /* demo.conf */,
+				3C1EE6B41AB1080900576EE9 /* dvr.path.conf */,
+				3C1EE6B51AB1080900576EE9 /* dvr.segment.conf */,
+				3C1EE6B61AB1080900576EE9 /* dvr.session.conf */,
+				3C1EE6B71AB1080900576EE9 /* edge.conf */,
+				3C1EE6B81AB1080900576EE9 /* edge.token.traverse.conf */,
+				3C1EE6B91AB1080900576EE9 /* ffmpeg.transcode.conf */,
+				3C1EE6BA1AB1080900576EE9 /* forward.master.conf */,
+				3C1EE6BB1AB1080900576EE9 /* forward.slave.conf */,
+				3C1EE6BC1AB1080900576EE9 /* full.conf */,
+				3C1EE6BD1AB1080900576EE9 /* hds.conf */,
+				3C1EE6BE1AB1080900576EE9 /* hls.conf */,
+				3C1EE6BF1AB1080900576EE9 /* http.aac.live.conf */,
+				3C1EE6C01AB1080900576EE9 /* http.flv.live.conf */,
+				3C1EE6C11AB1080900576EE9 /* http.heartbeat.conf */,
+				3C1EE6C21AB1080900576EE9 /* http.hls.conf */,
+				3C1EE6C31AB1080900576EE9 /* http.hooks.callback.conf */,
+				3C1EE6C41AB1080900576EE9 /* http.mp3.live.conf */,
+				3C1EE6C51AB1080900576EE9 /* http.server.conf */,
+				3C1EE6C61AB1080900576EE9 /* http.ts.live.conf */,
+				3C1EE6C71AB1080900576EE9 /* ingest.conf */,
+				3C1EE6C81AB1080900576EE9 /* mac.dev.conf */,
+				3C1EE6C91AB1080900576EE9 /* origin.conf */,
+				3C1EE6CA1AB1080900576EE9 /* push.mpegts.over.udp.conf */,
+				3C1EE6CB1AB1080900576EE9 /* push.rtsp.conf */,
+				3C1EE6CC1AB1080900576EE9 /* ram.hls.conf */,
+				3C1EE6CD1AB1080900576EE9 /* realtime.conf */,
+				3C1EE6CE1AB1080900576EE9 /* rtmp.conf */,
+				3C1EE6CF1AB1080900576EE9 /* security.deny.publish.conf */,
+				3C1EE6D01AB1080900576EE9 /* srs.conf */,
+				3C1EE6D11AB1080900576EE9 /* transcode2hls.audio.only.conf */,
+			);
+			name = conf;
+			sourceTree = "";
+		};
+		3C1EE6D21AB1366500576EE9 /* doc */ = {
+			isa = PBXGroup;
+			children = (
+				3C1EE6D31AB1367D00576EE9 /* AUTHORS.txt */,
+				3C1EE6D41AB1367D00576EE9 /* DONATIONS.txt */,
+				3C1EE6D51AB1367D00576EE9 /* LICENSE */,
+				3C1EE6D61AB1367D00576EE9 /* README.md */,
+			);
+			name = doc;
+			sourceTree = "";
+		};
+		3C36DB541ABD1CA70066CCAF /* libs */ = {
+			isa = PBXGroup;
+			children = (
+				3C36DB551ABD1CB90066CCAF /* srs_lib_bandwidth.cpp */,
+				3C36DB561ABD1CB90066CCAF /* srs_lib_bandwidth.hpp */,
+				3C36DB571ABD1CB90066CCAF /* srs_lib_simple_socket.cpp */,
+				3C36DB581ABD1CB90066CCAF /* srs_lib_simple_socket.hpp */,
+				3C36DB591ABD1CB90066CCAF /* srs_librtmp.cpp */,
+				3C36DB5A1ABD1CB90066CCAF /* srs_librtmp.hpp */,
+			);
+			name = libs;
+			sourceTree = "";
+		};
+		3C663F001AB014B500286D8B /* research */ = {
+			isa = PBXGroup;
+			children = (
+				3C663F021AB0155100286D8B /* srs_aac_raw_publish.c */,
+				3C663F031AB0155100286D8B /* srs_audio_raw_publish.c */,
+				3C663F041AB0155100286D8B /* srs_bandwidth_check.c */,
+				3C663F051AB0155100286D8B /* srs_detect_rtmp.c */,
+				3C663F061AB0155100286D8B /* srs_flv_injecter.c */,
+				3C663F071AB0155100286D8B /* srs_flv_parser.c */,
+				3C663F081AB0155100286D8B /* srs_h264_raw_publish.c */,
+				3C663F091AB0155100286D8B /* srs_ingest_flv.c */,
+				3C663F0A1AB0155100286D8B /* srs_ingest_rtmp.c */,
+				3C663F0B1AB0155100286D8B /* srs_play.c */,
+				3C663F0C1AB0155100286D8B /* srs_publish.c */,
+				3C663F0D1AB0155100286D8B /* srs_rtmp_dump.c */,
+			);
+			name = research;
+			path = srs_xcode;
+			sourceTree = "";
+		};
+		3C689F901AB6AA9100C9CEEE /* st-1.9 */ = {
+			isa = PBXGroup;
+			children = (
+				3C689F991AB6AAC800C9CEEE /* md.S */,
+				3C689F9A1AB6AAC800C9CEEE /* public.h */,
+				3C689F9B1AB6AAC800C9CEEE /* sched.c */,
+				3C689F9C1AB6AAC800C9CEEE /* stk.c */,
+				3C689F9D1AB6AAC800C9CEEE /* sync.c */,
+				3C689F911AB6AAAC00C9CEEE /* common.h */,
+				3C689F921AB6AAAC00C9CEEE /* event.c */,
+				3C689F931AB6AAAC00C9CEEE /* io.c */,
+				3C689F941AB6AAAC00C9CEEE /* key.c */,
+				3C689F951AB6AAAC00C9CEEE /* md.h */,
+			);
+			name = "st-1.9";
+			sourceTree = "";
+		};
+		3CC52DC91ACE4006006FEB01 /* utest */ = {
+			isa = PBXGroup;
+			children = (
+				3CC52DCA1ACE4023006FEB01 /* srs_utest_amf0.cpp */,
+				3CC52DCB1ACE4023006FEB01 /* srs_utest_amf0.hpp */,
+				3CC52DCC1ACE4023006FEB01 /* srs_utest_config.cpp */,
+				3CC52DCD1ACE4023006FEB01 /* srs_utest_config.hpp */,
+				3CC52DCE1ACE4023006FEB01 /* srs_utest_core.cpp */,
+				3CC52DCF1ACE4023006FEB01 /* srs_utest_core.hpp */,
+				3CC52DD01ACE4023006FEB01 /* srs_utest_kernel.cpp */,
+				3CC52DD11ACE4023006FEB01 /* srs_utest_kernel.hpp */,
+				3CC52DD21ACE4023006FEB01 /* srs_utest_protocol.cpp */,
+				3CC52DD31ACE4023006FEB01 /* srs_utest_protocol.hpp */,
+				3CC52DD41ACE4023006FEB01 /* srs_utest_reload.cpp */,
+				3CC52DD51ACE4023006FEB01 /* srs_utest_reload.hpp */,
+				3CC52DD61ACE4023006FEB01 /* srs_utest.cpp */,
+				3CC52DD71ACE4023006FEB01 /* srs_utest.hpp */,
+			);
+			name = utest;
+			sourceTree = "";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		3C1231E41AAE64A400CE8F6C /* srs_xcode */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 3C1231EC1AAE64A400CE8F6C /* Build configuration list for PBXNativeTarget "srs_xcode" */;
+			buildPhases = (
+				3C1231E11AAE64A400CE8F6C /* Sources */,
+				3C1231E21AAE64A400CE8F6C /* Frameworks */,
+				3C1231E31AAE64A400CE8F6C /* CopyFiles */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = srs_xcode;
+			productName = srs_xcode;
+			productReference = 3C1231E51AAE64A400CE8F6C /* srs_xcode */;
+			productType = "com.apple.product-type.tool";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		3C1231DD1AAE64A400CE8F6C /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				LastUpgradeCheck = 0610;
+				ORGANIZATIONNAME = winlin;
+				TargetAttributes = {
+					3C1231E41AAE64A400CE8F6C = {
+						CreatedOnToolsVersion = 6.1.1;
+					};
+				};
+			};
+			buildConfigurationList = 3C1231E01AAE64A400CE8F6C /* Build configuration list for PBXProject "srs_xcode" */;
+			compatibilityVersion = "Xcode 3.2";
+			developmentRegion = English;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+			);
+			mainGroup = 3C1231DC1AAE64A400CE8F6C;
+			productRefGroup = 3C1231E61AAE64A400CE8F6C /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				3C1231E41AAE64A400CE8F6C /* srs_xcode */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXSourcesBuildPhase section */
+		3C1231E11AAE64A400CE8F6C /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				3C1232951AAE81D900CE8F6C /* srs_app_config.cpp in Sources */,
+				3C663F0F1AB0155100286D8B /* srs_aac_raw_publish.c in Sources */,
+				3C689FA01AB6AAC800C9CEEE /* stk.c in Sources */,
+				3CD88B3F1ACA9C58000359E0 /* srs_app_async_call.cpp in Sources */,
+				3C1232961AAE81D900CE8F6C /* srs_app_conn.cpp in Sources */,
+				3C12322A1AAE814D00CE8F6C /* srs_kernel_ts.cpp in Sources */,
+				3C12329E1AAE81D900CE8F6C /* srs_app_hls.cpp in Sources */,
+				3CC52DD91ACE4023006FEB01 /* srs_utest_config.cpp in Sources */,
+				3C663F171AB0155100286D8B /* srs_ingest_rtmp.c in Sources */,
+				3C663F131AB0155100286D8B /* srs_flv_injecter.c in Sources */,
+				3C1232971AAE81D900CE8F6C /* srs_app_dvr.cpp in Sources */,
+				3C1232271AAE814D00CE8F6C /* srs_kernel_log.cpp in Sources */,
+				3C689F961AB6AAAC00C9CEEE /* event.c in Sources */,
+				3C1232A81AAE81D900CE8F6C /* srs_app_log.cpp in Sources */,
+				3C1232A41AAE81D900CE8F6C /* srs_app_ingest.cpp in Sources */,
+				3C1232B41AAE81D900CE8F6C /* srs_app_st.cpp in Sources */,
+				3C1232481AAE81A400CE8F6C /* srs_rtmp_stack.cpp in Sources */,
+				3C1232B01AAE81D900CE8F6C /* srs_app_security.cpp in Sources */,
+				3C12322B1AAE814D00CE8F6C /* srs_kernel_utility.cpp in Sources */,
+				3C12324A1AAE81A400CE8F6C /* srs_rtsp_stack.cpp in Sources */,
+				3C1232A51AAE81D900CE8F6C /* srs_app_json.cpp in Sources */,
+				3C36DB5D1ABD1CB90066CCAF /* srs_librtmp.cpp in Sources */,
+				3C12329F1AAE81D900CE8F6C /* srs_app_http_api.cpp in Sources */,
+				3C1EE6AE1AB1055800576EE9 /* srs_app_hds.cpp in Sources */,
+				3C663F101AB0155100286D8B /* srs_audio_raw_publish.c in Sources */,
+				3C663F111AB0155100286D8B /* srs_bandwidth_check.c in Sources */,
+				3CC52DDE1ACE4023006FEB01 /* srs_utest.cpp in Sources */,
+				3C1232A11AAE81D900CE8F6C /* srs_app_http_conn.cpp in Sources */,
+				3C1232AC1AAE81D900CE8F6C /* srs_app_refer.cpp in Sources */,
+				3C1232991AAE81D900CE8F6C /* srs_app_empty.cpp in Sources */,
+				3CC52DDA1ACE4023006FEB01 /* srs_utest_core.cpp in Sources */,
+				3C36DB5C1ABD1CB90066CCAF /* srs_lib_simple_socket.cpp in Sources */,
+				3C1232201AAE814D00CE8F6C /* srs_kernel_aac.cpp in Sources */,
+				3C1232941AAE81D900CE8F6C /* srs_app_bandwidth.cpp in Sources */,
+				3C1232221AAE814D00CE8F6C /* srs_kernel_codec.cpp in Sources */,
+				3C1232B71AAE81D900CE8F6C /* srs_app_utility.cpp in Sources */,
+				3C1232AB1AAE81D900CE8F6C /* srs_app_recv_thread.cpp in Sources */,
+				3CC52DDC1ACE4023006FEB01 /* srs_utest_protocol.cpp in Sources */,
+				3C663F151AB0155100286D8B /* srs_h264_raw_publish.c in Sources */,
+				3C1231F61AAE652D00CE8F6C /* srs_core_autofree.cpp in Sources */,
+				3C1EE6D71AB1367D00576EE9 /* README.md in Sources */,
+				3C1232411AAE81A400CE8F6C /* srs_raw_avc.cpp in Sources */,
+				3C1232491AAE81A400CE8F6C /* srs_rtmp_utility.cpp in Sources */,
+				3C663F191AB0155100286D8B /* srs_publish.c in Sources */,
+				3C1232A01AAE81D900CE8F6C /* srs_app_http_client.cpp in Sources */,
+				3C689F981AB6AAAC00C9CEEE /* key.c in Sources */,
+				3C12329B1AAE81D900CE8F6C /* srs_app_ffmpeg.cpp in Sources */,
+				3C1232421AAE81A400CE8F6C /* srs_rtmp_amf0.cpp in Sources */,
+				3C1232AA1AAE81D900CE8F6C /* srs_app_pithy_print.cpp in Sources */,
+				3C12329C1AAE81D900CE8F6C /* srs_app_forward.cpp in Sources */,
+				3C1232251AAE814D00CE8F6C /* srs_kernel_file.cpp in Sources */,
+				3C1232AD1AAE81D900CE8F6C /* srs_app_reload.cpp in Sources */,
+				3C1231F81AAE652D00CE8F6C /* srs_core.cpp in Sources */,
+				3C1232A21AAE81D900CE8F6C /* srs_app_http_hooks.cpp in Sources */,
+				3C663F121AB0155100286D8B /* srs_detect_rtmp.c in Sources */,
+				3C1232B11AAE81D900CE8F6C /* srs_app_server.cpp in Sources */,
+				3C689F9F1AB6AAC800C9CEEE /* sched.c in Sources */,
+				3C1232B31AAE81D900CE8F6C /* srs_app_st_socket.cpp in Sources */,
+				3C1232061AAE812C00CE8F6C /* srs_main_server.cpp in Sources */,
+				3C1232281AAE814D00CE8F6C /* srs_kernel_mp3.cpp in Sources */,
+				3C1232B21AAE81D900CE8F6C /* srs_app_source.cpp in Sources */,
+				3C1231F71AAE652D00CE8F6C /* srs_core_performance.cpp in Sources */,
+				3CC52DD81ACE4023006FEB01 /* srs_utest_amf0.cpp in Sources */,
+				3C1232981AAE81D900CE8F6C /* srs_app_edge.cpp in Sources */,
+				3CC52DDB1ACE4023006FEB01 /* srs_utest_kernel.cpp in Sources */,
+				3C689F9E1AB6AAC800C9CEEE /* md.S in Sources */,
+				3C1232461AAE81A400CE8F6C /* srs_rtmp_msg_array.cpp in Sources */,
+				3C1232A71AAE81D900CE8F6C /* srs_app_listener.cpp in Sources */,
+				3C1232261AAE814D00CE8F6C /* srs_kernel_flv.cpp in Sources */,
+				3C663F1A1AB0155100286D8B /* srs_rtmp_dump.c in Sources */,
+				3CE6CD311AE4AFB800706E07 /* srs_main_ingest_hls.cpp in Sources */,
+				3C28EDDF1AF5C43F00A3AEAC /* srs_app_caster_flv.cpp in Sources */,
+				3C1232241AAE814D00CE8F6C /* srs_kernel_error.cpp in Sources */,
+				3C1232441AAE81A400CE8F6C /* srs_rtmp_handshake.cpp in Sources */,
+				3C1232291AAE814D00CE8F6C /* srs_kernel_stream.cpp in Sources */,
+				3C663F181AB0155100286D8B /* srs_play.c in Sources */,
+				3C689F971AB6AAAC00C9CEEE /* io.c in Sources */,
+				3C1232B61AAE81D900CE8F6C /* srs_app_thread.cpp in Sources */,
+				3C1232A91AAE81D900CE8F6C /* srs_app_mpegts_udp.cpp in Sources */,
+				3C1232AE1AAE81D900CE8F6C /* srs_app_rtmp_conn.cpp in Sources */,
+				3C1232B51AAE81D900CE8F6C /* srs_app_statistic.cpp in Sources */,
+				3C663F161AB0155100286D8B /* srs_ingest_flv.c in Sources */,
+				3C663F141AB0155100286D8B /* srs_flv_parser.c in Sources */,
+				3C1232451AAE81A400CE8F6C /* srs_rtmp_io.cpp in Sources */,
+				3C1232431AAE81A400CE8F6C /* srs_rtmp_buffer.cpp in Sources */,
+				3C1232211AAE814D00CE8F6C /* srs_kernel_buffer.cpp in Sources */,
+				3C1232471AAE81A400CE8F6C /* srs_rtmp_sdk.cpp in Sources */,
+				3C36DB5B1ABD1CB90066CCAF /* srs_lib_bandwidth.cpp in Sources */,
+				3C1232A61AAE81D900CE8F6C /* srs_app_kbps.cpp in Sources */,
+				3C12329D1AAE81D900CE8F6C /* srs_app_heartbeat.cpp in Sources */,
+				3C1232231AAE814D00CE8F6C /* srs_kernel_consts.cpp in Sources */,
+				3C1232AF1AAE81D900CE8F6C /* srs_app_rtsp.cpp in Sources */,
+				3CC52DDD1ACE4023006FEB01 /* srs_utest_reload.cpp in Sources */,
+				3C689FA11AB6AAC800C9CEEE /* sync.c in Sources */,
+				3C1232A31AAE81D900CE8F6C /* srs_app_http.cpp in Sources */,
+				3C12329A1AAE81D900CE8F6C /* srs_app_encoder.cpp in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+		3C1231EA1AAE64A400CE8F6C /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				COPY_PHASE_STRIP = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_SYMBOLS_PRIVATE_EXTERN = NO;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.10;
+				MTL_ENABLE_DEBUG_INFO = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = macosx;
+			};
+			name = Debug;
+		};
+		3C1231EB1AAE64A400CE8F6C /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				COPY_PHASE_STRIP = YES;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.10;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = macosx;
+			};
+			name = Release;
+		};
+		3C1231ED1AAE64A400CE8F6C /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "c++98";
+				CLANG_CXX_LIBRARY = "libstdc++";
+				LIBRARY_SEARCH_PATHS = (
+					"../../objs/**",
+					"/Users/winlin/Desktop/git/simple-rtmp-server/trunk/objs/http-parser-2.1",
+				);
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				"USER_HEADER_SEARCH_PATHS[arch=*]" = "../../src/** ../../objs ../../objs/st ../../objs/hp ../../objs/openssl";
+			};
+			name = Debug;
+		};
+		3C1231EE1AAE64A400CE8F6C /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "c++98";
+				CLANG_CXX_LIBRARY = "libstdc++";
+				LIBRARY_SEARCH_PATHS = (
+					"../../objs/**",
+					"/Users/winlin/Desktop/git/simple-rtmp-server/trunk/objs/http-parser-2.1",
+				);
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		3C1231E01AAE64A400CE8F6C /* Build configuration list for PBXProject "srs_xcode" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				3C1231EA1AAE64A400CE8F6C /* Debug */,
+				3C1231EB1AAE64A400CE8F6C /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		3C1231EC1AAE64A400CE8F6C /* Build configuration list for PBXNativeTarget "srs_xcode" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				3C1231ED1AAE64A400CE8F6C /* Debug */,
+				3C1231EE1AAE64A400CE8F6C /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 3C1231DD1AAE64A400CE8F6C /* Project object */;
+}
diff --git a/trunk/ide/srs_xcode/srs_xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/trunk/ide/srs_xcode/srs_xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000000..60847b6bd4
--- /dev/null
+++ b/trunk/ide/srs_xcode/srs_xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+   
+   
+
diff --git a/trunk/ide/srs_xcode/srs_xcode/readme.txt b/trunk/ide/srs_xcode/srs_xcode/readme.txt
new file mode 100644
index 0000000000..94cb20b467
--- /dev/null
+++ b/trunk/ide/srs_xcode/srs_xcode/readme.txt
@@ -0,0 +1 @@
+the xcode project for osx.
diff --git a/trunk/modules/readme.txt b/trunk/modules/readme.txt
new file mode 100644
index 0000000000..dd2f9b4e54
--- /dev/null
+++ b/trunk/modules/readme.txt
@@ -0,0 +1,12 @@
+SRS模块规则:
+1. 一个模块一个目录
+2. 目录下放一个config文件
+3. 所有的configure中的变量模块中可以使用
+
+模块中需要定义变量,例如:
+1. SRS_MODULE_NAME:模块名称,用来做Makefile的phony以及执行binary文件名。
+2. SRS_MODULE_MAIN:模块的main函数所在的cpp文件,在src/main目录。
+3. SRS_MODULE_APP:模块在src/app目录的源文件列表。
+4. SRS_MODULE_DEFINES: 模块编译时的额外宏定义。
+
+winlin, 2015.3
diff --git a/trunk/research/api-server/server.py b/trunk/research/api-server/server.py
index b7d145a7a0..fac4b50e84 100755
--- a/trunk/research/api-server/server.py
+++ b/trunk/research/api-server/server.py
@@ -2,7 +2,7 @@
 '''
 The MIT License (MIT)
 
-Copyright (c) 2013-2014 winlin
+Copyright (c) 2013-2015 SRS(simple-rtmp-server)
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of
 this software and associated documentation files (the "Software"), to deal in
@@ -36,7 +36,7 @@
 exec("sys.setdefaultencoding('utf-8')")
 assert sys.getdefaultencoding().lower() == "utf-8"
 
-import os, json, time, datetime, cherrypy, threading
+import os, json, time, datetime, cherrypy, threading, urllib2
 
 # simple log functions.
 def trace(msg):
@@ -87,6 +87,7 @@ def GET(self):
                   "action": "on_connect",
                   "client_id": 1985,
                   "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live",
+                  "tcUrl": "rtmp://video.test.com/live?key=d2fa801d08e3f90ed1e1670e6e52651a",
                   "pageUrl": "http://www.test.com/live.html"
               }
     on_close:
@@ -239,6 +240,195 @@ def __on_unpublish(self, req):
 
         return code
 
+'''
+handle the dvrs requests: dvr stream.
+'''
+class RESTDvrs(object):
+    exposed = True
+
+    def GET(self):
+        enable_crossdomain()
+
+        dvrs = {}
+        return json.dumps(dvrs)
+
+    '''
+    for SRS hook: on_dvr
+    on_dvr:
+        when srs reap a dvr file, call the hook,
+        the request in the POST data string is a object encode by json:
+              {
+                  "action": "on_dvr",
+                  "client_id": 1985,
+                  "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live",
+                  "stream": "livestream",
+                  "cwd": "/usr/local/srs",
+                  "file": "./objs/nginx/html/live/livestream.1420254068776.flv"
+              }
+    if valid, the hook must return HTTP code 200(Stauts OK) and response
+    an int value specifies the error code(0 corresponding to success):
+          0
+    '''
+    def POST(self):
+        enable_crossdomain()
+
+        # return the error code in str
+        code = Error.success
+
+        req = cherrypy.request.body.read()
+        trace("post to dvrs, req=%s"%(req))
+        try:
+            json_req = json.loads(req)
+        except Exception, ex:
+            code = Error.system_parse_json
+            trace("parse the request to json failed, req=%s, ex=%s, code=%s"%(req, ex, code))
+            return str(code)
+
+        action = json_req["action"]
+        if action == "on_dvr":
+            code = self.__on_dvr(json_req)
+        else:
+            trace("invalid request action: %s"%(json_req["action"]))
+            code = Error.request_invalid_action
+
+        return str(code)
+
+    def OPTIONS(self, *args, **kwargs):
+        enable_crossdomain()
+
+    def __on_dvr(self, req):
+        code = Error.success
+
+        trace("srs %s: client id=%s, ip=%s, vhost=%s, app=%s, stream=%s, cwd=%s, file=%s"%(
+            req["action"], req["client_id"], req["ip"], req["vhost"], req["app"], req["stream"],
+            req["cwd"], req["file"]
+        ))
+
+        # TODO: process the on_dvr event
+
+        return code
+
+
+'''
+handle the hls proxy requests: hls stream.
+'''
+class RESTProxy(object):
+    exposed = True
+
+    '''
+    for SRS hook: on_hls_notify
+    on_hls_notify:
+        when srs reap a ts file of hls, call this hook,
+        used to push file to cdn network, by get the ts file from cdn network.
+        so we use HTTP GET and use the variable following:
+              [app], replace with the app.
+              [stream], replace with the stream.
+              [ts_url], replace with the ts url.
+        ignore any return data of server.
+    '''
+    def GET(self, *args, **kwargs):
+        enable_crossdomain()
+        
+        url = "http://" + "/".join(args);
+        print "start to proxy url: %s"%url
+        
+        f = None
+        try:
+            f = urllib2.urlopen(url)
+            f.read()
+        except:
+            print "error proxy url: %s"%url
+        finally:
+            if f: f.close()
+            print "completed proxy url: %s"%url
+        return url
+
+'''
+handle the hls requests: hls stream.
+'''
+class RESTHls(object):
+    exposed = True
+
+    '''
+    for SRS hook: on_hls_notify
+    on_hls_notify:
+        when srs reap a ts file of hls, call this hook,
+        used to push file to cdn network, by get the ts file from cdn network.
+        so we use HTTP GET and use the variable following:
+              [app], replace with the app.
+              [stream], replace with the stream.
+              [ts_url], replace with the ts url.
+        ignore any return data of server.
+    '''
+    def GET(self, *args, **kwargs):
+        enable_crossdomain()
+
+        hls = {
+            "args": args,
+            "kwargs": kwargs
+        }
+        return json.dumps(hls)
+
+    '''
+    for SRS hook: on_hls
+    on_hls:
+        when srs reap a dvr file, call the hook,
+        the request in the POST data string is a object encode by json:
+              {
+                  "action": "on_dvr",
+                  "client_id": 1985,
+                  "ip": "192.168.1.10", 
+                  "vhost": "video.test.com", 
+                  "app": "live",
+                  "stream": "livestream",
+                  "duration": 9.68, // in seconds
+                  "cwd": "/usr/local/srs",
+                  "file": "./objs/nginx/html/live/livestream.1420254068776-100.ts",
+                  "seq_no": 100
+              }
+    if valid, the hook must return HTTP code 200(Stauts OK) and response
+    an int value specifies the error code(0 corresponding to success):
+          0
+    '''
+    def POST(self):
+        enable_crossdomain()
+
+        # return the error code in str
+        code = Error.success
+
+        req = cherrypy.request.body.read()
+        trace("post to hls, req=%s"%(req))
+        try:
+            json_req = json.loads(req)
+        except Exception, ex:
+            code = Error.system_parse_json
+            trace("parse the request to json failed, req=%s, ex=%s, code=%s"%(req, ex, code))
+            return str(code)
+
+        action = json_req["action"]
+        if action == "on_hls":
+            code = self.__on_hls(json_req)
+        else:
+            trace("invalid request action: %s"%(json_req["action"]))
+            code = Error.request_invalid_action
+
+        return str(code)
+
+    def OPTIONS(self, *args, **kwargs):
+        enable_crossdomain()
+
+    def __on_hls(self, req):
+        code = Error.success
+
+        trace("srs %s: client id=%s, ip=%s, vhost=%s, app=%s, stream=%s, duration=%s, cwd=%s, file=%s, seq_no=%s"%(
+            req["action"], req["client_id"], req["ip"], req["vhost"], req["app"], req["stream"], req["duration"],
+            req["cwd"], req["file"], req["seq_no"]
+        ))
+
+        # TODO: process the on_hls event
+
+        return code
+
 '''
 handle the sessions requests: client play/stop stream
 '''
@@ -1038,6 +1228,9 @@ def __init__(self):
         self.clients = RESTClients()
         self.streams = RESTStreams()
         self.sessions = RESTSessions()
+        self.dvrs = RESTDvrs()
+        self.hls = RESTHls()
+        self.proxy = RESTProxy()
         self.chats = RESTChats()
         self.servers = RESTServers()
         self.nodes = RESTNodes()
@@ -1047,6 +1240,7 @@ def GET(self):
             "clients": "for srs http callback, to handle the clients requests: connect/disconnect vhost/app.", 
             "streams": "for srs http callback, to handle the streams requests: publish/unpublish stream.",
             "sessions": "for srs http callback, to handle the sessions requests: client play/stop stream",
+            "dvrs": "for srs http callback, to handle the dvr requests: dvr stream.",
             "chats": "for srs demo meeting, the chat streams, public chat room.",
             "nodes": {
                 "summary": "for srs cdn node",
@@ -1082,13 +1276,13 @@ def OPTIONS(self, *args, **kwargs):
 
 # check the user options
 if len(sys.argv) <= 1:
-    print "SRS api callback server, Copyright (c) 2013-2014 winlin"
+    print "SRS api callback server, Copyright (c) 2013-2015 SRS(simple-rtmp-server)"
     print "Usage: python %s "%(sys.argv[0])
     print "    port: the port to listen at."
     print "For example:"
     print "    python %s 8085"%(sys.argv[0])
     print ""
-    print "See also: https://github.com/winlinvip/simple-rtmp-server"
+    print "See also: https://github.com/simple-rtmp-server/srs"
     sys.exit(1)
 
 # parse port from user options.
diff --git a/trunk/research/api-server/static-dir/index.html b/trunk/research/api-server/static-dir/index.html
index 7607685aa1..4a3d3aac87 100755
--- a/trunk/research/api-server/static-dir/index.html
+++ b/trunk/research/api-server/static-dir/index.html
@@ -44,7 +44,7 @@
 
 
diff --git a/trunk/research/arm/jmp.cpp b/trunk/research/arm/jmp.cpp
index d9469405dc..c19a87ffdf 100644
--- a/trunk/research/arm/jmp.cpp
+++ b/trunk/research/arm/jmp.cpp
@@ -1,48 +1,48 @@
 /*
-# see: https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SrsLinuxArm
- arm-linux-gnueabi-g++ -o jmp jmp.cpp -static
- arm-linux-gnueabi-strip jmp
+# see: https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SrsLinuxArm
+    g++ -g -O0 -o jmp jmp.cpp
+    arm-linux-gnueabi-g++ -o jmp jmp.cpp -static
+    arm-linux-gnueabi-strip jmp
 */
 #include 
 #include 
+#include 
 #include 
 
+bool func1_ok = false, func2_ok = false;
 jmp_buf env_func1, env_func2;
 
 int sum = 0;
 
 void func1() {
     int ret = setjmp(env_func1);
-    printf("setjmp func1 ret=%d\n", ret);
+    printf("[func1] setjmp ret=%d, sum++=%d\n", ret, sum++);
+    func1_ok = true;
     
-    if (sum <= 0) {
-        return;
-    }
-    
-    if (sum++ > 1000) {
-        return;
-    }
+    sleep(1);
     
     // jmp to func2
-    longjmp(env_func2, 3);
+    if (func2_ok) {
+        longjmp(env_func2, 1);
+    }
 }
 
 void func2() {
     int ret = setjmp(env_func2);
-    printf("setjmp func2 ret=%d\n", ret);
+    printf("[func2] setjmp ret=%d, sum++=%d\n", ret, sum++);
+    func2_ok = true;
     
-    if (sum <= 0) {
-        return;
-    }
+    sleep(1);
     
     // jmp to func1
-    longjmp(env_func1, 2);
+    if (func1_ok) {
+        longjmp(env_func1, 2);
+    }
 }
 
 int main(int argc, char** argv) {
     printf("hello, setjmp/longjmp!\n");
     func1();
-    sum++;
     func2();
     printf("jmp finished, sum=%d\n", sum);
     return 0;
diff --git a/trunk/research/arm/jmp_2flow.cpp b/trunk/research/arm/jmp_2flow.cpp
new file mode 100644
index 0000000000..b86ed72dd3
--- /dev/null
+++ b/trunk/research/arm/jmp_2flow.cpp
@@ -0,0 +1,65 @@
+/*
+# http://blog.csdn.net/win_lin/article/details/40948277
+# for all supports setjmp and longjmp:
+    g++ -g -O0 -o jmp_2flow jmp_2flow.cpp
+*/
+#include 
+#include 
+#include 
+
+jmp_buf context_thread_0;
+jmp_buf context_thread_1;
+
+void thread0_functions()
+{
+    int ret = setjmp(context_thread_0);
+    // when ret is 0, create thread,
+    // when ret is not 0, longjmp to this thread.
+    if (ret == 0) {
+        return;
+    }
+    
+    int age = 10000;
+    const char* name = "winlin";
+    printf("[thread0] age=%d, name=%s\n", age, name);
+    if (!setjmp(context_thread_0)) {
+        printf("[thread0] switch to thread1\n");
+        longjmp(context_thread_1, 1);
+    }
+    
+    // crash, for the stack is modified by thread1.
+    // name = 0x2b67004009c8 
+    printf("[thread0] terminated, age=%d, name=%s\n", age, name);
+    exit(0);
+}
+
+void thread1_functions()
+{
+    int ret = setjmp(context_thread_1);
+    // when ret is 0, create thread,
+    // when ret is not 0, longjmp to this thread.
+    if (ret == 0) {
+        return;
+    }
+    
+    int age = 11111;
+    printf("[thread1] age=%d\n", age);
+    if (!setjmp(context_thread_1)) {
+        printf("[thread1] switch to thread0\n");
+        longjmp(context_thread_0, 1);
+    }
+    
+    printf("[thread1] terminated, age=%d\n", age);
+    exit(0);
+}
+
+int main(int argc, char** argv) 
+{
+    thread0_functions();
+    thread1_functions();
+    
+    // kickstart
+    longjmp(context_thread_0, 1);
+    
+    return 0;
+}
diff --git a/trunk/research/arm/jmp_flow.cpp b/trunk/research/arm/jmp_flow.cpp
new file mode 100644
index 0000000000..e9a3fdbbc5
--- /dev/null
+++ b/trunk/research/arm/jmp_flow.cpp
@@ -0,0 +1,28 @@
+/*
+# http://blog.csdn.net/win_lin/article/details/40948277
+# for all supports setjmp and longjmp:
+    g++ -g -O0 -o jmp_flow jmp_flow.cpp
+*/
+#include 
+#include 
+#include 
+
+jmp_buf context_level_0;
+
+void func_level_0()
+{
+    const char* level_0_0 = "stack variables for func_level_0";
+    int ret = setjmp(context_level_0);
+    printf("func_level_0 ret=%d\n", ret);
+    if (ret != 0) {
+        printf("call by longjmp.\n");
+        exit(0);
+    }
+}
+
+int main(int argc, char** argv) 
+{
+    func_level_0();
+    longjmp(context_level_0, 1);
+    return 0;
+}
diff --git a/trunk/research/arm/jmp_sp.cpp b/trunk/research/arm/jmp_sp.cpp
index 1fb0be89de..4fa4bc83a7 100644
--- a/trunk/research/arm/jmp_sp.cpp
+++ b/trunk/research/arm/jmp_sp.cpp
@@ -1,39 +1,156 @@
 /*
-# see: https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SrsLinuxArm
- arm-linux-gnueabi-g++ -g -o jmp_sp jmp_sp.cpp -static
- arm-linux-gnueabi-strip jmp_sp
+# see: https://github.com/simple-rtmp-server/srs/issues/190
+# see: https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SrsLinuxArm
+    g++ -g -O0 -o jmp_sp jmp_sp.cpp
+    arm-linux-gnueabi-g++ -g -o jmp_sp jmp_sp.cpp -static
+    arm-linux-gnueabi-strip jmp_sp
 */
 #include 
 #include 
 #include 
 
-int main(int argc, char** argv) {
+jmp_buf context;
+
+void func1()
+{
 #if defined(__amd64__) || defined(__x86_64__)
-    printf("x86_64 sizeof(long int)=%d, sizeof(long)=%d, sizeof(int)=%d\n", (int)sizeof(long int), (int)sizeof(long), (int)sizeof(int));
-#else
-    printf("arm sizeof(long int)=%d, sizeof(long)=%d, sizeof(int)=%d\n", (int)sizeof(long int), (int)sizeof(long), (int)sizeof(int));
-#endif
+    register long int rsp0 asm("rsp");
     
-    jmp_buf env;
+    int ret = setjmp(context);
+    printf("setjmp func1 ret=%d, rsp=%#lx\n", ret, rsp0);
+    // enter by longjmp
+    if (ret != 0) {
+        printf("call by longjmp.\n");
+        exit(0);
+    }
+#endif
+}
+
+void func0()
+{
+    /**
+    the definition of jmp_buf:
+        typedef struct __jmp_buf_tag jmp_buf[1];
+        struct __jmp_buf_tag {
+             __jmp_buf __jmpbuf;
+             int __mask_was_saved;
+             __sigset_t __saved_mask;
+        };
+    */
+#if defined(__amd64__) || defined(__x86_64__)
+    // http://ftp.gnu.org/gnu/glibc/glibc-2.12.2.tar.xz
+    // http://ftp.gnu.org/gnu/glibc/glibc-2.12.1.tar.gz
+    /*
+     * Starting with glibc 2.4, JB_SP definitions are not public anymore.
+     * They, however, can still be found in glibc source tree in
+     * architecture-specific "jmpbuf-offsets.h" files.
+     * Most importantly, the content of jmp_buf is mangled by setjmp to make
+     * it completely opaque (the mangling can be disabled by setting the
+     * LD_POINTER_GUARD environment variable before application execution).
+     * Therefore we will use built-in _st_md_cxt_save/_st_md_cxt_restore
+     * functions as a setjmp/longjmp replacement wherever they are available
+     * unless USE_LIBC_SETJMP is defined.
+     */
+    // for glibc 2.4+, it's not possible to get and set the sp in jmp_buf
+    /**
+    for example, the following is show the jmp_buf when setjmp:
+        (gdb) x /64xb context[0].__jmpbuf
+        0x600ca0 :	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
+        0x600ca8 :	0xf8	0xc1	0x71	0xe5	0xa8	0x88	0xb4	0x15
+        0x600cb0 :	0xa0	0x05	0x40	0x00	0x00	0x00	0x00	0x00
+        0x600cb8 :	0x90	0xe4	0xff	0xff	0xff	0x7f	0x00	0x00
+        0x600cc0 :	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
+        0x600cc8 :	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
+        0x600cd0 :	0xf8	0xc1	0x51	0xe5	0xa8	0x88	0xb4	0x15
+        0x600cd8 :	0xf8	0xc1	0xd9	0x2f	0xd7	0x77	0x4b	0xea
+        (gdb) p /x $sp
+        $4 = 0x7fffffffe380
+    we cannot finger the sp out.
+    where the glibc is 2.12.
+    */
+    register long int rsp0 asm("rsp");
     
-    int ret = setjmp(env);
-    printf("setjmp func1 ret=%d\n", ret);
+    int ret = setjmp(context);
+    printf("setjmp func0 ret=%d, rsp=%#lx\n", ret, rsp0);
     
-#if defined(__amd64__) || defined(__x86_64__)
-    // typedef lint64_t __jmp_buf[8];
     printf("after setjmp: ");
     for (int i = 0; i < 8; i++) {
-        printf("env[%d]=%#x, ", i, (int)env[0].__jmpbuf[i]);
+        printf("env[%d]=%#x, ", i, (int)context[0].__jmpbuf[i]);
     }
     printf("\n");
-#else
-    // typedef int32_t __jmp_buf[64] __attribute__((__aligned__ (8)));
+    
+    func1();
+#endif
+
+#if defined(__arm__)
+    /**
+        /usr/arm-linux-gnueabi/include/bits/setjmp.h
+        #ifndef _ASM
+        The exact set of registers saved may depend on the particular core
+           in use, as some coprocessor registers may need to be saved.  The C
+           Library ABI requires that the buffer be 8-byte aligned, and
+           recommends that the buffer contain 64 words.  The first 28 words
+           are occupied by v1-v6, sl, fp, sp, pc, d8-d15, and fpscr.  (Note
+           that d8-15 require 17 words, due to the use of fstmx.)
+        typedef int __jmp_buf[64] __attribute__((__aligned__ (8)));
+        
+        the layout of setjmp for arm:
+            0-5: v1-v6 
+            6: sl
+            7: fp
+            8: sp
+            9: pc
+            10-26: d8-d15 17words
+            27: fpscr
+    */
+    /**
+    For example, on raspberry-pi, armv6 cpu:
+        (gdb) x /64 context[0].__jmpbuf
+            v1, 0:  0x00	0x00	0x00	0x00	
+            v2, 1:  0x00	0x00	0x00	0x00
+            v3, 2:  0x2c	0x84	0x00	0x00	
+            v4, 3:  0x00	0x00	0x00	0x00
+            v5, 4:  0x00	0x00	0x00	0x00	
+            v6, 5:  0x00	0x00	0x00	0x00
+            sl, 6:  0x00	0xf0	0xff	0xb6	
+            fp, 7:  0x9c	0xfb	0xff	0xbe
+            sp, 8:  0x88	0xfb	0xff	0xbe	
+            pc, 9:  0x08	0x85	0x00	0x00
+        (gdb) p /x $sp
+        $5 = 0xbefffb88
+        (gdb) p /x $pc
+        $4 = 0x850c
+    */
+    int ret = setjmp(context);
+    printf("setjmp func1 ret=%d\n", ret);
+    
     printf("after setjmp: ");
     for (int i = 0; i < 64; i++) {
-        printf("env[%d]=%#x, ", i, (int)env[0].__jmpbuf[i]);
+        printf("env[%d]=%#x, ", i, (int)context[0].__jmpbuf[i]);
     }
-    printf("\n");
+    
+    printf("func0 terminated\n");
+#endif
+}
+
+int main(int argc, char** argv) 
+{
+#if defined(__amd64__) || defined(__x86_64__)
+    printf("x86_64 sizeof(long int)=%d, sizeof(long)=%d, "
+        "sizeof(int)=%d, __WORDSIZE=%d, __GLIBC__=%d, __GLIBC_MINOR__=%d\n", 
+        (int)sizeof(long int), (int)sizeof(long), (int)sizeof(int), 
+        (int)__WORDSIZE, (int)__GLIBC__, (int)__GLIBC_MINOR__);
+#else
+    printf("arm sizeof(long int)=%d, sizeof(long)=%d, "
+        "sizeof(int)=%d, __GLIBC__=%d,__GLIBC_MINOR__=%d\n", 
+        (int)sizeof(long int), (int)sizeof(long), (int)sizeof(int), 
+        (int)__GLIBC__, (int)__GLIBC_MINOR__);
 #endif
 
+    func0();
+    longjmp(context, 1);
+    
+    printf("terminated\n");
+
     return 0;
 }
diff --git a/trunk/research/arm/pipe_fds.cpp b/trunk/research/arm/pipe_fds.cpp
new file mode 100644
index 0000000000..d1c024f51b
--- /dev/null
+++ b/trunk/research/arm/pipe_fds.cpp
@@ -0,0 +1,48 @@
+/**
+g++ pipe_fds.cpp -g -O0 -o pipe_fds
+
+About the limits:
+[winlin@dev6 srs]$ ulimit -n
+    1024
+[winlin@dev6 srs]$ sudo lsof -p 21182
+    pipe_fds 21182 winlin    0u   CHR  136,4      0t0       7 /dev/pts/4
+    pipe_fds 21182 winlin    1u   CHR  136,4      0t0       7 /dev/pts/4
+    pipe_fds 21182 winlin    2u   CHR  136,4      0t0       7 /dev/pts/4
+    pipe_fds 21182 winlin    3r  FIFO    0,8      0t0  464543 pipe
+    pipe_fds 21182 winlin 1021r  FIFO    0,8      0t0  465052 pipe
+    pipe_fds 21182 winlin 1022w  FIFO    0,8      0t0  465052 pipe
+So, all fds can be open is <1024, that is, can open 1023 files.
+The 0, 1, 2 is opened file, so can open 1023-3=1020files, 
+Where 1020/2=512, so we can open 512 pipes.
+*/
+#include 
+#include 
+#include 
+#include 
+#include 
+
+int main(int argc, char** argv)
+{
+    if (argc <= 1) {
+        printf("Usage: %s \n"
+            "   nb_pipes the pipes to open.\n"
+            "For example:\n"
+            "   %s 1024\n", argv[0], argv[0]);
+        exit(-1);
+    }
+    
+    int nb_pipes = ::atoi(argv[1]);
+    for (int i = 0; i < nb_pipes; i++) {
+        int fds[2];
+        if (pipe(fds) < 0) {
+            printf("failed to create pipe. i=%d, errno=%d(%s)\n", 
+                i, errno, strerror(errno));
+            break;
+        }
+    }
+    
+    printf("Press CTRL+C to quit, use bellow command to show the fds opened:\n");
+    printf("    sudo lsof -p %d\n", getpid());
+    sleep(-1);
+    return 0;
+}
diff --git a/trunk/research/arm/test.cpp b/trunk/research/arm/test.cpp
index 1bad17d1cf..26cef0a199 100644
--- a/trunk/research/arm/test.cpp
+++ b/trunk/research/arm/test.cpp
@@ -1,5 +1,5 @@
 /*
-# see: https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SrsLinuxArm
+# see: https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SrsLinuxArm
  arm-linux-gnueabi-g++ -o test test.cpp -static
  arm-linux-gnueabi-strip test
 */
diff --git a/trunk/research/code-statistic/cs.py b/trunk/research/code-statistic/cs.py
index a5a7a4928a..6c8aa18d1e 100755
--- a/trunk/research/code-statistic/cs.py
+++ b/trunk/research/code-statistic/cs.py
@@ -2,7 +2,7 @@
 '''
 The MIT License (MIT)
 
-Copyright (c) 2013-2014 winlin
+Copyright (c) 2013-2015 SRS(simple-rtmp-server)
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of
 this software and associated documentation files (the "Software"), to deal in
diff --git a/trunk/research/code-statistic/csr.py b/trunk/research/code-statistic/csr.py
index 47d200bda7..e264b09e7b 100755
--- a/trunk/research/code-statistic/csr.py
+++ b/trunk/research/code-statistic/csr.py
@@ -2,7 +2,7 @@
 '''
 The MIT License (MIT)
 
-Copyright (c) 2013-2014 winlin
+Copyright (c) 2013-2015 SRS(simple-rtmp-server)
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of
 this software and associated documentation files (the "Software"), to deal in
diff --git a/trunk/research/ffempty/ffempty.cc b/trunk/research/ffempty/ffempty.cc
index 8df9c7d1b2..3634f7507c 100644
--- a/trunk/research/ffempty/ffempty.cc
+++ b/trunk/research/ffempty/ffempty.cc
@@ -1,7 +1,7 @@
 /*
 The MIT License (MIT)
 
-Copyright (c) 2013-2014 winlin
+Copyright (c) 2013-2015 SRS(simple-rtmp-server)
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of
 this software and associated documentation files (the "Software"), to deal in
diff --git a/trunk/research/gperftools/cpu-profiler/cpu_profiler.cc b/trunk/research/gperftools/cpu-profiler/cpu_profiler.cc
index 32068208a6..e5b9e89fa9 100644
--- a/trunk/research/gperftools/cpu-profiler/cpu_profiler.cc
+++ b/trunk/research/gperftools/cpu-profiler/cpu_profiler.cc
@@ -1,7 +1,7 @@
 /*
 The MIT License (MIT)
 
-Copyright (c) 2013-2014 winlin
+Copyright (c) 2013-2015 SRS(simple-rtmp-server)
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of
 this software and associated documentation files (the "Software"), to deal in
diff --git a/trunk/research/gperftools/heap-checker/heap_checker.cc b/trunk/research/gperftools/heap-checker/heap_checker.cc
index 8415029f1e..da2dec750e 100644
--- a/trunk/research/gperftools/heap-checker/heap_checker.cc
+++ b/trunk/research/gperftools/heap-checker/heap_checker.cc
@@ -1,7 +1,7 @@
 /*
 The MIT License (MIT)
 
-Copyright (c) 2013-2014 winlin
+Copyright (c) 2013-2015 SRS(simple-rtmp-server)
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of
 this software and associated documentation files (the "Software"), to deal in
diff --git a/trunk/research/gperftools/heap-profiler/heap_profiler.cc b/trunk/research/gperftools/heap-profiler/heap_profiler.cc
index d13aa6a5dd..314c41d83c 100644
--- a/trunk/research/gperftools/heap-profiler/heap_profiler.cc
+++ b/trunk/research/gperftools/heap-profiler/heap_profiler.cc
@@ -1,7 +1,7 @@
 /*
 The MIT License (MIT)
 
-Copyright (c) 2013-2014 winlin
+Copyright (c) 2013-2015 SRS(simple-rtmp-server)
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of
 this software and associated documentation files (the "Software"), to deal in
diff --git a/trunk/research/hls/check_hls_backup.sh b/trunk/research/hls/check_hls_backup.sh
new file mode 100755
index 0000000000..45f0ef10ff
--- /dev/null
+++ b/trunk/research/hls/check_hls_backup.sh
@@ -0,0 +1,99 @@
+if [[ $# -lt 2 ]]; then
+    echo "Usage: $0   [keep_ts]"
+    echo "      keep_ts to keep the download ts, default is off"
+    echo "For example:"
+    echo "      $0 http://192.168.1.137:1980/hls/live/stone/live.m3u8 http://192.168.1.137:1984/hls/live/stone/live.m3u8"
+    echo "      $0 http://192.168.1.137:1980/hls/live/livestream/live.m3u8 http://192.168.1.137:1984/hls/live/livestream/live.m3u8"
+    echo "      $0 http://ossrs.net:1984/hls/live/livestream/live.m3u8 http://ossrs.net:1996/hls/live/livestream/live.m3u8"
+    exit 1
+fi
+ 
+hls0=$1
+hls1=$2
+keep_ts=NO
+if [[ $# -gt 2 ]]; then
+    keep_ts=YES
+fi
+#echo "check hls backup of $hls0 vs $hls1, keep_ts=$keep_ts"
+
+hls0_tss=`curl $hls0 2>/dev/null |grep "\.ts"`
+hls1_tss=`curl $hls1 2>/dev/null |grep "\.ts"`
+
+hls0_prefix=`dirname $hls0`
+hls1_prefix=`dirname $hls1`
+work_dir="./hbt_temp_`date +%s`"
+
+md5_tool="md5"
+`md5sum --version >/dev/null 2>&1` && md5_tool="md5sum"
+#echo "use md5 tool: $md5_tool"
+
+CHECKED=NO
+OK=YES
+for ts in $hls0_tss; do
+    match=NO
+    for ts1 in $hls1_tss; do
+        if [[ $ts == $ts1 ]]; then
+            #echo "check ts $ts"
+            match=YES
+            break
+        fi
+    done
+    #echo "check ts $ts, match=$match"
+    
+    if [ $match = NO ]; then
+        echo "skip $ts"
+        continue
+    fi
+    
+    ts0_uri=$hls0_prefix/$ts
+    ts1_uri=$hls1_prefix/$ts
+    ts0_tmp=$work_dir/hls0/`basename $ts`
+    ts1_tmp=$work_dir/hls1/`basename $ts`
+    #echo "start check $ts0_uri($ts0_tmp) vs $ts1_uri($ts1_tmp)"
+    
+    mkdir -p `dirname $ts0_tmp` &&
+    curl $ts0_uri >$ts0_tmp 2>/dev/null &&
+    ret=$?; if [[ $ret -ne 0 ]]; then echo "download $ts0_uri to $ts0_tmp failed. ret=$ret"; exit $ret; fi
+    
+    mkdir -p `dirname $ts1_tmp` &&
+    curl $ts1_uri >$ts1_tmp 2>/dev/null &&
+    ret=$?; if [[ $ret -ne 0 ]]; then echo "download $ts1_uri to $ts1_tmp failed. ret=$ret"; exit $ret; fi
+    
+    if [[ $md5_tool == "md5" ]]; then
+        ts0_cs=`$md5_tool $ts0_tmp|awk '{print $4}'`
+    else
+        ts0_cs=`$md5_tool $ts0_tmp|awk '{print $1}'`
+    fi
+    #echo "hls0: md5sum($ts0_tmp)=$ts0_cs"
+    
+    if [[ $md5_tool == "md5" ]]; then
+        ts1_cs=`$md5_tool $ts1_tmp|awk '{print $4}'`
+    else
+        ts1_cs=`$md5_tool $ts1_tmp|awk '{print $1}'`
+    fi
+    #echo "hls1: md5sum($ts1_tmp)=$ts1_cs"
+    
+    if [[ $ts0_cs != $ts1_cs ]]; then
+        echo "$ts0_uri($ts0_cs) not equals to $ts1_uri($ts1_cs)"
+        OK=NO
+    fi
+    CHECKED=YES
+done
+
+if [ $keep_ts = NO ]; then
+    #echo "clenaup work dir $work_dir"
+    rm -rf $work_dir
+else
+    echo "keep work dir $work_dir"
+fi
+
+#echo "====================================================="
+if [[ $OK = YES && $CHECKED = YES ]]; then
+    echo "OK"
+    exit 0
+else
+    echo "FAILED"
+    exit 1
+fi
+
+exit 0
\ No newline at end of file
diff --git a/trunk/research/hls/ts_info.cc b/trunk/research/hls/ts_info.cc
index 99ff200b5d..96728c46dd 100644
--- a/trunk/research/hls/ts_info.cc
+++ b/trunk/research/hls/ts_info.cc
@@ -1,7 +1,7 @@
 /*
 The MIT License (MIT)
 
-Copyright (c) 2013-2014 winlin
+Copyright (c) 2013-2015 SRS(simple-rtmp-server)
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of
 this software and associated documentation files (the "Software"), to deal in
@@ -1539,7 +1539,8 @@ int TSPayloadPES::demux(TSContext* ctx, TSPacket* pkt, u_int8_t* start, u_int8_t
         // for (i = 0; i < PES_packet_length; i++) {
         //         PES_packet_data_byte
         // }
-    } else if (stream_id != PES_padding_stream) {
+        // TODO: FIXME: implements it.
+    } else if (stream_id == PES_padding_stream) {
         // for (i = 0; i < PES_packet_length; i++) {
         //         padding_byte
         // }
@@ -2255,9 +2256,9 @@ int main(int argc, char** argv)
             int64_t pcr = msg->pcr;
             static int64_t last_pcr_dts = 0;
             trace("demuxer+report id=%d, type=%s, size=%d, dts=%d, pts=%d, cts=%d, pcr=%d, dts-pcr=%d, ref=%d, unit=%d, dts(diff-pcr)=%d",
-                ctx.ts_packet_count, (msg->type == TSPidTypeVideo)? "video":"audio", 
-                msg->parsed_packet_size, dts, pts, pts - dts, pcr, pcr? dts - pcr : 0,
-                msg->nal_ref_idc, msg->nal_unit_type, pcr? dts - last_pcr_dts: 0);
+                (int)ctx.ts_packet_count, (msg->type == TSPidTypeVideo)? "video":"audio", 
+                (int)msg->parsed_packet_size, (int)dts, (int)pts, (int)(pts - dts), (int)pcr, (int)(pcr? dts - pcr : 0),
+                (int)msg->nal_ref_idc, (int)msg->nal_unit_type, (int)(pcr? dts - last_pcr_dts: 0));
             if (pcr > 0) {
                 last_pcr_dts = dts;
             }
diff --git a/trunk/research/librtmp/Makefile b/trunk/research/librtmp/Makefile
index 7e0dc35bcf..2eed9f4a99 100755
--- a/trunk/research/librtmp/Makefile
+++ b/trunk/research/librtmp/Makefile
@@ -6,7 +6,9 @@ else
     ST_ALL      = objs/srs_flv_parser \
                 objs/srs_flv_injecter objs/srs_publish objs/srs_play \
                 objs/srs_ingest_flv objs/srs_ingest_rtmp objs/srs_detect_rtmp \
-                objs/srs_bandwidth_check
+                objs/srs_bandwidth_check objs/srs_h264_raw_publish \
+                objs/srs_audio_raw_publish objs/srs_aac_raw_publish \
+                objs/srs_rtmp_dump
 endif
 
 .PHONY: default clean help ssl nossl
@@ -23,16 +25,20 @@ help:
 	@echo "     srs_flv_parser          parse flv file, print detail info."
 	@echo "     srs_flv_injecter        inject keyframes information to metadata."
 	@echo "     srs_publish             publish program using srs-librtmp"
+	@echo "     srs_h264_raw_publish    publish raw h.264 stream to SSR by srs-librtmp"
+	@echo "     srs_audio_raw_publish   publish raw audio stream to SSR by srs-librtmp"
+	@echo "     srs_aac_raw_publish     publish raw aac stream to SSR by srs-librtmp"
 	@echo "     srs_play                play program using srs-librtmp"
 	@echo "     srs_ingest_flv          ingest flv file and publish to RTMP server."
 	@echo "     srs_ingest_rtmp         ingest RTMP and publish to RTMP server."
 	@echo "     srs_detect_rtmp         detect RTMP stream info."
 	@echo "     srs_bandwidth_check     bandwidth check/test tool."
+	@echo "     srs_rtmp_dump           dump rtmp stream to flv file."
 	@echo "Remark: about simple/complex handshake, see: http://blog.csdn.net/win_lin/article/details/13006803"
 	@echo "Remark: srs Makefile will auto invoke this by --with/without-ssl, "
 	@echo "     that is, if user specified ssl(by --with-ssl), srs will make this by 'make ssl'"
 	@echo "     that is, if user not use ssl(by --without-ssl), use 'make nossl'"
-	@echo "     see: https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_v1_Build"
+	@echo "     see: https://github.com/simple-rtmp-server/srs/wiki/v1_CN_v1_Build"
 	@echo "Remark: before make this sample, user must make the srs, with/without ssl"
     
 clean:
@@ -45,8 +51,8 @@ SRS_LIBRTMP_I = $(SRS_OBJS)/include/srs_librtmp.h
 SRS_LIBRTMP_L = $(SRS_OBJS)/lib/srs_librtmp.a
 # openssl for complex handshake, built by srs.
 SRS_LIBSSL_L =
-# the research public headers
-SRS_RESEARCH_DEPS = Makefile srs_research_public.h
+# public depends, the Makefile or public headers.
+SRS_RESEARCH_DEPS = Makefile
 
 # for x86/x64 platform
 ifeq ($(GCC), gcc)
@@ -81,6 +87,15 @@ objs/srs_flv_injecter: srs_flv_injecter.c $(SRS_RESEARCH_DEPS) $(SRS_LIBRTMP_I)
 objs/srs_publish: srs_publish.c $(SRS_RESEARCH_DEPS) $(SRS_LIBRTMP_I) $(SRS_LIBRTMP_L) $(SRS_LIBSSL_L)
 	$(GCC) srs_publish.c $(SRS_LIBRTMP_L) $(SRS_LIBSSL_L) $(EXTRA_CXX_FLAG) -o objs/srs_publish
 
+objs/srs_h264_raw_publish: srs_h264_raw_publish.c $(SRS_RESEARCH_DEPS) $(SRS_LIBRTMP_I) $(SRS_LIBRTMP_L) $(SRS_LIBSSL_L)
+	$(GCC) srs_h264_raw_publish.c $(SRS_LIBRTMP_L) $(SRS_LIBSSL_L) $(EXTRA_CXX_FLAG) -o objs/srs_h264_raw_publish
+
+objs/srs_audio_raw_publish: srs_audio_raw_publish.c $(SRS_RESEARCH_DEPS) $(SRS_LIBRTMP_I) $(SRS_LIBRTMP_L) $(SRS_LIBSSL_L)
+	$(GCC) srs_audio_raw_publish.c $(SRS_LIBRTMP_L) $(SRS_LIBSSL_L) $(EXTRA_CXX_FLAG) -o objs/srs_audio_raw_publish
+
+objs/srs_aac_raw_publish: srs_aac_raw_publish.c $(SRS_RESEARCH_DEPS) $(SRS_LIBRTMP_I) $(SRS_LIBRTMP_L) $(SRS_LIBSSL_L)
+	$(GCC) srs_aac_raw_publish.c $(SRS_LIBRTMP_L) $(SRS_LIBSSL_L) $(EXTRA_CXX_FLAG) -o objs/srs_aac_raw_publish
+
 objs/srs_play: srs_play.c $(SRS_RESEARCH_DEPS) $(SRS_LIBRTMP_I) $(SRS_LIBRTMP_L) $(SRS_LIBSSL_L)
 	$(GCC) srs_play.c $(SRS_LIBRTMP_L) $(SRS_LIBSSL_L) $(EXTRA_CXX_FLAG) -o objs/srs_play
 
@@ -95,3 +110,6 @@ objs/srs_detect_rtmp: srs_detect_rtmp.c $(SRS_RESEARCH_DEPS) $(SRS_LIBRTMP_I) $(
 
 objs/srs_bandwidth_check: srs_bandwidth_check.c $(SRS_RESEARCH_DEPS) $(SRS_LIBRTMP_I) $(SRS_LIBRTMP_L) $(SRS_LIBSSL_L)
 	$(GCC) srs_bandwidth_check.c $(SRS_LIBRTMP_L) $(SRS_LIBSSL_L) $(EXTRA_CXX_FLAG) -o objs/srs_bandwidth_check
+
+objs/srs_rtmp_dump: srs_rtmp_dump.c $(SRS_RESEARCH_DEPS) $(SRS_LIBRTMP_I) $(SRS_LIBRTMP_L) $(SRS_LIBSSL_L)
+	$(GCC) srs_rtmp_dump.c $(SRS_LIBRTMP_L) $(SRS_LIBSSL_L) $(EXTRA_CXX_FLAG) -o objs/srs_rtmp_dump
diff --git a/trunk/research/librtmp/srs_aac_raw_publish.c b/trunk/research/librtmp/srs_aac_raw_publish.c
new file mode 100644
index 0000000000..649d4c91a3
--- /dev/null
+++ b/trunk/research/librtmp/srs_aac_raw_publish.c
@@ -0,0 +1,193 @@
+/*
+The MIT License (MIT)
+
+Copyright (c) 2013-2015 SRS(simple-rtmp-server)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+/**
+gcc srs_aac_raw_publish.c ../../objs/lib/srs_librtmp.a -g -O0 -lstdc++ -o srs_aac_raw_publish
+*/
+
+#include 
+#include 
+#include 
+
+// for open audio raw file.
+#include 
+#include 
+#include 
+       
+#include "../../objs/include/srs_librtmp.h"
+
+// https://github.com/simple-rtmp-server/srs/issues/212#issuecomment-64145910
+int read_audio_frame(char* data, int size, char** pp, char** frame, int* frame_size) 
+{
+    char* p = *pp;
+    
+    // @remark, for this demo, to publish aac raw file to SRS,
+    // we search the adts frame from the buffer which cached the aac data.
+    // please get aac adts raw data from device, it always a encoded frame.
+    if (!srs_aac_is_adts(p, size - (p - data))) {
+        srs_human_trace("aac adts raw data invalid.");
+        return -1;
+    }
+    
+    // @see srs_audio_write_raw_frame
+    // each frame prefixed aac adts header, '1111 1111 1111'B, that is 0xFFF., 
+    // for instance, frame = FF F1 5C 80 13 A0 FC 00 D0 33 83 E8 5B
+    *frame = p;
+    // skip some data. 
+    // @remark, user donot need to do this.
+    p += srs_aac_adts_frame_size(p, size - (p - data));
+    
+    *pp = p;
+    *frame_size = p - *frame;
+    if (*frame_size <= 0) {
+        srs_human_trace("aac adts raw data invalid.");
+        return -1;
+    }
+    
+    return 0;
+}
+
+int main(int argc, char** argv)
+{
+    printf("publish raw audio as rtmp stream to server like FMLE/FFMPEG/Encoder\n");
+    printf("SRS(simple-rtmp-server) client librtmp library.\n");
+    printf("version: %d.%d.%d\n", srs_version_major(), srs_version_minor(), srs_version_revision());
+    
+    if (argc <= 2) {
+        printf("Usage: %s  \n", argv[0]);
+        printf("     audio_raw_file: the audio raw steam file.\n");
+        printf("     rtmp_publish_url: the rtmp publish url.\n");
+        printf("For example:\n");
+        printf("     %s ./audio.raw.aac rtmp://127.0.0.1:1935/live/livestream\n", argv[0]);
+        printf("Where the file: http://winlinvip.github.io/srs.release/3rdparty/audio.raw.aac\n");
+        printf("See: https://github.com/simple-rtmp-server/srs/issues/212\n");
+        exit(-1);
+    }
+    
+    const char* raw_file = argv[1];
+    const char* rtmp_url = argv[2];
+    srs_human_trace("raw_file=%s, rtmp_url=%s", raw_file, rtmp_url);
+    
+    // open file
+    int raw_fd = open(raw_file, O_RDONLY);
+    if (raw_fd < 0) {
+        srs_human_trace("open audio raw file %s failed.", raw_file);
+        goto rtmp_destroy;
+    }
+    
+    off_t file_size = lseek(raw_fd, 0, SEEK_END);
+    if (file_size <= 0) {
+        srs_human_trace("audio raw file %s empty.", raw_file);
+        goto rtmp_destroy;
+    }
+    srs_human_trace("read entirely audio raw file, size=%dKB", (int)(file_size / 1024));
+    
+    char* audio_raw = (char*)malloc(file_size);
+    if (!audio_raw) {
+        srs_human_trace("alloc raw buffer failed for file %s.", raw_file);
+        goto rtmp_destroy;
+    }
+    
+    lseek(raw_fd, 0, SEEK_SET);
+    ssize_t nb_read = 0;
+    if ((nb_read = read(raw_fd, audio_raw, file_size)) != file_size) {
+        srs_human_trace("buffer %s failed, expect=%dKB, actual=%dKB.", 
+            raw_file, (int)(file_size / 1024), (int)(nb_read / 1024));
+        goto rtmp_destroy;
+    }
+    
+    // connect rtmp context
+    srs_rtmp_t rtmp = srs_rtmp_create(rtmp_url);
+    
+    if (srs_rtmp_handshake(rtmp) != 0) {
+        srs_human_trace("simple handshake failed.");
+        goto rtmp_destroy;
+    }
+    srs_human_trace("simple handshake success");
+    
+    if (srs_rtmp_connect_app(rtmp) != 0) {
+        srs_human_trace("connect vhost/app failed.");
+        goto rtmp_destroy;
+    }
+    srs_human_trace("connect vhost/app success");
+    
+    if (srs_rtmp_publish_stream(rtmp) != 0) {
+        srs_human_trace("publish stream failed.");
+        goto rtmp_destroy;
+    }
+    srs_human_trace("publish stream success");
+    
+    u_int32_t timestamp = 0;
+    u_int32_t time_delta = 45;
+    // @remark, to decode the file.
+    char* p = audio_raw;
+    for (;p < audio_raw + file_size;) {
+        // @remark, read a frame from file buffer.
+        char* data = NULL;
+        int size = 0;
+        if (read_audio_frame(audio_raw, file_size, &p, &data, &size) < 0) {
+            srs_human_trace("read a frame from file buffer failed.");
+            goto rtmp_destroy;
+        }
+        
+        // 0 = Linear PCM, platform endian
+        // 1 = ADPCM
+        // 2 = MP3
+        // 7 = G.711 A-law logarithmic PCM
+        // 8 = G.711 mu-law logarithmic PCM
+        // 10 = AAC
+        // 11 = Speex
+        char sound_format = 10;
+        // 2 = 22 kHz
+        char sound_rate = 2;
+        // 1 = 16-bit samples
+        char sound_size = 1;
+        // 1 = Stereo sound
+        char sound_type = 1;
+        
+        timestamp += time_delta;
+        
+        int ret = 0;
+        if ((ret = srs_audio_write_raw_frame(rtmp, 
+            sound_format, sound_rate, sound_size, sound_type,
+            data, size, timestamp)) != 0
+        ) {
+            srs_human_trace("send audio raw data failed. ret=%d", ret);
+            goto rtmp_destroy;
+        }
+        
+        srs_human_trace("sent packet: type=%s, time=%d, size=%d, codec=%d, rate=%d, sample=%d, channel=%d", 
+            srs_human_flv_tag_type2string(SRS_RTMP_TYPE_AUDIO), timestamp, size, sound_format, sound_rate, sound_size,
+            sound_type);
+        
+        // @remark, when use encode device, it not need to sleep.
+        usleep(1000 * time_delta);
+    }
+    
+rtmp_destroy:
+    srs_rtmp_destroy(rtmp);
+    close(raw_fd);
+    free(audio_raw);
+    
+    return 0;
+}
+
diff --git a/trunk/research/librtmp/srs_audio_raw_publish.c b/trunk/research/librtmp/srs_audio_raw_publish.c
new file mode 100644
index 0000000000..8e4296dfe6
--- /dev/null
+++ b/trunk/research/librtmp/srs_audio_raw_publish.c
@@ -0,0 +1,190 @@
+/*
+The MIT License (MIT)
+
+Copyright (c) 2013-2015 SRS(simple-rtmp-server)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+/**
+gcc srs_audio_raw_publish.c ../../objs/lib/srs_librtmp.a -g -O0 -lstdc++ -o srs_audio_raw_publish
+*/
+
+#include 
+#include 
+#include 
+
+// for open audio raw file.
+#include 
+#include 
+#include 
+       
+#include "../../objs/include/srs_librtmp.h"
+
+// https://github.com/simple-rtmp-server/srs/issues/212#issuecomment-63648892
+// allspace:
+//      Take this file as an example: https://github.com/allspace/files/blob/master/srs.pcm
+//      It's captured using SDK callback method. I have filtered out h264 video, so it's audio only now.
+//      For every frame, it's a 8 bytes vendor specific header, following 160 bytes audio frame. 
+//      The header part can be ignored.
+int read_audio_frame(char* audio_raw, int file_size, char** pp, char** pdata, int* psize) 
+{
+    char* p = *pp;
+    
+    if (file_size - (p - audio_raw) < 168) {
+        srs_human_trace("audio must be 160+8 bytes. left %d bytes.", 
+            (int)(file_size - (p - audio_raw)));
+        return - 1;
+    }
+    
+    // ignore 8bytes vendor specific header.
+    p += 8;
+    
+    // 160 bytes audio frame
+    *pdata = p;
+    *psize = 160;
+    
+    // next frame.
+    *pp = p + *psize;
+    
+    return 0;
+}
+
+int main(int argc, char** argv)
+{
+    printf("publish raw audio as rtmp stream to server like FMLE/FFMPEG/Encoder\n");
+    printf("SRS(simple-rtmp-server) client librtmp library.\n");
+    printf("version: %d.%d.%d\n", srs_version_major(), srs_version_minor(), srs_version_revision());
+    
+    if (argc <= 2) {
+        printf("Usage: %s  \n", argv[0]);
+        printf("     audio_raw_file: the audio raw steam file.\n");
+        printf("     rtmp_publish_url: the rtmp publish url.\n");
+        printf("For example:\n");
+        printf("     %s ./audio.raw.pcm rtmp://127.0.0.1:1935/live/livestream\n", argv[0]);
+        printf("Where the file: http://winlinvip.github.io/srs.release/3rdparty/audio.raw.pcm\n");
+        printf("See: https://github.com/simple-rtmp-server/srs/issues/212\n");
+        exit(-1);
+    }
+    
+    const char* raw_file = argv[1];
+    const char* rtmp_url = argv[2];
+    srs_human_trace("raw_file=%s, rtmp_url=%s", raw_file, rtmp_url);
+    
+    // open file
+    int raw_fd = open(raw_file, O_RDONLY);
+    if (raw_fd < 0) {
+        srs_human_trace("open audio raw file %s failed.", raw_file);
+        goto rtmp_destroy;
+    }
+    
+    off_t file_size = lseek(raw_fd, 0, SEEK_END);
+    if (file_size <= 0) {
+        srs_human_trace("audio raw file %s empty.", raw_file);
+        goto rtmp_destroy;
+    }
+    srs_human_trace("read entirely audio raw file, size=%dKB", (int)(file_size / 1024));
+    
+    char* audio_raw = (char*)malloc(file_size);
+    if (!audio_raw) {
+        srs_human_trace("alloc raw buffer failed for file %s.", raw_file);
+        goto rtmp_destroy;
+    }
+    
+    lseek(raw_fd, 0, SEEK_SET);
+    ssize_t nb_read = 0;
+    if ((nb_read = read(raw_fd, audio_raw, file_size)) != file_size) {
+        srs_human_trace("buffer %s failed, expect=%dKB, actual=%dKB.", 
+            raw_file, (int)(file_size / 1024), (int)(nb_read / 1024));
+        goto rtmp_destroy;
+    }
+    
+    // connect rtmp context
+    srs_rtmp_t rtmp = srs_rtmp_create(rtmp_url);
+    
+    if (srs_rtmp_handshake(rtmp) != 0) {
+        srs_human_trace("simple handshake failed.");
+        goto rtmp_destroy;
+    }
+    srs_human_trace("simple handshake success");
+    
+    if (srs_rtmp_connect_app(rtmp) != 0) {
+        srs_human_trace("connect vhost/app failed.");
+        goto rtmp_destroy;
+    }
+    srs_human_trace("connect vhost/app success");
+    
+    if (srs_rtmp_publish_stream(rtmp) != 0) {
+        srs_human_trace("publish stream failed.");
+        goto rtmp_destroy;
+    }
+    srs_human_trace("publish stream success");
+    
+    u_int32_t timestamp = 0;
+    u_int32_t time_delta = 17;
+    // @remark, to decode the file.
+    char* p = audio_raw;
+    for (;p < audio_raw + file_size;) {
+        // @remark, read a frame from file buffer.
+        char* data = NULL;
+        int size = 0;
+        if (read_audio_frame(audio_raw, file_size, &p, &data, &size) < 0) {
+            srs_human_trace("read a frame from file buffer failed.");
+            goto rtmp_destroy;
+        }
+        
+        // 0 = Linear PCM, platform endian
+        // 1 = ADPCM
+        // 2 = MP3
+        // 7 = G.711 A-law logarithmic PCM
+        // 8 = G.711 mu-law logarithmic PCM
+        // 10 = AAC
+        // 11 = Speex
+        char sound_format = 1;
+        // 3 = 44 kHz
+        char sound_rate = 3;
+        // 1 = 16-bit samples
+        char sound_size = 1;
+        // 1 = Stereo sound
+        char sound_type = 1;
+        
+        timestamp += time_delta;
+        
+        if (srs_audio_write_raw_frame(rtmp, 
+            sound_format, sound_rate, sound_size, sound_type,
+            data, size, timestamp) != 0
+        ) {
+            srs_human_trace("send audio raw data failed.");
+            goto rtmp_destroy;
+        }
+        
+        srs_human_trace("sent packet: type=%s, time=%d, size=%d, codec=%d, rate=%d, sample=%d, channel=%d", 
+            srs_human_flv_tag_type2string(SRS_RTMP_TYPE_AUDIO), timestamp, size, sound_format, sound_rate, sound_size,
+            sound_type);
+        
+        // @remark, when use encode device, it not need to sleep.
+        usleep(1000 * time_delta);
+    }
+    
+rtmp_destroy:
+    srs_rtmp_destroy(rtmp);
+    close(raw_fd);
+    free(audio_raw);
+    
+    return 0;
+}
+
diff --git a/trunk/research/librtmp/srs_bandwidth_check.c b/trunk/research/librtmp/srs_bandwidth_check.c
index 014d59d4b3..11be798fd7 100644
--- a/trunk/research/librtmp/srs_bandwidth_check.c
+++ b/trunk/research/librtmp/srs_bandwidth_check.c
@@ -1,7 +1,7 @@
 /*
 The MIT License (MIT)
 
-Copyright (c) 2013-2014 winlin
+Copyright (c) 2013-2015 SRS(simple-rtmp-server)
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of
 this software and associated documentation files (the "Software"), to deal in
@@ -35,9 +35,10 @@ int main(int argc, char** argv)
     srs_rtmp_t rtmp;
     
     // packet data
-    int type, size;
-    u_int32_t timestamp = 0;
+    int size;
+    char type;
     char* data;
+    u_int32_t timestamp;
     
     // srs debug info.
     char srs_server_ip[128];
@@ -64,6 +65,10 @@ int main(int argc, char** argv)
     srs_authors[0] = 0;
     srs_version[0] = 0;
     
+    printf("RTMP bandwidth check/test with server.\n");
+    printf("srs(simple-rtmp-server) client librtmp library.\n");
+    printf("version: %d.%d.%d\n", srs_version_major(), srs_version_minor(), srs_version_revision());
+    
     if (argc <= 1) {
         printf("RTMP bandwidth check/test with server.\n"
             "Usage: %s \n"
@@ -73,45 +78,41 @@ int main(int argc, char** argv)
             "   %s rtmp://127.0.0.1:1935/app?key=35c9b402c12a7246868752e2878f7e0e,vhost=bandcheck.srs.com>/dev/null\n"
             "@remark, output text to stdout, while json to stderr.\n",
             argv[0], argv[0], argv[0]);
-        ret = 1;
-        exit(ret);
-        return ret;
+        exit(-1);
     }
     
     rtmp = srs_rtmp_create2(argv[1]);
     
-    printf("RTMP bandwidth check/test with server.\n");
-    printf("srs(simple-rtmp-server) client librtmp library.\n");
-    printf("version: %d.%d.%d\n", srs_version_major(), srs_version_minor(), srs_version_revision());
-    printf("bandwidth check/test url: %s\n", argv[1]);
+    srs_human_trace("bandwidth check/test url: %s", argv[1]);
     
-    if ((ret = srs_simple_handshake(rtmp)) != 0) {
-        printf("simple handshake failed.\n");
+    if ((ret = srs_rtmp_handshake(rtmp)) != 0) {
+        srs_human_trace("simple handshake failed.");
         goto rtmp_destroy;
     }
-    printf("simple handshake success\n");
+    srs_human_trace("simple handshake success");
     
-    if ((ret = srs_connect_app2(rtmp, 
-        srs_server_ip, srs_server, srs_primary, srs_authors, srs_version, &srs_id, &srs_pid)) != 0) {
-        printf("connect vhost/app failed.\n");
+    if ((ret = srs_rtmp_connect_app2(rtmp, 
+        srs_server_ip, srs_server, srs_primary, srs_authors, srs_version, 
+        &srs_id, &srs_pid)) != 0) {
+        srs_human_trace("connect vhost/app failed.");
         goto rtmp_destroy;
     }
-    printf("connect vhost/app success\n");
+    srs_human_trace("connect vhost/app success");
     
-    if ((ret = srs_bandwidth_check(rtmp, 
+    if ((ret = srs_rtmp_bandwidth_check(rtmp, 
         &start_time, &end_time, &play_kbps, &publish_kbps,
         &play_bytes, &publish_bytes, &play_duration, &publish_duration)) != 0
     ) {
-        printf("bandwidth check/test failed.\n");
+        srs_human_trace("bandwidth check/test failed.");
         goto rtmp_destroy;
     }
-    printf("bandwidth check/test success\n");
+    srs_human_trace("bandwidth check/test success");
     
-    printf("\n%s, %s, %s\n"
+    srs_human_trace("\n%s, %s, %s\n"
         "%s, %s, srs_pid=%d, srs_id=%d\n"
         "duration: %dms(%d+%d)\n"
         "play: %dkbps\n"
-        "publish: %dkbps\n\n", 
+        "publish: %dkbps", 
         (char*)srs_server, (char*)srs_primary, (char*)srs_authors,
         (char*)srs_server_ip, (char*)srs_version, srs_pid, srs_id,
         (int)(end_time - start_time), play_duration, publish_duration,
@@ -121,8 +122,6 @@ int main(int argc, char** argv)
 rtmp_destroy:
     srs_rtmp_destroy(rtmp);
     
-    printf("terminate with ret=%d\n\n", ret);
-    
     fprintf(stderr, "{\"code\":%d,"
         "\"srs_server\":\"%s\", "
         "\"srs_primary\":\"%s\", "
@@ -133,6 +132,7 @@ int main(int argc, char** argv)
         "\"srs_id\":%d, "
         "\"duration\":%d, "
         "\"play_duration\":%d, "
+        "\"publish_duration\":%d,"
         "\"play_kbps\":%d, "
         "\"publish_kbps\":%d"
         "}",
@@ -141,5 +141,9 @@ int main(int argc, char** argv)
         (char*)srs_server_ip, (char*)srs_version, srs_pid, srs_id,
         (int)(end_time - start_time), play_duration, publish_duration,
         play_kbps, publish_kbps);
+    
+    srs_human_trace(" ");
+    srs_human_trace("completed");
+    
     return ret;
 }
diff --git a/trunk/research/librtmp/srs_detect_rtmp.c b/trunk/research/librtmp/srs_detect_rtmp.c
index 0ebcb82d39..191248dace 100644
--- a/trunk/research/librtmp/srs_detect_rtmp.c
+++ b/trunk/research/librtmp/srs_detect_rtmp.c
@@ -1,7 +1,7 @@
 /*
 The MIT License (MIT)
 
-Copyright (c) 2013-2014 winlin
+Copyright (c) 2013-2015 SRS(simple-rtmp-server)
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of
 this software and associated documentation files (the "Software"), to deal in
@@ -35,7 +35,7 @@ int main(int argc, char** argv)
     srs_rtmp_t rtmp;
     
     // time
-    int64_t time_startup = srs_get_time_ms();
+    int64_t time_startup = srs_utils_time_ms();
     int64_t time_dns_resolve = 0;
     int64_t time_socket_connect = 0;
     int64_t time_play_stream = 0;
@@ -49,18 +49,23 @@ int main(int argc, char** argv)
     int64_t bytes_nrecv = 0;
     
     // packet data
-    int type, size;
-    u_int32_t basetime = 0;
-    u_int32_t timestamp = 0;
+    int size;
+    char type;
     char* data;
+    u_int32_t timestamp;
+    u_int32_t basetime = 0;
     
     // user options
     const char* rtmp_url = NULL;
     int duration = 0;
     int timeout = 0;
+
+    printf("detect rtmp stream\n");
+    printf("srs(simple-rtmp-server) client librtmp library.\n");
+    printf("version: %d.%d.%d\n", srs_version_major(), srs_version_minor(), srs_version_revision());
     
     if (argc <= 3) {
-        printf("detect stream on RTMP server\n"
+        printf("detect stream on RTMP server, print result to stderr.\n"
             "Usage: %s   \n"
             "   rtmp_url     RTMP stream url to play\n"
             "   duration     how long to play, in seconds, stream time.\n"
@@ -68,73 +73,67 @@ int main(int argc, char** argv)
             "For example:\n"
             "   %s rtmp://127.0.0.1:1935/live/livestream 3 10\n",
             argv[0], argv[0]);
-        ret = 1;
-        exit(ret);
-        return ret;
+        exit(-1);
     }
     
     rtmp_url = argv[1];
     duration = atoi(argv[2]);
     timeout = atoi(argv[3]);
     
-    printf("detect rtmp stream\n");
-    printf("srs(simple-rtmp-server) client librtmp library.\n");
-    printf("version: %d.%d.%d\n", srs_version_major(), srs_version_minor(), srs_version_revision());
-    printf("rtmp url: %s\n", rtmp_url);
-    printf("duration: %ds, timeout:%ds\n", duration, timeout);
+    srs_human_trace("rtmp url: %s", rtmp_url);
+    srs_human_trace("duration: %ds, timeout:%ds", duration, timeout);
     
     if (duration <= 0 || timeout <= 0) {
-        ret = 1;
-        fprintf(stderr, "duration and timeout must be positive. ret=%d\n", ret);
-        exit(ret);
-        return ret;
+        srs_human_trace("duration and timeout must be positive.");
+        exit(-2);
     }
     
     rtmp = srs_rtmp_create(rtmp_url);
     
-    if ((ret = __srs_dns_resolve(rtmp)) != 0) {
-        fprintf(stderr, "dns resolve failed. ret=%d\n", ret);
+    if ((ret = srs_rtmp_dns_resolve(rtmp)) != 0) {
+        srs_human_trace("dns resolve failed. ret=%d", ret);
         goto rtmp_destroy;
     }
-    printf("dns resolve success\n");
-    time_dns_resolve = srs_get_time_ms();
+    srs_human_trace("dns resolve success");
+    time_dns_resolve = srs_utils_time_ms();
     
-    if ((ret = __srs_connect_server(rtmp)) != 0) {
-        fprintf(stderr, "socket connect failed. ret=%d\n", ret);
+    if ((ret = srs_rtmp_connect_server(rtmp)) != 0) {
+        srs_human_trace("socket connect failed. ret=%d", ret);
         goto rtmp_destroy;
     }
-    printf("socket connect success\n");
-    time_socket_connect = srs_get_time_ms();
+    srs_human_trace("socket connect success");
+    time_socket_connect = srs_utils_time_ms();
     
-    if ((ret = __srs_do_simple_handshake(rtmp)) != 0) {
-        fprintf(stderr, "do simple handshake failed. ret=%d\n", ret);
+    if ((ret = srs_rtmp_do_simple_handshake(rtmp)) != 0) {
+        srs_human_trace("do simple handshake failed. ret=%d", ret);
         goto rtmp_destroy;
     }
-    printf("do simple handshake success\n");
+    srs_human_trace("do simple handshake success");
     
-    if ((ret = srs_connect_app(rtmp)) != 0) {
-        fprintf(stderr, "connect vhost/app failed. ret=%d\n", ret);
+    if ((ret = srs_rtmp_connect_app(rtmp)) != 0) {
+        srs_human_trace("connect vhost/app failed. ret=%d", ret);
         goto rtmp_destroy;
     }
-    printf("connect vhost/app success\n");
+    srs_human_trace("connect vhost/app success");
     
-    if ((ret = srs_play_stream(rtmp)) != 0) {
-        fprintf(stderr, "play stream failed. ret=%d\n", ret);
+    if ((ret = srs_rtmp_play_stream(rtmp)) != 0) {
+        srs_human_trace("play stream failed. ret=%d", ret);
         goto rtmp_destroy;
     }
-    printf("play stream success\n");
-    time_play_stream = srs_get_time_ms();
+    srs_human_trace("play stream success");
+    time_play_stream = srs_utils_time_ms();
     
     for (;;) {
-        if ((ret = srs_read_packet(rtmp, &type, ×tamp, &data, &size)) != 0) {
-            fprintf(stderr, "read packet failed. ret=%d\n", ret);
+        if ((ret = srs_rtmp_read_packet(rtmp, &type, ×tamp, &data, &size)) != 0) {
+            srs_human_trace("read packet failed. ret=%d", ret);
             goto rtmp_destroy;
         }
-        printf("got packet: type=%s, time=%d, size=%d\n", srs_type2string(type), timestamp, size);
+        srs_human_trace("got packet: type=%s, time=%d, size=%d", 
+            srs_human_flv_tag_type2string(type), timestamp, size);
         
         if (SRS_RTMP_TYPE_VIDEO == type || SRS_RTMP_TYPE_AUDIO == type) {
             if (time_first_packet <= 0) {
-                time_first_packet = srs_get_time_ms();
+                time_first_packet = srs_utils_time_ms();
             }
             if (basetime <= 0) {
                 basetime = timestamp;
@@ -143,23 +142,23 @@ int main(int argc, char** argv)
         
         free(data);
         
-        if (srs_get_time_ms() - time_startup > timeout * 1000) {
-            printf("timeout, terminate.\n");
+        if (srs_utils_time_ms() - time_startup > timeout * 1000) {
+            srs_human_trace("timeout, terminate.");
             goto rtmp_destroy;
         }
         
         if ((timestamp - basetime) > duration * 1000) {
-            printf("duration exceed, terminate.\n");
+            srs_human_trace("duration exceed, terminate.");
             goto rtmp_destroy;
         }
     }
     
 rtmp_destroy:
-    bytes_nsend = srs_get_nsend_bytes(rtmp);
-    bytes_nrecv = srs_get_nrecv_bytes(rtmp);
+    bytes_nsend = srs_utils_send_bytes(rtmp);
+    bytes_nrecv = srs_utils_recv_bytes(rtmp);
     
     srs_rtmp_destroy(rtmp);
-    time_cleanup = srs_get_time_ms();
+    time_cleanup = srs_utils_time_ms();
     time_duration = (int)(time_cleanup - time_startup);
     
     // print result to stderr.
@@ -197,7 +196,9 @@ int main(int argc, char** argv)
         "\"remark1\": \"delay = stream - (time_cleanup - time_first_packet)\"",
         "\"remark2\": \"if code is not 0, user must ignore all data\""
     );
-    printf("\n");
+    
+    srs_human_trace(" ");
+    srs_human_trace("completed");
     
     return ret;
 }
diff --git a/trunk/research/librtmp/srs_flv_injecter.c b/trunk/research/librtmp/srs_flv_injecter.c
index 85a8136471..b53348460a 100644
--- a/trunk/research/librtmp/srs_flv_injecter.c
+++ b/trunk/research/librtmp/srs_flv_injecter.c
@@ -1,7 +1,7 @@
 /*
 The MIT License (MIT)
 
-Copyright (c) 2013-2014 winlin
+Copyright (c) 2013-2015 SRS(simple-rtmp-server)
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of
 this software and associated documentation files (the "Software"), to deal in
@@ -26,6 +26,7 @@ gcc srs_flv_injecter.c ../../objs/lib/srs_librtmp.a -g -O0 -lstdc++ -o srs_flv_i
 
 #include 
 #include 
+#include 
 #include 
 
 #include 
@@ -33,11 +34,13 @@ gcc srs_flv_injecter.c ../../objs/lib/srs_librtmp.a -g -O0 -lstdc++ -o srs_flv_i
 #include 
 
 #include "../../objs/include/srs_librtmp.h"
-#include "srs_research_public.h"
 
 #define ERROR_INJECTED 10000
 
 int process(const char* in_flv_file, const char* out_flv_file, srs_flv_t* pic, srs_flv_t* poc);
+int build_keyframes(srs_flv_t ic, srs_amf0_t *pname, srs_amf0_t* pdata, srs_amf0_t* pfilepositions, int64_t* pmetadata_end_offset);
+int do_inject_flv(srs_flv_t ic, srs_flv_t oc, srs_amf0_t amf0_name, srs_amf0_t amf0_data, srs_amf0_t filepositions, int64_t metadata_end_offset);
+
 int main(int argc, char** argv)
 {
     int ret = 0;
@@ -52,6 +55,10 @@ int main(int argc, char** argv)
     // temp variables.
     int tmp_file_size = 0;
     char* tmp_file;
+
+    printf("inject flv file keyframes to metadata.\n");
+    printf("srs(simple-rtmp-server) client librtmp library.\n");
+    printf("version: %d.%d.%d\n", srs_version_major(), srs_version_minor(), srs_version_revision());
     
     if (argc <= 2) {
         printf("inject flv file keyframes to metadata\n"
@@ -59,11 +66,10 @@ int main(int argc, char** argv)
             "   in_flv_file         input flv file to inject.\n"
             "   out_flv_file        the inject output file, can be in_flv_file.\n"
             "For example:\n"
+            "   %s doc/source.200kbps.768x320.flv injected.flv\n"
             "   %s ../../doc/source.200kbps.768x320.flv injected.flv\n",
-            argv[0], argv[0]);
-        ret = 1;
-        exit(ret);
-        return ret;
+            argv[0], argv[0], argv[0]);
+        exit(-1);
     }
     
     in_flv_file = argv[1];
@@ -73,12 +79,9 @@ int main(int argc, char** argv)
     tmp_file = (char*)malloc(tmp_file_size);
     snprintf(tmp_file, tmp_file_size, "%s.tmp", out_flv_file);
     
-    trace("inject flv file keyframes to metadata.");
-    trace("srs(simple-rtmp-server) client librtmp library.");
-    trace("version: %d.%d.%d", srs_version_major(), srs_version_minor(), srs_version_revision());
-    trace("input:  %s", in_flv_file);
-    trace("output:  %s", out_flv_file);
-    trace("tmp_file:  %s", tmp_file);
+    srs_human_trace("input:  %s", in_flv_file);
+    srs_human_trace("output:  %s", out_flv_file);
+    srs_human_trace("tmp_file:  %s", tmp_file);
 
     ret = process(in_flv_file, tmp_file, &ic, &oc);
     
@@ -89,13 +92,13 @@ int main(int argc, char** argv)
         unlink(tmp_file);
         if (ret == ERROR_INJECTED) {
             ret = 0;
-            trace("file already injected.");
+            srs_human_trace("file already injected.");
         } else {
-            trace("error, remove tmp file.");
+            srs_human_trace("error, remove tmp file.");
         }
     } else {
         rename(tmp_file, out_flv_file);
-        trace("completed, rename to %s", out_flv_file);
+        srs_human_trace("completed, rename to %s", out_flv_file);
     }
     
     free(tmp_file);
@@ -123,14 +126,14 @@ int process(const char* in_flv_file, const char* out_flv_file, srs_flv_t* pic, s
     
     if ((ic = srs_flv_open_read(in_flv_file)) == NULL) {
         ret = 2;
-        trace("open input flv file failed. ret=%d", ret);
+        srs_human_trace("open input flv file failed. ret=%d", ret);
         return ret;
     }
     *pic = ic;
     
     if ((oc = srs_flv_open_write(out_flv_file)) == NULL) {
         ret = 2;
-        trace("open output flv file failed. ret=%d", ret);
+        srs_human_trace("open output flv file failed. ret=%d", ret);
         return ret;
     }
     *poc = oc;
@@ -164,13 +167,13 @@ int parse_metadata(char* data, int size, srs_amf0_t* pname, srs_amf0_t* pdata)
     *pname = srs_amf0_parse(data, size, &nparsed);
     
     if (*pname == NULL || nparsed >= size) {
-        trace("invalid amf0 name data.");
+        srs_human_trace("invalid amf0 name data.");
         return -1;
     }
     
     *pdata = srs_amf0_parse(data + nparsed, size - nparsed, &nparsed);
     if (*pdata == NULL || nparsed > size) {
-        trace("invalid amf0 value data");
+        srs_human_trace("invalid amf0 value data");
         return -1;
     }
     
@@ -206,22 +209,22 @@ int build_keyframes(srs_flv_t ic, srs_amf0_t *pname, srs_amf0_t* pdata, srs_amf0
         return ret;
     }
     
-    trace("build keyframe infos from flv");
+    srs_human_trace("build keyframe infos from flv");
     for (;;) {
         offset = srs_flv_tellg(ic);
         
         // tag header
         if ((ret = srs_flv_read_tag_header(ic, &type, &size, ×tamp)) != 0) {
             if (srs_flv_is_eof(ret)) {
-                trace("parse completed.");
+                srs_human_trace("parse completed.");
                 return 0;
             }
-            trace("flv get packet failed. ret=%d", ret);
+            srs_human_trace("flv get packet failed. ret=%d", ret);
             return ret;
         }
         
         if (size <= 0) {
-            trace("invalid size=%d", size);
+            srs_human_trace("invalid size=%d", size);
             return ret;
         }
         
@@ -343,20 +346,20 @@ int do_inject_flv(srs_flv_t ic, srs_flv_t oc, srs_amf0_t amf0_name, srs_amf0_t a
         free(data);
     }
     
-    trace("build keyframe infos from flv");
+    srs_human_trace("build keyframe infos from flv");
     for (;;) {
         // tag header
         if ((ret = srs_flv_read_tag_header(ic, &type, &size, ×tamp)) != 0) {
             if (srs_flv_is_eof(ret)) {
-                trace("parse completed.");
+                srs_human_trace("parse completed.");
                 return 0;
             }
-            trace("flv get packet failed. ret=%d", ret);
+            srs_human_trace("flv get packet failed. ret=%d", ret);
             return ret;
         }
         
         if (size <= 0) {
-            trace("invalid size=%d", size);
+            srs_human_trace("invalid size=%d", size);
             break;
         }
         
diff --git a/trunk/research/librtmp/srs_flv_parser.c b/trunk/research/librtmp/srs_flv_parser.c
index 638257eb6b..9e6743c820 100644
--- a/trunk/research/librtmp/srs_flv_parser.c
+++ b/trunk/research/librtmp/srs_flv_parser.c
@@ -1,7 +1,7 @@
 /*
 The MIT License (MIT)
 
-Copyright (c) 2013-2014 winlin
+Copyright (c) 2013-2015 SRS(simple-rtmp-server)
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of
 this software and associated documentation files (the "Software"), to deal in
@@ -25,6 +25,7 @@ gcc srs_ingest_flv.c ../../objs/lib/srs_librtmp.a -g -O0 -lstdc++ -o srs_ingest_
 */
 
 #include 
+#include 
 #include 
 #include 
 
@@ -33,7 +34,6 @@ gcc srs_ingest_flv.c ../../objs/lib/srs_librtmp.a -g -O0 -lstdc++ -o srs_ingest_
 #include 
 
 #include "../../objs/include/srs_librtmp.h"
-#include "srs_research_public.h"
 
 int parse_flv(srs_flv_t flv);
 int main(int argc, char** argv)
@@ -45,28 +45,27 @@ int main(int argc, char** argv)
     // flv handler
     srs_flv_t flv;
     
+    printf("parse and show flv file detail.\n");
+    printf("srs(simple-rtmp-server) client librtmp library.\n");
+    printf("version: %d.%d.%d\n", srs_version_major(), srs_version_minor(), srs_version_revision());
+    
     if (argc <= 1) {
         printf("parse and show flv file detail\n"
             "Usage: %s in_flv_file\n"
             "   in_flv_file         flv file to parse and show.\n"
             "For example:\n"
+            "   %s doc/source.200kbps.768x320.flv\n"
             "   %s ../../doc/source.200kbps.768x320.flv\n",
-            argv[0], argv[0]);
-        ret = 1;
-        exit(ret);
-        return ret;
+            argv[0], argv[0], argv[0]);
+        exit(-1);
     }
     
     in_flv_file = argv[1];
-    
-    trace("parse and show flv file detail.");
-    trace("srs(simple-rtmp-server) client librtmp library.");
-    trace("version: %d.%d.%d", srs_version_major(), srs_version_minor(), srs_version_revision());
-    trace("input:  %s", in_flv_file);
+    srs_human_trace("input:  %s", in_flv_file);
 
     if ((flv = srs_flv_open_read(in_flv_file)) == NULL) {
         ret = 2;
-        trace("open flv file failed. ret=%d", ret);
+        srs_human_trace("open flv file failed. ret=%d", ret);
         return ret;
     }
     
@@ -123,86 +122,8 @@ int parse_bytes(char* data, int size, char* hbuf, int hsize, char* tbuf, int tsi
     if (size > print_size * 2) {
         digit_to_char(data + size - print_size, size, tbuf, tsize - 1);
     }
-}
-
-#define FLV_HEADER_SIZE 11
-int parse_script_data(u_int32_t timestamp, char* data, int size, int64_t offset)
-{
-    int ret = 0;
-    
-    char hbuf[48];
-    char tbuf[48];
-    
-    int amf0_size = 0;
-    int nparsed = 0;
-    
-    srs_amf0_t amf0_name;
-    char* amf0_name_str = NULL;
-    
-    srs_amf0_t amf0_data;
-    char* amf0_data_str = NULL;
-    
-    // bytes
-    parse_bytes(data, size, hbuf, sizeof(hbuf), tbuf, sizeof(tbuf), 16);
-    
-    // amf0
-    amf0_name = srs_amf0_parse(data, size, &nparsed);
-    if (amf0_name == NULL || nparsed >= size) {
-        trace("invalid amf0 name data.");
-        return -1;
-    }
-    amf0_data = srs_amf0_parse(data + nparsed, size - nparsed, &nparsed);
-    
-    trace("packet type=%s, time=%d, size=%d, data-size=%d, \n"
-        "offset=%d\n[+00, +15] %s\n[-15, EOF] %s\n%s%s", 
-        srs_type2string(SRS_RTMP_TYPE_SCRIPT), timestamp, size + FLV_HEADER_SIZE, size, 
-        (int)offset, hbuf, tbuf, 
-        srs_amf0_human_print(amf0_name, &amf0_name_str, &amf0_size), 
-        srs_amf0_human_print(amf0_data, &amf0_data_str, &amf0_size));
-    
-    srs_amf0_free(amf0_name);
-    srs_amf0_free_bytes(amf0_name_str);
-    
-    srs_amf0_free(amf0_data);
-    srs_amf0_free_bytes(amf0_data_str);
 
-    return ret;
-}
-
-int parse_audio_data(u_int32_t timestamp, char* data, int size, int64_t offset)
-{
-    int ret = 0;
-    
-    char hbuf[48];
-    char tbuf[48];
-    
-    // bytes
-    parse_bytes(data, size, hbuf, sizeof(hbuf), tbuf, sizeof(tbuf), 16);
-    
-    trace("packet type=%s, time=%d, size=%d, data-size=%d, \n"
-        "offset=%d\n[+00, +15] %s\n[-15, EOF] %s\n", 
-        srs_type2string(SRS_RTMP_TYPE_AUDIO), timestamp, size + FLV_HEADER_SIZE, size, 
-        (int)offset, hbuf, tbuf);
-    
-    return ret;
-}
-
-int parse_video_data(u_int32_t timestamp, char* data, int size, int64_t offset)
-{
-    int ret = 0;
-    
-    char hbuf[48];
-    char tbuf[48];
-    
-    // bytes
-    parse_bytes(data, size, hbuf, sizeof(hbuf), tbuf, sizeof(tbuf), 16);
-    
-    trace("packet type=%s, time=%d, size=%d, data-size=%d, \n"
-        "offset=%d\n[+00, +15] %s\n[-15, EOF] %s\n", 
-        srs_type2string(SRS_RTMP_TYPE_VIDEO), timestamp, size + FLV_HEADER_SIZE, size, 
-        (int)offset, hbuf, tbuf);
-        
-    return ret;
+    return 0;
 }
 
 int parse_flv(srs_flv_t flv)
@@ -222,46 +143,46 @@ int parse_flv(srs_flv_t flv)
         return ret;
     }
     
-    trace("start parse flv");
+    srs_human_trace("start parse flv");
     for (;;) {
         offset = srs_flv_tellg(flv);
         
         // tag header
         if ((ret = srs_flv_read_tag_header(flv, &type, &size, ×tamp)) != 0) {
             if (srs_flv_is_eof(ret)) {
-                trace("parse completed.");
+                srs_human_trace("parse completed.");
                 return 0;
             }
-            trace("flv get packet failed. ret=%d", ret);
+            srs_human_trace("flv get packet failed. ret=%d", ret);
             return ret;
         }
         
         if (size <= 0) {
-            trace("invalid size=%d", size);
+            srs_human_trace("invalid size=%d", size);
             break;
         }
         
         data = (char*)malloc(size);
-        if ((ret = srs_flv_read_tag_data(flv, data, size)) != 0) {
-            return ret;
-        }
         
-        // data tag
-        if (type == SRS_RTMP_TYPE_AUDIO) {
-            if ((ret = parse_audio_data(timestamp, data, size, offset)) != 0) {
-                return ret;
-            }
-        } else if (type == SRS_RTMP_TYPE_VIDEO) {
-            if ((ret = parse_video_data(timestamp, data, size, offset)) != 0) {
-                return ret;
+        if ((ret = srs_flv_read_tag_data(flv, data, size)) == 0) {
+            if ((ret = srs_human_print_rtmp_packet(type, timestamp, data, size)) == 0) {
+                char hbuf[48]; char tbuf[48];
+                parse_bytes(data, size, hbuf, sizeof(hbuf), tbuf, sizeof(tbuf), 16);
+                srs_human_raw("offset=%d, first and last 16 bytes:\n"
+                    "[+00, +15] %s\n[-15, EOF] %s\n", (int)offset, hbuf, tbuf);
+            } else {
+                srs_human_trace("print packet failed. ret=%d", ret);
             }
         } else {
-            if ((ret = parse_script_data(timestamp, data, size, offset)) != 0) {
-                return ret;
-            }
+            srs_human_trace("read flv failed. ret=%d", ret);
         }
         
         free(data);
+        
+        if (ret != 0) {
+            srs_human_trace("parse failed, ret=%d", ret);
+            return ret;
+        }
     }
     
     return ret;
diff --git a/trunk/research/librtmp/srs_h264_raw_publish.c b/trunk/research/librtmp/srs_h264_raw_publish.c
new file mode 100644
index 0000000000..722f567cf9
--- /dev/null
+++ b/trunk/research/librtmp/srs_h264_raw_publish.c
@@ -0,0 +1,200 @@
+/*
+The MIT License (MIT)
+
+Copyright (c) 2013-2015 SRS(simple-rtmp-server)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+/**
+gcc srs_h264_raw_publish.c ../../objs/lib/srs_librtmp.a -g -O0 -lstdc++ -o srs_h264_raw_publish
+*/
+
+#include 
+#include 
+#include 
+
+// for open h264 raw file.
+#include 
+#include 
+#include 
+       
+#include "../../objs/include/srs_librtmp.h"
+
+int read_h264_frame(char* data, int size, char** pp, int* pnb_start_code, int fps,
+    char** frame, int* frame_size, int* dts, int* pts)
+{
+    char* p = *pp;
+    
+    // @remark, for this demo, to publish h264 raw file to SRS,
+    // we search the h264 frame from the buffer which cached the h264 data.
+    // please get h264 raw data from device, it always a encoded frame.
+    if (!srs_h264_startswith_annexb(p, size - (p - data), pnb_start_code)) {
+        srs_human_trace("h264 raw data invalid.");
+        return -1;
+    }
+    
+    // @see srs_write_h264_raw_frames
+    // each frame prefixed h.264 annexb header, by N[00] 00 00 01, where N>=0, 
+    // for instance, frame = header(00 00 00 01) + payload(67 42 80 29 95 A0 14 01 6E 40)
+    *frame = p;
+    p += *pnb_start_code;
+    
+    for (;p < data + size; p++) {
+        if (srs_h264_startswith_annexb(p, size - (p - data), NULL)) {
+            break;
+        }
+    }
+    
+    *pp = p;
+    *frame_size = p - *frame;
+    if (*frame_size <= 0) {
+        srs_human_trace("h264 raw data invalid.");
+        return -1;
+    }
+    
+    // @remark, please get the dts and pts from device,
+    // we assume there is no B frame, and the fps can guess the fps and dts,
+    // while the dts and pts must read from encode lib or device.
+    *dts += 1000 / fps;
+    *pts = *dts;
+
+    return 0;
+}
+
+int main(int argc, char** argv)
+{
+    printf("publish raw h.264 as rtmp stream to server like FMLE/FFMPEG/Encoder\n");
+    printf("SRS(simple-rtmp-server) client librtmp library.\n");
+    printf("version: %d.%d.%d\n", srs_version_major(), srs_version_minor(), srs_version_revision());
+    
+    if (argc <= 2) {
+        printf("Usage: %s  \n", argv[0]);
+        printf("     h264_raw_file: the h264 raw steam file.\n");
+        printf("     rtmp_publish_url: the rtmp publish url.\n");
+        printf("For example:\n");
+        printf("     %s ./720p.h264.raw rtmp://127.0.0.1:1935/live/livestream\n", argv[0]);
+        printf("Where the file: http://winlinvip.github.io/srs.release/3rdparty/720p.h264.raw\n");
+        printf("See: https://github.com/simple-rtmp-server/srs/issues/66\n");
+        exit(-1);
+    }
+    
+    const char* raw_file = argv[1];
+    const char* rtmp_url = argv[2];
+    srs_human_trace("raw_file=%s, rtmp_url=%s", raw_file, rtmp_url);
+    
+    // open file
+    int raw_fd = open(raw_file, O_RDONLY);
+    if (raw_fd < 0) {
+        srs_human_trace("open h264 raw file %s failed.", raw_file);
+        goto rtmp_destroy;
+    }
+    
+    off_t file_size = lseek(raw_fd, 0, SEEK_END);
+    if (file_size <= 0) {
+        srs_human_trace("h264 raw file %s empty.", raw_file);
+        goto rtmp_destroy;
+    }
+    srs_human_trace("read entirely h264 raw file, size=%dKB", (int)(file_size / 1024));
+    
+    char* h264_raw = (char*)malloc(file_size);
+    if (!h264_raw) {
+        srs_human_trace("alloc raw buffer failed for file %s.", raw_file);
+        goto rtmp_destroy;
+    }
+    
+    lseek(raw_fd, 0, SEEK_SET);
+    ssize_t nb_read = 0;
+    if ((nb_read = read(raw_fd, h264_raw, file_size)) != file_size) {
+        srs_human_trace("buffer %s failed, expect=%dKB, actual=%dKB.", 
+            raw_file, (int)(file_size / 1024), (int)(nb_read / 1024));
+        goto rtmp_destroy;
+    }
+    
+    // connect rtmp context
+    srs_rtmp_t rtmp = srs_rtmp_create(rtmp_url);
+    
+    if (srs_rtmp_handshake(rtmp) != 0) {
+        srs_human_trace("simple handshake failed.");
+        goto rtmp_destroy;
+    }
+    srs_human_trace("simple handshake success");
+    
+    if (srs_rtmp_connect_app(rtmp) != 0) {
+        srs_human_trace("connect vhost/app failed.");
+        goto rtmp_destroy;
+    }
+    srs_human_trace("connect vhost/app success");
+    
+    if (srs_rtmp_publish_stream(rtmp) != 0) {
+        srs_human_trace("publish stream failed.");
+        goto rtmp_destroy;
+    }
+    srs_human_trace("publish stream success");
+    
+    int dts = 0;
+    int pts = 0;
+    // @remark, the dts and pts if read from device, for instance, the encode lib,
+    // so we assume the fps is 25, and each h264 frame is 1000ms/25fps=40ms/f.
+    int fps = 25;
+    // @remark, to decode the file.
+    char* p = h264_raw;
+    for (;p < h264_raw + file_size;) {
+        // @remark, read a frame from file buffer.
+        char* data = NULL;
+        int size = 0;
+        int nb_start_code = 0;
+        if (read_h264_frame(h264_raw, (int)file_size, &p, &nb_start_code, fps, &data, &size, &dts, &pts) < 0) {
+            srs_human_trace("read a frame from file buffer failed.");
+            goto rtmp_destroy;
+        }
+        
+        // send out the h264 packet over RTMP
+        int ret = srs_h264_write_raw_frames(rtmp, data, size, dts, pts);
+        if (ret != 0) {
+            if (srs_h264_is_dvbsp_error(ret)) {
+                srs_human_trace("ignore drop video error, code=%d", ret);
+            } else if (srs_h264_is_duplicated_sps_error(ret)) {
+                srs_human_trace("ignore duplicated sps, code=%d", ret);
+            } else if (srs_h264_is_duplicated_pps_error(ret)) {
+                srs_human_trace("ignore duplicated pps, code=%d", ret);
+            } else {
+                srs_human_trace("send h264 raw data failed. ret=%d", ret);
+                goto rtmp_destroy;
+            }
+        }
+        
+        // 5bits, 7.3.1 NAL unit syntax, 
+        // H.264-AVC-ISO_IEC_14496-10.pdf, page 44.
+        u_int8_t nut = (char)data[nb_start_code] & 0x1f;
+        srs_human_trace("sent packet: type=%s, time=%d, size=%d, fps=%d, b[%d]=%#x(%s)", 
+            srs_human_flv_tag_type2string(SRS_RTMP_TYPE_VIDEO), dts, size, fps, nb_start_code, (char)data[nb_start_code],
+            (nut == 7? "SPS":(nut == 8? "PPS":(nut == 5? "I":(nut == 1? "P":"Unknown")))));
+        
+        // @remark, when use encode device, it not need to sleep.
+        usleep(1000 / fps * 1000);
+    }
+    srs_human_trace("h264 raw data completed");
+    
+rtmp_destroy:
+    srs_rtmp_destroy(rtmp);
+    close(raw_fd);
+    free(h264_raw);
+    
+    return 0;
+}
+
diff --git a/trunk/research/librtmp/srs_ingest_flv.c b/trunk/research/librtmp/srs_ingest_flv.c
index 04f588b34e..7afb2d3b76 100644
--- a/trunk/research/librtmp/srs_ingest_flv.c
+++ b/trunk/research/librtmp/srs_ingest_flv.c
@@ -1,7 +1,7 @@
 /*
 The MIT License (MIT)
 
-Copyright (c) 2013-2014 winlin
+Copyright (c) 2013-2015 SRS(simple-rtmp-server)
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of
 this software and associated documentation files (the "Software"), to deal in
@@ -33,15 +33,14 @@ gcc srs_ingest_flv.c ../../objs/lib/srs_librtmp.a -g -O0 -lstdc++ -o srs_ingest_
 #include 
 
 #include "../../objs/include/srs_librtmp.h"
-#include "srs_research_public.h"
 
 int proxy(srs_flv_t flv, srs_rtmp_t ortmp);
 int connect_oc(srs_rtmp_t ortmp);
 
 #define RE_PULSE_MS 300
 int64_t re_create();
-void re_update(int64_t re, u_int32_t time);
-void re_cleanup(int64_t re, u_int32_t time);
+void re_update(int64_t re, int32_t starttime, u_int32_t time);
+void re_cleanup(int64_t re, int32_t starttime, u_int32_t time);
 
 int64_t tools_main_entrance_startup_time;
 int main(int argc, char** argv)
@@ -49,60 +48,78 @@ int main(int argc, char** argv)
     int ret = 0;
     
     // main function
-    tools_main_entrance_startup_time = srs_get_time_ms();
+    tools_main_entrance_startup_time = srs_utils_time_ms();
     
     // user option parse index.
     int opt = 0;
     // user options.
-    char* in_flv_file; char* out_rtmp_url;
+    char* in_flv_file = NULL;
+    char* out_rtmp_url = NULL;
     // rtmp handler
     srs_rtmp_t ortmp;
     // flv handler
     srs_flv_t flv;
     
+    printf("ingest flv file and publish to RTMP server like FFMPEG.\n");
+    printf("srs(simple-rtmp-server) client librtmp library.\n");
+    printf("version: %d.%d.%d\n", srs_version_major(), srs_version_minor(), srs_version_revision());
+    
     if (argc <= 2) {
         printf("ingest flv file and publish to RTMP server\n"
             "Usage: %s <-i in_flv_file> <-y out_rtmp_url>\n"
             "   in_flv_file     input flv file, ingest from this file.\n"
             "   out_rtmp_url    output rtmp url, publish to this url.\n"
             "For example:\n"
-            "   %s -i ../../doc/source.200kbps.768x320.flv -y rtmp://127.0.0.1/live/demo\n",
-            argv[0], argv[0]);
-        ret = 1;
-        exit(ret);
-        return ret;
+            "   %s -i doc/source.200kbps.768x320.flv -y rtmp://127.0.0.1/live/livestream\n"
+            "   %s -i ../../doc/source.200kbps.768x320.flv -y rtmp://127.0.0.1/live/livestream\n",
+            argv[0], argv[0], argv[0]);
+        exit(-1);
+    }
+    
+    for (opt = 0; opt < argc; opt++) {
+        srs_human_trace("argv[%d]=%s", opt, argv[opt]);
     }
     
-    // parse options in FFMPEG format.
-    while ((opt = getopt(argc, argv, "i:y:")) != -1) {
-        switch (opt) {
-            case 'i':
-                in_flv_file = optarg;
-                break;
-            case 'y':
-                out_rtmp_url = optarg;
-                break;
-            default:
-                break;
+    // fill the options for mac
+    for (opt = 0; opt < argc - 1; opt++) {
+        // ignore all options except -i and -y.
+        char* p = argv[opt];
+        
+        // only accept -x
+        if (p[0] != '-' || p[1] == 0 || p[2] != 0) {
+            continue;
+        }
+        
+        // parse according the option name.
+        switch (p[1]) {
+            case 'i': in_flv_file = argv[opt + 1]; break;
+            case 'y': out_rtmp_url = argv[opt + 1]; break;
+            default: break;
         }
     }
     
-    trace("ingest flv file and publish to RTMP server like FFMPEG.");
-    trace("srs(simple-rtmp-server) client librtmp library.");
-    trace("version: %d.%d.%d", srs_version_major(), srs_version_minor(), srs_version_revision());
-    trace("input:  %s", in_flv_file);
-    trace("output: %s", out_rtmp_url);
+    if (!in_flv_file) {
+        srs_human_trace("input invalid, use -i ");
+        return -1;
+    }
+    if (!out_rtmp_url) {
+        srs_human_trace("output invalid, use -y ");
+        return -1;
+    }
+    
+    srs_human_trace("input:  %s", in_flv_file);
+    srs_human_trace("output: %s", out_rtmp_url);
 
     if ((flv = srs_flv_open_read(in_flv_file)) == NULL) {
         ret = 2;
-        trace("open flv file failed. ret=%d", ret);
+        srs_human_trace("open flv file failed. ret=%d", ret);
         return ret;
     }
     
     ortmp = srs_rtmp_create(out_rtmp_url);
 
     ret = proxy(flv, ortmp);
-    trace("ingest flv to RTMP completed");
+    srs_human_trace("ingest flv to RTMP completed");
     
     srs_rtmp_destroy(ortmp);
     srs_flv_close(flv);
@@ -110,7 +127,7 @@ int main(int argc, char** argv)
     return ret;
 }
 
-int do_proxy(srs_flv_t flv, srs_rtmp_t ortmp, int64_t re, u_int32_t* ptimestamp)
+int do_proxy(srs_flv_t flv, srs_rtmp_t ortmp, int64_t re, int32_t* pstarttime, u_int32_t* ptimestamp)
 {
     int ret = 0;
     
@@ -119,20 +136,20 @@ int do_proxy(srs_flv_t flv, srs_rtmp_t ortmp, int64_t re, u_int32_t* ptimestamp)
     int size;
     char* data = NULL;
     
-    trace("start ingest flv to RTMP stream");
+    srs_human_trace("start ingest flv to RTMP stream");
     for (;;) {
         // tag header
         if ((ret = srs_flv_read_tag_header(flv, &type, &size, ptimestamp)) != 0) {
             if (srs_flv_is_eof(ret)) {
-                trace("parse completed.");
+                srs_human_trace("parse completed.");
                 return 0;
             }
-            trace("flv get packet failed. ret=%d", ret);
+            srs_human_trace("flv get packet failed. ret=%d", ret);
             return ret;
         }
         
         if (size <= 0) {
-            trace("invalid size=%d", size);
+            srs_human_trace("invalid size=%d", size);
             break;
         }
         
@@ -142,14 +159,23 @@ int do_proxy(srs_flv_t flv, srs_rtmp_t ortmp, int64_t re, u_int32_t* ptimestamp)
             return ret;
         }
         
-        if ((ret = srs_write_packet(ortmp, type, *ptimestamp, data, size)) != 0) {
-            trace("irtmp get packet failed. ret=%d", ret);
+        u_int32_t timestamp = *ptimestamp;
+        
+        if ((ret = srs_human_print_rtmp_packet(type, timestamp, data, size)) != 0) {
+            srs_human_trace("print packet failed. ret=%d", ret);
             return ret;
         }
-        verbose("ortmp sent packet: type=%s, time=%d, size=%d", 
-            srs_type2string(type), *ptimestamp, size);
         
-        re_update(re, *ptimestamp);
+        if ((ret = srs_rtmp_write_packet(ortmp, type, *ptimestamp, data, size)) != 0) {
+            srs_human_trace("irtmp get packet failed. ret=%d", ret);
+            return ret;
+        }
+            
+        if (*pstarttime < 0 && srs_utils_flv_tag_is_av(type)) {
+            *pstarttime = *ptimestamp;
+        }
+        
+        re_update(re, *pstarttime, *ptimestamp);
     }
     
     return ret;
@@ -159,6 +185,7 @@ int proxy(srs_flv_t flv, srs_rtmp_t ortmp)
 {
     int ret = 0;
     u_int32_t timestamp = 0;
+    int32_t starttime = -1;
     
     char header[13];
     if ((ret = srs_flv_read_header(flv, header)) != 0) {
@@ -170,10 +197,10 @@ int proxy(srs_flv_t flv, srs_rtmp_t ortmp)
     
     int64_t re = re_create();
     
-    ret = do_proxy(flv, ortmp, re, ×tamp);
+    ret = do_proxy(flv, ortmp, re, &starttime, ×tamp);
     
     // for the last pulse, always sleep.
-    re_cleanup(re, timestamp);
+    re_cleanup(re, starttime, timestamp);
     
     return ret;
 }
@@ -182,23 +209,23 @@ int connect_oc(srs_rtmp_t ortmp)
 {
     int ret = 0;
     
-    if ((ret = srs_simple_handshake(ortmp)) != 0) {
-        trace("ortmp simple handshake failed. ret=%d", ret);
+    if ((ret = srs_rtmp_handshake(ortmp)) != 0) {
+        srs_human_trace("ortmp simple handshake failed. ret=%d", ret);
         return ret;
     }
-    trace("ortmp simple handshake success");
+    srs_human_trace("ortmp simple handshake success");
     
-    if ((ret = srs_connect_app(ortmp)) != 0) {
-        trace("ortmp connect vhost/app failed. ret=%d", ret);
+    if ((ret = srs_rtmp_connect_app(ortmp)) != 0) {
+        srs_human_trace("ortmp connect vhost/app failed. ret=%d", ret);
         return ret;
     }
-    trace("ortmp connect vhost/app success");
+    srs_human_trace("ortmp connect vhost/app success");
     
-    if ((ret = srs_publish_stream(ortmp)) != 0) {
-        trace("ortmp publish stream failed. ret=%d", ret);
+    if ((ret = srs_rtmp_publish_stream(ortmp)) != 0) {
+        srs_human_trace("ortmp publish stream failed. ret=%d", ret);
         return ret;
     }
-    trace("ortmp publish stream success");
+    srs_human_trace("ortmp publish stream success");
     
     return ret;
 }
@@ -206,41 +233,42 @@ int connect_oc(srs_rtmp_t ortmp)
 int64_t re_create()
 {
     // if not very precise, we can directly use this as re.
-    int64_t re = srs_get_time_ms();
+    int64_t re = srs_utils_time_ms();
     
     // use the starttime to get the deviation
     int64_t deviation = re - tools_main_entrance_startup_time;
-    trace("deviation is %d ms, pulse is %d ms", (int)(deviation), (int)(RE_PULSE_MS));
+    srs_human_trace("deviation is %d ms, pulse is %d ms", (int)(deviation), (int)(RE_PULSE_MS));
     
     // so, we adjust time to max(0, deviation)
     // because the last pulse, we already sleeped
     int adjust = (int)(deviation);
     if (adjust > 0) {
-        trace("adjust re time for %d ms", adjust);
+        srs_human_trace("adjust re time for %d ms", adjust);
         re -= adjust;
     } else {
-        trace("no need to adjust re time");
+        srs_human_trace("no need to adjust re time");
     }
     
     return re;
 }
-void re_update(int64_t re, u_int32_t time)
+void re_update(int64_t re, int32_t starttime, u_int32_t time)
 {
     // send by pulse algorithm.
-    int64_t now = srs_get_time_ms();
-    int64_t diff = time - (now -re);
+    int64_t now = srs_utils_time_ms();
+    int64_t diff = time - starttime - (now -re);
     if (diff > RE_PULSE_MS) {
-        usleep(diff * 1000);
+        usleep((useconds_t)(diff * 1000));
     }
 }
-void re_cleanup(int64_t re, u_int32_t time)
+void re_cleanup(int64_t re, int32_t starttime, u_int32_t time)
 {
     // for the last pulse, always sleep.
     // for the virtual live encoder long time publishing.
-    int64_t now = srs_get_time_ms();
-    int64_t diff = time - (now -re);
+    int64_t now = srs_utils_time_ms();
+    int64_t diff = time - starttime - (now -re);
     if (diff > 0) {
-        trace("re_cleanup sleep for the last pulse for %d ms", (int)diff);
-        usleep(diff * 1000);
+        srs_human_trace("re_cleanup, diff=%d, start=%d, last=%d ms", 
+            (int)diff, starttime, time);
+        usleep((useconds_t)(diff * 1000));
     }
 }
diff --git a/trunk/research/librtmp/srs_ingest_rtmp.c b/trunk/research/librtmp/srs_ingest_rtmp.c
index 28cbb4d337..85475fe70d 100644
--- a/trunk/research/librtmp/srs_ingest_rtmp.c
+++ b/trunk/research/librtmp/srs_ingest_rtmp.c
@@ -1,7 +1,7 @@
 /*
 The MIT License (MIT)
 
-Copyright (c) 2013-2014 winlin
+Copyright (c) 2013-2015 SRS(simple-rtmp-server)
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of
 this software and associated documentation files (the "Software"), to deal in
@@ -29,7 +29,6 @@ gcc srs_ingest_rtmp.c ../../objs/lib/srs_librtmp.a -g -O0 -lstdc++ -o srs_ingest
 #include 
 
 #include "../../objs/include/srs_librtmp.h"
-#include "srs_research_public.h"
 
 int connect_ic(srs_rtmp_t irtmp);
 int connect_oc(srs_rtmp_t ortmp);
@@ -42,10 +41,15 @@ int main(int argc, char** argv)
     // user option parse index.
     int opt = 0;
     // user options.
-    char* in_rtmp_url; char* out_rtmp_url;
+    char* in_rtmp_url = NULL;
+    char* out_rtmp_url = NULL;
     // rtmp handler
     srs_rtmp_t irtmp, ortmp;
     
+    printf("ingest RTMP and publish to RTMP server like edge.\n");
+    printf("srs(simple-rtmp-server) client librtmp library.\n");
+    printf("version: %d.%d.%d\n", srs_version_major(), srs_version_minor(), srs_version_revision());
+    
     if (argc <= 2) {
         printf("ingest RTMP and publish to RTMP server\n"
             "Usage: %s <-i in_rtmp_url> <-y out_rtmp_url>\n"
@@ -54,36 +58,44 @@ int main(int argc, char** argv)
             "For example:\n"
             "   %s -i rtmp://127.0.0.1/live/livestream -y rtmp://127.0.0.1/live/demo\n",
             argv[0], argv[0]);
-        ret = 1;
-        exit(ret);
-        return ret;
+        exit(-1);
     }
     
-    // parse options in FFMPEG format.
-    while ((opt = getopt(argc, argv, "i:y:")) != -1) {
-        switch (opt) {
-            case 'i':
-                in_rtmp_url = optarg;
-                break;
-            case 'y':
-                out_rtmp_url = optarg;
-                break;
-            default:
-                break;
+    // fill the options for mac
+    for (opt = 0; opt < argc - 1; opt++) {
+        // ignore all options except -i and -y.
+        char* p = argv[opt];
+        
+        // only accept -x
+        if (p[0] != '-' || p[1] == 0 || p[2] != 0) {
+            continue;
+        }
+        
+        // parse according the option name.
+        switch (p[1]) {
+            case 'i': in_rtmp_url = argv[opt + 1]; break;
+            case 'y': out_rtmp_url = argv[opt + 1]; break;
+            default: break;
         }
     }
     
-    trace("ingest RTMP and publish to RTMP server like edge.");
-    trace("srs(simple-rtmp-server) client librtmp library.");
-    trace("version: %d.%d.%d", srs_version_major(), srs_version_minor(), srs_version_revision());
-    trace("input:  %s", in_rtmp_url);
-    trace("output: %s", out_rtmp_url);
+    if (!in_rtmp_url) {
+        srs_human_trace("input invalid, use -i ");
+        return -1;
+    }
+    if (!out_rtmp_url) {
+        srs_human_trace("output invalid, use -y ");
+        return -1;
+    }
+    
+    srs_human_trace("input:  %s", in_rtmp_url);
+    srs_human_trace("output: %s", out_rtmp_url);
     
     irtmp = srs_rtmp_create(in_rtmp_url);
     ortmp = srs_rtmp_create(out_rtmp_url);
 
     ret = proxy(irtmp, ortmp);
-    trace("proxy completed");
+    srs_human_trace("proxy completed");
     
     srs_rtmp_destroy(irtmp);
     srs_rtmp_destroy(ortmp);
@@ -96,9 +108,10 @@ int proxy(srs_rtmp_t irtmp, srs_rtmp_t ortmp)
     int ret = 0;
     
     // packet data
-    int type, size;
-    u_int32_t timestamp = 0;
-    char* data = NULL;
+    int size;
+    char type;
+    char* data;
+    u_int32_t timestamp;
 
     if ((ret = connect_ic(irtmp)) != 0) {
         return ret;
@@ -107,21 +120,30 @@ int proxy(srs_rtmp_t irtmp, srs_rtmp_t ortmp)
         return ret;
     }
     
-    trace("start proxy RTMP stream");
+    srs_human_trace("start proxy RTMP stream");
     for (;;) {
-        if ((ret = srs_read_packet(irtmp, &type, ×tamp, &data, &size)) != 0) {
-            trace("irtmp get packet failed. ret=%d", ret);
+        if ((ret = srs_rtmp_read_packet(irtmp, &type, ×tamp, &data, &size)) != 0) {
+            srs_human_trace("irtmp get packet failed. ret=%d", ret);
+            return ret;
+        }
+        
+        if (!srs_utils_flv_tag_is_ok(type)) {
+            srs_human_trace("ignore invalid flv tag=%d, dts=%d, %d bytes", type, timestamp, size);
+            free(data);
+            continue;
+        }
+        
+        if ((ret = srs_human_print_rtmp_packet(type, timestamp, data, size)) != 0) {
+            srs_human_trace("print packet failed. ret=%d", ret);
             return ret;
         }
-        verbose("irtmp got packet: type=%s, time=%d, size=%d", 
-            srs_type2string(type), timestamp, size);
         
-        if ((ret = srs_write_packet(ortmp, type, timestamp, data, size)) != 0) {
-            trace("irtmp get packet failed. ret=%d", ret);
+        if ((ret = srs_rtmp_write_packet(ortmp, type, timestamp, data, size)) != 0) {
+            srs_human_trace("irtmp get packet failed. ret=%d", ret);
             return ret;
         }
-        verbose("ortmp sent packet: type=%s, time=%d, size=%d", 
-            srs_type2string(type), timestamp, size);
+        srs_human_verbose("ortmp sent packet: type=%s, time=%d, size=%d", 
+            srs_human_flv_tag_type2string(type), timestamp, size);
     }
     
     return ret;
@@ -131,23 +153,23 @@ int connect_ic(srs_rtmp_t irtmp)
 {
     int ret = 0;
     
-    if ((ret = srs_simple_handshake(irtmp)) != 0) {
-        trace("irtmp simple handshake failed. ret=%d", ret);
+    if ((ret = srs_rtmp_handshake(irtmp)) != 0) {
+        srs_human_trace("irtmp simple handshake failed. ret=%d", ret);
         return ret;
     }
-    trace("irtmp simple handshake success");
+    srs_human_trace("irtmp simple handshake success");
     
-    if ((ret = srs_connect_app(irtmp)) != 0) {
-        trace("irtmp connect vhost/app failed. ret=%d", ret);
+    if ((ret = srs_rtmp_connect_app(irtmp)) != 0) {
+        srs_human_trace("irtmp connect vhost/app failed. ret=%d", ret);
         return ret;
     }
-    trace("irtmp connect vhost/app success");
+    srs_human_trace("irtmp connect vhost/app success");
     
-    if ((ret = srs_play_stream(irtmp)) != 0) {
-        trace("irtmp play stream failed. ret=%d", ret);
+    if ((ret = srs_rtmp_play_stream(irtmp)) != 0) {
+        srs_human_trace("irtmp play stream failed. ret=%d", ret);
         return ret;
     }
-    trace("irtmp play stream success");
+    srs_human_trace("irtmp play stream success");
     
     return ret;
 }
@@ -156,23 +178,23 @@ int connect_oc(srs_rtmp_t ortmp)
 {
     int ret = 0;
     
-    if ((ret = srs_simple_handshake(ortmp)) != 0) {
-        trace("ortmp simple handshake failed. ret=%d", ret);
+    if ((ret = srs_rtmp_handshake(ortmp)) != 0) {
+        srs_human_trace("ortmp simple handshake failed. ret=%d", ret);
         return ret;
     }
-    trace("ortmp simple handshake success");
+    srs_human_trace("ortmp simple handshake success");
     
-    if ((ret = srs_connect_app(ortmp)) != 0) {
-        trace("ortmp connect vhost/app failed. ret=%d", ret);
+    if ((ret = srs_rtmp_connect_app(ortmp)) != 0) {
+        srs_human_trace("ortmp connect vhost/app failed. ret=%d", ret);
         return ret;
     }
-    trace("ortmp connect vhost/app success");
+    srs_human_trace("ortmp connect vhost/app success");
     
-    if ((ret = srs_publish_stream(ortmp)) != 0) {
-        trace("ortmp publish stream failed. ret=%d", ret);
+    if ((ret = srs_rtmp_publish_stream(ortmp)) != 0) {
+        srs_human_trace("ortmp publish stream failed. ret=%d", ret);
         return ret;
     }
-    trace("ortmp publish stream success");
+    srs_human_trace("ortmp publish stream success");
     
     return ret;
 }
diff --git a/trunk/research/librtmp/srs_play.c b/trunk/research/librtmp/srs_play.c
index e0710a433b..c42ab2a2b5 100644
--- a/trunk/research/librtmp/srs_play.c
+++ b/trunk/research/librtmp/srs_play.c
@@ -1,7 +1,7 @@
 /*
 The MIT License (MIT)
 
-Copyright (c) 2013-2014 winlin
+Copyright (c) 2013-2015 SRS(simple-rtmp-server)
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of
 this software and associated documentation files (the "Software"), to deal in
@@ -31,55 +31,53 @@ gcc srs_play.c ../../objs/lib/srs_librtmp.a -g -O0 -lstdc++ -o srs_play
 
 int main(int argc, char** argv)
 {
-    srs_rtmp_t rtmp;
-    
-    // packet data
-    int type, size;
-    u_int32_t timestamp = 0;
-    char* data;
+    printf("suck rtmp stream like rtmpdump\n");
+    printf("srs(simple-rtmp-server) client librtmp library.\n");
+    printf("version: %d.%d.%d\n", srs_version_major(), srs_version_minor(), srs_version_revision());
     
     if (argc <= 1) {
-        printf("play stream on RTMP server\n"
-            "Usage: %s \n"
+        printf("Usage: %s \n"
             "   rtmp_url     RTMP stream url to play\n"
             "For example:\n"
             "   %s rtmp://127.0.0.1:1935/live/livestream\n",
             argv[0], argv[0]);
-        int ret = 1;
-        exit(ret);
-        return ret;
+        exit(-1);
     }
     
-    rtmp = srs_rtmp_create(argv[1]);
-    
-    printf("suck rtmp stream like rtmpdump\n");
-    printf("srs(simple-rtmp-server) client librtmp library.\n");
-    printf("version: %d.%d.%d\n", srs_version_major(), srs_version_minor(), srs_version_revision());
-    printf("rtmp url: %s\n", argv[1]);
+    srs_human_trace("rtmp url: %s", argv[1]);
+    srs_rtmp_t rtmp = srs_rtmp_create(argv[1]);
     
-    if (srs_simple_handshake(rtmp) != 0) {
-        printf("simple handshake failed.\n");
+    if (srs_rtmp_handshake(rtmp) != 0) {
+        srs_human_trace("simple handshake failed.");
         goto rtmp_destroy;
     }
-    printf("simple handshake success\n");
+    srs_human_trace("simple handshake success");
     
-    if (srs_connect_app(rtmp) != 0) {
-        printf("connect vhost/app failed.\n");
+    if (srs_rtmp_connect_app(rtmp) != 0) {
+        srs_human_trace("connect vhost/app failed.");
         goto rtmp_destroy;
     }
-    printf("connect vhost/app success\n");
+    srs_human_trace("connect vhost/app success");
     
-    if (srs_play_stream(rtmp) != 0) {
-        printf("play stream failed.\n");
+    if (srs_rtmp_play_stream(rtmp) != 0) {
+        srs_human_trace("play stream failed.");
         goto rtmp_destroy;
     }
-    printf("play stream success\n");
+    srs_human_trace("play stream success");
     
     for (;;) {
-        if (srs_read_packet(rtmp, &type, ×tamp, &data, &size) != 0) {
+        int size;
+        char type;
+        char* data;
+        u_int32_t timestamp;
+        
+        if (srs_rtmp_read_packet(rtmp, &type, ×tamp, &data, &size) != 0) {
+            goto rtmp_destroy;
+        }
+        
+        if (srs_human_print_rtmp_packet(type, timestamp, data, size) != 0) {
             goto rtmp_destroy;
         }
-        printf("got packet: type=%s, time=%d, size=%d\n", srs_type2string(type), timestamp, size);
         
         free(data);
     }
diff --git a/trunk/research/librtmp/srs_publish.c b/trunk/research/librtmp/srs_publish.c
index 30c2af255b..70abab5777 100644
--- a/trunk/research/librtmp/srs_publish.c
+++ b/trunk/research/librtmp/srs_publish.c
@@ -1,7 +1,7 @@
 /*
 The MIT License (MIT)
 
-Copyright (c) 2013-2014 winlin
+Copyright (c) 2013-2015 SRS(simple-rtmp-server)
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of
 this software and associated documentation files (the "Software"), to deal in
@@ -32,54 +32,60 @@ gcc srs_publish.c ../../objs/lib/srs_librtmp.a -g -O0 -lstdc++ -o srs_publish
 
 int main(int argc, char** argv)
 {
-    srs_rtmp_t rtmp;
-    
-    // packet data
-    int type, size;
-    u_int32_t timestamp = 0;
-    char* data;
-    
     printf("publish rtmp stream to server like FMLE/FFMPEG/Encoder\n");
     printf("srs(simple-rtmp-server) client librtmp library.\n");
     printf("version: %d.%d.%d\n", srs_version_major(), srs_version_minor(), srs_version_revision());
+    
+    if (argc <= 1) {
+        printf("Usage: %s \n"
+            "   rtmp_url     RTMP stream url to publish\n"
+            "For example:\n"
+            "   %s rtmp://127.0.0.1:1935/live/livestream\n",
+            argv[0], argv[0]);
+        exit(-1);
+    }
+    
     // warn it .
-    // @see: https://github.com/winlinvip/simple-rtmp-server/issues/126
-    printf("\033[33m%s\033[0m", 
+    // @see: https://github.com/simple-rtmp-server/srs/issues/126
+    srs_human_trace("\033[33m%s\033[0m", 
         "[warning] it's only a sample to use librtmp. "
         "please never use it to publish and test forward/transcode/edge/HLS whatever. "
-        "you should refer to this tool to use the srs-librtmp to publish the real media stream.");
-    printf("\n");
-    
-    rtmp = srs_rtmp_create("rtmp://127.0.0.1:1935/live/livestream");
+        "you should refer to this tool to use the srs-librtmp to publish the real media stream."
+        "read about: https://github.com/simple-rtmp-server/srs/issues/126");
+    srs_human_trace("rtmp url: %s", argv[1]);
+    srs_rtmp_t rtmp = srs_rtmp_create(argv[1]);
     
-    if (srs_simple_handshake(rtmp) != 0) {
-        printf("simple handshake failed.\n");
+    if (srs_rtmp_handshake(rtmp) != 0) {
+        srs_human_trace("simple handshake failed.");
         goto rtmp_destroy;
     }
-    printf("simple handshake success\n");
+    srs_human_trace("simple handshake success");
     
-    if (srs_connect_app(rtmp) != 0) {
-        printf("connect vhost/app failed.\n");
+    if (srs_rtmp_connect_app(rtmp) != 0) {
+        srs_human_trace("connect vhost/app failed.");
         goto rtmp_destroy;
     }
-    printf("connect vhost/app success\n");
+    srs_human_trace("connect vhost/app success");
     
-    if (srs_publish_stream(rtmp) != 0) {
-        printf("publish stream failed.\n");
+    if (srs_rtmp_publish_stream(rtmp) != 0) {
+        srs_human_trace("publish stream failed.");
         goto rtmp_destroy;
     }
-    printf("publish stream success\n");
+    srs_human_trace("publish stream success");
     
+    u_int32_t timestamp = 0;
     for (;;) {
-        type = SRS_RTMP_TYPE_VIDEO;
+        char type = SRS_RTMP_TYPE_VIDEO;
+        int size = 4096;
+        char* data = (char*)malloc(4096);
+        
         timestamp += 40;
-        size = 4096;
-        data = (char*)malloc(4096);
         
-        if (srs_write_packet(rtmp, type, timestamp, data, size) != 0) {
+        if (srs_rtmp_write_packet(rtmp, type, timestamp, data, size) != 0) {
             goto rtmp_destroy;
         }
-        printf("sent packet: type=%s, time=%d, size=%d\n", srs_type2string(type), timestamp, size);
+        srs_human_trace("sent packet: type=%s, time=%d, size=%d", 
+            srs_human_flv_tag_type2string(type), timestamp, size);
         
         usleep(40 * 1000);
     }
diff --git a/trunk/research/librtmp/srs_rtmp_dump.c b/trunk/research/librtmp/srs_rtmp_dump.c
new file mode 100644
index 0000000000..b1cd6a3b3d
--- /dev/null
+++ b/trunk/research/librtmp/srs_rtmp_dump.c
@@ -0,0 +1,306 @@
+/*
+The MIT License (MIT)
+
+Copyright (c) 2013-2015 SRS(simple-rtmp-server)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+/**
+gcc srs_rtmp_dump.c ../../objs/lib/srs_librtmp.a -g -O0 -lstdc++ -o srs_rtmp_dump
+*/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "../../objs/include/srs_librtmp.h"
+
+void parse_amf0_object(char* p, srs_amf0_t args)
+{
+    char opvt = 0; // object property value type.
+    const char* opnp = NULL; // object property name ptr.
+    const char* opvp = NULL; // object property value ptr.
+    
+    while (*p) {
+        switch (*p++) {
+            case 'O':
+                while (*p && *p++ != ':') {
+                }
+                if (*p++ == '1') {
+                    printf("amf0 object start\n");
+                } else {
+                    printf("amf0 object end\n");
+                }
+                break;
+            case 'N':
+                opvt = *p++;
+                if (*p++ != ':') {
+                    printf("object property must split by :.\n");
+                    exit(-1);
+                }
+                opnp = p++;
+                while (*p && *p++ != ':') {
+                }
+                p[-1] = 0;
+                opvp = p;
+                printf("amf0 %c property[%s]=%s\n", opvt, opnp, opvp);
+                switch(opvt) {
+                    case 'S':
+                        srs_amf0_object_property_set(args, opnp, srs_amf0_create_string(opvp));
+                        break;
+                    default:
+                        printf("unsupported object property.\n");
+                        exit(-1);
+                }
+                *p=0;
+                break;
+            default:
+                printf("only supports an object arg.\n");
+                exit(-1);
+        }
+    }
+}
+
+int main(int argc, char** argv)
+{
+    srs_flv_t flv = NULL;
+    srs_rtmp_t rtmp = NULL;
+    
+    printf("dump rtmp stream to flv file\n");
+    printf("srs(simple-rtmp-server) client librtmp library.\n");
+    printf("version: %d.%d.%d\n", srs_version_major(), srs_version_minor(), srs_version_revision());
+    printf("@refer to http://rtmpdump.mplayerhq.hu/rtmpdump.1.html\n");
+    
+    struct option long_options[] = {
+        {"rtmp", required_argument, 0, 'r'},
+        {"flv", required_argument, 0, 'o'},
+        {"swfUrl", required_argument, 0, 's'},
+        {"tcUrl", required_argument, 0, 't'},
+        {"pageUrl", required_argument, 0, 'p'},
+        {"conn", required_argument, 0, 'C'},
+        {"complex", no_argument, 0, 'x'},
+        {"help", no_argument, 0, 'h'},
+        {0, 0, 0, 0}
+    };
+    
+    int show_help = 0;
+    int complex_handshake = 0;
+    const char* rtmp_url = NULL;
+    const char* output_flv = NULL;
+    const char* swfUrl = NULL;
+    const char* tcUrl = NULL;
+    const char* pageUrl = NULL;
+    srs_amf0_t args = NULL;
+    
+    int opt = 0;
+    int option_index = 0;
+    while((opt = getopt_long(argc, argv, "hxr:o:s:t:p:C:", long_options, &option_index)) != -1){
+        switch(opt){
+            case 'r':
+                rtmp_url = optarg;
+                break;
+            case 'o':
+                output_flv = optarg;
+                break;
+            case 's':
+                swfUrl = optarg;
+                break;
+            case 't':
+                tcUrl = optarg;
+                break;
+            case 'p':
+                pageUrl = optarg;
+                break;
+            case 'C':
+                if (!args) {
+                    args = srs_amf0_create_object();
+                }
+                char* p = (char*)optarg;
+                parse_amf0_object(p, args);
+                break;
+            case 'x':
+                complex_handshake = 1;
+                break;
+            case 'h':
+                show_help = 1;
+                break;
+            default:
+                printf("unsupported opt.\n");
+                exit(-1);
+        }
+    }
+    
+    if (!rtmp_url || show_help) {
+        printf("Usage: %s -r url [-o output] [-s swfUrl] [-t tcUrl] [-p pageUrl] [-C conndata] [--complex] [-h]\n"
+            "Options:\n"
+            "   --rtmp -r url\n"
+            "       URL of the server and media content.\n"
+            "   --flv -o output\n"
+            "       Specify the output file name. If the name is − or is omitted, the stream is written to stdout.\n"
+            "   --complex\n"
+            "       Whether use complex handshake(srs-librtmp with ssl required).\n"
+            "   --swfUrl -s url\n"
+            "       URL of the SWF player for the media. By default no value will be sent.\n"
+            "   --tcUrl -t url\n"
+            "       URL of the target stream. Defaults to rtmp[e]://host[:port]/app/playpath.\n"
+            "   --pageUrl -p url\n"
+            "       URL of the web page in which the media was embedded. By default no value will be sent.\n"
+            "   −−conn −C type:data\n"
+            "       Append arbitrary AMF data to the Connect message. The type must be B for Boolean, N for number, S for string, O for object, or Z for null. For Booleans the data must be either 0 or 1 for FALSE or TRUE, respectively. Likewise for Objects the data must be 0 or 1 to end or begin an object, respectively. Data items in subobjects may be named, by prefixing the type with 'N' and specifying the name before the value, e.g. NB:myFlag:1. This option may be used multiple times to construct arbitrary AMF sequences. E.g.\n"
+            "       −C B:1 −C S:authMe −C O:1 −C NN:code:1.23 −C NS:flag:ok −C O:0\n"
+            "       -C O:1 -C NS:CONN:\" -C B:4Rg9vr0\" -C O:0\n"
+            "       @remark, support a object args only.\n"
+            "   --help -h\n"
+            "       Print a summary of command options.\n"
+            "For example:\n"
+            "   %s -r rtmp://127.0.0.1:1935/live/livestream -o output.flv\n"
+            "   %s -h\n",
+            argv[0], argv[0], argv[0]);
+        exit(-1);
+    }
+    
+    srs_human_trace("rtmp url: %s", rtmp_url);
+    srs_human_trace("handshake: %s", (complex_handshake? "complex" : "simple"));
+    srs_human_trace("swfUrl: %s", swfUrl);
+    srs_human_trace("pageUrl: %s", pageUrl);
+    srs_human_trace("tcUrl: %s", tcUrl);
+    if (output_flv) {
+        srs_human_trace("flv output path: %s", output_flv);
+    } else {
+        srs_human_trace("output to console");
+    }
+    
+    rtmp = srs_rtmp_create(rtmp_url);
+    
+    if (srs_rtmp_dns_resolve(rtmp) != 0) {
+        srs_human_trace("dns resolve failed.");
+        goto rtmp_destroy;
+    }
+    
+    if (srs_rtmp_connect_server(rtmp) != 0) {
+        srs_human_trace("connect to server failed.");
+        goto rtmp_destroy;
+    }
+    
+    if (complex_handshake) {
+        if (srs_rtmp_do_complex_handshake(rtmp) != 0) {
+            srs_human_trace("complex handshake failed.");
+            goto rtmp_destroy;
+        }
+        srs_human_trace("do complex handshake success");
+    } else {
+        if (srs_rtmp_do_simple_handshake(rtmp) != 0) {
+            srs_human_trace("simple handshake failed.");
+            goto rtmp_destroy;
+        }
+        srs_human_trace("do simple handshake success");
+    }
+    
+    if (srs_rtmp_set_connect_args(rtmp, tcUrl, swfUrl, pageUrl, args) != 0) {
+        srs_human_trace("set connect args failed.");
+        goto rtmp_destroy;
+    }
+    
+    if (srs_rtmp_connect_app(rtmp) != 0) {
+        srs_human_trace("connect vhost/app failed.");
+        goto rtmp_destroy;
+    }
+    srs_human_trace("connect vhost/app success");
+    
+    if (srs_rtmp_play_stream(rtmp) != 0) {
+        srs_human_trace("play stream failed.");
+        goto rtmp_destroy;
+    }
+    srs_human_trace("play stream success");
+    
+    if (output_flv) {
+        flv = srs_flv_open_write(output_flv);
+    }
+    
+    if (flv) {
+        // flv header
+        char header[9];
+        // 3bytes, signature, "FLV",
+        header[0] = 'F';
+        header[1] = 'L';
+        header[2] = 'V';
+        // 1bytes, version, 0x01,
+        header[3] = 0x01;
+        // 1bytes, flags, UB[5] 0, UB[1] audio present, UB[1] 0, UB[1] video present.
+        header[4] = 0x03; // audio + video.
+        // 4bytes, dataoffset
+        header[5] = 0x00;
+        header[6] = 0x00;
+        header[7] = 0x00;
+        header[8] = 0x09;
+        if (srs_flv_write_header(flv, header) != 0) {
+            srs_human_trace("write flv header failed.");
+            goto rtmp_destroy;
+        }
+    }
+    
+    for (;;) {
+        int size;
+        char type;
+        char* data;
+        u_int32_t timestamp;
+        
+        if (srs_rtmp_read_packet(rtmp, &type, ×tamp, &data, &size) != 0) {
+            srs_human_trace("read rtmp packet failed.");
+            goto rtmp_destroy;
+        }
+        
+        if (srs_human_print_rtmp_packet(type, timestamp, data, size) != 0) {
+            srs_human_trace("print rtmp packet failed.");
+            goto rtmp_destroy;
+        }
+        
+        // we only write some types of messages to flv file.
+        int is_flv_msg = type == SRS_RTMP_TYPE_AUDIO
+            || type == SRS_RTMP_TYPE_VIDEO || type == SRS_RTMP_TYPE_SCRIPT;
+            
+        // for script data, ignore except onMetaData
+        if (type == SRS_RTMP_TYPE_SCRIPT) {
+            if (!srs_rtmp_is_onMetaData(type, data, size)) {
+                is_flv_msg = 0;
+            }
+        }
+
+        if (flv) {
+            if (is_flv_msg) {
+                if (srs_flv_write_tag(flv, type, timestamp, data, size) != 0) {
+                    srs_human_trace("dump rtmp packet failed.");
+                    goto rtmp_destroy;
+                }
+            } else {
+                srs_human_trace("drop message type=%#x, size=%dB", type, size);
+            }
+        }
+        
+        free(data);
+    }
+    
+rtmp_destroy:
+    srs_rtmp_destroy(rtmp);
+    srs_flv_close(flv);
+    srs_human_trace("completed");
+    
+    return 0;
+}
diff --git a/trunk/research/players/index.html b/trunk/research/players/index.html
index ee2b2687cb..1b5ec57fb4 100644
--- a/trunk/research/players/index.html
+++ b/trunk/research/players/index.html
@@ -56,7 +56,7 @@
 
 
diff --git a/trunk/research/players/js/srs.player.js b/trunk/research/players/js/srs.player.js
index dad0ba9278..f04859ca9c 100755
--- a/trunk/research/players/js/srs.player.js
+++ b/trunk/research/players/js/srs.player.js
@@ -22,7 +22,7 @@ function SrsPlayer(container, width, height, private_object) {
     this.height = height;
     this.id = SrsPlayer.__id++;
     this.stream_url = null;
-    this.buffer_time = 0.8; // default to 0.8
+    this.buffer_time = 0.3; // default to 0.3
     this.volume = 1.0; // default to 100%
     this.callbackObj = null;
     
diff --git a/trunk/research/players/jwplayer6.html b/trunk/research/players/jwplayer6.html
index d95272ab77..b7f41b9f23 100644
--- a/trunk/research/players/jwplayer6.html
+++ b/trunk/research/players/jwplayer6.html
@@ -126,7 +126,7 @@ 

JWPlayer6


diff --git a/trunk/research/players/osmf.html b/trunk/research/players/osmf.html index 379e5ef0cd..561acd59c2 100644 --- a/trunk/research/players/osmf.html +++ b/trunk/research/players/osmf.html @@ -116,7 +116,7 @@

AdobeOSMF


diff --git a/trunk/research/players/srs_bwt.html b/trunk/research/players/srs_bwt.html index 09f26aac31..43e2a96be5 100644 --- a/trunk/research/players/srs_bwt.html +++ b/trunk/research/players/srs_bwt.html @@ -132,7 +132,7 @@

SRS Bandwidth Check


diff --git a/trunk/research/players/srs_bwt/release/srs_bwt.swf b/trunk/research/players/srs_bwt/release/srs_bwt.swf index 0bc6df727a..6f5a4c5f93 100755 Binary files a/trunk/research/players/srs_bwt/release/srs_bwt.swf and b/trunk/research/players/srs_bwt/release/srs_bwt.swf differ diff --git a/trunk/research/players/srs_bwt/src/SrsBandwidth.as b/trunk/research/players/srs_bwt/src/SrsBandwidth.as index 68806ceeed..30d501c4b8 100755 --- a/trunk/research/players/srs_bwt/src/SrsBandwidth.as +++ b/trunk/research/players/srs_bwt/src/SrsBandwidth.as @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/trunk/research/players/srs_bwt/src/srs.bandwidth.js b/trunk/research/players/srs_bwt/src/srs.bandwidth.js index e7abef2143..512b746af6 100755 --- a/trunk/research/players/srs_bwt/src/srs.bandwidth.js +++ b/trunk/research/players/srs_bwt/src/srs.bandwidth.js @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/trunk/research/players/srs_bwt/src/srs_bwt.as b/trunk/research/players/srs_bwt/src/srs_bwt.as index 4fd5fdeadf..10bccc5c33 100755 --- a/trunk/research/players/srs_bwt/src/srs_bwt.as +++ b/trunk/research/players/srs_bwt/src/srs_bwt.as @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/trunk/research/players/srs_chat.html b/trunk/research/players/srs_chat.html index 70eee5bd60..b0cf2f4471 100644 --- a/trunk/research/players/srs_chat.html +++ b/trunk/research/players/srs_chat.html @@ -781,7 +781,7 @@

音频编码


diff --git a/trunk/research/players/srs_player.html b/trunk/research/players/srs_player.html old mode 100644 new mode 100755 index 4e26ed0468..8f506628dc --- a/trunk/research/players/srs_player.html +++ b/trunk/research/players/srs_player.html @@ -96,7 +96,7 @@ srs_player = new SrsPlayer("player_id", srs_get_player_width(), srs_get_player_height()); srs_player.on_player_ready = function() { - select_buffer_time("#btn_bt_0_8", 0.8); + select_buffer_time("#btn_bt_0_1", 0.1); this.play(url); }; srs_player.on_player_metadata = function(metadata) { @@ -231,6 +231,15 @@ } if (true) { + $("#btn_bt_0_1").click(function(){ + select_buffer_time("#btn_bt_0_1", 0.1); + }); + $("#btn_bt_0_2").click(function(){ + select_buffer_time("#btn_bt_0_2", 0.2); + }); + $("#btn_bt_0_3").click(function(){ + select_buffer_time("#btn_bt_0_3", 0.3); + }); $("#btn_bt_0_5").click(function(){ select_buffer_time("#btn_bt_0_5", 0.5); }); @@ -297,7 +306,7 @@

- 注意:必须按照SRS DEMO + 注意:必须按照SRS DEMO 启动和设置服务器,下面所有的链接才能观看。
@@ -462,7 +471,7 @@ \ No newline at end of file diff --git a/trunk/research/players/srs_player/release/srs_player.swf b/trunk/research/players/srs_player/release/srs_player.swf index a6754aedc1..9a2ead0637 100755 Binary files a/trunk/research/players/srs_player/release/srs_player.swf and b/trunk/research/players/srs_player/release/srs_player.swf differ diff --git a/trunk/research/players/srs_publisher.html b/trunk/research/players/srs_publisher.html index 1bf431b233..6c253f085f 100644 --- a/trunk/research/players/srs_publisher.html +++ b/trunk/research/players/srs_publisher.html @@ -460,7 +460,7 @@

音频编码

diff --git a/trunk/research/players/srs_publisher/release/srs_publisher.swf b/trunk/research/players/srs_publisher/release/srs_publisher.swf index 15e92bba00..24644897f4 100755 Binary files a/trunk/research/players/srs_publisher/release/srs_publisher.swf and b/trunk/research/players/srs_publisher/release/srs_publisher.swf differ diff --git a/trunk/research/players/srs_reuse_conn/.actionScriptProperties b/trunk/research/players/srs_reuse_conn/.actionScriptProperties new file mode 100755 index 0000000000..ea33e282e2 --- /dev/null +++ b/trunk/research/players/srs_reuse_conn/.actionScriptProperties @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/trunk/research/players/srs_reuse_conn/.project b/trunk/research/players/srs_reuse_conn/.project new file mode 100755 index 0000000000..11dc3f9c2c --- /dev/null +++ b/trunk/research/players/srs_reuse_conn/.project @@ -0,0 +1,17 @@ + + + srs_reuse_conn + + + + + + com.adobe.flexbuilder.project.flexbuilder + + + + + + com.adobe.flexbuilder.project.actionscriptnature + + diff --git a/trunk/research/players/srs_reuse_conn/.settings/org.eclipse.core.resources.prefs b/trunk/research/players/srs_reuse_conn/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000000..0fc6bb311a --- /dev/null +++ b/trunk/research/players/srs_reuse_conn/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,3 @@ +#Sat Nov 22 18:40:22 CST 2014 +eclipse.preferences.version=1 +encoding/=utf-8 diff --git a/trunk/research/players/srs_reuse_conn/FlashCS5UI.swc b/trunk/research/players/srs_reuse_conn/FlashCS5UI.swc new file mode 100644 index 0000000000..ea19d9d5c5 Binary files /dev/null and b/trunk/research/players/srs_reuse_conn/FlashCS5UI.swc differ diff --git a/trunk/research/players/srs_reuse_conn/release/srs_reuse_conn.swf b/trunk/research/players/srs_reuse_conn/release/srs_reuse_conn.swf new file mode 100644 index 0000000000..c072720169 Binary files /dev/null and b/trunk/research/players/srs_reuse_conn/release/srs_reuse_conn.swf differ diff --git a/trunk/research/players/srs_reuse_conn/src/srs_reuse_conn.as b/trunk/research/players/srs_reuse_conn/src/srs_reuse_conn.as new file mode 100644 index 0000000000..c534b647bd --- /dev/null +++ b/trunk/research/players/srs_reuse_conn/src/srs_reuse_conn.as @@ -0,0 +1,123 @@ +package +{ + import fl.controls.Button; + import fl.controls.TextInput; + + import flash.display.Sprite; + import flash.display.StageAlign; + import flash.display.StageScaleMode; + import flash.events.Event; + import flash.events.MouseEvent; + import flash.events.NetStatusEvent; + import flash.media.Video; + import flash.net.NetConnection; + import flash.net.NetStream; + + [SWF(backgroundColor="0xEEEEEE",frameRate="30",width="1024",height="576")] + public class srs_reuse_conn extends Sprite + { + public function srs_reuse_conn() + { + if (stage) { + onAddedToStage(null); + } else { + addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); + } + } + + private function onAddedToStage(evt:Event):void + { + stage.align = StageAlign.TOP_LEFT; + stage.scaleMode = StageScaleMode.NO_SCALE; + + var txtUrl:TextInput = new TextInput(); + var btnConn:Button = new Button(); + var btnPlay:Button = new Button(); + + txtUrl.x = 10; + txtUrl.y = 10; + txtUrl.width = 400; + txtUrl.text = "rtmp://dev/live/livestream"; + addChild(txtUrl); + + btnConn.label = "Connect"; + btnConn.x = txtUrl.x + txtUrl.width + 10; + btnConn.y = txtUrl.y; + btnConn.width = 100; + addChild(btnConn); + + btnPlay.label = "Play"; + btnPlay.x = btnConn.x + btnConn.width + 10; + btnPlay.y = btnConn.y; + btnPlay.width = 100; + addChild(btnPlay); + + var video:Video = new Video(); + video.x = txtUrl.x; + video.y = txtUrl.y + txtUrl.height + 10; + addChild(video); + + var conn:NetConnection = null; + var stream:NetStream = null; + + var tcUrl:Function = function():String { + var url:String = txtUrl.text; + return url.substr(0, url.lastIndexOf("/")); + } + var streamName:Function = function():String { + var url:String = txtUrl.text; + return url.substr(tcUrl().length + 1); + } + + var closeConnection:Function = function():void { + if (stream) { + stream.close(); + stream = null; + } + if (conn) { + conn.close(); + conn = null; + } + btnConn.label = "Connect"; + btnPlay.visible = false; + }; + + btnPlay.visible = false; + btnConn.addEventListener(MouseEvent.CLICK, function(e:MouseEvent):void { + if (btnConn.label == "Connect") { + conn = new NetConnection(); + conn.client = { + onBWDone: function():void{} + }; + conn.addEventListener(NetStatusEvent.NET_STATUS, function(ne:NetStatusEvent):void { + if (ne.info.code == "NetConnection.Connect.Success") { + btnPlay.visible = true; + } else if (ne.info.code == "NetConnection.Connect.Closed") { + closeConnection(); + } + trace(ne.info.code); + }); + conn.connect(tcUrl()); + btnConn.label = "Close"; + } else { + closeConnection(); + } + }); + btnPlay.addEventListener(MouseEvent.CLICK, function(e:MouseEvent):void { + if (stream) { + stream.close(); + stream = null; + } + stream = new NetStream(conn); + stream.client = { + onMetaData: function(metadata:Object):void { + video.width = metadata.width; + video.height = metadata.height; + } + }; + video.attachNetStream(stream); + stream.play(streamName()); + }); + } + } +} \ No newline at end of file diff --git a/trunk/research/players/vlc.html b/trunk/research/players/vlc.html index 18e7561dd6..015ac7e885 100644 --- a/trunk/research/players/vlc.html +++ b/trunk/research/players/vlc.html @@ -45,7 +45,7 @@ diff --git a/trunk/research/st/Makefile b/trunk/research/st/Makefile new file mode 100755 index 0000000000..0b24bb80c2 --- /dev/null +++ b/trunk/research/st/Makefile @@ -0,0 +1,100 @@ +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Netscape Portable Runtime library. +# +# The Initial Developer of the Original Code is Netscape +# Communications Corporation. Portions created by Netscape are +# Copyright (C) 1994-2000 Netscape Communications Corporation. All +# Rights Reserved. +# +# Contributor(s): Silicon Graphics, Inc. +# +# Portions created by SGI are Copyright (C) 2000-2001 Silicon +# Graphics, Inc. All Rights Reserved. +# +# Alternatively, the contents of this file may be used under the +# terms of the GNU General Public License Version 2 or later (the +# "GPL"), in which case the provisions of the GPL are applicable +# instead of those above. If you wish to allow use of your +# version of this file only under the terms of the GPL and not to +# allow others to use your version of this file under the MPL, +# indicate your decision by deleting the provisions above and +# replace them with the notice and other provisions required by +# the GPL. If you do not delete the provisions above, a recipient +# may use your version of this file under either the MPL or the +# GPL. + +########################## +# Target dir and cc: +CC = cc +TARGETDIR = objs + +########################## +# Supported OSes: +OS = LINUX + +ifneq ($(shell test -f /usr/include/sys/epoll.h && echo yes), yes) +default: + @echo "epoll not found" + @exit 1 +endif + +EXTRA_OBJS = $(TARGETDIR)/md.o + +CFLAGS = +OTHER_FLAGS += -Wall -g -O0 +DEFINES = -D$(OS) -DDEBUG -DMD_HAVE_EPOLL -DMALLOC_STACK + +########################## +# Other possible defines: +# To use malloc(3) instead of mmap(2) for stack allocation: +# DEFINES += -DMALLOC_STACK +# +# To provision more than the default 16 thread-specific-data keys +# (but not too many!): +# DEFINES += -DST_KEYS_MAX= +# +# Note that you can also add these defines by specifying them as +# make/gmake arguments (without editing this Makefile). For example: +# +# make EXTRA_CFLAGS=-UMD_HAVE_EPOLL +# +########################## + +CFLAGS += $(DEFINES) $(OTHER_FLAGS) $(EXTRA_CFLAGS) + +OBJS = $(TARGETDIR)/sched.o \ + $(TARGETDIR)/stk.o \ + $(TARGETDIR)/sync.o \ + $(TARGETDIR)/key.o \ + $(TARGETDIR)/io.o \ + $(TARGETDIR)/event.o \ + $(TARGETDIR)/srs.o +OBJS += $(EXTRA_OBJS) +SRS = $(TARGETDIR)/srs + +linux-debug: all +all: $(TARGETDIR) $(SRS) + +$(TARGETDIR): + if [ ! -d $(TARGETDIR) ]; then mkdir $(TARGETDIR); fi + +$(SRS): $(OBJS) + $(CC) $(CFLAGS) -o $@ $(OBJS) + +$(TARGETDIR)/md.o: md.S + $(CC) $(CFLAGS) -c $< -o $@ + +$(TARGETDIR)/%.o: %.c common.h md.h Makefile + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + rm -rf $(TARGETDIR) diff --git a/trunk/research/st/common.h b/trunk/research/st/common.h new file mode 100644 index 0000000000..b83e575caf --- /dev/null +++ b/trunk/research/st/common.h @@ -0,0 +1,445 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape Portable Runtime library. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): Silicon Graphics, Inc. + * + * Portions created by SGI are Copyright (C) 2000-2001 Silicon + * Graphics, Inc. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * This file is derived directly from Netscape Communications Corporation, + * and consists of extensive modifications made during the year(s) 1999-2000. + */ + +#ifndef __ST_COMMON_H__ +#define __ST_COMMON_H__ + +#include +#include +#include +#include +#include + +/* Enable assertions only if DEBUG is defined */ +#ifndef DEBUG + #define NDEBUG +#endif +#include +#define ST_ASSERT(expr) assert(expr) + +#define ST_BEGIN_MACRO { +#define ST_END_MACRO } + +#ifdef DEBUG + #define ST_HIDDEN /*nothing*/ +#else + #define ST_HIDDEN static +#endif + +#include "public.h" +#include "md.h" + +/***************************************** + * Circular linked list definitions + */ + +typedef struct _st_clist { + struct _st_clist *next; + struct _st_clist *prev; +} _st_clist_t; + +/* Insert element "_e" into the list, before "_l" */ +#define ST_INSERT_BEFORE(_e,_l) \ + ST_BEGIN_MACRO \ + (_e)->next = (_l); \ + (_e)->prev = (_l)->prev; \ + (_l)->prev->next = (_e); \ + (_l)->prev = (_e); \ + ST_END_MACRO + +/* Insert element "_e" into the list, after "_l" */ +#define ST_INSERT_AFTER(_e,_l) \ + ST_BEGIN_MACRO \ + (_e)->next = (_l)->next; \ + (_e)->prev = (_l); \ + (_l)->next->prev = (_e); \ + (_l)->next = (_e); \ + ST_END_MACRO + +/* Return the element following element "_e" */ +#define ST_NEXT_LINK(_e) ((_e)->next) + +/* Append an element "_e" to the end of the list "_l" */ +#define ST_APPEND_LINK(_e,_l) ST_INSERT_BEFORE(_e,_l) + +/* Insert an element "_e" at the head of the list "_l" */ +#define ST_INSERT_LINK(_e,_l) ST_INSERT_AFTER(_e,_l) + +/* Return the head/tail of the list */ +#define ST_LIST_HEAD(_l) (_l)->next +#define ST_LIST_TAIL(_l) (_l)->prev + +/* Remove the element "_e" from it's circular list */ +#define ST_REMOVE_LINK(_e) \ + ST_BEGIN_MACRO \ + (_e)->prev->next = (_e)->next; \ + (_e)->next->prev = (_e)->prev; \ + ST_END_MACRO + +/* Return non-zero if the given circular list "_l" is empty, */ +/* zero if the circular list is not empty */ +#define ST_CLIST_IS_EMPTY(_l) \ + ((_l)->next == (_l)) + +/* Initialize a circular list */ +#define ST_INIT_CLIST(_l) \ + ST_BEGIN_MACRO \ + (_l)->next = (_l); \ + (_l)->prev = (_l); \ + ST_END_MACRO + +#define ST_INIT_STATIC_CLIST(_l) \ + {(_l), (_l)} + + +/***************************************** + * Basic types definitions + */ + +typedef void (*_st_destructor_t)(void *); + +typedef struct _st_stack { + _st_clist_t links; + char *vaddr; /* Base of stack's allocated memory */ + int vaddr_size; /* Size of stack's allocated memory */ + int stk_size; /* Size of usable portion of the stack */ + char *stk_bottom; /* Lowest address of stack's usable portion */ + char *stk_top; /* Highest address of stack's usable portion */ + void *sp; /* Stack pointer from C's point of view */ +} _st_stack_t; + + +typedef struct _st_cond { + _st_clist_t wait_q; /* Condition variable wait queue */ +} _st_cond_t; + + +typedef struct _st_thread _st_thread_t; + +struct _st_thread { + int state; /* Thread's state */ + int flags; /* Thread's flags */ + + void *(*start)(void *arg); /* The start function of the thread */ + void *arg; /* Argument of the start function */ + void *retval; /* Return value of the start function */ + + _st_stack_t *stack; /* Info about thread's stack */ + + _st_clist_t links; /* For putting on run/sleep/zombie queue */ + _st_clist_t wait_links; /* For putting on mutex/condvar wait queue */ + #ifdef DEBUG + _st_clist_t tlink; /* For putting on thread queue */ + #endif + + st_utime_t due; /* Wakeup time when thread is sleeping */ + _st_thread_t *left; /* For putting in timeout heap */ + _st_thread_t *right; /* -- see docs/timeout_heap.txt for details */ + int heap_index; + + void **private_data; /* Per thread private data */ + + _st_cond_t *term; /* Termination condition variable for join */ + + jmp_buf context; /* Thread's context */ +}; + + +typedef struct _st_mutex { + _st_thread_t *owner; /* Current mutex owner */ + _st_clist_t wait_q; /* Mutex wait queue */ +} _st_mutex_t; + + +typedef struct _st_pollq { + _st_clist_t links; /* For putting on io queue */ + _st_thread_t *thread; /* Polling thread */ + struct pollfd *pds; /* Array of poll descriptors */ + int npds; /* Length of the array */ + int on_ioq; /* Is it on ioq? */ +} _st_pollq_t; + + +typedef struct _st_eventsys_ops { + const char *name; /* Name of this event system */ + int val; /* Type of this event system */ + int (*init)(void); /* Initialization */ + void (*dispatch)(void); /* Dispatch function */ + int (*pollset_add)(struct pollfd *, int); /* Add descriptor set */ + void (*pollset_del)(struct pollfd *, int); /* Delete descriptor set */ + int (*fd_new)(int); /* New descriptor allocated */ + int (*fd_close)(int); /* Descriptor closed */ + int (*fd_getlimit)(void); /* Descriptor hard limit */ +} _st_eventsys_t; + + +typedef struct _st_vp { + _st_thread_t *idle_thread; /* Idle thread for this vp */ + st_utime_t last_clock; /* The last time we went into vp_check_clock() */ + + _st_clist_t run_q; /* run queue for this vp */ + _st_clist_t io_q; /* io queue for this vp */ + _st_clist_t zombie_q; /* zombie queue for this vp */ + #ifdef DEBUG + _st_clist_t thread_q; /* all threads of this vp */ + #endif + int pagesize; + + _st_thread_t *sleep_q; /* sleep queue for this vp */ + int sleepq_size; /* number of threads on sleep queue */ + + #ifdef ST_SWITCH_CB + st_switch_cb_t switch_out_cb; /* called when a thread is switched out */ + st_switch_cb_t switch_in_cb; /* called when a thread is switched in */ + #endif +} _st_vp_t; + + +typedef struct _st_netfd { + int osfd; /* Underlying OS file descriptor */ + int inuse; /* In-use flag */ + void *private_data; /* Per descriptor private data */ + _st_destructor_t destructor; /* Private data destructor function */ + void *aux_data; /* Auxiliary data for internal use */ + struct _st_netfd *next; /* For putting on the free list */ +} _st_netfd_t; + + +/***************************************** + * Current vp, thread, and event system + */ + +extern _st_vp_t _st_this_vp; +extern _st_thread_t *_st_this_thread; +extern _st_eventsys_t *_st_eventsys; + +#define _ST_CURRENT_THREAD() (_st_this_thread) +#define _ST_SET_CURRENT_THREAD(_thread) (_st_this_thread = (_thread)) + +#define _ST_LAST_CLOCK (_st_this_vp.last_clock) + +#define _ST_RUNQ (_st_this_vp.run_q) +#define _ST_IOQ (_st_this_vp.io_q) +#define _ST_ZOMBIEQ (_st_this_vp.zombie_q) +#ifdef DEBUG + #define _ST_THREADQ (_st_this_vp.thread_q) +#endif + +#define _ST_PAGE_SIZE (_st_this_vp.pagesize) + +#define _ST_SLEEPQ (_st_this_vp.sleep_q) +#define _ST_SLEEPQ_SIZE (_st_this_vp.sleepq_size) + +#define _ST_VP_IDLE() (*_st_eventsys->dispatch)() + + +/***************************************** + * vp queues operations + */ + +#define _ST_ADD_IOQ(_pq) ST_APPEND_LINK(&_pq.links, &_ST_IOQ) +#define _ST_DEL_IOQ(_pq) ST_REMOVE_LINK(&_pq.links) + +#define _ST_ADD_RUNQ(_thr) ST_APPEND_LINK(&(_thr)->links, &_ST_RUNQ) +#define _ST_DEL_RUNQ(_thr) ST_REMOVE_LINK(&(_thr)->links) + +#define _ST_ADD_SLEEPQ(_thr, _timeout) _st_add_sleep_q(_thr, _timeout) +#define _ST_DEL_SLEEPQ(_thr) _st_del_sleep_q(_thr) + +#define _ST_ADD_ZOMBIEQ(_thr) ST_APPEND_LINK(&(_thr)->links, &_ST_ZOMBIEQ) +#define _ST_DEL_ZOMBIEQ(_thr) ST_REMOVE_LINK(&(_thr)->links) + +#ifdef DEBUG + #define _ST_ADD_THREADQ(_thr) ST_APPEND_LINK(&(_thr)->tlink, &_ST_THREADQ) + #define _ST_DEL_THREADQ(_thr) ST_REMOVE_LINK(&(_thr)->tlink) +#endif + + +/***************************************** + * Thread states and flags + */ + +#define _ST_ST_RUNNING 0 +#define _ST_ST_RUNNABLE 1 +#define _ST_ST_IO_WAIT 2 +#define _ST_ST_LOCK_WAIT 3 +#define _ST_ST_COND_WAIT 4 +#define _ST_ST_SLEEPING 5 +#define _ST_ST_ZOMBIE 6 +#define _ST_ST_SUSPENDED 7 + +#define _ST_FL_PRIMORDIAL 0x01 +#define _ST_FL_IDLE_THREAD 0x02 +#define _ST_FL_ON_SLEEPQ 0x04 +#define _ST_FL_INTERRUPT 0x08 +#define _ST_FL_TIMEDOUT 0x10 + +/***************************************** + * Pointer conversion + */ + +#ifndef offsetof + #define offsetof(type, identifier) ((size_t)&(((type *)0)->identifier)) +#endif + +#define _ST_THREAD_PTR(_qp) \ + ((_st_thread_t *)((char *)(_qp) - offsetof(_st_thread_t, links))) + +#define _ST_THREAD_WAITQ_PTR(_qp) \ + ((_st_thread_t *)((char *)(_qp) - offsetof(_st_thread_t, wait_links))) + +#define _ST_THREAD_STACK_PTR(_qp) \ + ((_st_stack_t *)((char*)(_qp) - offsetof(_st_stack_t, links))) + +#define _ST_POLLQUEUE_PTR(_qp) \ + ((_st_pollq_t *)((char *)(_qp) - offsetof(_st_pollq_t, links))) + +#ifdef DEBUG + #define _ST_THREAD_THREADQ_PTR(_qp) \ + ((_st_thread_t *)((char *)(_qp) - offsetof(_st_thread_t, tlink))) +#endif + + +/***************************************** + * Constants + */ + +#ifndef ST_UTIME_NO_TIMEOUT + #define ST_UTIME_NO_TIMEOUT ((st_utime_t) -1LL) +#endif + +#define ST_DEFAULT_STACK_SIZE (64*1024) + +#ifndef ST_KEYS_MAX + #define ST_KEYS_MAX 16 +#endif + +#ifndef ST_MIN_POLLFDS_SIZE + #define ST_MIN_POLLFDS_SIZE 64 +#endif + + +/***************************************** + * Threads context switching + */ + +#ifdef DEBUG + void _st_iterate_threads(void); + #define ST_DEBUG_ITERATE_THREADS() _st_iterate_threads() +#else + #define ST_DEBUG_ITERATE_THREADS() +#endif + +#ifdef ST_SWITCH_CB + #define ST_SWITCH_OUT_CB(_thread) \ + if (_st_this_vp.switch_out_cb != NULL && \ + _thread != _st_this_vp.idle_thread && \ + _thread->state != _ST_ST_ZOMBIE) { \ + _st_this_vp.switch_out_cb(); \ + } + #define ST_SWITCH_IN_CB(_thread) \ + if (_st_this_vp.switch_in_cb != NULL && \ + _thread != _st_this_vp.idle_thread && \ + _thread->state != _ST_ST_ZOMBIE) { \ + _st_this_vp.switch_in_cb(); \ + } +#else + #define ST_SWITCH_OUT_CB(_thread) + #define ST_SWITCH_IN_CB(_thread) +#endif + +/* + * Switch away from the current thread context by saving its state and + * calling the thread scheduler + */ +#define _ST_SWITCH_CONTEXT(_thread) \ + ST_BEGIN_MACRO \ + ST_SWITCH_OUT_CB(_thread); \ + if (!MD_SETJMP((_thread)->context)) { \ + _st_vp_schedule(); \ + } \ + ST_DEBUG_ITERATE_THREADS(); \ + ST_SWITCH_IN_CB(_thread); \ + ST_END_MACRO + +/* + * Restore a thread context that was saved by _ST_SWITCH_CONTEXT or + * initialized by _ST_INIT_CONTEXT + */ +#define _ST_RESTORE_CONTEXT(_thread) \ + ST_BEGIN_MACRO \ + _ST_SET_CURRENT_THREAD(_thread); \ + MD_LONGJMP((_thread)->context, 1); \ + ST_END_MACRO + +/* + * Number of bytes reserved under the stack "bottom" + */ +#define _ST_STACK_PAD_SIZE MD_STACK_PAD_SIZE + + +/***************************************** + * Forward declarations + */ + +void _st_vp_schedule(void); +void _st_vp_check_clock(void); +void *_st_idle_thread_start(void *arg); +void _st_thread_main(void); +void _st_thread_cleanup(_st_thread_t *thread); +void _st_add_sleep_q(_st_thread_t *thread, st_utime_t timeout); +void _st_del_sleep_q(_st_thread_t *thread); +_st_stack_t *_st_stack_new(int stack_size); +void _st_stack_free(_st_stack_t *ts); +int _st_io_init(void); + +st_utime_t st_utime(void); +_st_cond_t *st_cond_new(void); +int st_cond_destroy(_st_cond_t *cvar); +int st_cond_timedwait(_st_cond_t *cvar, st_utime_t timeout); +int st_cond_signal(_st_cond_t *cvar); +ssize_t st_read(_st_netfd_t *fd, void *buf, size_t nbyte, st_utime_t timeout); +ssize_t st_write(_st_netfd_t *fd, const void *buf, size_t nbyte, st_utime_t timeout); +int st_poll(struct pollfd *pds, int npds, st_utime_t timeout); +_st_thread_t *st_thread_create(void *(*start)(void *arg), void *arg, int joinable, int stk_size); + +#endif /* !__ST_COMMON_H__ */ + diff --git a/trunk/research/st/event.c b/trunk/research/st/event.c new file mode 100644 index 0000000000..10b1dd15ad --- /dev/null +++ b/trunk/research/st/event.c @@ -0,0 +1,483 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape Portable Runtime library. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): Silicon Graphics, Inc. + * Yahoo! Inc. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +#include +#include +#include +#include +#include +#include +#include "common.h" + +#ifdef USE_POLL + #error "Not support USE_POLL" +#endif +#ifdef MD_HAVE_KQUEUE + #error "Not support MD_HAVE_KQUEUE" +#endif +#ifdef MD_HAVE_POLL + #error "Not support MD_HAVE_POLL" +#endif +#ifndef MD_HAVE_EPOLL + #error "Only support MD_HAVE_EPOLL" +#endif + +#include + +typedef struct _epoll_fd_data { + int rd_ref_cnt; + int wr_ref_cnt; + int ex_ref_cnt; + int revents; +} _epoll_fd_data_t; + +static struct _st_epolldata { + _epoll_fd_data_t *fd_data; + struct epoll_event *evtlist; + int fd_data_size; + int evtlist_size; + int evtlist_cnt; + int fd_hint; + int epfd; + pid_t pid; +} *_st_epoll_data; + +#ifndef ST_EPOLL_EVTLIST_SIZE +/* Not a limit, just a hint */ +#define ST_EPOLL_EVTLIST_SIZE 4096 +#endif + +#define _ST_EPOLL_READ_CNT(fd) (_st_epoll_data->fd_data[fd].rd_ref_cnt) +#define _ST_EPOLL_WRITE_CNT(fd) (_st_epoll_data->fd_data[fd].wr_ref_cnt) +#define _ST_EPOLL_EXCEP_CNT(fd) (_st_epoll_data->fd_data[fd].ex_ref_cnt) +#define _ST_EPOLL_REVENTS(fd) (_st_epoll_data->fd_data[fd].revents) + +#define _ST_EPOLL_READ_BIT(fd) (_ST_EPOLL_READ_CNT(fd) ? EPOLLIN : 0) +#define _ST_EPOLL_WRITE_BIT(fd) (_ST_EPOLL_WRITE_CNT(fd) ? EPOLLOUT : 0) +#define _ST_EPOLL_EXCEP_BIT(fd) (_ST_EPOLL_EXCEP_CNT(fd) ? EPOLLPRI : 0) +#define _ST_EPOLL_EVENTS(fd) \ + (_ST_EPOLL_READ_BIT(fd)|_ST_EPOLL_WRITE_BIT(fd)|_ST_EPOLL_EXCEP_BIT(fd)) + +_st_eventsys_t *_st_eventsys = NULL; + +/***************************************** + * epoll event system + */ + +ST_HIDDEN int _st_epoll_init(void) +{ + int fdlim; + int err = 0; + int rv = 0; + + _st_epoll_data = (struct _st_epolldata *) calloc(1, sizeof(*_st_epoll_data)); + if (!_st_epoll_data) { + return -1; + } + + fdlim = st_getfdlimit(); + _st_epoll_data->fd_hint = (fdlim > 0 && fdlim < ST_EPOLL_EVTLIST_SIZE) ? fdlim : ST_EPOLL_EVTLIST_SIZE; + + if ((_st_epoll_data->epfd = epoll_create(_st_epoll_data->fd_hint)) < 0) { + err = errno; + rv = -1; + goto cleanup_epoll; + } + fcntl(_st_epoll_data->epfd, F_SETFD, FD_CLOEXEC); + _st_epoll_data->pid = getpid(); + + /* Allocate file descriptor data array */ + _st_epoll_data->fd_data_size = _st_epoll_data->fd_hint; + _st_epoll_data->fd_data = (_epoll_fd_data_t *)calloc(_st_epoll_data->fd_data_size, sizeof(_epoll_fd_data_t)); + if (!_st_epoll_data->fd_data) { + err = errno; + rv = -1; + goto cleanup_epoll; + } + + /* Allocate event lists */ + _st_epoll_data->evtlist_size = _st_epoll_data->fd_hint; + _st_epoll_data->evtlist = (struct epoll_event *)malloc(_st_epoll_data->evtlist_size * sizeof(struct epoll_event)); + if (!_st_epoll_data->evtlist) { + err = errno; + rv = -1; + } + + cleanup_epoll: + if (rv < 0) { + if (_st_epoll_data->epfd >= 0) { + close(_st_epoll_data->epfd); + } + free(_st_epoll_data->fd_data); + free(_st_epoll_data->evtlist); + free(_st_epoll_data); + _st_epoll_data = NULL; + errno = err; + } + + return rv; +} + +ST_HIDDEN int _st_epoll_fd_data_expand(int maxfd) +{ + _epoll_fd_data_t *ptr; + int n = _st_epoll_data->fd_data_size; + + while (maxfd >= n) { + n <<= 1; + } + + ptr = (_epoll_fd_data_t *)realloc(_st_epoll_data->fd_data, n * sizeof(_epoll_fd_data_t)); + if (!ptr) { + return -1; + } + + memset(ptr + _st_epoll_data->fd_data_size, 0, (n - _st_epoll_data->fd_data_size) * sizeof(_epoll_fd_data_t)); + + _st_epoll_data->fd_data = ptr; + _st_epoll_data->fd_data_size = n; + + return 0; +} + +ST_HIDDEN void _st_epoll_evtlist_expand(void) +{ + struct epoll_event *ptr; + int n = _st_epoll_data->evtlist_size; + + while (_st_epoll_data->evtlist_cnt > n) { + n <<= 1; + } + + ptr = (struct epoll_event *)realloc(_st_epoll_data->evtlist, n * sizeof(struct epoll_event)); + if (ptr) { + _st_epoll_data->evtlist = ptr; + _st_epoll_data->evtlist_size = n; + } +} + +ST_HIDDEN void _st_epoll_pollset_del(struct pollfd *pds, int npds) +{ + struct epoll_event ev; + struct pollfd *pd; + struct pollfd *epd = pds + npds; + int old_events, events, op; + + /* + * It's more or less OK if deleting fails because a descriptor + * will either be closed or deleted in dispatch function after + * it fires. + */ + for (pd = pds; pd < epd; pd++) { + old_events = _ST_EPOLL_EVENTS(pd->fd); + + if (pd->events & POLLIN) { + _ST_EPOLL_READ_CNT(pd->fd)--; + } + if (pd->events & POLLOUT) { + _ST_EPOLL_WRITE_CNT(pd->fd)--; + } + if (pd->events & POLLPRI) { + _ST_EPOLL_EXCEP_CNT(pd->fd)--; + } + + events = _ST_EPOLL_EVENTS(pd->fd); + /* + * The _ST_EPOLL_REVENTS check below is needed so we can use + * this function inside dispatch(). Outside of dispatch() + * _ST_EPOLL_REVENTS is always zero for all descriptors. + */ + if (events != old_events && _ST_EPOLL_REVENTS(pd->fd) == 0) { + op = events ? EPOLL_CTL_MOD : EPOLL_CTL_DEL; + ev.events = events; + ev.data.fd = pd->fd; + if (epoll_ctl(_st_epoll_data->epfd, op, pd->fd, &ev) == 0 && op == EPOLL_CTL_DEL) { + _st_epoll_data->evtlist_cnt--; + } + } + } +} + +ST_HIDDEN int _st_epoll_pollset_add(struct pollfd *pds, int npds) +{ + struct epoll_event ev; + int i, fd; + int old_events, events, op; + + /* Do as many checks as possible up front */ + for (i = 0; i < npds; i++) { + fd = pds[i].fd; + if (fd < 0 || !pds[i].events || (pds[i].events & ~(POLLIN | POLLOUT | POLLPRI))) { + errno = EINVAL; + return -1; + } + if (fd >= _st_epoll_data->fd_data_size && _st_epoll_fd_data_expand(fd) < 0) { + return -1; + } + } + + for (i = 0; i < npds; i++) { + fd = pds[i].fd; + old_events = _ST_EPOLL_EVENTS(fd); + + if (pds[i].events & POLLIN) { + _ST_EPOLL_READ_CNT(fd)++; + } + if (pds[i].events & POLLOUT) { + _ST_EPOLL_WRITE_CNT(fd)++; + } + if (pds[i].events & POLLPRI) { + _ST_EPOLL_EXCEP_CNT(fd)++; + } + + events = _ST_EPOLL_EVENTS(fd); + if (events != old_events) { + op = old_events ? EPOLL_CTL_MOD : EPOLL_CTL_ADD; + ev.events = events; + ev.data.fd = fd; + if (epoll_ctl(_st_epoll_data->epfd, op, fd, &ev) < 0 && (op != EPOLL_CTL_ADD || errno != EEXIST)) { + break; + } + if (op == EPOLL_CTL_ADD) { + _st_epoll_data->evtlist_cnt++; + if (_st_epoll_data->evtlist_cnt > _st_epoll_data->evtlist_size) { + _st_epoll_evtlist_expand(); + } + } + } + } + + if (i < npds) { + /* Error */ + int err = errno; + /* Unroll the state */ + _st_epoll_pollset_del(pds, i + 1); + errno = err; + return -1; + } + + return 0; +} + +ST_HIDDEN void _st_epoll_dispatch(void) +{ + st_utime_t min_timeout; + _st_clist_t *q; + _st_pollq_t *pq; + struct pollfd *pds, *epds; + struct epoll_event ev; + int timeout, nfd, i, osfd, notify; + int events, op; + short revents; + + if (_ST_SLEEPQ == NULL) { + timeout = -1; + } else { + min_timeout = (_ST_SLEEPQ->due <= _ST_LAST_CLOCK) ? 0 : (_ST_SLEEPQ->due - _ST_LAST_CLOCK); + timeout = (int) (min_timeout / 1000); + } + + if (_st_epoll_data->pid != getpid()) { + // WINLIN: remove it for bug introduced. + // @see: https://github.com/simple-rtmp-server/srs/issues/193 + exit(-1); + } + + /* Check for I/O operations */ + nfd = epoll_wait(_st_epoll_data->epfd, _st_epoll_data->evtlist, _st_epoll_data->evtlist_size, timeout); + + if (nfd > 0) { + for (i = 0; i < nfd; i++) { + osfd = _st_epoll_data->evtlist[i].data.fd; + _ST_EPOLL_REVENTS(osfd) = _st_epoll_data->evtlist[i].events; + if (_ST_EPOLL_REVENTS(osfd) & (EPOLLERR | EPOLLHUP)) { + /* Also set I/O bits on error */ + _ST_EPOLL_REVENTS(osfd) |= _ST_EPOLL_EVENTS(osfd); + } + } + + for (q = _ST_IOQ.next; q != &_ST_IOQ; q = q->next) { + pq = _ST_POLLQUEUE_PTR(q); + notify = 0; + epds = pq->pds + pq->npds; + + for (pds = pq->pds; pds < epds; pds++) { + if (_ST_EPOLL_REVENTS(pds->fd) == 0) { + pds->revents = 0; + continue; + } + osfd = pds->fd; + events = pds->events; + revents = 0; + if ((events & POLLIN) && (_ST_EPOLL_REVENTS(osfd) & EPOLLIN)) { + revents |= POLLIN; + } + if ((events & POLLOUT) && (_ST_EPOLL_REVENTS(osfd) & EPOLLOUT)) { + revents |= POLLOUT; + } + if ((events & POLLPRI) && (_ST_EPOLL_REVENTS(osfd) & EPOLLPRI)) { + revents |= POLLPRI; + } + if (_ST_EPOLL_REVENTS(osfd) & EPOLLERR) { + revents |= POLLERR; + } + if (_ST_EPOLL_REVENTS(osfd) & EPOLLHUP) { + revents |= POLLHUP; + } + + pds->revents = revents; + if (revents) { + notify = 1; + } + } + if (notify) { + ST_REMOVE_LINK(&pq->links); + pq->on_ioq = 0; + /* + * Here we will only delete/modify descriptors that + * didn't fire (see comments in _st_epoll_pollset_del()). + */ + _st_epoll_pollset_del(pq->pds, pq->npds); + + if (pq->thread->flags & _ST_FL_ON_SLEEPQ) { + _ST_DEL_SLEEPQ(pq->thread); + } + pq->thread->state = _ST_ST_RUNNABLE; + _ST_ADD_RUNQ(pq->thread); + } + } + + for (i = 0; i < nfd; i++) { + /* Delete/modify descriptors that fired */ + osfd = _st_epoll_data->evtlist[i].data.fd; + _ST_EPOLL_REVENTS(osfd) = 0; + events = _ST_EPOLL_EVENTS(osfd); + op = events ? EPOLL_CTL_MOD : EPOLL_CTL_DEL; + ev.events = events; + ev.data.fd = osfd; + if (epoll_ctl(_st_epoll_data->epfd, op, osfd, &ev) == 0 && op == EPOLL_CTL_DEL) { + _st_epoll_data->evtlist_cnt--; + } + } + } +} + +ST_HIDDEN int _st_epoll_fd_new(int osfd) +{ + if (osfd >= _st_epoll_data->fd_data_size && _st_epoll_fd_data_expand(osfd) < 0) { + return -1; + } + + return 0; +} + +ST_HIDDEN int _st_epoll_fd_close(int osfd) +{ + if (_ST_EPOLL_READ_CNT(osfd) || _ST_EPOLL_WRITE_CNT(osfd) || _ST_EPOLL_EXCEP_CNT(osfd)) { + errno = EBUSY; + return -1; + } + + return 0; +} + +ST_HIDDEN int _st_epoll_fd_getlimit(void) +{ + /* zero means no specific limit */ + return 0; +} + +/* + * Check if epoll functions are just stubs. + */ +ST_HIDDEN int _st_epoll_is_supported(void) +{ + struct epoll_event ev; + + ev.events = EPOLLIN; + ev.data.ptr = NULL; + /* Guaranteed to fail */ + epoll_ctl(-1, EPOLL_CTL_ADD, -1, &ev); + + return (errno != ENOSYS); +} + +static _st_eventsys_t _st_epoll_eventsys = { + "epoll", + ST_EVENTSYS_ALT, + _st_epoll_init, + _st_epoll_dispatch, + _st_epoll_pollset_add, + _st_epoll_pollset_del, + _st_epoll_fd_new, + _st_epoll_fd_close, + _st_epoll_fd_getlimit +}; + +/***************************************** + * Public functions + */ + +int st_set_eventsys(int eventsys) +{ + if (_st_eventsys) { + errno = EBUSY; + return -1; + } + + switch (eventsys) { + case ST_EVENTSYS_DEFAULT: + case ST_EVENTSYS_ALT: + default: + if (_st_epoll_is_supported()) { + _st_eventsys = &_st_epoll_eventsys; + break; + } + errno = EINVAL; + return -1; + } + + return 0; +} + +int st_get_eventsys(void) +{ + return _st_eventsys ? _st_eventsys->val : -1; +} + +const char *st_get_eventsys_name(void) +{ + return _st_eventsys ? _st_eventsys->name : ""; +} + diff --git a/trunk/research/st/io.c b/trunk/research/st/io.c new file mode 100644 index 0000000000..bc77dc8e10 --- /dev/null +++ b/trunk/research/st/io.c @@ -0,0 +1,792 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape Portable Runtime library. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): Silicon Graphics, Inc. + * + * Portions created by SGI are Copyright (C) 2000-2001 Silicon + * Graphics, Inc. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * This file is derived directly from Netscape Communications Corporation, + * and consists of extensive modifications made during the year(s) 1999-2000. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" + +#if EAGAIN != EWOULDBLOCK + #define _IO_NOT_READY_ERROR ((errno == EAGAIN) || (errno == EWOULDBLOCK)) +#else + #define _IO_NOT_READY_ERROR (errno == EAGAIN) +#endif + +#define _LOCAL_MAXIOV 16 + +/* File descriptor object free list */ +static _st_netfd_t *_st_netfd_freelist = NULL; +/* Maximum number of file descriptors that the process can open */ +static int _st_osfd_limit = -1; + +static void _st_netfd_free_aux_data(_st_netfd_t *fd); + +int _st_io_init(void) +{ + struct sigaction sigact; + struct rlimit rlim; + int fdlim; + + /* Ignore SIGPIPE */ + sigact.sa_handler = SIG_IGN; + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0; + if (sigaction(SIGPIPE, &sigact, NULL) < 0) { + return -1; + } + + /* Set maximum number of open file descriptors */ + if (getrlimit(RLIMIT_NOFILE, &rlim) < 0) { + return -1; + } + + fdlim = (*_st_eventsys->fd_getlimit)(); + if (fdlim > 0 && rlim.rlim_max > (rlim_t) fdlim) { + rlim.rlim_max = fdlim; + } + rlim.rlim_cur = rlim.rlim_max; + if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) { + return -1; + } + _st_osfd_limit = (int) rlim.rlim_max; + + return 0; +} + +int st_getfdlimit(void) +{ + return _st_osfd_limit; +} + +void st_netfd_free(_st_netfd_t *fd) +{ + if (!fd->inuse) { + return; + } + + fd->inuse = 0; + if (fd->aux_data) { + _st_netfd_free_aux_data(fd); + } + if (fd->private_data && fd->destructor) { + (*(fd->destructor))(fd->private_data); + } + fd->private_data = NULL; + fd->destructor = NULL; + fd->next = _st_netfd_freelist; + _st_netfd_freelist = fd; +} + +static _st_netfd_t *_st_netfd_new(int osfd, int nonblock, int is_socket) +{ + _st_netfd_t *fd; + int flags = 1; + + if ((*_st_eventsys->fd_new)(osfd) < 0) { + return NULL; + } + + if (_st_netfd_freelist) { + fd = _st_netfd_freelist; + _st_netfd_freelist = _st_netfd_freelist->next; + } else { + fd = calloc(1, sizeof(_st_netfd_t)); + if (!fd) { + return NULL; + } + } + + fd->osfd = osfd; + fd->inuse = 1; + fd->next = NULL; + + if (nonblock) { + /* Use just one system call */ + if (is_socket && ioctl(osfd, FIONBIO, &flags) != -1) { + return fd; + } + /* Do it the Posix way */ + if ((flags = fcntl(osfd, F_GETFL, 0)) < 0 || fcntl(osfd, F_SETFL, flags | O_NONBLOCK) < 0) { + st_netfd_free(fd); + return NULL; + } + } + + return fd; +} + +_st_netfd_t *st_netfd_open(int osfd) +{ + return _st_netfd_new(osfd, 1, 0); +} + +_st_netfd_t *st_netfd_open_socket(int osfd) +{ + return _st_netfd_new(osfd, 1, 1); +} + +int st_netfd_close(_st_netfd_t *fd) +{ + if ((*_st_eventsys->fd_close)(fd->osfd) < 0) { + return -1; + } + + st_netfd_free(fd); + return close(fd->osfd); +} + +int st_netfd_fileno(_st_netfd_t *fd) +{ + return (fd->osfd); +} + +void st_netfd_setspecific(_st_netfd_t *fd, void *value, _st_destructor_t destructor) +{ + if (value != fd->private_data) { + /* Free up previously set non-NULL data value */ + if (fd->private_data && fd->destructor) { + (*(fd->destructor))(fd->private_data); + } + } + fd->private_data = value; + fd->destructor = destructor; +} + +void *st_netfd_getspecific(_st_netfd_t *fd) +{ + return (fd->private_data); +} + +/* + * Wait for I/O on a single descriptor. + */ +int st_netfd_poll(_st_netfd_t *fd, int how, st_utime_t timeout) +{ + struct pollfd pd; + int n; + + pd.fd = fd->osfd; + pd.events = (short) how; + pd.revents = 0; + + if ((n = st_poll(&pd, 1, timeout)) < 0) { + return -1; + } + if (n == 0) { + /* Timed out */ + errno = ETIME; + return -1; + } + if (pd.revents & POLLNVAL) { + errno = EBADF; + return -1; + } + + return 0; +} + +#ifdef MD_ALWAYS_UNSERIALIZED_ACCEPT +/* No-op */ +int st_netfd_serialize_accept(_st_netfd_t *fd) +{ + fd->aux_data = NULL; + return 0; +} + +/* No-op */ +static void _st_netfd_free_aux_data(_st_netfd_t *fd) +{ + fd->aux_data = NULL; +} + +_st_netfd_t *st_accept(_st_netfd_t *fd, struct sockaddr *addr, int *addrlen, st_utime_t timeout) +{ + int osfd, err; + _st_netfd_t *newfd; + + while ((osfd = accept(fd->osfd, addr, (socklen_t *)addrlen)) < 0) { + if (errno == EINTR) { + continue; + } + if (!_IO_NOT_READY_ERROR) { + return NULL; + } + /* Wait until the socket becomes readable */ + if (st_netfd_poll(fd, POLLIN, timeout) < 0) { + return NULL; + } + } + + /* On some platforms the new socket created by accept() inherits */ + /* the nonblocking attribute of the listening socket */ +#if defined (MD_ACCEPT_NB_INHERITED) + newfd = _st_netfd_new(osfd, 0, 1); +#elif defined (MD_ACCEPT_NB_NOT_INHERITED) + newfd = _st_netfd_new(osfd, 1, 1); +#else + #error Unknown OS +#endif + + if (!newfd) { + err = errno; + close(osfd); + errno = err; + } + + return newfd; +} + +#else /* MD_ALWAYS_UNSERIALIZED_ACCEPT */ +/* + * On some platforms accept() calls from different processes + * on the same listen socket must be serialized. + * The following code serializes accept()'s without process blocking. + * A pipe is used as an inter-process semaphore. + */ +int st_netfd_serialize_accept(_st_netfd_t *fd) +{ + _st_netfd_t **p; + int osfd[2], err; + + if (fd->aux_data) { + errno = EINVAL; + return -1; + } + if ((p = (_st_netfd_t **)calloc(2, sizeof(_st_netfd_t *))) == NULL) { + return -1; + } + if (pipe(osfd) < 0) { + free(p); + return -1; + } + if ((p[0] = st_netfd_open(osfd[0])) != NULL && (p[1] = st_netfd_open(osfd[1])) != NULL && write(osfd[1], " ", 1) == 1) { + fd->aux_data = p; + return 0; + } + /* Error */ + err = errno; + if (p[0]) { + st_netfd_free(p[0]); + } + if (p[1]) { + st_netfd_free(p[1]); + } + close(osfd[0]); + close(osfd[1]); + free(p); + errno = err; + + return -1; +} + +static void _st_netfd_free_aux_data(_st_netfd_t *fd) +{ + _st_netfd_t **p = (_st_netfd_t **) fd->aux_data; + + st_netfd_close(p[0]); + st_netfd_close(p[1]); + free(p); + fd->aux_data = NULL; +} + +_st_netfd_t *st_accept(_st_netfd_t *fd, struct sockaddr *addr, int *addrlen, st_utime_t timeout) +{ + int osfd, err; + _st_netfd_t *newfd; + _st_netfd_t **p = (_st_netfd_t **) fd->aux_data; + ssize_t n; + char c; + + for ( ; ; ) { + if (p == NULL) { + osfd = accept(fd->osfd, addr, (socklen_t *)addrlen); + } else { + /* Get the lock */ + n = st_read(p[0], &c, 1, timeout); + if (n < 0) { + return NULL; + } + ST_ASSERT(n == 1); + /* Got the lock */ + osfd = accept(fd->osfd, addr, (socklen_t *)addrlen); + /* Unlock */ + err = errno; + n = st_write(p[1], &c, 1, timeout); + ST_ASSERT(n == 1); + errno = err; + } + if (osfd >= 0) { + break; + } + if (errno == EINTR) { + continue; + } + if (!_IO_NOT_READY_ERROR) { + return NULL; + } + /* Wait until the socket becomes readable */ + if (st_netfd_poll(fd, POLLIN, timeout) < 0) { + return NULL; + } + } + + /* On some platforms the new socket created by accept() inherits */ + /* the nonblocking attribute of the listening socket */ +#if defined (MD_ACCEPT_NB_INHERITED) + newfd = _st_netfd_new(osfd, 0, 1); +#elif defined (MD_ACCEPT_NB_NOT_INHERITED) + newfd = _st_netfd_new(osfd, 1, 1); +#else + #error Unknown OS +#endif + + if (!newfd) { + err = errno; + close(osfd); + errno = err; + } + + return newfd; +} +#endif /* MD_ALWAYS_UNSERIALIZED_ACCEPT */ + +int st_connect(_st_netfd_t *fd, const struct sockaddr *addr, int addrlen, st_utime_t timeout) +{ + int n, err = 0; + + while (connect(fd->osfd, addr, addrlen) < 0) { + if (errno != EINTR) { + /* + * On some platforms, if connect() is interrupted (errno == EINTR) + * after the kernel binds the socket, a subsequent connect() + * attempt will fail with errno == EADDRINUSE. Ignore EADDRINUSE + * iff connect() was previously interrupted. See Rich Stevens' + * "UNIX Network Programming," Vol. 1, 2nd edition, p. 413 + * ("Interrupted connect"). + */ + if (errno != EINPROGRESS && (errno != EADDRINUSE || err == 0)) { + return -1; + } + /* Wait until the socket becomes writable */ + if (st_netfd_poll(fd, POLLOUT, timeout) < 0) { + return -1; + } + /* Try to find out whether the connection setup succeeded or failed */ + n = sizeof(int); + if (getsockopt(fd->osfd, SOL_SOCKET, SO_ERROR, (char *)&err, (socklen_t *)&n) < 0) { + return -1; + } + if (err) { + errno = err; + return -1; + } + break; + } + err = 1; + } + + return 0; +} + +ssize_t st_read(_st_netfd_t *fd, void *buf, size_t nbyte, st_utime_t timeout) +{ + ssize_t n; + + while ((n = read(fd->osfd, buf, nbyte)) < 0) { + if (errno == EINTR) { + continue; + } + if (!_IO_NOT_READY_ERROR) { + return -1; + } + /* Wait until the socket becomes readable */ + if (st_netfd_poll(fd, POLLIN, timeout) < 0) { + return -1; + } + } + + return n; +} + +int st_read_resid(_st_netfd_t *fd, void *buf, size_t *resid, st_utime_t timeout) +{ + struct iovec iov, *riov; + int riov_size, rv; + + iov.iov_base = buf; + iov.iov_len = *resid; + riov = &iov; + riov_size = 1; + rv = st_readv_resid(fd, &riov, &riov_size, timeout); + *resid = iov.iov_len; + return rv; +} + +ssize_t st_readv(_st_netfd_t *fd, const struct iovec *iov, int iov_size, st_utime_t timeout) +{ + ssize_t n; + + while ((n = readv(fd->osfd, iov, iov_size)) < 0) { + if (errno == EINTR) { + continue; + } + if (!_IO_NOT_READY_ERROR) { + return -1; + } + /* Wait until the socket becomes readable */ + if (st_netfd_poll(fd, POLLIN, timeout) < 0) { + return -1; + } + } + + return n; +} + +int st_readv_resid(_st_netfd_t *fd, struct iovec **iov, int *iov_size, st_utime_t timeout) +{ + ssize_t n; + + while (*iov_size > 0) { + if (*iov_size == 1) { + n = read(fd->osfd, (*iov)->iov_base, (*iov)->iov_len); + } else { + n = readv(fd->osfd, *iov, *iov_size); + } + if (n < 0) { + if (errno == EINTR) { + continue; + } + if (!_IO_NOT_READY_ERROR) { + return -1; + } + } else if (n == 0) { + break; + } else { + while ((size_t) n >= (*iov)->iov_len) { + n -= (*iov)->iov_len; + (*iov)->iov_base = (char *) (*iov)->iov_base + (*iov)->iov_len; + (*iov)->iov_len = 0; + (*iov)++; + (*iov_size)--; + if (n == 0) { + break; + } + } + if (*iov_size == 0) { + break; + } + (*iov)->iov_base = (char *) (*iov)->iov_base + n; + (*iov)->iov_len -= n; + } + /* Wait until the socket becomes readable */ + if (st_netfd_poll(fd, POLLIN, timeout) < 0) { + return -1; + } + } + + return 0; +} + +ssize_t st_read_fully(_st_netfd_t *fd, void *buf, size_t nbyte, st_utime_t timeout) +{ + size_t resid = nbyte; + return st_read_resid(fd, buf, &resid, timeout) == 0 ? (ssize_t) (nbyte - resid) : -1; +} + +int st_write_resid(_st_netfd_t *fd, const void *buf, size_t *resid, st_utime_t timeout) +{ + struct iovec iov, *riov; + int riov_size, rv; + + iov.iov_base = (void *) buf; /* we promise not to modify buf */ + iov.iov_len = *resid; + riov = &iov; + riov_size = 1; + rv = st_writev_resid(fd, &riov, &riov_size, timeout); + *resid = iov.iov_len; + return rv; +} + +ssize_t st_write(_st_netfd_t *fd, const void *buf, size_t nbyte, st_utime_t timeout) +{ + size_t resid = nbyte; + return st_write_resid(fd, buf, &resid, timeout) == 0 ? (ssize_t) (nbyte - resid) : -1; +} + +ssize_t st_writev(_st_netfd_t *fd, const struct iovec *iov, int iov_size, st_utime_t timeout) +{ + ssize_t n, rv; + size_t nleft, nbyte; + int index, iov_cnt; + struct iovec *tmp_iov; + struct iovec local_iov[_LOCAL_MAXIOV]; + + /* Calculate the total number of bytes to be sent */ + nbyte = 0; + for (index = 0; index < iov_size; index++) { + nbyte += iov[index].iov_len; + } + + rv = (ssize_t)nbyte; + nleft = nbyte; + tmp_iov = (struct iovec *) iov; /* we promise not to modify iov */ + iov_cnt = iov_size; + + while (nleft > 0) { + if (iov_cnt == 1) { + if (st_write(fd, tmp_iov[0].iov_base, nleft, timeout) != (ssize_t) nleft) { + rv = -1; + } + break; + } + if ((n = writev(fd->osfd, tmp_iov, iov_cnt)) < 0) { + if (errno == EINTR) { + continue; + } + if (!_IO_NOT_READY_ERROR) { + rv = -1; + break; + } + } else { + if ((size_t) n == nleft) { + break; + } + nleft -= n; + /* Find the next unwritten vector */ + n = (ssize_t)(nbyte - nleft); + for (index = 0; (size_t) n >= iov[index].iov_len; index++) { + n -= iov[index].iov_len; + } + + if (tmp_iov == iov) { + /* Must copy iov's around */ + if (iov_size - index <= _LOCAL_MAXIOV) { + tmp_iov = local_iov; + } else { + tmp_iov = calloc(1, (iov_size - index) * sizeof(struct iovec)); + if (tmp_iov == NULL) { + return -1; + } + } + } + + /* Fill in the first partial read */ + tmp_iov[0].iov_base = &(((char *)iov[index].iov_base)[n]); + tmp_iov[0].iov_len = iov[index].iov_len - n; + index++; + /* Copy the remaining vectors */ + for (iov_cnt = 1; index < iov_size; iov_cnt++, index++) { + tmp_iov[iov_cnt].iov_base = iov[index].iov_base; + tmp_iov[iov_cnt].iov_len = iov[index].iov_len; + } + } + /* Wait until the socket becomes writable */ + if (st_netfd_poll(fd, POLLOUT, timeout) < 0) { + rv = -1; + break; + } + } + + if (tmp_iov != iov && tmp_iov != local_iov) { + free(tmp_iov); + } + + return rv; +} + +int st_writev_resid(_st_netfd_t *fd, struct iovec **iov, int *iov_size, st_utime_t timeout) +{ + ssize_t n; + + while (*iov_size > 0) { + if (*iov_size == 1) { + n = write(fd->osfd, (*iov)->iov_base, (*iov)->iov_len); + } else { + n = writev(fd->osfd, *iov, *iov_size); + } + if (n < 0) { + if (errno == EINTR) { + continue; + } + if (!_IO_NOT_READY_ERROR) { + return -1; + } + } else { + while ((size_t) n >= (*iov)->iov_len) { + n -= (*iov)->iov_len; + (*iov)->iov_base = (char *) (*iov)->iov_base + (*iov)->iov_len; + (*iov)->iov_len = 0; + (*iov)++; + (*iov_size)--; + if (n == 0) { + break; + } + } + if (*iov_size == 0) { + break; + } + (*iov)->iov_base = (char *) (*iov)->iov_base + n; + (*iov)->iov_len -= n; + } + /* Wait until the socket becomes writable */ + if (st_netfd_poll(fd, POLLOUT, timeout) < 0) { + return -1; + } + } + + return 0; +} + +/* + * Simple I/O functions for UDP. + */ +int st_recvfrom(_st_netfd_t *fd, void *buf, int len, struct sockaddr *from, int *fromlen, st_utime_t timeout) +{ + int n; + + while ((n = recvfrom(fd->osfd, buf, len, 0, from, (socklen_t *)fromlen)) < 0) { + if (errno == EINTR) { + continue; + } + if (!_IO_NOT_READY_ERROR) { + return -1; + } + /* Wait until the socket becomes readable */ + if (st_netfd_poll(fd, POLLIN, timeout) < 0) { + return -1; + } + } + + return n; +} + +int st_sendto(_st_netfd_t *fd, const void *msg, int len, const struct sockaddr *to, int tolen, st_utime_t timeout) +{ + int n; + + while ((n = sendto(fd->osfd, msg, len, 0, to, tolen)) < 0) { + if (errno == EINTR) { + continue; + } + if (!_IO_NOT_READY_ERROR) { + return -1; + } + /* Wait until the socket becomes writable */ + if (st_netfd_poll(fd, POLLOUT, timeout) < 0) { + return -1; + } + } + + return n; +} + +int st_recvmsg(_st_netfd_t *fd, struct msghdr *msg, int flags, st_utime_t timeout) +{ + int n; + + while ((n = recvmsg(fd->osfd, msg, flags)) < 0) { + if (errno == EINTR) { + continue; + } + if (!_IO_NOT_READY_ERROR) { + return -1; + } + /* Wait until the socket becomes readable */ + if (st_netfd_poll(fd, POLLIN, timeout) < 0) { + return -1; + } + } + + return n; +} + +int st_sendmsg(_st_netfd_t *fd, const struct msghdr *msg, int flags, st_utime_t timeout) +{ + int n; + + while ((n = sendmsg(fd->osfd, msg, flags)) < 0) { + if (errno == EINTR) { + continue; + } + if (!_IO_NOT_READY_ERROR) { + return -1; + } + /* Wait until the socket becomes writable */ + if (st_netfd_poll(fd, POLLOUT, timeout) < 0) { + return -1; + } + } + + return n; +} + +/* + * To open FIFOs or other special files. + */ +_st_netfd_t *st_open(const char *path, int oflags, mode_t mode) +{ + int osfd, err; + _st_netfd_t *newfd; + + while ((osfd = open(path, oflags | O_NONBLOCK, mode)) < 0) { + if (errno != EINTR) { + return NULL; + } + } + + newfd = _st_netfd_new(osfd, 0, 0); + if (!newfd) { + err = errno; + close(osfd); + errno = err; + } + + return newfd; +} + diff --git a/trunk/research/st/key.c b/trunk/research/st/key.c new file mode 100644 index 0000000000..49d778a187 --- /dev/null +++ b/trunk/research/st/key.c @@ -0,0 +1,116 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape Portable Runtime library. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): Silicon Graphics, Inc. + * + * Portions created by SGI are Copyright (C) 2000-2001 Silicon + * Graphics, Inc. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * This file is derived directly from Netscape Communications Corporation, + * and consists of extensive modifications made during the year(s) 1999-2000. + */ + +#include +#include +#include "common.h" + +/* + * Destructor table for per-thread private data + */ +static _st_destructor_t _st_destructors[ST_KEYS_MAX]; +static int key_max = 0; + +/* + * Return a key to be used for thread specific data + */ +int st_key_create(int *keyp, _st_destructor_t destructor) +{ + if (key_max >= ST_KEYS_MAX) { + errno = EAGAIN; + return -1; + } + + *keyp = key_max++; + _st_destructors[*keyp] = destructor; + + return 0; +} + +int st_key_getlimit(void) +{ + return ST_KEYS_MAX; +} + +int st_thread_setspecific(int key, void *value) +{ + _st_thread_t *me = _ST_CURRENT_THREAD(); + + if (key < 0 || key >= key_max) { + errno = EINVAL; + return -1; + } + + if (value != me->private_data[key]) { + /* free up previously set non-NULL data value */ + if (me->private_data[key] && _st_destructors[key]) { + (*_st_destructors[key])(me->private_data[key]); + } + me->private_data[key] = value; + } + + return 0; +} + +void *st_thread_getspecific(int key) +{ + if (key < 0 || key >= key_max) { + return NULL; + } + + return ((_ST_CURRENT_THREAD())->private_data[key]); +} + +/* + * Free up all per-thread private data + */ +void _st_thread_cleanup(_st_thread_t *thread) +{ + int key; + + for (key = 0; key < key_max; key++) { + if (thread->private_data[key] && _st_destructors[key]) { + (*_st_destructors[key])(thread->private_data[key]); + thread->private_data[key] = NULL; + } + } +} + diff --git a/trunk/research/st/md.S b/trunk/research/st/md.S new file mode 100755 index 0000000000..883da302b2 --- /dev/null +++ b/trunk/research/st/md.S @@ -0,0 +1,151 @@ +/* + * Portions created by SGI are Copyright (C) 2000 Silicon Graphics, Inc. + * All Rights Reserved. + */ + +/****************************************************************/ + +#if defined(__i386__) + +/* + * Internal __jmp_buf layout + */ +#define JB_BX 0 +#define JB_SI 1 +#define JB_DI 2 +#define JB_BP 3 +#define JB_SP 4 +#define JB_PC 5 + + .file "md.S" + .text + + /* _st_md_cxt_save(__jmp_buf env) */ +.globl _st_md_cxt_save + .type _st_md_cxt_save, @function + .align 16 +_st_md_cxt_save: + movl 4(%esp), %eax + + /* + * Save registers. + */ + movl %ebx, (JB_BX*4)(%eax) + movl %esi, (JB_SI*4)(%eax) + movl %edi, (JB_DI*4)(%eax) + /* Save SP */ + leal 4(%esp), %ecx + movl %ecx, (JB_SP*4)(%eax) + /* Save PC we are returning to */ + movl 0(%esp), %ecx + movl %ecx, (JB_PC*4)(%eax) + /* Save caller frame pointer */ + movl %ebp, (JB_BP*4)(%eax) + xorl %eax, %eax + ret + .size _st_md_cxt_save, .-_st_md_cxt_save + + +/****************************************************************/ + + /* _st_md_cxt_restore(__jmp_buf env, int val) */ +.globl _st_md_cxt_restore + .type _st_md_cxt_restore, @function + .align 16 +_st_md_cxt_restore: + /* First argument is jmp_buf */ + movl 4(%esp), %ecx + /* Second argument is return value */ + movl 8(%esp), %eax + /* Set the return address */ + movl (JB_PC*4)(%ecx), %edx + /* + * Restore registers. + */ + movl (JB_BX*4)(%ecx), %ebx + movl (JB_SI*4)(%ecx), %esi + movl (JB_DI*4)(%ecx), %edi + movl (JB_BP*4)(%ecx), %ebp + movl (JB_SP*4)(%ecx), %esp + testl %eax, %eax + jnz 1f + incl %eax + /* Jump to saved PC */ +1: jmp *%edx + .size _st_md_cxt_restore, .-_st_md_cxt_restore + +/****************************************************************/ + +#elif defined(__amd64__) || defined(__x86_64__) + +/* + * Internal __jmp_buf layout + */ +#define JB_RBX 0 +#define JB_RBP 1 +#define JB_R12 2 +#define JB_R13 3 +#define JB_R14 4 +#define JB_R15 5 +#define JB_RSP 6 +#define JB_PC 7 + + .file "md.S" + .text + + /* _st_md_cxt_save(__jmp_buf env) */ +.globl _st_md_cxt_save + .type _st_md_cxt_save, @function + .align 16 +_st_md_cxt_save: + /* + * Save registers. + */ + movq %rbx, (JB_RBX*8)(%rdi) + movq %rbp, (JB_RBP*8)(%rdi) + movq %r12, (JB_R12*8)(%rdi) + movq %r13, (JB_R13*8)(%rdi) + movq %r14, (JB_R14*8)(%rdi) + movq %r15, (JB_R15*8)(%rdi) + /* Save SP */ + leaq 8(%rsp), %rdx + movq %rdx, (JB_RSP*8)(%rdi) + /* Save PC we are returning to */ + movq (%rsp), %rax + movq %rax, (JB_PC*8)(%rdi) + xorq %rax, %rax + ret + .size _st_md_cxt_save, .-_st_md_cxt_save + + +/****************************************************************/ + + /* _st_md_cxt_restore(__jmp_buf env, int val) */ +.globl _st_md_cxt_restore + .type _st_md_cxt_restore, @function + .align 16 +_st_md_cxt_restore: + /* + * Restore registers. + */ + movq (JB_RBX*8)(%rdi), %rbx + movq (JB_RBP*8)(%rdi), %rbp + movq (JB_R12*8)(%rdi), %r12 + movq (JB_R13*8)(%rdi), %r13 + movq (JB_R14*8)(%rdi), %r14 + movq (JB_R15*8)(%rdi), %r15 + /* Set return value */ + test %esi, %esi + mov $01, %eax + cmove %eax, %esi + mov %esi, %eax + movq (JB_PC*8)(%rdi), %rdx + movq (JB_RSP*8)(%rdi), %rsp + /* Jump to saved PC */ + jmpq *%rdx + .size _st_md_cxt_restore, .-_st_md_cxt_restore + +/****************************************************************/ + +#endif + diff --git a/trunk/research/st/md.h b/trunk/research/st/md.h new file mode 100644 index 0000000000..bf82f4d55c --- /dev/null +++ b/trunk/research/st/md.h @@ -0,0 +1,193 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape Portable Runtime library. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): Silicon Graphics, Inc. + * + * Portions created by SGI are Copyright (C) 2000-2001 Silicon + * Graphics, Inc. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * This file is derived directly from Netscape Communications Corporation, + * and consists of extensive modifications made during the year(s) 1999-2000. + */ + +#ifndef __ST_MD_H__ +#define __ST_MD_H__ + +#if defined(ETIMEDOUT) && !defined(ETIME) + #define ETIME ETIMEDOUT +#endif + +#if defined(MAP_ANONYMOUS) && !defined(MAP_ANON) + #define MAP_ANON MAP_ANONYMOUS +#endif + +#ifndef MAP_FAILED + #define MAP_FAILED -1 +#endif + +/***************************************** + * Platform specifics + */ +#if defined (LINUX) + /* linux ok, defined bellow */ +#elif defined (AIX) + #error "AIX not supported" +#elif defined (CYGWIN) + #error "CYGWIN not supported" +#elif defined (DARWIN) + #error "DARWIN not supported" +#elif defined (FREEBSD) + #error "FREEBSD not supported" +#elif defined (HPUX) + #error "HPUX not supported" +#elif defined (IRIX) + #error "IRIX not supported" +#elif defined (NETBSD) + #error "NETBSD not supported" +#elif defined (OPENBSD) + #error "OPENBSD not supported" +#elif defined (OSF1) + #error "OSF1 not supported" +#elif defined (SOLARIS) + #error "SOLARIS not supported" +#else + #error "Unknown OS" +#endif /* OS */ + +/* linux only, defined bellow */ +/* + * These are properties of the linux kernel and are the same on every + * flavor and architecture. + */ +#define MD_USE_BSD_ANON_MMAP +#define MD_ACCEPT_NB_NOT_INHERITED +#define MD_ALWAYS_UNSERIALIZED_ACCEPT +/* + * Modern GNU/Linux is Posix.1g compliant. + */ +#define MD_HAVE_SOCKLEN_T + +/* + * All architectures and flavors of linux have the gettimeofday + * function but if you know of a faster way, use it. + */ +#define MD_GET_UTIME() \ + struct timeval tv; \ + (void) gettimeofday(&tv, NULL); \ + return (tv.tv_sec * 1000000LL + tv.tv_usec) + +#if defined(__mips__) + #define MD_STACK_GROWS_DOWN +#else /* Not or mips */ + /* + * On linux, there are a few styles of jmpbuf format. These vary based + * on architecture/glibc combination. + * + * Most of the glibc based toggles were lifted from: + * mozilla/nsprpub/pr/include/md/_linux.h + */ + /* + * Starting with glibc 2.4, JB_SP definitions are not public anymore. + * They, however, can still be found in glibc source tree in + * architecture-specific "jmpbuf-offsets.h" files. + * Most importantly, the content of jmp_buf is mangled by setjmp to make + * it completely opaque (the mangling can be disabled by setting the + * LD_POINTER_GUARD environment variable before application execution). + * Therefore we will use built-in _st_md_cxt_save/_st_md_cxt_restore + * functions as a setjmp/longjmp replacement wherever they are available + * unless USE_LIBC_SETJMP is defined. + */ + #if defined(__i386__) + #define MD_STACK_GROWS_DOWN + #define MD_USE_BUILTIN_SETJMP + + #if defined(__GLIBC__) && __GLIBC__ >= 2 + #ifndef JB_SP + #define JB_SP 4 + #endif + #define MD_GET_SP(_t) (_t)->context[0].__jmpbuf[JB_SP] + #else + /* not an error but certainly cause for caution */ + #error "Untested use of old glibc on i386" + #define MD_GET_SP(_t) (_t)->context[0].__jmpbuf[0].__sp + #endif + #elif defined(__amd64__) || defined(__x86_64__) + #define MD_STACK_GROWS_DOWN + #define MD_USE_BUILTIN_SETJMP + + #ifndef JB_RSP + #define JB_RSP 6 + #endif + #define MD_GET_SP(_t) (_t)->context[0].__jmpbuf[JB_RSP] + #elif defined(__arm__) + #define MD_STACK_GROWS_DOWN + + #if defined(__GLIBC__) && __GLIBC__ >= 2 + #define MD_GET_SP(_t) (_t)->context[0].__jmpbuf[8] + #else + #error "ARM/Linux pre-glibc2 not supported yet" + #endif /* defined(__GLIBC__) && __GLIBC__ >= 2 */ + #else + #error "Unknown CPU architecture" + #endif /* Cases with common MD_INIT_CONTEXT and different SP locations */ +#endif /* Cases with different MD_INIT_CONTEXT */ + +#if defined(MD_USE_BUILTIN_SETJMP) && !defined(USE_LIBC_SETJMP) + /* i386/x86_64 */ + #define MD_SETJMP(env) _st_md_cxt_save(env) + #define MD_LONGJMP(env, val) _st_md_cxt_restore(env, val) + + extern int _st_md_cxt_save(jmp_buf env); + extern void _st_md_cxt_restore(jmp_buf env, int val); +#else + /* arm/mips */ + #define MD_SETJMP(env) setjmp(env) + #define MD_LONGJMP(env, val) longjmp(env, val) +#endif + +/***************************************** + * Other defines + */ +#ifndef MD_STACK_PAD_SIZE + #define MD_STACK_PAD_SIZE 128 +#endif + +#if !defined(MD_HAVE_SOCKLEN_T) && !defined(socklen_t) + #define socklen_t int +#endif + +#ifndef MD_CAP_STACK + #define MD_CAP_STACK(var_addr) +#endif + +#endif /* !__ST_MD_H__ */ + diff --git a/trunk/research/st/public.h b/trunk/research/st/public.h new file mode 100644 index 0000000000..3275191bc7 --- /dev/null +++ b/trunk/research/st/public.h @@ -0,0 +1,164 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape Portable Runtime library. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): Silicon Graphics, Inc. + * + * Portions created by SGI are Copyright (C) 2000-2001 Silicon + * Graphics, Inc. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +#ifndef __ST_THREAD_H__ +#define __ST_THREAD_H__ + +#include +#include +#include +#include +#include +#include +#include + +#define ST_VERSION "1.9" +#define ST_VERSION_MAJOR 1 +#define ST_VERSION_MINOR 9 + +/* Undefine this to remove the context switch callback feature. */ +#define ST_SWITCH_CB + +#ifndef ETIME + #define ETIME ETIMEDOUT +#endif + +#ifndef ST_UTIME_NO_TIMEOUT + #define ST_UTIME_NO_TIMEOUT ((st_utime_t) -1LL) +#endif + +#ifndef ST_UTIME_NO_WAIT + #define ST_UTIME_NO_WAIT 0 +#endif + +#define ST_EVENTSYS_DEFAULT 0 +#define ST_EVENTSYS_SELECT 1 +#define ST_EVENTSYS_POLL 2 +#define ST_EVENTSYS_ALT 3 + +#ifdef __cplusplus +extern "C" { +#endif + typedef unsigned long long st_utime_t; + typedef struct _st_thread * st_thread_t; + typedef struct _st_cond * st_cond_t; + typedef struct _st_mutex * st_mutex_t; + typedef struct _st_netfd * st_netfd_t; + #ifdef ST_SWITCH_CB + typedef void (*st_switch_cb_t)(void); + #endif + + extern int st_init(void); + extern int st_getfdlimit(void); + + extern int st_set_eventsys(int eventsys); + extern int st_get_eventsys(void); + extern const char *st_get_eventsys_name(void); + + #ifdef ST_SWITCH_CB + extern st_switch_cb_t st_set_switch_in_cb(st_switch_cb_t cb); + extern st_switch_cb_t st_set_switch_out_cb(st_switch_cb_t cb); + #endif + + extern st_thread_t st_thread_self(void); + extern void st_thread_exit(void *retval); + extern int st_thread_join(st_thread_t trd, void **retvalp); + extern void st_thread_interrupt(st_thread_t trd); + extern st_thread_t st_thread_create(void *(*start)(void *arg), void *arg, int joinable, int stack_size); + extern int st_randomize_stacks(int on); + extern int st_set_utime_function(st_utime_t (*func)(void)); + + extern st_utime_t st_utime(void); + extern st_utime_t st_utime_last_clock(void); + extern int st_timecache_set(int on); + extern time_t st_time(void); + extern int st_usleep(st_utime_t usecs); + extern int st_sleep(int secs); + extern st_cond_t st_cond_new(void); + extern int st_cond_destroy(st_cond_t cvar); + extern int st_cond_timedwait(st_cond_t cvar, st_utime_t timeout); + extern int st_cond_wait(st_cond_t cvar); + extern int st_cond_signal(st_cond_t cvar); + extern int st_cond_broadcast(st_cond_t cvar); + extern st_mutex_t st_mutex_new(void); + extern int st_mutex_destroy(st_mutex_t lock); + extern int st_mutex_lock(st_mutex_t lock); + extern int st_mutex_unlock(st_mutex_t lock); + extern int st_mutex_trylock(st_mutex_t lock); + + extern int st_key_create(int *keyp, void (*destructor)(void *)); + extern int st_key_getlimit(void); + extern int st_thread_setspecific(int key, void *value); + extern void *st_thread_getspecific(int key); + + extern st_netfd_t st_netfd_open(int osfd); + extern st_netfd_t st_netfd_open_socket(int osfd); + extern void st_netfd_free(st_netfd_t fd); + extern int st_netfd_close(st_netfd_t fd); + extern int st_netfd_fileno(st_netfd_t fd); + extern void st_netfd_setspecific(st_netfd_t fd, void *value, void (*destructor)(void *)); + extern void *st_netfd_getspecific(st_netfd_t fd); + extern int st_netfd_serialize_accept(st_netfd_t fd); + extern int st_netfd_poll(st_netfd_t fd, int how, st_utime_t timeout); + + extern int st_poll(struct pollfd *pds, int npds, st_utime_t timeout); + extern st_netfd_t st_accept(st_netfd_t fd, struct sockaddr *addr, int *addrlen, st_utime_t timeout); + extern int st_connect(st_netfd_t fd, const struct sockaddr *addr, int addrlen, st_utime_t timeout); + extern ssize_t st_read(st_netfd_t fd, void *buf, size_t nbyte, st_utime_t timeout); + extern ssize_t st_read_fully(st_netfd_t fd, void *buf, size_t nbyte, st_utime_t timeout); + extern int st_read_resid(st_netfd_t fd, void *buf, size_t *resid, st_utime_t timeout); + extern ssize_t st_readv(st_netfd_t fd, const struct iovec *iov, int iov_size, st_utime_t timeout); + extern int st_readv_resid(st_netfd_t fd, struct iovec **iov, int *iov_size, st_utime_t timeout); + extern ssize_t st_write(st_netfd_t fd, const void *buf, size_t nbyte, st_utime_t timeout); + extern int st_write_resid(st_netfd_t fd, const void *buf, size_t *resid, st_utime_t timeout); + extern ssize_t st_writev(st_netfd_t fd, const struct iovec *iov, int iov_size, st_utime_t timeout); + extern int st_writev_resid(st_netfd_t fd, struct iovec **iov, int *iov_size, st_utime_t timeout); + extern int st_recvfrom(st_netfd_t fd, void *buf, int len, struct sockaddr *from, int *fromlen, st_utime_t timeout); + extern int st_sendto(st_netfd_t fd, const void *msg, int len, const struct sockaddr *to, int tolen, st_utime_t timeout); + extern int st_recvmsg(st_netfd_t fd, struct msghdr *msg, int flags, st_utime_t timeout); + extern int st_sendmsg(st_netfd_t fd, const struct msghdr *msg, int flags, st_utime_t timeout); + extern st_netfd_t st_open(const char *path, int oflags, mode_t mode); + + #ifdef DEBUG + extern void _st_show_thread_stack(st_thread_t thread, const char *messg); + extern void _st_iterate_threads(void); + #endif +#ifdef __cplusplus +} +#endif + +#endif /* !__ST_THREAD_H__ */ + diff --git a/trunk/research/st/sched.c b/trunk/research/st/sched.c new file mode 100755 index 0000000000..66095cfd1b --- /dev/null +++ b/trunk/research/st/sched.c @@ -0,0 +1,680 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape Portable Runtime library. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): Silicon Graphics, Inc. + * + * Portions created by SGI are Copyright (C) 2000-2001 Silicon + * Graphics, Inc. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * This file is derived directly from Netscape Communications Corporation, + * and consists of extensive modifications made during the year(s) 1999-2000. + */ + +#include +#include +#include +#include +#include +#include +#include "common.h" + +/* Global data */ +_st_vp_t _st_this_vp; /* This VP */ +_st_thread_t *_st_this_thread; /* Current thread */ +int _st_active_count = 0; /* Active thread count */ + +time_t _st_curr_time = 0; /* Current time as returned by time(2) */ +st_utime_t _st_last_tset; /* Last time it was fetched */ + +int st_poll(struct pollfd *pds, int npds, st_utime_t timeout) +{ + struct pollfd *pd; + struct pollfd *epd = pds + npds; + _st_pollq_t pq; + _st_thread_t *me = _ST_CURRENT_THREAD(); + int n; + + if (me->flags & _ST_FL_INTERRUPT) { + me->flags &= ~_ST_FL_INTERRUPT; + errno = EINTR; + return -1; + } + + if ((*_st_eventsys->pollset_add)(pds, npds) < 0) { + return -1; + } + + pq.pds = pds; + pq.npds = npds; + pq.thread = me; + pq.on_ioq = 1; + _ST_ADD_IOQ(pq); + if (timeout != ST_UTIME_NO_TIMEOUT) { + _ST_ADD_SLEEPQ(me, timeout); + } + me->state = _ST_ST_IO_WAIT; + + _ST_SWITCH_CONTEXT(me); + + n = 0; + if (pq.on_ioq) { + /* If we timed out, the pollq might still be on the ioq. Remove it */ + _ST_DEL_IOQ(pq); + (*_st_eventsys->pollset_del)(pds, npds); + } else { + /* Count the number of ready descriptors */ + for (pd = pds; pd < epd; pd++) { + if (pd->revents) { + n++; + } + } + } + + if (me->flags & _ST_FL_INTERRUPT) { + me->flags &= ~_ST_FL_INTERRUPT; + errno = EINTR; + return -1; + } + + return n; +} + +void _st_vp_schedule(void) +{ + _st_thread_t *trd; + + if (_ST_RUNQ.next != &_ST_RUNQ) { + /* Pull thread off of the run queue */ + trd = _ST_THREAD_PTR(_ST_RUNQ.next); + _ST_DEL_RUNQ(trd); + } else { + /* If there are no threads to run, switch to the idle thread */ + trd = _st_this_vp.idle_thread; + } + ST_ASSERT(trd->state == _ST_ST_RUNNABLE); + + /* Resume the thread */ + trd->state = _ST_ST_RUNNING; + _ST_RESTORE_CONTEXT(trd); +} + +/* + * Initialize this Virtual Processor + */ +int st_init(void) +{ + _st_thread_t *trd; + + if (_st_active_count) { + /* Already initialized */ + return 0; + } + + /* We can ignore return value here */ + st_set_eventsys(ST_EVENTSYS_DEFAULT); + + if (_st_io_init() < 0) { + return -1; + } + + memset(&_st_this_vp, 0, sizeof(_st_vp_t)); + + ST_INIT_CLIST(&_ST_RUNQ); + ST_INIT_CLIST(&_ST_IOQ); + ST_INIT_CLIST(&_ST_ZOMBIEQ); +#ifdef DEBUG + ST_INIT_CLIST(&_ST_THREADQ); +#endif + + if ((*_st_eventsys->init)() < 0) { + return -1; + } + + _st_this_vp.pagesize = getpagesize(); + _st_this_vp.last_clock = st_utime(); + + /* + * Create idle thread + */ + _st_this_vp.idle_thread = st_thread_create(_st_idle_thread_start, NULL, 0, 0); + if (!_st_this_vp.idle_thread) { + return -1; + } + _st_this_vp.idle_thread->flags = _ST_FL_IDLE_THREAD; + _st_active_count--; + _ST_DEL_RUNQ(_st_this_vp.idle_thread); + + /* + * Initialize primordial thread + */ + trd = (_st_thread_t *) calloc(1, sizeof(_st_thread_t) + + (ST_KEYS_MAX * sizeof(void *))); + if (!trd) { + return -1; + } + trd->private_data = (void **) (trd + 1); + trd->state = _ST_ST_RUNNING; + trd->flags = _ST_FL_PRIMORDIAL; + _ST_SET_CURRENT_THREAD(trd); + _st_active_count++; +#ifdef DEBUG + _ST_ADD_THREADQ(trd); +#endif + + return 0; +} + +#ifdef ST_SWITCH_CB +st_switch_cb_t st_set_switch_in_cb(st_switch_cb_t cb) +{ + st_switch_cb_t ocb = _st_this_vp.switch_in_cb; + _st_this_vp.switch_in_cb = cb; + return ocb; +} + +st_switch_cb_t st_set_switch_out_cb(st_switch_cb_t cb) +{ + st_switch_cb_t ocb = _st_this_vp.switch_out_cb; + _st_this_vp.switch_out_cb = cb; + return ocb; +} +#endif + +/* + * Start function for the idle thread + */ +/* ARGSUSED */ +void *_st_idle_thread_start(void *arg) +{ + _st_thread_t *me = _ST_CURRENT_THREAD(); + + while (_st_active_count > 0) { + /* Idle vp till I/O is ready or the smallest timeout expired */ + _ST_VP_IDLE(); + + /* Check sleep queue for expired threads */ + _st_vp_check_clock(); + + me->state = _ST_ST_RUNNABLE; + _ST_SWITCH_CONTEXT(me); + } + + /* No more threads */ + exit(0); + + /* NOTREACHED */ + return NULL; +} + +void st_thread_exit(void *retval) +{ + _st_thread_t *trd = _ST_CURRENT_THREAD(); + + trd->retval = retval; + _st_thread_cleanup(trd); + _st_active_count--; + if (trd->term) { + /* Put thread on the zombie queue */ + trd->state = _ST_ST_ZOMBIE; + _ST_ADD_ZOMBIEQ(trd); + + /* Notify on our termination condition variable */ + st_cond_signal(trd->term); + + /* Switch context and come back later */ + _ST_SWITCH_CONTEXT(trd); + + /* Continue the cleanup */ + st_cond_destroy(trd->term); + trd->term = NULL; + } + +#ifdef DEBUG + _ST_DEL_THREADQ(trd); +#endif + + if (!(trd->flags & _ST_FL_PRIMORDIAL)) { + _st_stack_free(trd->stack); + } + + /* Find another thread to run */ + _ST_SWITCH_CONTEXT(trd); + /* Not going to land here */ +} + +int st_thread_join(_st_thread_t *trd, void **retvalp) +{ + _st_cond_t *term = trd->term; + + /* Can't join a non-joinable thread */ + if (term == NULL) { + errno = EINVAL; + return -1; + } + if (_ST_CURRENT_THREAD() == trd) { + errno = EDEADLK; + return -1; + } + + /* Multiple threads can't wait on the same joinable thread */ + if (term->wait_q.next != &term->wait_q) { + errno = EINVAL; + return -1; + } + + while (trd->state != _ST_ST_ZOMBIE) { + if (st_cond_timedwait(term, ST_UTIME_NO_TIMEOUT) != 0) { + return -1; + } + } + + if (retvalp) { + *retvalp = trd->retval; + } + + /* + * Remove target thread from the zombie queue and make it runnable. + * When it gets scheduled later, it will do the clean up. + */ + trd->state = _ST_ST_RUNNABLE; + _ST_DEL_ZOMBIEQ(trd); + _ST_ADD_RUNQ(trd); + + return 0; +} + +void _st_thread_main(void) +{ + _st_thread_t *trd = _ST_CURRENT_THREAD(); + + /* + * Cap the stack by zeroing out the saved return address register + * value. This allows some debugging/profiling tools to know when + * to stop unwinding the stack. It's a no-op on most platforms. + */ + MD_CAP_STACK(&trd); + + /* Run thread main */ + trd->retval = (*trd->start)(trd->arg); + + /* All done, time to go away */ + st_thread_exit(trd->retval); +} + +/* + * Insert "thread" into the timeout heap, in the position + * specified by thread->heap_index. See docs/timeout_heap.txt + * for details about the timeout heap. + */ +static _st_thread_t **heap_insert(_st_thread_t *trd) +{ + int target = trd->heap_index; + int s = target; + _st_thread_t **p = &_ST_SLEEPQ; + int bits = 0; + int bit; + int index = 1; + + while (s) { + s >>= 1; + bits++; + } + + for (bit = bits - 2; bit >= 0; bit--) { + if (trd->due < (*p)->due) { + _st_thread_t *t = *p; + trd->left = t->left; + trd->right = t->right; + *p = trd; + trd->heap_index = index; + trd = t; + } + index <<= 1; + if (target & (1 << bit)) { + p = &((*p)->right); + index |= 1; + } else { + p = &((*p)->left); + } + } + + trd->heap_index = index; + *p = trd; + trd->left = trd->right = NULL; + + return p; +} + +/* + * Delete "thread" from the timeout heap. + */ +static void heap_delete(_st_thread_t *trd) +{ + _st_thread_t *t, **p; + int bits = 0; + int s, bit; + + /* First find and unlink the last heap element */ + p = &_ST_SLEEPQ; + s = _ST_SLEEPQ_SIZE; + while (s) { + s >>= 1; + bits++; + } + + for (bit = bits - 2; bit >= 0; bit--) { + if (_ST_SLEEPQ_SIZE & (1 << bit)) { + p = &((*p)->right); + } else { + p = &((*p)->left); + } + } + + t = *p; + *p = NULL; + --_ST_SLEEPQ_SIZE; + if (t != trd) { + /* + * Insert the unlinked last element in place of the element we are deleting + */ + t->heap_index = trd->heap_index; + p = heap_insert(t); + t = *p; + t->left = trd->left; + t->right = trd->right; + + /* + * Reestablish the heap invariant. + */ + for (;;) { + _st_thread_t *y; /* The younger child */ + int index_tmp; + + if (t->left == NULL) { + break; + } else if (t->right == NULL) { + y = t->left; + } else if (t->left->due < t->right->due) { + y = t->left; + } else { + y = t->right; + } + + if (t->due > y->due) { + _st_thread_t *tl = y->left; + _st_thread_t *tr = y->right; + *p = y; + if (y == t->left) { + y->left = t; + y->right = t->right; + p = &y->left; + } else { + y->left = t->left; + y->right = t; + p = &y->right; + } + t->left = tl; + t->right = tr; + index_tmp = t->heap_index; + t->heap_index = y->heap_index; + y->heap_index = index_tmp; + } else { + break; + } + } + } + + trd->left = trd->right = NULL; +} + +void _st_add_sleep_q(_st_thread_t *trd, st_utime_t timeout) +{ + trd->due = _ST_LAST_CLOCK + timeout; + trd->flags |= _ST_FL_ON_SLEEPQ; + trd->heap_index = ++_ST_SLEEPQ_SIZE; + heap_insert(trd); +} + +void _st_del_sleep_q(_st_thread_t *trd) +{ + heap_delete(trd); + trd->flags &= ~_ST_FL_ON_SLEEPQ; +} + +void _st_vp_check_clock(void) +{ + _st_thread_t *trd; + st_utime_t elapsed, now; + + now = st_utime(); + elapsed = now - _ST_LAST_CLOCK; + _ST_LAST_CLOCK = now; + + if (_st_curr_time && now - _st_last_tset > 999000) { + _st_curr_time = time(NULL); + _st_last_tset = now; + } + + while (_ST_SLEEPQ != NULL) { + trd = _ST_SLEEPQ; + ST_ASSERT(trd->flags & _ST_FL_ON_SLEEPQ); + if (trd->due > now) { + break; + } + _ST_DEL_SLEEPQ(trd); + + /* If thread is waiting on condition variable, set the time out flag */ + if (trd->state == _ST_ST_COND_WAIT) { + trd->flags |= _ST_FL_TIMEDOUT; + } + + /* Make thread runnable */ + ST_ASSERT(!(trd->flags & _ST_FL_IDLE_THREAD)); + trd->state = _ST_ST_RUNNABLE; + _ST_ADD_RUNQ(trd); + } +} + +void st_thread_interrupt(_st_thread_t* trd) +{ + /* If thread is already dead */ + if (trd->state == _ST_ST_ZOMBIE) { + return; + } + + trd->flags |= _ST_FL_INTERRUPT; + + if (trd->state == _ST_ST_RUNNING || trd->state == _ST_ST_RUNNABLE) { + return; + } + + if (trd->flags & _ST_FL_ON_SLEEPQ) { + _ST_DEL_SLEEPQ(trd); + } + + /* Make thread runnable */ + trd->state = _ST_ST_RUNNABLE; + _ST_ADD_RUNQ(trd); +} + +_st_thread_t *st_thread_create(void *(*start)(void *arg), void *arg, int joinable, int stk_size) +{ + _st_thread_t *trd; + _st_stack_t *stack; + void **ptds; + char *sp; + + /* Adjust stack size */ + if (stk_size == 0) { + stk_size = ST_DEFAULT_STACK_SIZE; + } + stk_size = ((stk_size + _ST_PAGE_SIZE - 1) / _ST_PAGE_SIZE) * _ST_PAGE_SIZE; + stack = _st_stack_new(stk_size); + if (!stack) { + return NULL; + } + + /* Allocate thread object and per-thread data off the stack */ +#if defined (MD_STACK_GROWS_DOWN) + sp = stack->stk_top; + /* + * The stack segment is split in the middle. The upper half is used + * as backing store for the register stack which grows upward. + * The lower half is used for the traditional memory stack which + * grows downward. Both stacks start in the middle and grow outward + * from each other. + */ + /** + The below comments is by winlin: + The Stack public structure: + +--------------------------------------------------------------+ + | stack | + +--------------------------------------------------------------+ + bottom top + The code bellow use the stack as: + +-----------------+-----------------+-------------+------------+ + | stack of thread |pad+align(128B+) |thread(336B) | keys(128B) | + +-----------------+-----------------+-------------+------------+ + bottom sp trd ptds top + (context[0].__jmpbuf.sp) (private_data) + */ + sp = sp - (ST_KEYS_MAX * sizeof(void *)); + ptds = (void **) sp; + sp = sp - sizeof(_st_thread_t); + trd = (_st_thread_t *) sp; + + /* Make stack 64-byte aligned */ + if ((unsigned long)sp & 0x3f) { + sp = sp - ((unsigned long)sp & 0x3f); + } + stack->sp = sp - _ST_STACK_PAD_SIZE; +#else + #error "Only Supports Stack Grown Down" +#endif + + memset(trd, 0, sizeof(_st_thread_t)); + memset(ptds, 0, ST_KEYS_MAX * sizeof(void *)); + + /* Initialize thread */ + trd->private_data = ptds; + trd->stack = stack; + trd->start = start; + trd->arg = arg; + +// by winlin, expand macro MD_INIT_CONTEXT +#if defined(__mips__) + MD_SETJMP((trd)->context); + trd->context[0].__jmpbuf[0].__pc = (__ptr_t) _st_thread_main; + trd->context[0].__jmpbuf[0].__sp = stack->sp; +#else + if (MD_SETJMP((trd)->context)) { + _st_thread_main(); + } + MD_GET_SP(trd) = (long) (stack->sp); +#endif + + /* If thread is joinable, allocate a termination condition variable */ + if (joinable) { + trd->term = st_cond_new(); + if (trd->term == NULL) { + _st_stack_free(trd->stack); + return NULL; + } + } + + /* Make thread runnable */ + trd->state = _ST_ST_RUNNABLE; + _st_active_count++; + _ST_ADD_RUNQ(trd); +#ifdef DEBUG + _ST_ADD_THREADQ(trd); +#endif + + return trd; +} + +_st_thread_t *st_thread_self(void) +{ + return _ST_CURRENT_THREAD(); +} + +#ifdef DEBUG +/* ARGSUSED */ +void _st_show_thread_stack(_st_thread_t *trd, const char *messg) +{ +} + +/* To be set from debugger */ +int _st_iterate_threads_flag = 0; + +void _st_iterate_threads(void) +{ + static _st_thread_t *trd = NULL; + static jmp_buf orig_jb, save_jb; + _st_clist_t *q; + + if (!_st_iterate_threads_flag) { + if (trd) { + memcpy(trd->context, save_jb, sizeof(jmp_buf)); + MD_LONGJMP(orig_jb, 1); + } + return; + } + + if (trd) { + memcpy(trd->context, save_jb, sizeof(jmp_buf)); + _st_show_thread_stack(trd, NULL); + } else { + if (MD_SETJMP(orig_jb)) { + _st_iterate_threads_flag = 0; + trd = NULL; + _st_show_thread_stack(trd, "Iteration completed"); + return; + } + trd = _ST_CURRENT_THREAD(); + _st_show_thread_stack(trd, "Iteration started"); + } + + q = trd->tlink.next; + if (q == &_ST_THREADQ) { + q = q->next; + } + ST_ASSERT(q != &_ST_THREADQ); + trd = _ST_THREAD_THREADQ_PTR(q); + if (trd == _ST_CURRENT_THREAD()) { + MD_LONGJMP(orig_jb, 1); + } + memcpy(save_jb, trd->context, sizeof(jmp_buf)); + MD_LONGJMP(trd->context, 1); +} +#endif /* DEBUG */ + diff --git a/trunk/research/st/srs.c b/trunk/research/st/srs.c new file mode 100644 index 0000000000..09bc5eacca --- /dev/null +++ b/trunk/research/st/srs.c @@ -0,0 +1,497 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "public.h" + +#define srs_trace(msg, ...) printf(msg, ##__VA_ARGS__);printf("\n") + +int io_port = 1990; +int sleep_ms = 100; + +void stack_print(long int previous_sp, int level) +{ + if (level <= 0) { + return; + } + + register long int rsp asm("sp"); + char buf[level * 1024]; + + stack_print(rsp, level - 1); + + srs_trace("%d. psp=%#lx, sp=%#lx, size=%dB(%dB+%dKB)", + level, previous_sp, rsp, (int)(previous_sp - rsp), + (int)(previous_sp - rsp - sizeof(buf)), (int)(sizeof(buf) / 1024)); +} + +int huge_stack_test() +{ + srs_trace("==================================================="); + srs_trace("huge_stack test: start"); + + register long int rsp asm("sp"); + stack_print(rsp, 10); + + srs_trace("huge_stack test: end"); + + return 0; +} + +int sleep_test() +{ + srs_trace("==================================================="); + srs_trace("sleep test: start"); + + srs_trace("1. sleep..."); + st_utime_t start = st_utime(); + st_usleep(sleep_ms * 1000); + st_utime_t end = st_utime(); + + srs_trace("2. sleep ok, sleep=%dus, deviation=%dus", + (int)(sleep_ms * 1000), (int)(end - start - sleep_ms * 1000)); + + srs_trace("sleep test: end"); + + return 0; +} + +void* sleep2_func0(void* arg) +{ + int sleep_ms = 100; + st_utime_t start = st_utime(); + st_usleep(sleep_ms * 1000); + st_utime_t end = st_utime(); + + srs_trace("sleep ok, sleep=%dus, deviation=%dus", + (int)(sleep_ms * 1000), (int)(end - start - sleep_ms * 1000)); + + return NULL; +} + +void* sleep2_func1(void* arg) +{ + int sleep_ms = 250; + st_utime_t start = st_utime(); + st_usleep(sleep_ms * 1000); + st_utime_t end = st_utime(); + + srs_trace("sleep ok, sleep=%dus, deviation=%dus", + (int)(sleep_ms * 1000), (int)(end - start - sleep_ms * 1000)); + + return NULL; +} + +int sleep2_test() +{ + srs_trace("==================================================="); + srs_trace("sleep2 test: start"); + + st_thread_t trd0 = st_thread_create(sleep2_func0, NULL, 1, 0); + st_thread_t trd1 = st_thread_create(sleep2_func1, NULL, 1, 0); + st_thread_join(trd0, NULL); + st_thread_join(trd1, NULL); + + srs_trace("sleep test: end"); + + return 0; +} + +st_mutex_t sleep_work_cond = NULL; +void* sleep_deviation_func(void* arg) +{ + st_mutex_lock(sleep_work_cond); + srs_trace("2. work thread start."); + + int64_t i; + for (i = 0; i < 3000000000ULL; i++) { + } + + st_mutex_unlock(sleep_work_cond); + srs_trace("3. work thread end."); + + return NULL; +} + +int sleep_deviation_test() +{ + srs_trace("==================================================="); + srs_trace("sleep deviation test: start"); + + sleep_work_cond = st_mutex_new(); + + st_thread_create(sleep_deviation_func, NULL, 0, 0); + st_mutex_lock(sleep_work_cond); + + srs_trace("1. sleep..."); + st_utime_t start = st_utime(); + + // other thread to do some complex work. + st_mutex_unlock(sleep_work_cond); + st_usleep(1000 * 1000); + + st_utime_t end = st_utime(); + + srs_trace("4. sleep ok, sleep=%dus, deviation=%dus", + (int)(sleep_ms * 1000), (int)(end - start - sleep_ms * 1000)); + + st_mutex_lock(sleep_work_cond); + srs_trace("sleep deviation test: end"); + + st_mutex_destroy(sleep_work_cond); + + return 0; +} + +void* thread_func(void* arg) +{ + srs_trace("1. thread run"); + st_usleep(sleep_ms * 1000); + srs_trace("2. thread completed"); + return NULL; +} + +int thread_test() +{ + srs_trace("==================================================="); + srs_trace("thread test: start"); + + st_thread_t trd = st_thread_create(thread_func, NULL, 1, 0); + if (trd == NULL) { + srs_trace("st_thread_create failed"); + return -1; + } + + st_thread_join(trd, NULL); + srs_trace("3. thread joined"); + + srs_trace("thread test: end"); + + return 0; +} + +st_mutex_t sync_start = NULL; +st_cond_t sync_cond = NULL; +st_mutex_t sync_mutex = NULL; +st_cond_t sync_end = NULL; + +void* sync_master(void* arg) +{ + // wait for main to sync_start this thread. + st_mutex_lock(sync_start); + st_mutex_unlock(sync_start); + + st_usleep(sleep_ms * 1000); + st_cond_signal(sync_cond); + + st_mutex_lock(sync_mutex); + srs_trace("2. st mutex is ok"); + st_mutex_unlock(sync_mutex); + + st_usleep(sleep_ms * 1000); + srs_trace("3. st thread is ok"); + st_cond_signal(sync_cond); + + return NULL; +} + +void* sync_slave(void* arg) +{ + // lock mutex to control thread. + st_mutex_lock(sync_mutex); + + // wait for main to sync_start this thread. + st_mutex_lock(sync_start); + st_mutex_unlock(sync_start); + + // wait thread to ready. + st_cond_wait(sync_cond); + srs_trace("1. st cond is ok"); + + // release mutex to control thread + st_usleep(sleep_ms * 1000); + st_mutex_unlock(sync_mutex); + + // wait thread to exit. + st_cond_wait(sync_cond); + srs_trace("4. st is ok"); + + st_cond_signal(sync_end); + + return NULL; +} + +int sync_test() +{ + srs_trace("==================================================="); + srs_trace("sync test: start"); + + if ((sync_start = st_mutex_new()) == NULL) { + srs_trace("st_mutex_new sync_start failed"); + return -1; + } + st_mutex_lock(sync_start); + + if ((sync_cond = st_cond_new()) == NULL) { + srs_trace("st_cond_new cond failed"); + return -1; + } + + if ((sync_end = st_cond_new()) == NULL) { + srs_trace("st_cond_new end failed"); + return -1; + } + + if ((sync_mutex = st_mutex_new()) == NULL) { + srs_trace("st_mutex_new mutex failed"); + return -1; + } + + if (!st_thread_create(sync_master, NULL, 0, 0)) { + srs_trace("st_thread_create failed"); + return -1; + } + + if (!st_thread_create(sync_slave, NULL, 0, 0)) { + srs_trace("st_thread_create failed"); + return -1; + } + + // run all threads. + st_mutex_unlock(sync_start); + + st_cond_wait(sync_end); + srs_trace("sync test: end"); + + return 0; +} + +void* io_client(void* arg) +{ + + int fd; + if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + srs_trace("create linux socket error."); + return NULL; + } + srs_trace("6. client create linux socket success. fd=%d", fd); + + st_netfd_t stfd; + if ((stfd = st_netfd_open_socket(fd)) == NULL){ + srs_trace("st_netfd_open_socket open socket failed."); + return NULL; + } + srs_trace("7. client st open socket success. fd=%d", fd); + + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(io_port); + addr.sin_addr.s_addr = INADDR_ANY; + if (st_connect(stfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_in), ST_UTIME_NO_TIMEOUT) == -1) { + srs_trace("bind socket error."); + return NULL; + } + + char buf[1024]; + if (st_read_fully(stfd, buf, sizeof(buf), ST_UTIME_NO_TIMEOUT) != sizeof(buf)) { + srs_trace("st_read_fully failed"); + return NULL; + } + if (st_write(stfd, buf, sizeof(buf), ST_UTIME_NO_TIMEOUT) != sizeof(buf)) { + srs_trace("st_write failed"); + return NULL; + } + + st_netfd_close(stfd); + + return NULL; +} + +int io_test() +{ + srs_trace("==================================================="); + srs_trace("io test: start, port=%d", io_port); + + int fd; + if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + srs_trace("create linux socket error."); + return -1; + } + srs_trace("1. server create linux socket success. fd=%d", fd); + + int reuse_socket = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse_socket, sizeof(int)) == -1) { + srs_trace("setsockopt reuse-addr error."); + return -1; + } + srs_trace("2. server setsockopt reuse-addr success. fd=%d", fd); + + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(io_port); + addr.sin_addr.s_addr = INADDR_ANY; + if (bind(fd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_in)) == -1) { + srs_trace("bind socket error."); + return -1; + } + srs_trace("3. server bind socket success. fd=%d", fd); + + if (listen(fd, 10) == -1) { + srs_trace("listen socket error."); + return -1; + } + srs_trace("4. server listen socket success. fd=%d", fd); + + st_netfd_t stfd; + if ((stfd = st_netfd_open_socket(fd)) == NULL){ + srs_trace("st_netfd_open_socket open socket failed."); + return -1; + } + srs_trace("5. server st open socket success. fd=%d", fd); + + if (!st_thread_create(io_client, NULL, 0, 0)) { + srs_trace("st_thread_create failed"); + return -1; + } + + st_netfd_t client_stfd = st_accept(stfd, NULL, NULL, ST_UTIME_NO_TIMEOUT); + srs_trace("8. server get a client. fd=%d", st_netfd_fileno(client_stfd)); + + char buf[1024]; + if (st_write(client_stfd, buf, sizeof(buf), ST_UTIME_NO_TIMEOUT) != sizeof(buf)) { + srs_trace("st_write failed"); + return -1; + } + if (st_read_fully(client_stfd, buf, sizeof(buf), ST_UTIME_NO_TIMEOUT) != sizeof(buf)) { + srs_trace("st_read_fully failed"); + return -1; + } + srs_trace("9. server io completed."); + + st_netfd_close(stfd); + st_netfd_close(client_stfd); + + srs_trace("io test: end"); + return 0; +} + +int pipe_test() +{ + srs_trace("==================================================="); + srs_trace("pipe test: start"); + + int fds[2]; + if (pipe(fds) < 0) { + srs_trace("pipe failed"); + return -1; + } + srs_trace("1. pipe ok, %d=>%d", fds[1], fds[0]); + + st_netfd_t fdw; + if ((fdw = st_netfd_open_socket(fds[1])) == NULL) { + srs_trace("st_netfd_open_socket open socket failed."); + return -1; + } + srs_trace("2. open write fd ok"); + + st_netfd_t fdr; + if ((fdr = st_netfd_open_socket(fds[0])) == NULL) { + srs_trace("st_netfd_open_socket open socket failed."); + return -1; + } + srs_trace("3. open read fd ok"); + + char buf[1024]; + if (st_write(fdw, buf, sizeof(buf), ST_UTIME_NO_TIMEOUT) < 0) { + srs_trace("st_write socket failed."); + return -1; + } + srs_trace("4. write to pipe ok"); + + if (st_read(fdr, buf, sizeof(buf), ST_UTIME_NO_TIMEOUT) < 0) { + srs_trace("st_read socket failed."); + return -1; + } + srs_trace("5. read from pipe ok"); + + st_netfd_close(fdw); + st_netfd_close(fdr); + + srs_trace("pipe test: end"); + return 0; +} + +int main(int argc, char** argv) +{ + srs_trace("ETIME=%d", ETIME); + + if (st_set_eventsys(ST_EVENTSYS_ALT) < 0) { + srs_trace("st_set_eventsys failed"); + return -1; + } + + if (st_init() < 0) { + srs_trace("st_init failed"); + return -1; + } + + if (sleep2_test() < 0) { + srs_trace("sleep2_test failed"); + return -1; + } + + if (sleep_test() < 0) { + srs_trace("sleep_test failed"); + return -1; + } + + if (sleep_deviation_test() < 0) { + srs_trace("sleep_deviation_test failed"); + return -1; + } + + if (huge_stack_test() < 0) { + srs_trace("huge_stack_test failed"); + return -1; + } + + if (thread_test() < 0) { + srs_trace("thread_test failed"); + return -1; + } + + if (sync_test() < 0) { + srs_trace("sync_test failed"); + return -1; + } + + if (io_test() < 0) { + srs_trace("io_test failed"); + return -1; + } + + if (pipe_test() < 0) { + srs_trace("pipe_test failed"); + return -1; + } + + // cleanup. + srs_trace("wait for all thread completed"); + st_thread_exit(NULL); + // the following never enter, + // the above code will exit when all thread exit, + // current is a primordial st-thread, when all thread exit, + // the st idle thread will exit(0), see _st_idle_thread_start() + srs_trace("all thread completed"); + + return 0; +} + diff --git a/trunk/research/st/st/init b/trunk/research/st/st/init new file mode 100644 index 0000000000..61604b75fb --- /dev/null +++ b/trunk/research/st/st/init @@ -0,0 +1,3 @@ +#ifndef _st_icpp_init_stub +#define _st_icpp_init_stub +#endif diff --git a/trunk/research/st/st/st.upp b/trunk/research/st/st/st.upp new file mode 100755 index 0000000000..dab6d49589 --- /dev/null +++ b/trunk/research/st/st/st.upp @@ -0,0 +1,18 @@ +file + main readonly separator, + ..\srs.c, + st readonly separator, + ..\common.h, + ..\event.c, + ..\io.c, + ..\key.c, + ..\md.h, + ..\md.S, + ..\public.h, + ..\sched.c, + ..\stk.c, + ..\sync.c; + +mainconfig + "" = "MAIN"; + diff --git a/trunk/research/st/stk.c b/trunk/research/st/stk.c new file mode 100644 index 0000000000..c26223ba5f --- /dev/null +++ b/trunk/research/st/stk.c @@ -0,0 +1,169 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape Portable Runtime library. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): Silicon Graphics, Inc. + * + * Portions created by SGI are Copyright (C) 2000-2001 Silicon + * Graphics, Inc. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * This file is derived directly from Netscape Communications Corporation, + * and consists of extensive modifications made during the year(s) 1999-2000. + */ + +#include +#include +#include +#include +#include +#include "common.h" + +/* How much space to leave between the stacks, at each end */ +#define REDZONE _ST_PAGE_SIZE + +_st_clist_t _st_free_stacks = ST_INIT_STATIC_CLIST(&_st_free_stacks); +int _st_num_free_stacks = 0; +int _st_randomize_stacks = 0; + +static char *_st_new_stk_segment(int size); + +/** +The below comments is by winlin: +The stack memory struct: + | REDZONE | stack | extra | REDZONE | + +---------+------------------------+---------+---------+ + | 4k | | 4k/0 | 4k | + +---------+------------------------+---------+---------+ + vaddr bottom top +When _st_randomize_stacks is on, by st_randomize_stacks(), +the bottom and top will random movided in the extra: + long offset = (random() % extra) & ~0xf; + ts->stk_bottom += offset; + ts->stk_top += offset; +Both REDZONE are protected by mprotect when DEBUG is on. +*/ +_st_stack_t *_st_stack_new(int stack_size) +{ + _st_clist_t *qp; + _st_stack_t *ts; + int extra; + + // TODO: WINLIN: remove the stack reuse. + for (qp = _st_free_stacks.next; qp != &_st_free_stacks; qp = qp->next) { + ts = _ST_THREAD_STACK_PTR(qp); + if (ts->stk_size >= stack_size) { + /* Found a stack that is big enough */ + ST_REMOVE_LINK(&ts->links); + _st_num_free_stacks--; + ts->links.next = NULL; + ts->links.prev = NULL; + return ts; + } + } + + /* Make a new thread stack object. */ + if ((ts = (_st_stack_t *)calloc(1, sizeof(_st_stack_t))) == NULL) { + return NULL; + } + extra = _st_randomize_stacks ? _ST_PAGE_SIZE : 0; + ts->vaddr_size = stack_size + 2*REDZONE + extra; + ts->vaddr = _st_new_stk_segment(ts->vaddr_size); + if (!ts->vaddr) { + free(ts); + return NULL; + } + ts->stk_size = stack_size; + ts->stk_bottom = ts->vaddr + REDZONE; + ts->stk_top = ts->stk_bottom + stack_size; + +#ifdef DEBUG + mprotect(ts->vaddr, REDZONE, PROT_NONE); + mprotect(ts->stk_top + extra, REDZONE, PROT_NONE); +#endif + + if (extra) { + long offset = (random() % extra) & ~0xf; + + ts->stk_bottom += offset; + ts->stk_top += offset; + } + + return ts; +} + +/* + * Free the stack for the current thread + */ +void _st_stack_free(_st_stack_t *ts) +{ + if (!ts) { + return; + } + + /* Put the stack on the free list */ + ST_APPEND_LINK(&ts->links, _st_free_stacks.prev); + _st_num_free_stacks++; +} + +static char *_st_new_stk_segment(int size) +{ +#ifdef MALLOC_STACK + void *vaddr = malloc(size); +#else + #error "Only Supports Malloc Stack" +#endif + + return (char *)vaddr; +} + +/* Not used */ +#if 0 +void _st_delete_stk_segment(char *vaddr, int size) +{ +#ifdef MALLOC_STACK + free(vaddr); +#else + #error Unknown Stack Malloc +#endif +} +#endif + +int st_randomize_stacks(int on) +{ + int wason = _st_randomize_stacks; + + _st_randomize_stacks = on; + if (on) { + srandom((unsigned int) st_utime()); + } + + return wason; +} diff --git a/trunk/research/st/sync.c b/trunk/research/st/sync.c new file mode 100644 index 0000000000..3e5324084a --- /dev/null +++ b/trunk/research/st/sync.c @@ -0,0 +1,352 @@ +/* + * The contents of this file are subject to the Mozilla Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is the Netscape Portable Runtime library. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): Silicon Graphics, Inc. + * + * Portions created by SGI are Copyright (C) 2000-2001 Silicon + * Graphics, Inc. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* + * This file is derived directly from Netscape Communications Corporation, + * and consists of extensive modifications made during the year(s) 1999-2000. + */ + +#include +#include +#include +#include "common.h" + +extern time_t _st_curr_time; +extern st_utime_t _st_last_tset; +extern int _st_active_count; + +static st_utime_t (*_st_utime)(void) = NULL; + +/***************************************** + * Time functions + */ + +st_utime_t st_utime(void) +{ + if (_st_utime == NULL) { +#ifdef MD_GET_UTIME + MD_GET_UTIME(); +#else + #error Unknown OS +#endif + } + + return (*_st_utime)(); +} + +int st_set_utime_function(st_utime_t (*func)(void)) +{ + if (_st_active_count) { + errno = EINVAL; + return -1; + } + + _st_utime = func; + + return 0; +} + +st_utime_t st_utime_last_clock(void) +{ + return _ST_LAST_CLOCK; +} + +int st_timecache_set(int on) +{ + int wason = (_st_curr_time) ? 1 : 0; + + if (on) { + _st_curr_time = time(NULL); + _st_last_tset = st_utime(); + } else { + _st_curr_time = 0; + } + + return wason; +} + +time_t st_time(void) +{ + if (_st_curr_time) { + return _st_curr_time; + } + + return time(NULL); +} + +int st_usleep(st_utime_t usecs) +{ + _st_thread_t *me = _ST_CURRENT_THREAD(); + + if (me->flags & _ST_FL_INTERRUPT) { + me->flags &= ~_ST_FL_INTERRUPT; + errno = EINTR; + return -1; + } + + if (usecs != ST_UTIME_NO_TIMEOUT) { + me->state = _ST_ST_SLEEPING; + _ST_ADD_SLEEPQ(me, usecs); + } else { + me->state = _ST_ST_SUSPENDED; + } + + _ST_SWITCH_CONTEXT(me); + + if (me->flags & _ST_FL_INTERRUPT) { + me->flags &= ~_ST_FL_INTERRUPT; + errno = EINTR; + return -1; + } + + return 0; +} + +int st_sleep(int secs) +{ + return st_usleep((secs >= 0) ? secs * (st_utime_t) 1000000LL : ST_UTIME_NO_TIMEOUT); +} + +/***************************************** + * Condition variable functions + */ +_st_cond_t *st_cond_new(void) +{ + _st_cond_t *cvar; + + cvar = (_st_cond_t *) calloc(1, sizeof(_st_cond_t)); + if (cvar) { + ST_INIT_CLIST(&cvar->wait_q); + } + + return cvar; +} + +int st_cond_destroy(_st_cond_t *cvar) +{ + if (cvar->wait_q.next != &cvar->wait_q) { + errno = EBUSY; + return -1; + } + + free(cvar); + + return 0; +} + +int st_cond_timedwait(_st_cond_t *cvar, st_utime_t timeout) +{ + _st_thread_t *me = _ST_CURRENT_THREAD(); + int rv; + + if (me->flags & _ST_FL_INTERRUPT) { + me->flags &= ~_ST_FL_INTERRUPT; + errno = EINTR; + return -1; + } + + /* Put caller thread on the condition variable's wait queue */ + me->state = _ST_ST_COND_WAIT; + ST_APPEND_LINK(&me->wait_links, &cvar->wait_q); + + if (timeout != ST_UTIME_NO_TIMEOUT) { + _ST_ADD_SLEEPQ(me, timeout); + } + + _ST_SWITCH_CONTEXT(me); + + ST_REMOVE_LINK(&me->wait_links); + rv = 0; + + if (me->flags & _ST_FL_TIMEDOUT) { + me->flags &= ~_ST_FL_TIMEDOUT; + errno = ETIME; + rv = -1; + } + if (me->flags & _ST_FL_INTERRUPT) { + me->flags &= ~_ST_FL_INTERRUPT; + errno = EINTR; + rv = -1; + } + + return rv; +} + +int st_cond_wait(_st_cond_t *cvar) +{ + return st_cond_timedwait(cvar, ST_UTIME_NO_TIMEOUT); +} + +static int _st_cond_signal(_st_cond_t *cvar, int broadcast) +{ + _st_thread_t *thread; + _st_clist_t *q; + + for (q = cvar->wait_q.next; q != &cvar->wait_q; q = q->next) { + thread = _ST_THREAD_WAITQ_PTR(q); + if (thread->state == _ST_ST_COND_WAIT) { + if (thread->flags & _ST_FL_ON_SLEEPQ) { + _ST_DEL_SLEEPQ(thread); + } + + /* Make thread runnable */ + thread->state = _ST_ST_RUNNABLE; + _ST_ADD_RUNQ(thread); + if (!broadcast) { + break; + } + } + } + + return 0; +} + +int st_cond_signal(_st_cond_t *cvar) +{ + return _st_cond_signal(cvar, 0); +} + +int st_cond_broadcast(_st_cond_t *cvar) +{ + return _st_cond_signal(cvar, 1); +} + +/***************************************** + * Mutex functions + */ +_st_mutex_t *st_mutex_new(void) +{ + _st_mutex_t *lock; + + lock = (_st_mutex_t *) calloc(1, sizeof(_st_mutex_t)); + if (lock) { + ST_INIT_CLIST(&lock->wait_q); + lock->owner = NULL; + } + + return lock; +} + +int st_mutex_destroy(_st_mutex_t *lock) +{ + if (lock->owner != NULL || lock->wait_q.next != &lock->wait_q) { + errno = EBUSY; + return -1; + } + + free(lock); + + return 0; +} + +int st_mutex_lock(_st_mutex_t *lock) +{ + _st_thread_t *me = _ST_CURRENT_THREAD(); + + if (me->flags & _ST_FL_INTERRUPT) { + me->flags &= ~_ST_FL_INTERRUPT; + errno = EINTR; + return -1; + } + + if (lock->owner == NULL) { + /* Got the mutex */ + lock->owner = me; + return 0; + } + + if (lock->owner == me) { + errno = EDEADLK; + return -1; + } + + /* Put caller thread on the mutex's wait queue */ + me->state = _ST_ST_LOCK_WAIT; + ST_APPEND_LINK(&me->wait_links, &lock->wait_q); + + _ST_SWITCH_CONTEXT(me); + + ST_REMOVE_LINK(&me->wait_links); + + if ((me->flags & _ST_FL_INTERRUPT) && lock->owner != me) { + me->flags &= ~_ST_FL_INTERRUPT; + errno = EINTR; + return -1; + } + + return 0; +} + +int st_mutex_unlock(_st_mutex_t *lock) +{ + _st_thread_t *thread; + _st_clist_t *q; + + if (lock->owner != _ST_CURRENT_THREAD()) { + errno = EPERM; + return -1; + } + + for (q = lock->wait_q.next; q != &lock->wait_q; q = q->next) { + thread = _ST_THREAD_WAITQ_PTR(q); + if (thread->state == _ST_ST_LOCK_WAIT) { + lock->owner = thread; + /* Make thread runnable */ + thread->state = _ST_ST_RUNNABLE; + _ST_ADD_RUNQ(thread); + return 0; + } + } + + /* No threads waiting on this mutex */ + lock->owner = NULL; + + return 0; +} + +int st_mutex_trylock(_st_mutex_t *lock) +{ + if (lock->owner != NULL) { + errno = EBUSY; + return -1; + } + + /* Got the mutex */ + lock->owner = _ST_CURRENT_THREAD(); + + return 0; +} + diff --git a/trunk/scripts/_log.sh b/trunk/scripts/_log.sh index 89453aadd9..0dabad4140 100755 --- a/trunk/scripts/_log.sh +++ b/trunk/scripts/_log.sh @@ -4,11 +4,11 @@ ####################################### # color echo. ####################################### -RED="\\e[31m" -GREEN="\\e[32m" -YELLOW="\\e[33m" -BLACK="\\e[0m" -POS="\\e[103G" +RED="\\033[31m" +GREEN="\\033[32m" +YELLOW="\\033[33m" +BLACK="\\033[0m" +POS="\\033[103G" # if need to log to file, change the log path. if [[ ! $log ]]; then @@ -38,7 +38,7 @@ failed_msg(){ function check_log(){ log_dir="`dirname $log`" - (mkdir -p ${log_dir} && sudo chmod 777 ${log_dir} && touch $log) + (mkdir -p ${log_dir} && chmod 777 ${log_dir} && touch $log) ret=$?; if [[ $ret -ne 0 ]]; then failed_msg "create log failed, ret=$ret"; return $ret; fi ok_msg "create log( ${log} ) success" diff --git a/trunk/scripts/git.commit.sh b/trunk/scripts/git.commit.sh index 7426b9237b..e6f1e0d7c9 100755 --- a/trunk/scripts/git.commit.sh +++ b/trunk/scripts/git.commit.sh @@ -23,7 +23,7 @@ work_dir=`(cd ${work_dir}/.. && pwd)` product_dir=$work_dir # allow start script from any dir -cd $work_dir && git checkout develop +cd $work_dir && git checkout 2.0release . ${product_dir}/scripts/_log.sh ret=$?; if [[ $ret -ne 0 ]]; then exit $ret; fi @@ -41,7 +41,8 @@ function remote_check() fi ok_msg "remote $remote ok, url is $url" } -remote_check origin git@github.com:winlinvip/simple-rtmp-server.git +remote_check origin git@github.com:simple-rtmp-server/srs.git +remote_check srs.winlin git@github.com:winlinvip/simple-rtmp-server.git remote_check srs.csdn git@code.csdn.net:winlinvip/srs-csdn.git remote_check srs.oschina git@git.oschina.net:winlinvip/srs.oschina.git remote_check srs.gitlab git@gitlab.com:winlinvip/srs-gitlab.git @@ -63,11 +64,13 @@ function sync_push() } sync_push --all origin +sync_push --all srs.winlin sync_push --all srs.csdn sync_push --all srs.oschina sync_push --all srs.gitlab ok_msg "push refs ok" +sync_push --tags srs.winlin sync_push --tags srs.csdn sync_push --tags srs.oschina sync_push --tags srs.gitlab diff --git a/trunk/scripts/git2unix.sh b/trunk/scripts/git2unix.sh index 4f78ada6ff..9aeddfb002 100755 --- a/trunk/scripts/git2unix.sh +++ b/trunk/scripts/git2unix.sh @@ -15,7 +15,7 @@ ret=$?; if [[ 0 -ne $ret ]]; then exit $ret fi -files=`git status|egrep "(modified|new file)"|awk -F ':' '{print $2}'|awk '{print $1}'|egrep "(.hpp$|.cpp$|.cc$|.h$|.c$|.txt$|.sh$)"`; +files=`git status|egrep "(modified|new file)"|awk -F ':' '{print $2}'|awk '{print $1}'|egrep "(.hpp$|.cpp$|.cc$|.h$|.c$|.txt$|.sh|.conf$)"`; for file in $files; do dos2unix $file; echo $file|grep ".sh$" >/dev/null 2>&1; EOF_SH=$? diff --git a/trunk/scripts/install.sh b/trunk/scripts/install.sh index 54b0900a98..2b10d03845 100755 --- a/trunk/scripts/install.sh +++ b/trunk/scripts/install.sh @@ -108,7 +108,7 @@ else fi echo "" -echo "see: https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_LinuxService" +echo "see: https://github.com/simple-rtmp-server/srs/wiki/v1_CN_LinuxService" echo "install success, you can:" echo -e "${GREEN} sudo /etc/init.d/srs start${BLACK}" echo "srs root is ${INSTALL}" diff --git a/trunk/scripts/package.sh b/trunk/scripts/package.sh index 901b342e61..6218068b0a 100755 --- a/trunk/scripts/package.sh +++ b/trunk/scripts/package.sh @@ -108,7 +108,7 @@ fi ok_msg "real os is ${os_name}-${os_major_version} ${os_release} ${os_machine}" # build srs -# @see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Build +# @see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Build ok_msg "start build srs" if [ $ARM = YES ]; then ( diff --git a/trunk/scripts/run.sh b/trunk/scripts/run.sh index 4f48495477..c4d6833ed1 100755 --- a/trunk/scripts/run.sh +++ b/trunk/scripts/run.sh @@ -3,10 +3,10 @@ src_dir='src' if [[ ! -d $src_dir ]]; then echo "错误:必须在src同目录执行脚本"; exit 1; fi # linux shell color support. -RED="\\e[31m" -GREEN="\\e[32m" -YELLOW="\\e[33m" -BLACK="\\e[0m" +RED="\\033[31m" +GREEN="\\033[32m" +YELLOW="\\033[33m" +BLACK="\\033[0m" ./etc/init.d/srs-demo restart; ret=$?; if [[ 0 -ne $ret ]]; then echo "错误:启动SRS失败"; exit $ret; fi echo "启动SRS服务器成功" @@ -32,7 +32,8 @@ cat</dev/null 2>&1 +if [[ 0 -eq $? && `getenforce` != 'Disabled' ]]; then echo -e "${RED}请关闭selinux:${BLACK}"; echo -e "${RED} 打开配置文件:sudo vi /etc/sysconfig/selinux${BLACK}"; echo -e "${RED} 修改为:SELINUX=disabled${BLACK}"; @@ -51,4 +52,4 @@ echo -e "${GREEN}请在hosts中添加一行:${BLACK}" echo -e "${RED} $ip demo.srs.com${BLACK}" echo -e "${GREEN}演示地址:${BLACK}" echo -e "${RED} http://$ip:$port${BLACK}" -echo -e "@see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SampleDemo" +echo -e "@see https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SampleDemo" diff --git a/trunk/src/app/srs_app_async_call.cpp b/trunk/src/app/srs_app_async_call.cpp new file mode 100644 index 0000000000..24f7228ca6 --- /dev/null +++ b/trunk/src/app/srs_app_async_call.cpp @@ -0,0 +1,98 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include + +using namespace std; + +#include +#include + +// the sleep interval for http async callback. +#define SRS_AUTO_ASYNC_CALLBACL_SLEEP_US 300000 + +ISrsDvrAsyncCall::ISrsDvrAsyncCall() +{ +} + +ISrsDvrAsyncCall::~ISrsDvrAsyncCall() +{ +} + +SrsDvrAsyncCallThread::SrsDvrAsyncCallThread() +{ + pthread = new SrsThread("async", this, SRS_AUTO_ASYNC_CALLBACL_SLEEP_US, true); +} + +SrsDvrAsyncCallThread::~SrsDvrAsyncCallThread() +{ + stop(); + srs_freep(pthread); + + std::vector::iterator it; + for (it = callbacks.begin(); it != callbacks.end(); ++it) { + ISrsDvrAsyncCall* call = *it; + srs_freep(call); + } + callbacks.clear(); +} + +int SrsDvrAsyncCallThread::call(ISrsDvrAsyncCall* c) +{ + int ret = ERROR_SUCCESS; + + callbacks.push_back(c); + + return ret; +} + +int SrsDvrAsyncCallThread::start() +{ + return pthread->start(); +} + +void SrsDvrAsyncCallThread::stop() +{ + pthread->stop(); +} + +int SrsDvrAsyncCallThread::cycle() +{ + int ret = ERROR_SUCCESS; + + std::vector copies = callbacks; + callbacks.clear(); + + std::vector::iterator it; + for (it = copies.begin(); it != copies.end(); ++it) { + ISrsDvrAsyncCall* call = *it; + if ((ret = call->call()) != ERROR_SUCCESS) { + srs_warn("ignore async callback %s, ret=%d", call->to_string().c_str(), ret); + } + srs_freep(call); + } + + return ret; +} + + diff --git a/trunk/src/app/srs_app_async_call.hpp b/trunk/src/app/srs_app_async_call.hpp new file mode 100644 index 0000000000..e014e3cc79 --- /dev/null +++ b/trunk/src/app/srs_app_async_call.hpp @@ -0,0 +1,75 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef SRS_APP_ASYNC_CALL_HPP +#define SRS_APP_ASYNC_CALL_HPP + +/* +#include +*/ +#include + +#include +#include + +#include + +/** + * the async call for http hooks, + * for the http hooks will switch st-thread, + * so we must use isolate thread to avoid the thread corrupt, + * for example, when dvr call http hooks, the video receive thread got + * a video and pass it to the dvr again. + * futhurmore, the aync call never block the main worker thread. + */ +class ISrsDvrAsyncCall +{ +public: + ISrsDvrAsyncCall(); + virtual ~ISrsDvrAsyncCall(); +public: + virtual int call() = 0; + virtual std::string to_string() = 0; +}; + +/** +* the async callback for dvr. +*/ +class SrsDvrAsyncCallThread : public ISrsThreadHandler +{ +private: + SrsThread* pthread; + std::vector callbacks; +public: + SrsDvrAsyncCallThread(); + virtual ~SrsDvrAsyncCallThread(); +public: + virtual int call(ISrsDvrAsyncCall* c); +public: + virtual int start(); + virtual void stop(); + virtual int cycle(); +}; + +#endif + diff --git a/trunk/src/app/srs_app_avc_aac.cpp b/trunk/src/app/srs_app_avc_aac.cpp deleted file mode 100644 index cfb861eb48..0000000000 --- a/trunk/src/app/srs_app_avc_aac.cpp +++ /dev/null @@ -1,523 +0,0 @@ -/* -The MIT License (MIT) - -Copyright (c) 2013-2014 winlin - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#include - -#include -#include -#include -#include - -SrsCodecSampleUnit::SrsCodecSampleUnit() -{ - size = 0; - bytes = NULL; -} - -SrsCodecSampleUnit::~SrsCodecSampleUnit() -{ -} - -SrsCodecSample::SrsCodecSample() -{ - clear(); -} - -SrsCodecSample::~SrsCodecSample() -{ -} - -void SrsCodecSample::clear() -{ - is_video = false; - nb_sample_units = 0; - - cts = 0; - frame_type = SrsCodecVideoAVCFrameReserved; - avc_packet_type = SrsCodecVideoAVCTypeReserved; - - sound_rate = SrsCodecAudioSampleRateReserved; - sound_size = SrsCodecAudioSampleSizeReserved; - sound_type = SrsCodecAudioSoundTypeReserved; - aac_packet_type = SrsCodecAudioTypeReserved; -} - -int SrsCodecSample::add_sample_unit(char* bytes, int size) -{ - int ret = ERROR_SUCCESS; - - if (nb_sample_units >= __SRS_SRS_MAX_CODEC_SAMPLE) { - ret = ERROR_HLS_DECODE_ERROR; - srs_error("hls decode samples error, " - "exceed the max count: %d, ret=%d", __SRS_SRS_MAX_CODEC_SAMPLE, ret); - return ret; - } - - SrsCodecSampleUnit* sample_unit = &sample_units[nb_sample_units++]; - sample_unit->bytes = bytes; - sample_unit->size = size; - - return ret; -} - -SrsAvcAacCodec::SrsAvcAacCodec() -{ - width = 0; - height = 0; - duration = 0; - NAL_unit_length = 0; - frame_rate = 0; - video_data_rate = 0; - video_codec_id = 0; - audio_data_rate = 0; - audio_codec_id = 0; - avc_profile = 0; - avc_level = 0; - aac_profile = 0; - aac_sample_rate = __SRS_AAC_SAMPLE_RATE_UNSET; // sample rate ignored - aac_channels = 0; - avc_extra_size = 0; - avc_extra_data = NULL; - aac_extra_size = 0; - aac_extra_data = NULL; - sequenceParameterSetLength = 0; - sequenceParameterSetNALUnit = NULL; - pictureParameterSetLength = 0; - pictureParameterSetNALUnit = NULL; - - stream = new SrsStream(); -} - -SrsAvcAacCodec::~SrsAvcAacCodec() -{ - srs_freep(avc_extra_data); - srs_freep(aac_extra_data); - - srs_freep(stream); - srs_freep(sequenceParameterSetNALUnit); - srs_freep(pictureParameterSetNALUnit); -} - -int SrsAvcAacCodec::metadata_demux(SrsAmf0Object* metadata) -{ - int ret = ERROR_SUCCESS; - - srs_assert(metadata); - - SrsAmf0Object* obj = metadata; - - // finger out the codec info from metadata if possible. - SrsAmf0Any* prop = NULL; - - if ((prop = obj->get_property("duration")) != NULL && prop->is_number()) { - duration = (int)prop->to_number(); - } - if ((prop = obj->get_property("width")) != NULL && prop->is_number()) { - width = (int)prop->to_number(); - } - if ((prop = obj->get_property("height")) != NULL && prop->is_number()) { - height = (int)prop->to_number(); - } - if ((prop = obj->get_property("framerate")) != NULL && prop->is_number()) { - frame_rate = (int)prop->to_number(); - } - if ((prop = obj->get_property("videocodecid")) != NULL && prop->is_number()) { - video_codec_id = (int)prop->to_number(); - } - if ((prop = obj->get_property("videodatarate")) != NULL && prop->is_number()) { - video_data_rate = (int)(1000 * prop->to_number()); - } - - if ((prop = obj->get_property("audiocodecid")) != NULL && prop->is_number()) { - audio_codec_id = (int)prop->to_number(); - } - if ((prop = obj->get_property("audiodatarate")) != NULL && prop->is_number()) { - audio_data_rate = (int)(1000 * prop->to_number()); - } - - // ignore the following, for each flv/rtmp packet contains them: - // audiosamplerate, sample->sound_rate - // audiosamplesize, sample->sound_size - // stereo, sample->sound_type - - return ret; -} - -int SrsAvcAacCodec::audio_aac_demux(char* data, int size, SrsCodecSample* sample) -{ - int ret = ERROR_SUCCESS; - - sample->is_video = false; - - if (!data || size <= 0) { - srs_trace("no audio present, hls ignore it."); - return ret; - } - - if ((ret = stream->initialize(data, size)) != ERROR_SUCCESS) { - return ret; - } - - // audio decode - if (!stream->require(1)) { - ret = ERROR_HLS_DECODE_ERROR; - srs_error("hls decode audio sound_format failed. ret=%d", ret); - return ret; - } - - // @see: E.4.2 Audio Tags, video_file_format_spec_v10_1.pdf, page 76 - int8_t sound_format = stream->read_1bytes(); - - int8_t sound_type = sound_format & 0x01; - int8_t sound_size = (sound_format >> 1) & 0x01; - int8_t sound_rate = (sound_format >> 2) & 0x03; - sound_format = (sound_format >> 4) & 0x0f; - - audio_codec_id = sound_format; - sample->sound_type = (SrsCodecAudioSoundType)sound_type; - sample->sound_rate = (SrsCodecAudioSampleRate)sound_rate; - sample->sound_size = (SrsCodecAudioSampleSize)sound_size; - - // only support aac - if (audio_codec_id != SrsCodecAudioAAC) { - ret = ERROR_HLS_DECODE_ERROR; - srs_error("hls only support audio aac codec. actual=%d, ret=%d", audio_codec_id, ret); - return ret; - } - - if (!stream->require(1)) { - ret = ERROR_HLS_DECODE_ERROR; - srs_error("hls decode audio aac_packet_type failed. ret=%d", ret); - return ret; - } - - int8_t aac_packet_type = stream->read_1bytes(); - sample->aac_packet_type = (SrsCodecAudioType)aac_packet_type; - - if (aac_packet_type == SrsCodecAudioTypeSequenceHeader) { - // AudioSpecificConfig - // 1.6.2.1 AudioSpecificConfig, in aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 33. - aac_extra_size = stream->size() - stream->pos(); - if (aac_extra_size > 0) { - srs_freep(aac_extra_data); - aac_extra_data = new char[aac_extra_size]; - memcpy(aac_extra_data, stream->data() + stream->pos(), aac_extra_size); - } - - // only need to decode the first 2bytes: - // audioObjectType, aac_profile, 5bits. - // samplingFrequencyIndex, aac_sample_rate, 4bits. - // channelConfiguration, aac_channels, 4bits - if (!stream->require(2)) { - ret = ERROR_HLS_DECODE_ERROR; - srs_error("hls decode audio aac sequence header failed. ret=%d", ret); - return ret; - } - aac_profile = stream->read_1bytes(); - aac_sample_rate = stream->read_1bytes(); - - aac_channels = (aac_sample_rate >> 3) & 0x0f; - aac_sample_rate = ((aac_profile << 1) & 0x0e) | ((aac_sample_rate >> 7) & 0x01); - aac_profile = (aac_profile >> 3) & 0x1f; - - if (aac_profile == 0 || aac_profile == 0x1f) { - ret = ERROR_HLS_DECODE_ERROR; - srs_error("hls decode audio aac sequence header failed, " - "adts object=%d invalid. ret=%d", aac_profile, ret); - return ret; - } - - // the profile = object_id + 1 - // @see aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 78, - // Table 1. A.9 C MPEG-2 Audio profiles and MPEG-4 Audio object types - // so the aac_profile should plus 1, not minus 1, and nginx-rtmp used it to - // downcast aac SSR to LC. - // @see https://github.com/winlinvip/simple-rtmp-server/issues/310 - // TODO: FIXME: fix the following in future version. - // aac_profile = audioObjectType - 1 - aac_profile--; - - // TODO: FIXME: to support aac he/he-v2, see: ngx_rtmp_codec_parse_aac_header - // @see: https://github.com/winlinvip/nginx-rtmp-module/commit/3a5f9eea78fc8d11e8be922aea9ac349b9dcbfc2 - // - // donot force to LC, @see: https://github.com/winlinvip/simple-rtmp-server/issues/81 - // the source will print the sequence header info. - //if (aac_profile > 3) { - // Mark all extended profiles as LC - // to make Android as happy as possible. - // @see: ngx_rtmp_hls_parse_aac_header - //aac_profile = 1; - //} - } else if (aac_packet_type == SrsCodecAudioTypeRawData) { - // ensure the sequence header demuxed - if (aac_extra_size <= 0 || !aac_extra_data) { - ret = ERROR_HLS_DECODE_ERROR; - srs_error("hls decode audio aac failed, sequence header not found. ret=%d", ret); - return ret; - } - - // Raw AAC frame data in UI8 [] - // 6.3 Raw Data, aac-iso-13818-7.pdf, page 28 - if ((ret = sample->add_sample_unit(stream->data() + stream->pos(), stream->size() - stream->pos())) != ERROR_SUCCESS) { - srs_error("hls add audio sample failed. ret=%d", ret); - return ret; - } - } else { - // ignored. - } - - // reset the sample rate by sequence header - if (aac_sample_rate != __SRS_AAC_SAMPLE_RATE_UNSET) { - static int aac_sample_rates[] = { - 96000, 88200, 64000, 48000, - 44100, 32000, 24000, 22050, - 16000, 12000, 11025, 8000, - 7350, 0, 0, 0 - }; - switch (aac_sample_rates[aac_sample_rate]) { - case 11025: - sample->sound_rate = SrsCodecAudioSampleRate11025; - break; - case 22050: - sample->sound_rate = SrsCodecAudioSampleRate22050; - break; - case 44100: - sample->sound_rate = SrsCodecAudioSampleRate44100; - break; - default: - break; - }; - } - - srs_info("audio decoded, type=%d, codec=%d, asize=%d, rate=%d, format=%d, size=%d", - sound_type, audio_codec_id, sound_size, sound_rate, sound_format, size); - - return ret; -} - -int SrsAvcAacCodec::video_avc_demux(char* data, int size, SrsCodecSample* sample) -{ - int ret = ERROR_SUCCESS; - - sample->is_video = true; - - if (!data || size <= 0) { - srs_trace("no video present, hls ignore it."); - return ret; - } - - if ((ret = stream->initialize(data, size)) != ERROR_SUCCESS) { - return ret; - } - - // video decode - if (!stream->require(1)) { - ret = ERROR_HLS_DECODE_ERROR; - srs_error("hls decode video frame_type failed. ret=%d", ret); - return ret; - } - - // @see: E.4.3 Video Tags, video_file_format_spec_v10_1.pdf, page 78 - int8_t frame_type = stream->read_1bytes(); - int8_t codec_id = frame_type & 0x0f; - frame_type = (frame_type >> 4) & 0x0f; - - sample->frame_type = (SrsCodecVideoAVCFrame)frame_type; - - // ignore info frame without error, - // @see https://github.com/winlinvip/simple-rtmp-server/issues/288#issuecomment-69863909 - if (sample->frame_type == SrsCodecVideoAVCFrameVideoInfoFrame) { - srs_warn("hls igone the info frame, ret=%d", ret); - return ret; - } - - // only support h.264/avc - if (codec_id != SrsCodecVideoAVC) { - ret = ERROR_HLS_DECODE_ERROR; - srs_error("hls only support video h.264/avc codec. actual=%d, ret=%d", codec_id, ret); - return ret; - } - video_codec_id = codec_id; - - if (!stream->require(4)) { - ret = ERROR_HLS_DECODE_ERROR; - srs_error("hls decode video avc_packet_type failed. ret=%d", ret); - return ret; - } - int8_t avc_packet_type = stream->read_1bytes(); - int32_t composition_time = stream->read_3bytes(); - - // pts = dts + cts. - sample->cts = composition_time; - sample->avc_packet_type = (SrsCodecVideoAVCType)avc_packet_type; - - if (avc_packet_type == SrsCodecVideoAVCTypeSequenceHeader) { - // AVCDecoderConfigurationRecord - // 5.2.4.1.1 Syntax, H.264-AVC-ISO_IEC_14496-15.pdf, page 16 - avc_extra_size = stream->size() - stream->pos(); - if (avc_extra_size > 0) { - srs_freep(avc_extra_data); - avc_extra_data = new char[avc_extra_size]; - memcpy(avc_extra_data, stream->data() + stream->pos(), avc_extra_size); - } - - if (!stream->require(6)) { - ret = ERROR_HLS_DECODE_ERROR; - srs_error("hls decode video avc sequenc header failed. ret=%d", ret); - return ret; - } - //int8_t configurationVersion = stream->read_1bytes(); - stream->read_1bytes(); - //int8_t AVCProfileIndication = stream->read_1bytes(); - avc_profile = stream->read_1bytes(); - //int8_t profile_compatibility = stream->read_1bytes(); - stream->read_1bytes(); - //int8_t AVCLevelIndication = stream->read_1bytes(); - avc_level = stream->read_1bytes(); - - // parse the NALU size. - int8_t lengthSizeMinusOne = stream->read_1bytes(); - lengthSizeMinusOne &= 0x03; - NAL_unit_length = lengthSizeMinusOne; - - // 1 sps - if (!stream->require(1)) { - ret = ERROR_HLS_DECODE_ERROR; - srs_error("hls decode video avc sequenc header sps failed. ret=%d", ret); - return ret; - } - int8_t numOfSequenceParameterSets = stream->read_1bytes(); - numOfSequenceParameterSets &= 0x1f; - if (numOfSequenceParameterSets != 1) { - ret = ERROR_HLS_DECODE_ERROR; - srs_error("hls decode video avc sequenc header sps failed. ret=%d", ret); - return ret; - } - if (!stream->require(2)) { - ret = ERROR_HLS_DECODE_ERROR; - srs_error("hls decode video avc sequenc header sps size failed. ret=%d", ret); - return ret; - } - sequenceParameterSetLength = stream->read_2bytes(); - if (!stream->require(sequenceParameterSetLength)) { - ret = ERROR_HLS_DECODE_ERROR; - srs_error("hls decode video avc sequenc header sps data failed. ret=%d", ret); - return ret; - } - if (sequenceParameterSetLength > 0) { - srs_freep(sequenceParameterSetNALUnit); - sequenceParameterSetNALUnit = new char[sequenceParameterSetLength]; - memcpy(sequenceParameterSetNALUnit, stream->data() + stream->pos(), sequenceParameterSetLength); - stream->skip(sequenceParameterSetLength); - } - // 1 pps - if (!stream->require(1)) { - ret = ERROR_HLS_DECODE_ERROR; - srs_error("hls decode video avc sequenc header pps failed. ret=%d", ret); - return ret; - } - int8_t numOfPictureParameterSets = stream->read_1bytes(); - numOfPictureParameterSets &= 0x1f; - if (numOfPictureParameterSets != 1) { - ret = ERROR_HLS_DECODE_ERROR; - srs_error("hls decode video avc sequenc header pps failed. ret=%d", ret); - return ret; - } - if (!stream->require(2)) { - ret = ERROR_HLS_DECODE_ERROR; - srs_error("hls decode video avc sequenc header pps size failed. ret=%d", ret); - return ret; - } - pictureParameterSetLength = stream->read_2bytes(); - if (!stream->require(pictureParameterSetLength)) { - ret = ERROR_HLS_DECODE_ERROR; - srs_error("hls decode video avc sequenc header pps data failed. ret=%d", ret); - return ret; - } - if (pictureParameterSetLength > 0) { - srs_freep(pictureParameterSetNALUnit); - pictureParameterSetNALUnit = new char[pictureParameterSetLength]; - memcpy(pictureParameterSetNALUnit, stream->data() + stream->pos(), pictureParameterSetLength); - stream->skip(pictureParameterSetLength); - } - } else if (avc_packet_type == SrsCodecVideoAVCTypeNALU){ - // ensure the sequence header demuxed - if (avc_extra_size <= 0 || !avc_extra_data) { - ret = ERROR_HLS_DECODE_ERROR; - srs_error("hls decode video avc failed, sequence header not found. ret=%d", ret); - return ret; - } - - // One or more NALUs (Full frames are required) - // 5.3.4.2.1 Syntax, H.264-AVC-ISO_IEC_14496-15.pdf, page 20 - int PictureLength = stream->size() - stream->pos(); - for (int i = 0; i < PictureLength;) { - if (!stream->require(NAL_unit_length + 1)) { - ret = ERROR_HLS_DECODE_ERROR; - srs_error("hls decode video avc NALU size failed. ret=%d", ret); - return ret; - } - int32_t NALUnitLength = 0; - if (NAL_unit_length == 3) { - NALUnitLength = stream->read_4bytes(); - } else if (NAL_unit_length == 2) { - NALUnitLength = stream->read_3bytes(); - } else if (NAL_unit_length == 1) { - NALUnitLength = stream->read_2bytes(); - } else { - NALUnitLength = stream->read_1bytes(); - } - - // maybe stream is AnnexB format. - // see: https://github.com/winlinvip/simple-rtmp-server/issues/183 - if (NALUnitLength < 0) { - ret = ERROR_HLS_DECODE_ERROR; - srs_error("maybe stream is AnnexB format. ret=%d", ret); - return ret; - } - - // NALUnit - if (!stream->require(NALUnitLength)) { - ret = ERROR_HLS_DECODE_ERROR; - srs_error("hls decode video avc NALU data failed. ret=%d", ret); - return ret; - } - // 7.3.1 NAL unit syntax, H.264-AVC-ISO_IEC_14496-10.pdf, page 44. - if ((ret = sample->add_sample_unit(stream->data() + stream->pos(), NALUnitLength)) != ERROR_SUCCESS) { - srs_error("hls add video sample failed. ret=%d", ret); - return ret; - } - stream->skip(NALUnitLength); - - i += NAL_unit_length + 1 + NALUnitLength; - } - } else { - // ignored. - } - - srs_info("video decoded, type=%d, codec=%d, avc=%d, time=%d, size=%d", - frame_type, video_codec_id, avc_packet_type, composition_time, size); - - return ret; -} - diff --git a/trunk/src/app/srs_app_avc_aac.hpp b/trunk/src/app/srs_app_avc_aac.hpp deleted file mode 100644 index 1e0e42fe1a..0000000000 --- a/trunk/src/app/srs_app_avc_aac.hpp +++ /dev/null @@ -1,284 +0,0 @@ -/* -The MIT License (MIT) - -Copyright (c) 2013-2014 winlin - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#ifndef SRS_APP_AVC_AAC_HPP -#define SRS_APP_AVC_AAC_HPP - -/* -#include -*/ - -#include - -#include - -class SrsStream; -class SrsAmf0Object; - -#define __SRS_SRS_MAX_CODEC_SAMPLE 128 -#define __SRS_AAC_SAMPLE_RATE_UNSET 15 - -/** -* the FLV/RTMP supported audio sample rate. -* Sampling rate. The following values are defined: -* 0 = 5.5 kHz = 5512 Hz -* 1 = 11 kHz = 11025 Hz -* 2 = 22 kHz = 22050 Hz -* 3 = 44 kHz = 44100 Hz -*/ -enum SrsCodecAudioSampleRate -{ - // set to the max value to reserved, for array map. - SrsCodecAudioSampleRateReserved = 4, - - SrsCodecAudioSampleRate5512 = 0, - SrsCodecAudioSampleRate11025 = 1, - SrsCodecAudioSampleRate22050 = 2, - SrsCodecAudioSampleRate44100 = 3, -}; - -/** -* the FLV/RTMP supported audio sample size. -* Size of each audio sample. This parameter only pertains to -* uncompressed formats. Compressed formats always decode -* to 16 bits internally. -* 0 = 8-bit samples -* 1 = 16-bit samples -*/ -enum SrsCodecAudioSampleSize -{ - // set to the max value to reserved, for array map. - SrsCodecAudioSampleSizeReserved = 2, - - SrsCodecAudioSampleSize8bit = 0, - SrsCodecAudioSampleSize16bit = 1, -}; - -/** -* the FLV/RTMP supported audio sound type/channel. -* Mono or stereo sound -* 0 = Mono sound -* 1 = Stereo sound -*/ -enum SrsCodecAudioSoundType -{ - // set to the max value to reserved, for array map. - SrsCodecAudioSoundTypeReserved = 2, - - SrsCodecAudioSoundTypeMono = 0, - SrsCodecAudioSoundTypeStereo = 1, -}; - -/** -* the codec sample unit. -* for h.264 video packet, a NALU is a sample unit. -* for aac raw audio packet, a NALU is the entire aac raw data. -* for sequence header, it's not a sample unit. -*/ -class SrsCodecSampleUnit -{ -public: - /** - * the sample bytes is directly ptr to packet bytes, - * user should never use it when packet destroyed. - */ - int size; - char* bytes; -public: - SrsCodecSampleUnit(); - virtual ~SrsCodecSampleUnit(); -}; - -/** -* the samples in the flv audio/video packet. -* the sample used to analysis a video/audio packet, -* split the h.264 NALUs to buffers, or aac raw data to a buffer, -* and decode the video/audio specified infos. -* -* the sample unit: -* a video packet codec in h.264 contains many NALUs, each is a sample unit. -* a audio packet codec in aac is a sample unit. -* @remark, the video/audio sequence header is not sample unit, -* all sequence header stores as extra data, -* @see SrsAvcAacCodec.avc_extra_data and SrsAvcAacCodec.aac_extra_data -* @remark, user must clear all samples before decode a new video/audio packet. -*/ -class SrsCodecSample -{ -public: - /** - * each audio/video raw data packet will dumps to one or multiple buffers, - * the buffers will write to hls and clear to reset. - * generally, aac audio packet corresponding to one buffer, - * where avc/h264 video packet may contains multiple buffer. - */ - int nb_sample_units; - SrsCodecSampleUnit sample_units[__SRS_SRS_MAX_CODEC_SAMPLE]; -public: - /** - * whether the sample is video sample which demux from video packet. - */ - bool is_video; - /** - * CompositionTime, video_file_format_spec_v10_1.pdf, page 78. - * cts = pts - dts, where dts = flvheader->timestamp. - */ - int32_t cts; -public: - // video specified - SrsCodecVideoAVCFrame frame_type; - SrsCodecVideoAVCType avc_packet_type; -public: - // audio specified - SrsCodecAudioSampleRate sound_rate; - SrsCodecAudioSampleSize sound_size; - SrsCodecAudioSoundType sound_type; - SrsCodecAudioType aac_packet_type; -public: - SrsCodecSample(); - virtual ~SrsCodecSample(); -public: - /** - * clear all samples. - * the sample units never copy the bytes, it directly use the ptr, - * so when video/audio packet is destroyed, the sample must be clear. - * in a word, user must clear sample before demux it. - * @remark demux sample use SrsAvcAacCodec.audio_aac_demux or video_avc_demux. - */ - void clear(); - /** - * add the a sample unit, it's a h.264 NALU or aac raw data. - * the sample unit directly use the ptr of packet bytes, - * so user must never use sample unit when packet is destroyed. - * in a word, user must clear sample before demux it. - */ - int add_sample_unit(char* bytes, int size); -}; - -/** -* the h264/avc and aac codec, for media stream. -* -* to demux the FLV/RTMP video/audio packet to sample, -* add each NALUs of h.264 as a sample unit to sample, -* while the entire aac raw data as a sample unit. -* -* for sequence header, -* demux it and save it in the avc_extra_data and aac_extra_data, -* -* for the codec info, such as audio sample rate, -* decode from FLV/RTMP header, then use codec info in sequence -* header to override it. -*/ -class SrsAvcAacCodec -{ -private: - SrsStream* stream; -public: - /** - * metadata specified - */ - int duration; - int width; - int height; - int frame_rate; - // @see: SrsCodecVideo - int video_codec_id; - int video_data_rate; // in bps - // @see: SrsCod ecAudioType - int audio_codec_id; - int audio_data_rate; // in bps -public: - /** - * video specified - */ - // profile_idc, H.264-AVC-ISO_IEC_14496-10.pdf, page 45. - u_int8_t avc_profile; - // level_idc, H.264-AVC-ISO_IEC_14496-10.pdf, page 45. - u_int8_t avc_level; - // lengthSizeMinusOne, H.264-AVC-ISO_IEC_14496-15.pdf, page 16 - int8_t NAL_unit_length; - u_int16_t sequenceParameterSetLength; - char* sequenceParameterSetNALUnit; - u_int16_t pictureParameterSetLength; - char* pictureParameterSetNALUnit; -public: - /** - * audio specified - * 1.6.2.1 AudioSpecificConfig, in aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 33. - * audioObjectType, value defines in 7.1 Profiles, aac-iso-13818-7.pdf, page 40. - */ - u_int8_t aac_profile; - /** - * samplingFrequencyIndex - */ - u_int8_t aac_sample_rate; - /** - * channelConfiguration - */ - u_int8_t aac_channels; -public: - /** - * the avc extra data, the AVC sequence header, - * without the flv codec header, - * @see: ffmpeg, AVCodecContext::extradata - */ - int avc_extra_size; - char* avc_extra_data; - /** - * the aac extra data, the AAC sequence header, - * without the flv codec header, - * @see: ffmpeg, AVCodecContext::extradata - */ - int aac_extra_size; - char* aac_extra_data; -public: - SrsAvcAacCodec(); - virtual ~SrsAvcAacCodec(); -// the following function used for hls to build the sample and codec. -public: - /** - * demux the metadata, to to get the stream info, - * for instance, the width/height, sample rate. - * @param metadata, the metadata amf0 object. assert not NULL. - */ - virtual int metadata_demux(SrsAmf0Object* metadata); - /** - * demux the audio packet in aac codec. - * the packet mux in FLV/RTMP format defined in flv specification. - * demux the audio speicified data(sound_format, sound_size, ...) to sample. - * demux the aac specified data(aac_profile, ...) to codec from sequence header. - * demux the aac raw to sample units. - */ - virtual int audio_aac_demux(char* data, int size, SrsCodecSample* sample); - /** - * demux the video packet in h.264 codec. - * the packet mux in FLV/RTMP format defined in flv specification. - * demux the video specified data(frame_type, codec_id, ...) to sample. - * demux the h.264 sepcified data(avc_profile, ...) to codec from sequence header. - * demux the h.264 NALUs to sampe units. - */ - virtual int video_avc_demux(char* data, int size, SrsCodecSample* sample); -}; - -#endif - diff --git a/trunk/src/app/srs_app_bandwidth.cpp b/trunk/src/app/srs_app_bandwidth.cpp index c065f64eb5..9db380bbbd 100644 --- a/trunk/src/app/srs_app_bandwidth.cpp +++ b/trunk/src/app/srs_app_bandwidth.cpp @@ -1,8 +1,8 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 wenjiegit -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -29,8 +29,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using namespace std; -#include -#include +#include +#include #include #include #include @@ -94,12 +94,12 @@ int _srs_expect_bandwidth_packet(SrsRtmpServer* rtmp, _CheckPacketType pfn) int ret = ERROR_SUCCESS; while (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; SrsBandwidthPacket* pkt = NULL; if ((ret = rtmp->expect_message(&msg, &pkt)) != ERROR_SUCCESS) { return ret; } - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); SrsAutoFree(SrsBandwidthPacket, pkt); srs_info("get bwtc message success."); @@ -380,12 +380,12 @@ int SrsBandwidth::publish_checking(SrsBandwidthSample* sample, SrsKbpsLimit* lim srs_update_system_time_ms(); int64_t starttime = srs_get_system_time_ms(); while ((srs_get_system_time_ms() - starttime) < sample->duration_ms) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; SrsBandwidthPacket* pkt = NULL; if ((ret = _rtmp->expect_message(&msg, &pkt)) != ERROR_SUCCESS) { return ret; } - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); SrsAutoFree(SrsBandwidthPacket, pkt); srs_info("get publish message success."); diff --git a/trunk/src/app/srs_app_bandwidth.hpp b/trunk/src/app/srs_app_bandwidth.hpp index e52c0ed231..781fa4d63e 100644 --- a/trunk/src/app/srs_app_bandwidth.hpp +++ b/trunk/src/app/srs_app_bandwidth.hpp @@ -1,8 +1,8 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 wenjiegit -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/trunk/src/app/srs_app_caster_flv.cpp b/trunk/src/app/srs_app_caster_flv.cpp new file mode 100644 index 0000000000..8e88f37fb2 --- /dev/null +++ b/trunk/src/app/srs_app_caster_flv.cpp @@ -0,0 +1,445 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include + +#ifdef SRS_AUTO_STREAM_CASTER + +#include +using namespace std; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SRS_HTTP_FLV_STREAM_BUFFER 4096 + +SrsAppCasterFlv::SrsAppCasterFlv(SrsConfDirective* c) +{ + http_mux = new SrsHttpServeMux(); + output = _srs_config->get_stream_caster_output(c); +} + +SrsAppCasterFlv::~SrsAppCasterFlv() +{ +} + +int SrsAppCasterFlv::initialize() +{ + int ret = ERROR_SUCCESS; + + if ((ret = http_mux->handle("/", this)) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +int SrsAppCasterFlv::on_tcp_client(st_netfd_t stfd) +{ + int ret = ERROR_SUCCESS; + + SrsHttpConn* conn = new SrsDynamicHttpConn(this, stfd, http_mux); + conns.push_back(conn); + + if ((ret = conn->start()) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +void SrsAppCasterFlv::remove(SrsConnection* c) +{ + std::vector::iterator it; + if ((it = std::find(conns.begin(), conns.end(), c)) != conns.end()) { + conns.erase(it); + } +} + +int SrsAppCasterFlv::serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r) +{ + SrsDynamicHttpConn* conn = dynamic_cast(r->connection()); + srs_assert(conn); + + std::string app = srs_path_dirname(r->path()); + app = srs_string_trim_start(app, "/"); + + std::string stream = srs_path_basename(r->path()); + stream = srs_string_trim_start(stream, "/"); + + std::string o = output; + if (!app.empty() && app != "/") { + o = srs_string_replace(o, "[app]", app); + } + o = srs_string_replace(o, "[stream]", stream); + + // remove the extension. + if (srs_string_ends_with(o, ".flv")) { + o = o.substr(0, o.length() - 4); + } + + return conn->proxy(w, r, o); +} + +SrsDynamicHttpConn::SrsDynamicHttpConn(IConnectionManager* cm, st_netfd_t fd, SrsHttpServeMux* m) + : SrsHttpConn(cm, fd, m) +{ + + req = NULL; + io = NULL; + client = NULL; + stfd = NULL; + stream_id = 0; + + pprint = SrsPithyPrint::create_caster(); +} + +SrsDynamicHttpConn::~SrsDynamicHttpConn() +{ + close(); + + srs_freep(pprint); +} + +int SrsDynamicHttpConn::on_got_http_message(SrsHttpMessage* msg) +{ + int ret = ERROR_SUCCESS; + return ret; +} + +int SrsDynamicHttpConn::proxy(ISrsHttpResponseWriter* w, SrsHttpMessage* r, std::string o) +{ + int ret = ERROR_SUCCESS; + + output = o; + srs_trace("flv: proxy %s to %s", r->uri().c_str(), output.c_str()); + + char* buffer = new char[SRS_HTTP_FLV_STREAM_BUFFER]; + SrsAutoFree(char, buffer); + + ISrsHttpResponseReader* rr = r->body_reader(); + SrsHttpFileReader reader(rr); + SrsFlvDecoder dec; + + if ((ret = dec.initialize(&reader)) != ERROR_SUCCESS) { + return ret; + } + + char header[9]; + if ((ret = dec.read_header(header)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("flv: proxy flv header failed. ret=%d", ret); + } + return ret; + } + srs_trace("flv: proxy drop flv header."); + + char pps[4]; + if ((ret = dec.read_previous_tag_size(pps)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("flv: proxy flv header pps failed. ret=%d", ret); + } + return ret; + } + + while (!rr->eof()) { + pprint->elapse(); + + if ((ret = connect()) != ERROR_SUCCESS) { + return ret; + } + + char type; + int32_t size; + u_int32_t time; + if ((ret = dec.read_tag_header(&type, &size, &time)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("flv: proxy tag header failed. ret=%d", ret); + } + return ret; + } + + char* data = new char[size]; + if ((ret = dec.read_tag_data(data, size)) != ERROR_SUCCESS) { + srs_freep(data); + if (!srs_is_client_gracefully_close(ret)) { + srs_error("flv: proxy tag data failed. ret=%d", ret); + } + return ret; + } + + if ((ret = rtmp_write_packet(type, time, data, size)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("flv: proxy rtmp packet failed. ret=%d", ret); + } + return ret; + } + + if ((ret = dec.read_previous_tag_size(pps)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("flv: proxy tag header pps failed. ret=%d", ret); + } + return ret; + } + } + + return ret; +} + +int SrsDynamicHttpConn::rtmp_write_packet(char type, u_int32_t timestamp, char* data, int size) +{ + int ret = ERROR_SUCCESS; + + SrsSharedPtrMessage* msg = NULL; + + if ((ret = srs_rtmp_create_msg(type, timestamp, data, size, stream_id, &msg)) != ERROR_SUCCESS) { + srs_error("flv: create shared ptr msg failed. ret=%d", ret); + return ret; + } + srs_assert(msg); + + if (pprint->can_print()) { + srs_trace("flv: send msg %s age=%d, dts=%"PRId64", size=%d", + msg->is_audio()? "A":msg->is_video()? "V":"N", pprint->age(), msg->timestamp, msg->size); + } + + // send out encoded msg. + if ((ret = client->send_and_free_message(msg, stream_id)) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +int SrsDynamicHttpConn::connect() +{ + int ret = ERROR_SUCCESS; + + // when ok, ignore. + // TODO: FIXME: should reconnect when disconnected. + if (io || client) { + return ret; + } + + // parse uri + if (!req) { + req = new SrsRequest(); + + size_t pos = string::npos; + string uri = req->tcUrl = output; + + // tcUrl, stream + if ((pos = uri.rfind("/")) != string::npos) { + req->stream = uri.substr(pos + 1); + req->tcUrl = uri = uri.substr(0, pos); + } + + srs_discovery_tc_url(req->tcUrl, + req->schema, req->host, req->vhost, req->app, req->port, + req->param); + } + + // connect host. + if ((ret = srs_socket_connect(req->host, ::atoi(req->port.c_str()), ST_UTIME_NO_TIMEOUT, &stfd)) != ERROR_SUCCESS) { + srs_error("mpegts: connect server %s:%s failed. ret=%d", req->host.c_str(), req->port.c_str(), ret); + return ret; + } + io = new SrsStSocket(stfd); + client = new SrsRtmpClient(io); + + client->set_recv_timeout(SRS_CONSTS_RTMP_RECV_TIMEOUT_US); + client->set_send_timeout(SRS_CONSTS_RTMP_SEND_TIMEOUT_US); + + // connect to vhost/app + if ((ret = client->handshake()) != ERROR_SUCCESS) { + srs_error("mpegts: handshake with server failed. ret=%d", ret); + return ret; + } + if ((ret = connect_app(req->host, req->port)) != ERROR_SUCCESS) { + srs_error("mpegts: connect with server failed. ret=%d", ret); + return ret; + } + if ((ret = client->create_stream(stream_id)) != ERROR_SUCCESS) { + srs_error("mpegts: connect with server failed, stream_id=%d. ret=%d", stream_id, ret); + return ret; + } + + // publish. + if ((ret = client->publish(req->stream, stream_id)) != ERROR_SUCCESS) { + srs_error("mpegts: publish failed, stream=%s, stream_id=%d. ret=%d", + req->stream.c_str(), stream_id, ret); + return ret; + } + + return ret; +} + +// TODO: FIXME: refine the connect_app. +int SrsDynamicHttpConn::connect_app(string ep_server, string ep_port) +{ + int ret = ERROR_SUCCESS; + + // args of request takes the srs info. + if (req->args == NULL) { + req->args = SrsAmf0Any::object(); + } + + // notify server the edge identity, + // @see https://github.com/simple-rtmp-server/srs/issues/147 + SrsAmf0Object* data = req->args; + data->set("srs_sig", SrsAmf0Any::str(RTMP_SIG_SRS_KEY)); + data->set("srs_server", SrsAmf0Any::str(RTMP_SIG_SRS_KEY" "RTMP_SIG_SRS_VERSION" ("RTMP_SIG_SRS_URL_SHORT")")); + data->set("srs_license", SrsAmf0Any::str(RTMP_SIG_SRS_LICENSE)); + data->set("srs_role", SrsAmf0Any::str(RTMP_SIG_SRS_ROLE)); + data->set("srs_url", SrsAmf0Any::str(RTMP_SIG_SRS_URL)); + data->set("srs_version", SrsAmf0Any::str(RTMP_SIG_SRS_VERSION)); + data->set("srs_site", SrsAmf0Any::str(RTMP_SIG_SRS_WEB)); + data->set("srs_email", SrsAmf0Any::str(RTMP_SIG_SRS_EMAIL)); + data->set("srs_copyright", SrsAmf0Any::str(RTMP_SIG_SRS_COPYRIGHT)); + data->set("srs_primary", SrsAmf0Any::str(RTMP_SIG_SRS_PRIMARY)); + data->set("srs_authors", SrsAmf0Any::str(RTMP_SIG_SRS_AUTHROS)); + // for edge to directly get the id of client. + data->set("srs_pid", SrsAmf0Any::number(getpid())); + data->set("srs_id", SrsAmf0Any::number(_srs_context->get_id())); + + // local ip of edge + std::vector ips = srs_get_local_ipv4_ips(); + assert(_srs_config->get_stats_network() < (int)ips.size()); + std::string local_ip = ips[_srs_config->get_stats_network()]; + data->set("srs_server_ip", SrsAmf0Any::str(local_ip.c_str())); + + // generate the tcUrl + std::string param = ""; + std::string tc_url = srs_generate_tc_url(ep_server, req->vhost, req->app, ep_port, param); + + // upnode server identity will show in the connect_app of client. + // @see https://github.com/simple-rtmp-server/srs/issues/160 + // the debug_srs_upnode is config in vhost and default to true. + bool debug_srs_upnode = _srs_config->get_debug_srs_upnode(req->vhost); + if ((ret = client->connect_app(req->app, tc_url, req, debug_srs_upnode)) != ERROR_SUCCESS) { + srs_error("mpegts: connect with server failed, tcUrl=%s, dsu=%d. ret=%d", + tc_url.c_str(), debug_srs_upnode, ret); + return ret; + } + + return ret; +} + +void SrsDynamicHttpConn::close() +{ + srs_freep(client); + srs_freep(io); + srs_freep(req); + srs_close_stfd(stfd); +} + +SrsHttpFileReader::SrsHttpFileReader(ISrsHttpResponseReader* h) +{ + http = h; +} + +SrsHttpFileReader::~SrsHttpFileReader() +{ +} + +int SrsHttpFileReader::open(std::string /*file*/) +{ + return ERROR_SUCCESS; +} + +void SrsHttpFileReader::close() +{ +} + +bool SrsHttpFileReader::is_open() +{ + return true; +} + +int64_t SrsHttpFileReader::tellg() +{ + return 0; +} + +void SrsHttpFileReader::skip(int64_t /*size*/) +{ +} + +int64_t SrsHttpFileReader::lseek(int64_t offset) +{ + return offset; +} + +int64_t SrsHttpFileReader::filesize() +{ + return 0; +} + +int SrsHttpFileReader::read(void* buf, size_t count, ssize_t* pnread) +{ + int ret = ERROR_SUCCESS; + + if (http->eof()) { + ret = ERROR_HTTP_REQUEST_EOF; + srs_error("flv: encoder EOF. ret=%d", ret); + return ret; + } + + int total_read = 0; + while (total_read < count) { + int nread = 0; + if ((ret = http->read((char*)buf + total_read, (int)(count - total_read), &nread)) != ERROR_SUCCESS) { + return ret; + } + + if (nread == 0) { + ret = ERROR_HTTP_REQUEST_EOF; + srs_warn("flv: encoder read EOF. ret=%d", ret); + break; + } + + srs_assert(nread); + total_read += nread; + } + + if (pnread) { + *pnread = total_read; + } + + return ret; +} + +#endif diff --git a/trunk/src/app/srs_app_caster_flv.hpp b/trunk/src/app/srs_app_caster_flv.hpp new file mode 100644 index 0000000000..ac8987fd26 --- /dev/null +++ b/trunk/src/app/srs_app_caster_flv.hpp @@ -0,0 +1,145 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef SRS_APP_CASTER_FLV_HPP +#define SRS_APP_CASTER_FLV_HPP + +/* +#include +*/ + +#include + +#include +#include + +#ifdef SRS_AUTO_STREAM_CASTER + +class SrsConfDirective; +class SrsHttpServeMux; +class SrsHttpConn; +class SrsRtmpClient; +class SrsStSocket; +class SrsRequest; +class SrsPithyPrint; + +#include +#include +#include +#include +#include +#include + +/** + * the stream caster for flv stream over HTTP POST. + */ +class SrsAppCasterFlv : virtual public ISrsTcpHandler + , virtual public IConnectionManager, virtual public ISrsHttpHandler +{ +private: + std::string output; + SrsHttpServeMux* http_mux; + std::vector conns; +public: + SrsAppCasterFlv(SrsConfDirective* c); + virtual ~SrsAppCasterFlv(); +public: + virtual int initialize(); +// ISrsTcpHandler +public: + virtual int on_tcp_client(st_netfd_t stfd); +// IConnectionManager +public: + virtual void remove(SrsConnection* c); +// ISrsHttpHandler +public: + virtual int serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r); +}; + +/** + * the dynamic http connection, never drop the body. + */ +class SrsDynamicHttpConn : public SrsHttpConn +{ +private: + std::string output; + SrsPithyPrint* pprint; +private: + SrsRequest* req; + st_netfd_t stfd; + SrsStSocket* io; + SrsRtmpClient* client; + int stream_id; +public: + SrsDynamicHttpConn(IConnectionManager* cm, st_netfd_t fd, SrsHttpServeMux* m); + virtual ~SrsDynamicHttpConn(); +public: + virtual int on_got_http_message(SrsHttpMessage* msg); +public: + virtual int proxy(ISrsHttpResponseWriter* w, SrsHttpMessage* r, std::string o); +private: + virtual int rtmp_write_packet(char type, u_int32_t timestamp, char* data, int size); +private: + // connect to rtmp output url. + // @remark ignore when not connected, reconnect when disconnected. + virtual int connect(); + virtual int connect_app(std::string ep_server, std::string ep_port); + // close the connected io and rtmp to ready to be re-connect. + virtual void close(); +}; + +/** + * the http wrapper for file reader, + * to read http post stream like a file. + */ +class SrsHttpFileReader : public SrsFileReader +{ +private: + ISrsHttpResponseReader* http; +public: + SrsHttpFileReader(ISrsHttpResponseReader* h); + virtual ~SrsHttpFileReader(); +public: + /** + * open file reader, can open then close then open... + */ + virtual int open(std::string file); + virtual void close(); +public: + // TODO: FIXME: extract interface. + virtual bool is_open(); + virtual int64_t tellg(); + virtual void skip(int64_t size); + virtual int64_t lseek(int64_t offset); + virtual int64_t filesize(); +public: + /** + * read from file. + * @param pnread the output nb_read, NULL to ignore. + */ + virtual int read(void* buf, size_t count, ssize_t* pnread); +}; + +#endif + +#endif diff --git a/trunk/src/app/srs_app_config.cpp b/trunk/src/app/srs_app_config.cpp index 2372b69880..9943780ce6 100644 --- a/trunk/src/app/srs_app_config.cpp +++ b/trunk/src/app/srs_app_config.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -40,25 +40,30 @@ using namespace std; #include #include -#include +#include #include #include #include #include +#include using namespace _srs_internal; -#define SRS_WIKI_URL_LOG "https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SrsLog" +#define SRS_WIKI_URL_LOG "https://github.com/simple-rtmp-server/srs/wiki/v1_CN_SrsLog" + +// when user config an invalid value, macros to perfer true or false. +#define SRS_CONF_PERFER_FALSE(conf_arg) conf_arg == "on" +#define SRS_CONF_PERFER_TRUE(conf_arg) conf_arg != "off" // '\n' -#define __LF (char)0x0a +#define SRS_LF (char)0x0a // '\r' -#define __CR (char)0x0d +#define SRS_CR (char)0x0d bool is_common_space(char ch) { - return (ch == ' ' || ch == '\t' || ch == __CR || ch == __LF); + return (ch == ' ' || ch == '\t' || ch == SRS_CR || ch == SRS_LF); } SrsConfDirective::SrsConfDirective() @@ -139,6 +144,11 @@ bool SrsConfDirective::is_vhost() return name == "vhost"; } +bool SrsConfDirective::is_stream_caster() +{ + return name == "stream_caster"; +} + int SrsConfDirective::parse(SrsConfigBuffer* buffer) { return parse_conf(buffer, parse_file); @@ -236,7 +246,7 @@ int SrsConfDirective::read_token(SrsConfigBuffer* buffer, vector& args, char ch = *buffer->pos++; - if (ch == __LF) { + if (ch == SRS_LF) { buffer->line++; sharp_comment = false; } @@ -328,7 +338,7 @@ int SrsConfDirective::read_token(SrsConfigBuffer* buffer, vector& args, } if (found) { - int len = buffer->pos - pstart; + int len = (int)(buffer->pos - pstart); char* aword = new char[len]; memcpy(aword, pstart, len); aword[len - 1] = 0; @@ -433,7 +443,8 @@ int SrsConfig::reload_conf(SrsConfig* conf) // always support reload without additional code: // chunk_size, ff_log_dir, max_connections, // bandcheck, http_hooks, heartbeat, - // token_traverse, debug_srs_upnode + // token_traverse, debug_srs_upnode, + // security // merge config: listen if (!srs_directive_equals(root->get("listen"), old_root->get("listen"))) { @@ -495,16 +506,16 @@ int SrsConfig::reload_conf(SrsConfig* conf) srs_trace("reload srs_log_file success."); } - // merge config: pithy_print - if (!srs_directive_equals(root->get("pithy_print"), old_root->get("pithy_print"))) { + // merge config: pithy_print_ms + if (!srs_directive_equals(root->get("pithy_print_ms"), old_root->get("pithy_print_ms"))) { for (it = subscribes.begin(); it != subscribes.end(); ++it) { ISrsReloadHandler* subscribe = *it; if ((ret = subscribe->on_reload_pithy_print()) != ERROR_SUCCESS) { - srs_error("notify subscribes pithy_print listen failed. ret=%d", ret); + srs_error("notify subscribes pithy_print_ms listen failed. ret=%d", ret); return ret; } } - srs_trace("reload pithy_print success."); + srs_trace("reload pithy_print_ms success."); } // merge config: http_api @@ -517,6 +528,8 @@ int SrsConfig::reload_conf(SrsConfig* conf) return ret; } + // TODO: FIXME: support reload stream_caster. + // merge config: vhost if ((ret = reload_vhost(old_root)) != ERROR_SUCCESS) { return ret; @@ -604,7 +617,16 @@ int SrsConfig::reload_http_stream(SrsConfDirective* old_root) // ENABLED => ENABLED (modified) SrsConfDirective* new_http_stream = root->get("http_stream"); + // http_stream rename to http_server in SRS2. + if (!new_http_stream) { + new_http_stream = root->get("http_server"); + } + SrsConfDirective* old_http_stream = old_root->get("http_stream"); + // http_stream rename to http_server in SRS2. + if (!old_http_stream) { + old_http_stream = root->get("http_server"); + } // DISABLED => ENABLED if (!get_http_stream_enabled(old_http_stream) && get_http_stream_enabled(new_http_stream)) { @@ -785,6 +807,17 @@ int SrsConfig::reload_vhost(SrsConfDirective* old_root) } srs_trace("vhost %s reload time_jitter success.", vhost.c_str()); } + // mix_correct, only one per vhost + if (!srs_directive_equals(new_vhost->get("mix_correct"), old_vhost->get("mix_correct"))) { + for (it = subscribes.begin(); it != subscribes.end(); ++it) { + ISrsReloadHandler* subscribe = *it; + if ((ret = subscribe->on_reload_vhost_mix_correct(vhost)) != ERROR_SUCCESS) { + srs_error("vhost %s notify subscribes mix_correct failed. ret=%d", vhost.c_str(), ret); + return ret; + } + } + srs_trace("vhost %s reload mix_correct success.", vhost.c_str()); + } // forward, only one per vhost if (!srs_directive_equals(new_vhost->get("forward"), old_vhost->get("forward"))) { for (it = subscribes.begin(); it != subscribes.end(); ++it) { @@ -808,6 +841,19 @@ int SrsConfig::reload_vhost(SrsConfDirective* old_root) } srs_trace("vhost %s reload hls success.", vhost.c_str()); } + + // hds reload + if (!srs_directive_equals(new_vhost->get("hds"), old_vhost->get("hds"))) { + for (it = subscribes.begin(); it != subscribes.end(); ++it) { + ISrsReloadHandler* subscribe = *it; + if ((ret = subscribe->on_reload_vhost_hds(vhost)) != ERROR_SUCCESS) { + srs_error("vhost %s notify subscribes hds failed. ret=%d", vhost.c_str(), ret); + return ret; + } + } + srs_trace("vhost %s reload hds success.", vhost.c_str()); + } + // dvr, only one per vhost if (!srs_directive_equals(new_vhost->get("dvr"), old_vhost->get("dvr"))) { for (it = subscribes.begin(); it != subscribes.end(); ++it) { @@ -817,7 +863,51 @@ int SrsConfig::reload_vhost(SrsConfDirective* old_root) return ret; } } - srs_trace("vhost %s reload hls success.", vhost.c_str()); + srs_trace("vhost %s reload hlsdvrsuccess.", vhost.c_str()); + } + // mr, only one per vhost + if (!srs_directive_equals(new_vhost->get("mr"), old_vhost->get("mr"))) { + for (it = subscribes.begin(); it != subscribes.end(); ++it) { + ISrsReloadHandler* subscribe = *it; + if ((ret = subscribe->on_reload_vhost_mr(vhost)) != ERROR_SUCCESS) { + srs_error("vhost %s notify subscribes mr failed. ret=%d", vhost.c_str(), ret); + return ret; + } + } + srs_trace("vhost %s reload mr success.", vhost.c_str()); + } + // chunk_size, only one per vhost. + if (!srs_directive_equals(new_vhost->get("chunk_size"), old_vhost->get("chunk_size"))) { + for (it = subscribes.begin(); it != subscribes.end(); ++it) { + ISrsReloadHandler* subscribe = *it; + if ((ret = subscribe->on_reload_vhost_chunk_size(vhost)) != ERROR_SUCCESS) { + srs_error("vhost %s notify subscribes chunk_size failed. ret=%d", vhost.c_str(), ret); + return ret; + } + } + srs_trace("vhost %s reload chunk_size success.", vhost.c_str()); + } + // mw, only one per vhost + if (!srs_directive_equals(new_vhost->get("mw_latency"), old_vhost->get("mw_latency"))) { + for (it = subscribes.begin(); it != subscribes.end(); ++it) { + ISrsReloadHandler* subscribe = *it; + if ((ret = subscribe->on_reload_vhost_mw(vhost)) != ERROR_SUCCESS) { + srs_error("vhost %s notify subscribes mw failed. ret=%d", vhost.c_str(), ret); + return ret; + } + } + srs_trace("vhost %s reload mw success.", vhost.c_str()); + } + // min_latency, only one per vhost + if (!srs_directive_equals(new_vhost->get("min_latency"), old_vhost->get("min_latency"))) { + for (it = subscribes.begin(); it != subscribes.end(); ++it) { + ISrsReloadHandler* subscribe = *it; + if ((ret = subscribe->on_reload_vhost_realtime(vhost)) != ERROR_SUCCESS) { + srs_error("vhost %s notify subscribes min_latency failed. ret=%d", vhost.c_str(), ret); + return ret; + } + } + srs_trace("vhost %s reload min_latency success.", vhost.c_str()); } // http, only one per vhost. if (!srs_directive_equals(new_vhost->get("http"), old_vhost->get("http"))) { @@ -830,6 +920,29 @@ int SrsConfig::reload_vhost(SrsConfDirective* old_root) } srs_trace("vhost %s reload http success.", vhost.c_str()); } + // http_static, only one per vhost. + // @remark, http_static introduced as alias of http. + if (!srs_directive_equals(new_vhost->get("http_static"), old_vhost->get("http_static"))) { + for (it = subscribes.begin(); it != subscribes.end(); ++it) { + ISrsReloadHandler* subscribe = *it; + if ((ret = subscribe->on_reload_vhost_http_updated()) != ERROR_SUCCESS) { + srs_error("vhost %s notify subscribes http_static failed. ret=%d", vhost.c_str(), ret); + return ret; + } + } + srs_trace("vhost %s reload http_static success.", vhost.c_str()); + } + // http_remux, only one per vhost. + if (!srs_directive_equals(new_vhost->get("http_remux"), old_vhost->get("http_remux"))) { + for (it = subscribes.begin(); it != subscribes.end(); ++it) { + ISrsReloadHandler* subscribe = *it; + if ((ret = subscribe->on_reload_vhost_http_remux_updated()) != ERROR_SUCCESS) { + srs_error("vhost %s notify subscribes http_remux failed. ret=%d", vhost.c_str(), ret); + return ret; + } + } + srs_trace("vhost %s reload http_remux success.", vhost.c_str()); + } // transcode, many per vhost. if ((ret = reload_transcode(new_vhost, old_vhost)) != ERROR_SUCCESS) { return ret; @@ -1167,7 +1280,7 @@ void SrsConfig::print_help(char** argv) { printf( RTMP_SIG_SRS_NAME" "RTMP_SIG_SRS_VERSION" "RTMP_SIG_SRS_COPYRIGHT"\n" - "license: "RTMP_SIG_SRS_LICENSE"\n" + "License: "RTMP_SIG_SRS_LICENSE"\n" "Primary: "RTMP_SIG_SRS_PRIMARY"\n" "Authors: "RTMP_SIG_SRS_AUTHROS"\n" "Build: "SRS_AUTO_BUILD_DATE" Configuration:"SRS_AUTO_USER_CONFIGURE"\n" @@ -1217,6 +1330,7 @@ int SrsConfig::check_config() srs_trace("srs checking config..."); vector vhosts = get_vhosts(); + vector stream_casters = get_stream_casters(); //////////////////////////////////////////////////////////////////////// // check empty @@ -1236,9 +1350,10 @@ int SrsConfig::check_config() if (n != "listen" && n != "pid" && n != "chunk_size" && n != "ff_log_dir" && n != "srs_log_tank" && n != "srs_log_level" && n != "srs_log_file" && n != "max_connections" && n != "daemon" && n != "heartbeat" - && n != "http_api" && n != "http_stream" && n != "stats" && n != "vhost" - && n != "pithy_print") - { + && n != "http_api" && n != "stats" && n != "vhost" && n != "pithy_print_ms" + && n != "http_stream" && n != "http_server" && n != "stream_caster" + && n != "utc_time" + ) { ret = ERROR_SYSTEM_CONFIG_INVALID; srs_error("unsupported directive %s, ret=%d", n.c_str(), ret); return ret; @@ -1248,7 +1363,7 @@ int SrsConfig::check_config() SrsConfDirective* conf = get_http_api(); for (int i = 0; conf && i < (int)conf->directives.size(); i++) { string n = conf->at(i)->name; - if (n != "enabled" && n != "listen") { + if (n != "enabled" && n != "listen" && n != "crossdomain") { ret = ERROR_SYSTEM_CONFIG_INVALID; srs_error("unsupported http_api directive %s, ret=%d", n.c_str(), ret); return ret; @@ -1290,15 +1405,16 @@ int SrsConfig::check_config() } } } - if (true) { - SrsConfDirective* conf = get_pithy_print(); - for (int i = 0; conf && i < (int)conf->directives.size(); i++) { - string n = conf->at(i)->name; - if (n != "publish" && n != "play" && n != "forwarder" - && n != "encoder" && n != "ingester" && n != "hls" && n != "edge" + for (int n = 0; n < (int)stream_casters.size(); n++) { + SrsConfDirective* stream_caster = stream_casters[n]; + for (int i = 0; stream_caster && i < (int)stream_caster->directives.size(); i++) { + SrsConfDirective* conf = stream_caster->at(i); + string n = conf->name; + if (n != "enabled" && n != "caster" && n != "output" + && n != "listen" && n != "rtp_port_min" && n != "rtp_port_max" ) { ret = ERROR_SYSTEM_CONFIG_INVALID; - srs_error("unsupported pithy_print directive %s, ret=%d", n.c_str(), ret); + srs_error("unsupported stream_caster directive %s, ret=%d", n.c_str(), ret); return ret; } } @@ -1309,14 +1425,18 @@ int SrsConfig::check_config() SrsConfDirective* conf = vhost->at(i); string n = conf->name; if (n != "enabled" && n != "chunk_size" - && n != "mode" && n != "origin" && n != "token_traverse" - && n != "dvr" && n != "ingest" && n != "http" && n != "hls" && n != "http_hooks" + && n != "mode" && n != "origin" && n != "token_traverse" && n != "vhost" + && n != "dvr" && n != "ingest" && n != "hls" && n != "http_hooks" && n != "gop_cache" && n != "queue_length" && n != "refer" && n != "refer_publish" && n != "refer_play" && n != "forward" && n != "transcode" && n != "bandcheck" - && n != "time_jitter" + && n != "time_jitter" && n != "mix_correct" && n != "atc" && n != "atc_auto" && n != "debug_srs_upnode" + && n != "mr" && n != "mw_latency" && n != "min_latency" + && n != "security" && n != "http_remux" + && n != "http" && n != "http_static" + && n != "hds" ) { ret = ERROR_SYSTEM_CONFIG_INVALID; srs_error("unsupported vhost directive %s, ret=%d", n.c_str(), ret); @@ -1334,6 +1454,16 @@ int SrsConfig::check_config() return ret; } } + } else if (n == "mr") { + for (int j = 0; j < (int)conf->directives.size(); j++) { + string m = conf->at(j)->name.c_str(); + if (m != "enabled" && m != "latency" + ) { + ret = ERROR_SYSTEM_CONFIG_INVALID; + srs_error("unsupported vhost mr directive %s, ret=%d", m.c_str(), ret); + return ret; + } + } } else if (n == "ingest") { for (int j = 0; j < (int)conf->directives.size(); j++) { string m = conf->at(j)->name.c_str(); @@ -1345,7 +1475,7 @@ int SrsConfig::check_config() return ret; } } - } else if (n == "http") { + } else if (n == "http" || n == "http_static") { for (int j = 0; j < (int)conf->directives.size(); j++) { string m = conf->at(j)->name.c_str(); if (m != "enabled" && m != "mount" && m != "dir") { @@ -1354,10 +1484,22 @@ int SrsConfig::check_config() return ret; } } + } else if (n == "http_remux") { + for (int j = 0; j < (int)conf->directives.size(); j++) { + string m = conf->at(j)->name.c_str(); + if (m != "enabled" && m != "mount" && m != "fast_cache" && m != "hstrs") { + ret = ERROR_SYSTEM_CONFIG_INVALID; + srs_error("unsupported vhost http_remux directive %s, ret=%d", m.c_str(), ret); + return ret; + } + } } else if (n == "hls") { for (int j = 0; j < (int)conf->directives.size(); j++) { string m = conf->at(j)->name.c_str(); - if (m != "enabled" && m != "hls_path" && m != "hls_fragment" && m != "hls_window" && m != "hls_on_error") { + if (m != "enabled" && m != "hls_entry_prefix" && m != "hls_path" && m != "hls_fragment" && m != "hls_window" && m != "hls_on_error" + && m != "hls_storage" && m != "hls_mount" && m != "hls_td_ratio" && m != "hls_aof_ratio" && m != "hls_acodec" && m != "hls_vcodec" + && m != "hls_m3u8_file" && m != "hls_ts_file" && m != "hls_ts_floor" && m != "hls_cleanup" && m != "hls_nb_notify" && m != "hls_wait_keyframe" + ) { ret = ERROR_SYSTEM_CONFIG_INVALID; srs_error("unsupported vhost hls directive %s, ret=%d", m.c_str(), ret); return ret; @@ -1368,6 +1510,7 @@ int SrsConfig::check_config() string m = conf->at(j)->name.c_str(); if (m != "enabled" && m != "on_connect" && m != "on_close" && m != "on_publish" && m != "on_unpublish" && m != "on_play" && m != "on_stop" + && m != "on_dvr" && m != "on_hls" && m != "on_hls_notify" ) { ret = ERROR_SYSTEM_CONFIG_INVALID; srs_error("unsupported vhost http_hooks directive %s, ret=%d", m.c_str(), ret); @@ -1384,6 +1527,16 @@ int SrsConfig::check_config() return ret; } }*/ + } else if (n == "security") { + for (int j = 0; j < (int)conf->directives.size(); j++) { + SrsConfDirective* security = conf->at(j); + string m = security->name.c_str(); + if (m != "enabled" && m != "deny" && m != "allow") { + ret = ERROR_SYSTEM_CONFIG_INVALID; + srs_error("unsupported vhost security directive %s, ret=%d", m.c_str(), ret); + return ret; + } + } } else if (n == "transcode") { for (int j = 0; j < (int)conf->directives.size(); j++) { SrsConfDirective* trans = conf->at(j); @@ -1450,7 +1603,7 @@ int SrsConfig::check_config() // check listen for rtmp. //////////////////////////////////////////////////////////////////////// if (true) { - vector listens = get_listen(); + vector listens = get_listens(); if (listens.size() <= 0) { ret = ERROR_SYSTEM_CONFIG_INVALID; srs_error("directive \"listen\" is empty, ret=%d", ret); @@ -1477,12 +1630,36 @@ int SrsConfig::check_config() // check max connections of system limits if (true) { - int max_open_files = sysconf(_SC_OPEN_MAX); - if (get_max_connections() > max_open_files) { + int nb_consumed_fds = (int)get_listens().size(); + if (!get_http_api_listen().empty()) { + nb_consumed_fds++; + } + if (!get_http_stream_listen().empty()) { + nb_consumed_fds++; + } + if (get_log_tank_file()) { + nb_consumed_fds++; + } + // 0, 1, 2 for stdin, stdout and stderr. + nb_consumed_fds += 3; + + int nb_connections = get_max_connections(); + int nb_total = nb_connections + nb_consumed_fds; + + int max_open_files = (int)sysconf(_SC_OPEN_MAX); + int nb_canbe = max_open_files - nb_consumed_fds - 1; + + // for each play connections, we open a pipe(2fds) to convert SrsConsumver to io, + // refine performance, @see: https://github.com/simple-rtmp-server/srs/issues/194 + if (nb_total >= max_open_files) { ret = ERROR_SYSTEM_CONFIG_INVALID; - srs_error("invalid max_connections=%d, system limit to %d, ret=%d. " - "you can login as root and set the limit: ulimit -HSn %d", get_max_connections(), max_open_files, - ret, get_max_connections()); + srs_error("invalid max_connections=%d, required=%d, system limit to %d, " + "total=%d(max_connections=%d, nb_consumed_fds=%d), ret=%d. " + "you can change max_connections from %d to %d, or " + "you can login as root and set the limit: ulimit -HSn %d", + nb_connections, nb_total + 1, max_open_files, + nb_total, nb_connections, nb_consumed_fds, + ret, nb_connections, nb_canbe, nb_total + 1); return ret; } } @@ -1534,20 +1711,20 @@ int SrsConfig::check_config() //////////////////////////////////////////////////////////////////////// // check http api //////////////////////////////////////////////////////////////////////// - if (get_http_api_listen() <= 0) { + if (get_http_api_listen().empty()) { ret = ERROR_SYSTEM_CONFIG_INVALID; - srs_error("directive http_api listen invalid, listen=%d, ret=%d", - get_http_api_listen(), ret); + srs_error("directive http_api listen invalid, listen=%s, ret=%d", + get_http_api_listen().c_str(), ret); return ret; } //////////////////////////////////////////////////////////////////////// // check http stream //////////////////////////////////////////////////////////////////////// - if (get_http_stream_listen() <= 0) { + if (get_http_stream_listen().empty()) { ret = ERROR_SYSTEM_CONFIG_INVALID; - srs_error("directive http_stream listen invalid, listen=%d, ret=%d", - get_http_stream_listen(), ret); + srs_error("directive http_stream listen invalid, listen=%s, ret=%d", + get_http_stream_listen().c_str(), ret); return ret; } @@ -1642,6 +1819,7 @@ int SrsConfig::check_config() } } #endif + // TODO: FIXME: required http server when hls storage is ram or both. } return ret; @@ -1673,11 +1851,11 @@ bool SrsConfig::get_deamon() srs_assert(root); SrsConfDirective* conf = root->get("daemon"); - if (conf && conf->arg0() == "off") { - return false; + if (!conf || conf->arg0().empty()) { + return true; } - return true; + return SRS_CONF_PERFER_TRUE(conf->arg0()); } SrsConfDirective* SrsConfig::get_root() @@ -1697,7 +1875,7 @@ int SrsConfig::get_max_connections() return ::atoi(conf->arg0().c_str()); } -vector SrsConfig::get_listen() +vector SrsConfig::get_listens() { std::vector ports; @@ -1724,114 +1902,115 @@ string SrsConfig::get_pid_file() return conf->arg0(); } -SrsConfDirective* SrsConfig::get_pithy_print() -{ - return root->get("pithy_print"); -} - -int SrsConfig::get_pithy_print_publish() +int SrsConfig::get_pithy_print_ms() { - SrsConfDirective* pithy = get_pithy_print(); - if (!pithy) { - return SRS_CONF_DEFAULT_STAGE_PUBLISH_USER_INTERVAL_MS; - } - - pithy = pithy->get("publish"); - if (!pithy) { - return SRS_CONF_DEFAULT_STAGE_PUBLISH_USER_INTERVAL_MS; + SrsConfDirective* pithy = root->get("pithy_print_ms"); + if (!pithy || pithy->arg0().empty()) { + return SRS_CONF_DEFAULT_PITHY_PRINT_MS; } return ::atoi(pithy->arg0().c_str()); } -int SrsConfig::get_pithy_print_forwarder() +bool SrsConfig::get_utc_time() { - SrsConfDirective* pithy = get_pithy_print(); - if (!pithy) { - return SRS_CONF_DEFAULT_STAGE_FORWARDER_INTERVAL_MS; - } - - pithy = pithy->get("forwarder"); - if (!pithy) { - return SRS_CONF_DEFAULT_STAGE_FORWARDER_INTERVAL_MS; + SrsConfDirective* conf = root->get("utc_time"); + if (!conf || conf->arg0().empty()) { + return SRS_CONF_DEFAULT_UTC_TIME; } - return ::atoi(pithy->arg0().c_str()); + return SRS_CONF_PERFER_FALSE(conf->arg0()); } -int SrsConfig::get_pithy_print_encoder() +vector SrsConfig::get_stream_casters() { - SrsConfDirective* pithy = get_pithy_print(); - if (!pithy) { - return SRS_CONF_DEFAULT_STAGE_ENCODER_INTERVAL_MS; - } + srs_assert(root); - pithy = pithy->get("encoder"); - if (!pithy) { - return SRS_CONF_DEFAULT_STAGE_ENCODER_INTERVAL_MS; + std::vector stream_casters; + + for (int i = 0; i < (int)root->directives.size(); i++) { + SrsConfDirective* conf = root->at(i); + + if (!conf->is_stream_caster()) { + continue; + } + + stream_casters.push_back(conf); } - return ::atoi(pithy->arg0().c_str()); + return stream_casters; } -int SrsConfig::get_pithy_print_ingester() +bool SrsConfig::get_stream_caster_enabled(SrsConfDirective* sc) { - SrsConfDirective* pithy = get_pithy_print(); - if (!pithy) { - return SRS_CONF_DEFAULT_STAGE_INGESTER_INTERVAL_MS; - } - - pithy = pithy->get("ingester"); - if (!pithy) { - return SRS_CONF_DEFAULT_STAGE_INGESTER_INTERVAL_MS; + srs_assert(sc); + + SrsConfDirective* conf = sc->get("enabled"); + if (!conf || conf->arg0().empty()) { + return SRS_CONF_DEFAULT_STREAM_CASTER_ENABLED; } - return ::atoi(pithy->arg0().c_str()); + return SRS_CONF_PERFER_FALSE(conf->arg0()); } -int SrsConfig::get_pithy_print_hls() +string SrsConfig::get_stream_caster_engine(SrsConfDirective* sc) { - SrsConfDirective* pithy = get_pithy_print(); - if (!pithy) { - return SRS_CONF_DEFAULT_STAGE_HLS_INTERVAL_MS; - } - - pithy = pithy->get("hls"); - if (!pithy) { - return SRS_CONF_DEFAULT_STAGE_HLS_INTERVAL_MS; + srs_assert(sc); + + SrsConfDirective* conf = sc->get("caster"); + if (!conf || conf->arg0().empty()) { + return ""; } - - return ::atoi(pithy->arg0().c_str()); + + return conf->arg0(); } -int SrsConfig::get_pithy_print_play() +string SrsConfig::get_stream_caster_output(SrsConfDirective* sc) { - SrsConfDirective* pithy = get_pithy_print(); - if (!pithy) { - return SRS_CONF_DEFAULT_STAGE_PLAY_USER_INTERVAL_MS; + srs_assert(sc); + + SrsConfDirective* conf = sc->get("output"); + if (!conf || conf->arg0().empty()) { + return ""; } - - pithy = pithy->get("play"); - if (!pithy) { - return SRS_CONF_DEFAULT_STAGE_PLAY_USER_INTERVAL_MS; + + return conf->arg0(); +} + +int SrsConfig::get_stream_caster_listen(SrsConfDirective* sc) +{ + srs_assert(sc); + + SrsConfDirective* conf = sc->get("listen"); + if (!conf || conf->arg0().empty()) { + return 0; } - - return ::atoi(pithy->arg0().c_str()); + + return ::atoi(conf->arg0().c_str()); } -int SrsConfig::get_pithy_print_edge() +int SrsConfig::get_stream_caster_rtp_port_min(SrsConfDirective* sc) { - SrsConfDirective* pithy = get_pithy_print(); - if (!pithy) { - return SRS_CONF_DEFAULT_STAGE_EDGE_INTERVAL_MS; + srs_assert(sc); + + SrsConfDirective* conf = sc->get("rtp_port_min"); + if (!conf || conf->arg0().empty()) { + return 0; } - - pithy = pithy->get("edge"); - if (!pithy) { - return SRS_CONF_DEFAULT_STAGE_EDGE_INTERVAL_MS; + + return ::atoi(conf->arg0().c_str()); +} + +int SrsConfig::get_stream_caster_rtp_port_max(SrsConfDirective* sc) +{ + srs_assert(sc); + + SrsConfDirective* conf = sc->get("rtp_port_max"); + if (!conf || conf->arg0().empty()) { + return 0; } - - return ::atoi(pithy->arg0().c_str()); + + return ::atoi(conf->arg0().c_str()); } SrsConfDirective* SrsConfig::get_vhost(string vhost) @@ -1890,15 +2069,11 @@ bool SrsConfig::get_vhost_enabled(SrsConfDirective* vhost) } SrsConfDirective* conf = vhost->get("enabled"); - if (!conf) { + if (!conf || conf->arg0().empty()) { return true; } - if (conf->arg0() == "off") { - return false; - } - - return true; + return SRS_CONF_PERFER_TRUE(conf->arg0()); } bool SrsConfig::get_gop_cache(string vhost) @@ -1906,15 +2081,15 @@ bool SrsConfig::get_gop_cache(string vhost) SrsConfDirective* conf = get_vhost(vhost); if (!conf) { - return true; + return SRS_PERF_GOP_CACHE; } conf = conf->get("gop_cache"); - if (conf && conf->arg0() == "off") { - return false; + if (!conf || conf->arg0().empty()) { + return SRS_PERF_GOP_CACHE; } - return true; + return SRS_CONF_PERFER_TRUE(conf->arg0()); } bool SrsConfig::get_debug_srs_upnode(string vhost) @@ -1926,11 +2101,11 @@ bool SrsConfig::get_debug_srs_upnode(string vhost) } conf = conf->get("debug_srs_upnode"); - if (conf && conf->arg0() == "off") { - return false; + if (!conf || conf->arg0().empty()) { + return true; } - return true; + return SRS_CONF_PERFER_TRUE(conf->arg0()); } bool SrsConfig::get_atc(string vhost) @@ -1942,11 +2117,11 @@ bool SrsConfig::get_atc(string vhost) } conf = conf->get("atc"); - if (conf && conf->arg0() == "on") { - return true; + if (!conf || conf->arg0().empty()) { + return false; } - return false; + return SRS_CONF_PERFER_FALSE(conf->arg0()); } bool SrsConfig::get_atc_auto(string vhost) @@ -1954,27 +2129,27 @@ bool SrsConfig::get_atc_auto(string vhost) SrsConfDirective* conf = get_vhost(vhost); if (!conf) { - return true; + return SRS_CONF_DEFAULT_ATC_AUTO; } conf = conf->get("atc_auto"); - if (conf && conf->arg0() == "off") { - return false; + if (!conf || conf->arg0().empty()) { + return SRS_CONF_DEFAULT_ATC_AUTO; } - return true; + return SRS_CONF_PERFER_TRUE(conf->arg0()); } int SrsConfig::get_time_jitter(string vhost) { - SrsConfDirective* dvr = get_vhost(vhost); + SrsConfDirective* conf = get_vhost(vhost); std::string time_jitter = SRS_CONF_DEFAULT_TIME_JITTER; - if (dvr) { - SrsConfDirective* conf = dvr->get("time_jitter"); + if (conf) { + conf = conf->get("time_jitter"); - if (conf) { + if (conf && !conf->arg0().empty()) { time_jitter = conf->arg0(); } } @@ -1982,17 +2157,33 @@ int SrsConfig::get_time_jitter(string vhost) return _srs_time_jitter_string2int(time_jitter); } +bool SrsConfig::get_mix_correct(string vhost) +{ + SrsConfDirective* conf = get_vhost(vhost); + + if (!conf) { + return SRS_CONF_DEFAULT_MIX_CORRECT; + } + + conf = conf->get("mix_correct"); + if (!conf || conf->arg0().empty()) { + return SRS_CONF_DEFAULT_MIX_CORRECT; + } + + return SRS_CONF_PERFER_FALSE(conf->arg0()); +} + double SrsConfig::get_queue_length(string vhost) { SrsConfDirective* conf = get_vhost(vhost); if (!conf) { - return SRS_CONF_DEFAULT_QUEUE_LENGTH; + return SRS_PERF_PLAY_QUEUE; } conf = conf->get("queue_length"); if (!conf || conf->arg0().empty()) { - return SRS_CONF_DEFAULT_QUEUE_LENGTH; + return SRS_PERF_PLAY_QUEUE; } return ::atoi(conf->arg0().c_str()); @@ -2046,7 +2237,7 @@ int SrsConfig::get_chunk_size(string vhost) } conf = conf->get("chunk_size"); - if (!conf) { + if (!conf || conf->arg0().empty()) { // vhost does not specify the chunk size, // use the global instead. return get_global_chunk_size(); @@ -2055,10 +2246,84 @@ int SrsConfig::get_chunk_size(string vhost) return ::atoi(conf->arg0().c_str()); } +bool SrsConfig::get_mr_enabled(string vhost) +{ + SrsConfDirective* conf = get_vhost(vhost); + + if (!conf) { + return SRS_PERF_MR_ENABLED; + } + + conf = conf->get("mr"); + if (!conf) { + return SRS_PERF_MR_ENABLED; + } + + conf = conf->get("enabled"); + if (!conf || conf->arg0().empty()) { + return SRS_PERF_MR_ENABLED; + } + + return SRS_CONF_PERFER_FALSE(conf->arg0()); +} + +int SrsConfig::get_mr_sleep_ms(string vhost) +{ + SrsConfDirective* conf = get_vhost(vhost); + + if (!conf) { + return SRS_PERF_MR_SLEEP; + } + + conf = conf->get("mr"); + if (!conf) { + return SRS_PERF_MR_SLEEP; + } + + conf = conf->get("latency"); + if (!conf || conf->arg0().empty()) { + return SRS_PERF_MR_SLEEP; + } + + return ::atoi(conf->arg0().c_str()); +} + +int SrsConfig::get_mw_sleep_ms(string vhost) +{ + SrsConfDirective* conf = get_vhost(vhost); + + if (!conf) { + return SRS_PERF_MW_SLEEP; + } + + conf = conf->get("mw_latency"); + if (!conf || conf->arg0().empty()) { + return SRS_PERF_MW_SLEEP; + } + + return ::atoi(conf->arg0().c_str()); +} + +bool SrsConfig::get_realtime_enabled(string vhost) +{ + SrsConfDirective* conf = get_vhost(vhost); + + if (!conf) { + return SRS_PERF_MIN_LATENCY_ENABLED; + } + + conf = conf->get("min_latency"); + if (!conf || conf->arg0().empty()) { + return SRS_PERF_MIN_LATENCY_ENABLED; + } + + return SRS_CONF_PERFER_FALSE(conf->arg0()); +} + int SrsConfig::get_global_chunk_size() { SrsConfDirective* conf = root->get("chunk_size"); - if (!conf) { + if (!conf || conf->arg0().empty()) { return SRS_CONSTS_RTMP_SRS_CHUNK_SIZE; } @@ -2095,12 +2360,12 @@ bool SrsConfig::get_vhost_http_hooks_enabled(string vhost) return false; } - SrsConfDirective* enabled = conf->get("enabled"); - if (!enabled || enabled->arg0() != "on") { + conf = conf->get("enabled"); + if (!conf || conf->arg0().empty()) { return false; } - return true; + return SRS_CONF_PERFER_FALSE(conf->arg0()); } SrsConfDirective* SrsConfig::get_vhost_on_connect(string vhost) @@ -2169,6 +2434,39 @@ SrsConfDirective* SrsConfig::get_vhost_on_stop(string vhost) return conf->get("on_stop"); } +SrsConfDirective* SrsConfig::get_vhost_on_dvr(string vhost) +{ + SrsConfDirective* conf = get_vhost_http_hooks(vhost); + + if (!conf) { + return NULL; + } + + return conf->get("on_dvr"); +} + +SrsConfDirective* SrsConfig::get_vhost_on_hls(string vhost) +{ + SrsConfDirective* conf = get_vhost_http_hooks(vhost); + + if (!conf) { + return NULL; + } + + return conf->get("on_hls"); +} + +SrsConfDirective* SrsConfig::get_vhost_on_hls_notify(string vhost) +{ + SrsConfDirective* conf = get_vhost_http_hooks(vhost); + + if (!conf) { + return NULL; + } + + return conf->get("on_hls_notify"); +} + bool SrsConfig::get_bw_check_enabled(string vhost) { SrsConfDirective* conf = get_vhost(vhost); @@ -2183,11 +2481,11 @@ bool SrsConfig::get_bw_check_enabled(string vhost) } conf = conf->get("enabled"); - if (!conf || conf->arg0() != "on") { + if (!conf || conf->arg0().empty()) { return false; } - - return true; + + return SRS_CONF_PERFER_FALSE(conf->arg0()); } string SrsConfig::get_bw_check_key(string vhost) @@ -2264,15 +2562,15 @@ bool SrsConfig::get_vhost_is_edge(SrsConfDirective* vhost) SrsConfDirective* conf = vhost; if (!conf) { - return false; + return SRS_CONF_DEFAULT_EDGE_MODE; } conf = conf->get("mode"); - if (!conf || conf->arg0() != "remote") { - return false; + if (!conf || conf->arg0().empty()) { + return SRS_CONF_DEFAULT_EDGE_MODE; } - return true; + return conf->arg0() == "remote"; } SrsConfDirective* SrsConfig::get_vhost_edge_origin(string vhost) @@ -2291,15 +2589,68 @@ bool SrsConfig::get_vhost_edge_token_traverse(string vhost) SrsConfDirective* conf = get_vhost(vhost); if (!conf) { - return false; + return SRS_CONF_DEFAULT_EDGE_TOKEN_TRAVERSE; } conf = conf->get("token_traverse"); - if (!conf || conf->arg0() != "on") { - return false; + if (!conf || conf->arg0().empty()) { + return SRS_CONF_DEFAULT_EDGE_TOKEN_TRAVERSE; } - return true; + return SRS_CONF_PERFER_FALSE(conf->arg0()); +} + +string SrsConfig::get_vhost_edge_transform_vhost(string vhost) +{ + SrsConfDirective* conf = get_vhost(vhost); + + if (!conf) { + return SRS_CONF_DEFAULT_EDGE_TRANSFORM_VHOST; + } + + conf = conf->get("vhost"); + if (!conf || conf->arg0().empty()) { + return SRS_CONF_DEFAULT_EDGE_TRANSFORM_VHOST; + } + + return conf->arg0(); +} + +bool SrsConfig::get_security_enabled(string vhost) +{ + SrsConfDirective* conf = get_vhost(vhost); + + if (!conf) { + return SRS_CONF_DEFAULT_SECURITY_ENABLED; + } + + SrsConfDirective* security = conf->get("security"); + if (!security) { + return SRS_CONF_DEFAULT_SECURITY_ENABLED; + } + + conf = security->get("enabled"); + if (!conf || conf->arg0().empty()) { + return SRS_CONF_DEFAULT_SECURITY_ENABLED; + } + + return SRS_CONF_PERFER_FALSE(conf->arg0()); +} + +SrsConfDirective* SrsConfig::get_security_rules(string vhost) +{ + SrsConfDirective* conf = get_vhost(vhost); + + if (!conf) { + return NULL; + } + + SrsConfDirective* security = conf->get("security"); + if (!security) { + return NULL; + } + + return security; } SrsConfDirective* SrsConfig::get_transcode(string vhost, string scope) @@ -2329,11 +2680,11 @@ bool SrsConfig::get_transcode_enabled(SrsConfDirective* transcode) } SrsConfDirective* conf = transcode->get("enabled"); - if (!conf || conf->arg0() != "on") { + if (!conf || conf->arg0().empty()) { return false; } - return true; + return SRS_CONF_PERFER_FALSE(conf->arg0()); } string SrsConfig::get_transcode_ffmpeg(SrsConfDirective* transcode) @@ -2376,11 +2727,11 @@ bool SrsConfig::get_engine_enabled(SrsConfDirective* engine) } SrsConfDirective* conf = engine->get("enabled"); - if (!conf || conf->arg0() != "on") { + if (!conf || conf->arg0().empty()) { return false; } - return true; + return SRS_CONF_PERFER_FALSE(conf->arg0()); } string SrsConfig::get_engine_iformat(SrsConfDirective* engine) @@ -2710,11 +3061,11 @@ bool SrsConfig::get_ingest_enabled(SrsConfDirective* ingest) SrsConfDirective* conf = ingest->get("enabled"); - if (!conf || conf->arg0() != "on") { + if (!conf || conf->arg0().empty()) { return false; } - return true; + return SRS_CONF_PERFER_FALSE(conf->arg0()); } string SrsConfig::get_ingest_ffmpeg(SrsConfDirective* ingest) @@ -2779,11 +3130,11 @@ bool SrsConfig::get_log_tank_file() srs_assert(root); SrsConfDirective* conf = root->get("srs_log_tank"); - if (conf && conf->arg0() == SRS_CONF_DEFAULT_LOG_TANK_CONSOLE) { - return false; + if (!conf || conf->arg0().empty()) { + return true; } - return true; + return conf->arg0() != SRS_CONF_DEFAULT_LOG_TANK_CONSOLE; } string SrsConfig::get_log_level() @@ -2849,15 +3200,28 @@ bool SrsConfig::get_hls_enabled(string vhost) SrsConfDirective* conf = hls->get("enabled"); - if (!conf) { + if (!conf || conf->arg0().empty()) { return false; } - if (conf->arg0() == "on") { - return true; + return SRS_CONF_PERFER_FALSE(conf->arg0()); +} + +string SrsConfig::get_hls_entry_prefix(string vhost) +{ + SrsConfDirective* hls = get_hls(vhost); + + if (!hls) { + return ""; } - return false; + SrsConfDirective* conf = hls->get("hls_entry_prefix"); + + if (!conf) { + return ""; + } + + return conf->arg0(); } string SrsConfig::get_hls_path(string vhost) @@ -2873,10 +3237,61 @@ string SrsConfig::get_hls_path(string vhost) if (!conf) { return SRS_CONF_DEFAULT_HLS_PATH; } + + return conf->arg0(); +} + +string SrsConfig::get_hls_m3u8_file(string vhost) +{ + SrsConfDirective* hls = get_hls(vhost); + + if (!hls) { + return SRS_CONF_DEFAULT_HLS_M3U8_FILE; + } + + SrsConfDirective* conf = hls->get("hls_m3u8_file"); + + if (!conf) { + return SRS_CONF_DEFAULT_HLS_M3U8_FILE; + } + + return conf->arg0(); +} +string SrsConfig::get_hls_ts_file(string vhost) +{ + SrsConfDirective* hls = get_hls(vhost); + + if (!hls) { + return SRS_CONF_DEFAULT_HLS_TS_FILE; + } + + SrsConfDirective* conf = hls->get("hls_ts_file"); + + if (!conf) { + return SRS_CONF_DEFAULT_HLS_TS_FILE; + } + return conf->arg0(); } +bool SrsConfig::get_hls_ts_floor(string vhost) +{ + SrsConfDirective* hls = get_hls(vhost); + + if (!hls) { + return SRS_CONF_DEFAULT_HLS_TS_FLOOR; + } + + SrsConfDirective* conf = hls->get("hls_ts_floor"); + + if (!conf || conf->arg0().empty()) { + return SRS_CONF_DEFAULT_HLS_TS_FLOOR; + } + + return SRS_CONF_PERFER_FALSE(conf->arg0()); +} + double SrsConfig::get_hls_fragment(string vhost) { SrsConfDirective* hls = get_hls(vhost); @@ -2894,6 +3309,40 @@ double SrsConfig::get_hls_fragment(string vhost) return ::atof(conf->arg0().c_str()); } +double SrsConfig::get_hls_td_ratio(string vhost) +{ + SrsConfDirective* hls = get_hls(vhost); + + if (!hls) { + return SRS_CONF_DEFAULT_HLS_TD_RATIO; + } + + SrsConfDirective* conf = hls->get("hls_td_ratio"); + + if (!conf) { + return SRS_CONF_DEFAULT_HLS_TD_RATIO; + } + + return ::atof(conf->arg0().c_str()); +} + +double SrsConfig::get_hls_aof_ratio(string vhost) +{ + SrsConfDirective* hls = get_hls(vhost); + + if (!hls) { + return SRS_CONF_DEFAULT_HLS_AOF_RATIO; + } + + SrsConfDirective* conf = hls->get("hls_aof_ratio"); + + if (!conf) { + return SRS_CONF_DEFAULT_HLS_AOF_RATIO; + } + + return ::atof(conf->arg0().c_str()); +} + double SrsConfig::get_hls_window(string vhost) { SrsConfDirective* hls = get_hls(vhost); @@ -2928,6 +3377,203 @@ string SrsConfig::get_hls_on_error(string vhost) return conf->arg0(); } +string SrsConfig::get_hls_storage(string vhost) +{ + SrsConfDirective* hls = get_hls(vhost); + + if (!hls) { + return SRS_CONF_DEFAULT_HLS_STORAGE; + } + + SrsConfDirective* conf = hls->get("hls_storage"); + + if (!conf) { + return SRS_CONF_DEFAULT_HLS_STORAGE; + } + + return conf->arg0(); +} + +string SrsConfig::get_hls_mount(string vhost) +{ + SrsConfDirective* hls = get_hls(vhost); + + if (!hls) { + return SRS_CONF_DEFAULT_HLS_MOUNT; + } + + SrsConfDirective* conf = hls->get("hls_mount"); + + if (!conf) { + return SRS_CONF_DEFAULT_HLS_MOUNT; + } + + return conf->arg0(); +} + +string SrsConfig::get_hls_acodec(string vhost) +{ + SrsConfDirective* hls = get_hls(vhost); + + if (!hls) { + return SRS_CONF_DEFAULT_HLS_ACODEC; + } + + SrsConfDirective* conf = hls->get("hls_acodec"); + + if (!conf) { + return SRS_CONF_DEFAULT_HLS_ACODEC; + } + + return conf->arg0(); +} + +string SrsConfig::get_hls_vcodec(string vhost) +{ + SrsConfDirective* hls = get_hls(vhost); + + if (!hls) { + return SRS_CONF_DEFAULT_HLS_VCODEC; + } + + SrsConfDirective* conf = hls->get("hls_vcodec"); + + if (!conf || conf->arg0().empty()) { + return SRS_CONF_DEFAULT_HLS_VCODEC; + } + + return conf->arg0(); +} + +int SrsConfig::get_vhost_hls_nb_notify(string vhost) +{ + SrsConfDirective* conf = get_hls(vhost); + + if (!conf) { + return SRS_CONF_DEFAULT_HLS_NB_NOTIFY; + } + + conf = conf->get("hls_nb_notify"); + if (!conf || conf->arg0().empty()) { + return SRS_CONF_DEFAULT_HLS_NB_NOTIFY; + } + + return ::atoi(conf->arg0().c_str()); +} + +bool SrsConfig::get_hls_cleanup(string vhost) +{ + SrsConfDirective* hls = get_hls(vhost); + + if (!hls) { + return SRS_CONF_DEFAULT_HLS_CLEANUP; + } + + SrsConfDirective* conf = hls->get("hls_cleanup"); + + if (!conf || conf->arg0().empty()) { + return SRS_CONF_DEFAULT_HLS_CLEANUP; + } + + return SRS_CONF_PERFER_TRUE(conf->arg0()); +} + +bool SrsConfig::get_hls_wait_keyframe(string vhost) +{ + SrsConfDirective* hls = get_hls(vhost); + + if (!hls) { + return SRS_CONF_DEFAULT_HLS_WAIT_KEYFRAME; + } + + SrsConfDirective* conf = hls->get("hls_wait_keyframe"); + + if (!conf || conf->arg0().empty()) { + return SRS_CONF_DEFAULT_HLS_WAIT_KEYFRAME; + } + + return SRS_CONF_PERFER_TRUE(conf->arg0()); +} + +SrsConfDirective *SrsConfig::get_hds(const string &vhost) +{ + SrsConfDirective* conf = get_vhost(vhost); + + if (!conf) { + return NULL; + } + + return conf->get("hds"); +} + +bool SrsConfig::get_hds_enabled(const string &vhost) +{ + SrsConfDirective* hds = get_hds(vhost); + + if (!hds) { + return false; + } + + SrsConfDirective* conf = hds->get("enabled"); + + if (!conf || conf->arg0().empty()) { + return false; + } + + return SRS_CONF_PERFER_FALSE(conf->arg0()); +} + +string SrsConfig::get_hds_path(const string &vhost) +{ + SrsConfDirective* hds = get_hds(vhost); + + if (!hds) { + return SRS_CONF_DEFAULT_HDS_PATH; + } + + SrsConfDirective* conf = hds->get("hds_path"); + + if (!conf) { + return SRS_CONF_DEFAULT_HDS_PATH; + } + + return conf->arg0(); +} + +double SrsConfig::get_hds_fragment(const string &vhost) +{ + SrsConfDirective* hds = get_hds(vhost); + + if (!hds) { + return SRS_CONF_DEFAULT_HDS_FRAGMENT; + } + + SrsConfDirective* conf = hds->get("hds_fragment"); + + if (!conf) { + return SRS_CONF_DEFAULT_HDS_FRAGMENT; + } + + return ::atof(conf->arg0().c_str()); +} + +double SrsConfig::get_hds_window(const string &vhost) +{ + SrsConfDirective* hds = get_hds(vhost); + + if (!hds) { + return SRS_CONF_DEFAULT_HDS_WINDOW; + } + + SrsConfDirective* conf = hds->get("hds_window"); + + if (!conf) { + return SRS_CONF_DEFAULT_HDS_WINDOW; + } + + return ::atof(conf->arg0().c_str()); +} + SrsConfDirective* SrsConfig::get_dvr(string vhost) { SrsConfDirective* conf = get_vhost(vhost); @@ -2949,15 +3595,11 @@ bool SrsConfig::get_dvr_enabled(string vhost) SrsConfDirective* conf = dvr->get("enabled"); - if (!conf) { + if (!conf || conf->arg0().empty()) { return false; } - if (conf->arg0() == "on") { - return true; - } - - return false; + return SRS_CONF_PERFER_FALSE(conf->arg0()); } string SrsConfig::get_dvr_path(string vhost) @@ -3021,11 +3663,11 @@ bool SrsConfig::get_dvr_wait_keyframe(string vhost) SrsConfDirective* conf = dvr->get("dvr_wait_keyframe"); - if (!conf || conf->arg0() != "off") { + if (!conf || conf->arg0().empty()) { return true; } - return false; + return SRS_CONF_PERFER_TRUE(conf->arg0()); } int SrsConfig::get_dvr_time_jitter(string vhost) @@ -3063,14 +3705,14 @@ bool SrsConfig::get_http_api_enabled(SrsConfDirective* conf) } conf = conf->get("enabled"); - if (conf && conf->arg0() == "on") { - return true; + if (!conf || conf->arg0().empty()) { + return false; } - return false; + return SRS_CONF_PERFER_FALSE(conf->arg0()); } -int SrsConfig::get_http_api_listen() +string SrsConfig::get_http_api_listen() { SrsConfDirective* conf = get_http_api(); @@ -3083,7 +3725,23 @@ int SrsConfig::get_http_api_listen() return SRS_CONF_DEFAULT_HTTP_API_PORT; } - return ::atoi(conf->arg0().c_str()); + return conf->arg0(); +} + +bool SrsConfig::get_http_api_crossdomain() +{ + SrsConfDirective* conf = get_http_api(); + + if (!conf) { + return SRS_CONF_DEFAULT_HTTP_API_CROSSDOMAIN; + } + + conf = conf->get("crossdomain"); + if (!conf || conf->arg0().empty()) { + return SRS_CONF_DEFAULT_HTTP_API_CROSSDOMAIN; + } + + return SRS_CONF_PERFER_TRUE(conf->arg0()); } bool SrsConfig::get_http_stream_enabled() @@ -3094,7 +3752,13 @@ bool SrsConfig::get_http_stream_enabled() SrsConfDirective* SrsConfig::get_http_stream() { - return root->get("http_stream"); + SrsConfDirective* conf = root->get("http_stream"); + // http_stream renamed to http_server in SRS2. + if (!conf) { + conf = root->get("http_server"); + } + + return conf; } bool SrsConfig::get_http_stream_enabled(SrsConfDirective* conf) @@ -3104,14 +3768,14 @@ bool SrsConfig::get_http_stream_enabled(SrsConfDirective* conf) } conf = conf->get("enabled"); - if (conf && conf->arg0() == "on") { - return true; + if (!conf || conf->arg0().empty()) { + return false; } - return false; + return SRS_CONF_PERFER_FALSE(conf->arg0()); } -int SrsConfig::get_http_stream_listen() +string SrsConfig::get_http_stream_listen() { SrsConfDirective* conf = get_http_stream(); @@ -3124,7 +3788,7 @@ int SrsConfig::get_http_stream_listen() return SRS_CONF_DEFAULT_HTTP_STREAM_PORT; } - return ::atoi(conf->arg0().c_str()); + return conf->arg0(); } string SrsConfig::get_http_stream_dir() @@ -3148,38 +3812,41 @@ string SrsConfig::get_http_stream_dir() bool SrsConfig::get_vhost_http_enabled(string vhost) { - SrsConfDirective* conf = get_vhost(vhost); - if (!conf) { + SrsConfDirective* vconf = get_vhost(vhost); + if (!vconf) { return false; } - conf = conf->get("http"); + SrsConfDirective* conf = vconf->get("http"); if (!conf) { - return false; + conf = vconf->get("http_static"); } - conf = conf->get("enabled"); if (!conf) { return false; } - if (conf->arg0() == "on") { - return true; + conf = conf->get("enabled"); + if (!conf || conf->arg0().empty()) { + return false; } - return false; + return SRS_CONF_PERFER_FALSE(conf->arg0()); } string SrsConfig::get_vhost_http_mount(string vhost) { - SrsConfDirective* conf = get_vhost(vhost); - if (!conf) { + SrsConfDirective* vconf = get_vhost(vhost); + if (!vconf) { return SRS_CONF_DEFAULT_HTTP_MOUNT; } - conf = conf->get("http"); + SrsConfDirective* conf = vconf->get("http"); if (!conf) { - return SRS_CONF_DEFAULT_HTTP_MOUNT; + conf = vconf->get("http_static"); + if (!conf) { + return SRS_CONF_DEFAULT_HTTP_MOUNT; + } } conf = conf->get("mount"); @@ -3192,14 +3859,17 @@ string SrsConfig::get_vhost_http_mount(string vhost) string SrsConfig::get_vhost_http_dir(string vhost) { - SrsConfDirective* conf = get_vhost(vhost); - if (!conf) { + SrsConfDirective* vconf = get_vhost(vhost); + if (!vconf) { return SRS_CONF_DEFAULT_HTTP_DIR; } - conf = conf->get("http"); + SrsConfDirective* conf = vconf->get("http"); if (!conf) { - return SRS_CONF_DEFAULT_HTTP_DIR; + conf = vconf->get("http_static"); + if (!conf) { + return SRS_CONF_DEFAULT_HTTP_DIR; + } } conf = conf->get("dir"); @@ -3210,6 +3880,90 @@ string SrsConfig::get_vhost_http_dir(string vhost) return conf->arg0(); } +bool SrsConfig::get_vhost_http_remux_enabled(string vhost) +{ + SrsConfDirective* conf = get_vhost(vhost); + if (!conf) { + return false; + } + + conf = conf->get("http_remux"); + if (!conf) { + return false; + } + + conf = conf->get("enabled"); + if (!conf || conf->arg0().empty()) { + return false; + } + + return SRS_CONF_PERFER_FALSE(conf->arg0()); +} + +double SrsConfig::get_vhost_http_remux_fast_cache(string vhost) +{ + SrsConfDirective* conf = get_vhost(vhost); + if (!conf) { + return SRS_CONF_DEFAULT_HTTP_AUDIO_FAST_CACHE; + } + + conf = conf->get("http_remux"); + if (!conf) { + return SRS_CONF_DEFAULT_HTTP_AUDIO_FAST_CACHE; + } + + conf = conf->get("fast_cache"); + if (!conf) { + return SRS_CONF_DEFAULT_HTTP_AUDIO_FAST_CACHE; + } + + if (conf->arg0().empty()) { + return SRS_CONF_DEFAULT_HTTP_AUDIO_FAST_CACHE; + } + + return ::atof(conf->arg0().c_str()); +} + +string SrsConfig::get_vhost_http_remux_mount(string vhost) +{ + SrsConfDirective* conf = get_vhost(vhost); + if (!conf) { + return SRS_CONF_DEFAULT_HTTP_REMUX_MOUNT; + } + + conf = conf->get("http_remux"); + if (!conf) { + return SRS_CONF_DEFAULT_HTTP_REMUX_MOUNT; + } + + conf = conf->get("mount"); + if (!conf || conf->arg0().empty()) { + return SRS_CONF_DEFAULT_HTTP_REMUX_MOUNT; + } + + return conf->arg0(); +} + +bool SrsConfig::get_vhost_http_remux_hstrs(string vhost) +{ + SrsConfDirective* conf = get_vhost(vhost); + if (!conf) { + return false; + } + + conf = conf->get("http_remux"); + if (!conf) { + return false; + } + + conf = conf->get("hstrs"); + if (!conf || conf->arg0().empty()) { + return false; + } + + return SRS_CONF_PERFER_FALSE(conf->arg0()); +} + SrsConfDirective* SrsConfig::get_heartbeart() { return root->get("heartbeat"); @@ -3224,11 +3978,11 @@ bool SrsConfig::get_heartbeat_enabled() } conf = conf->get("enabled"); - if (!conf || conf->arg0() != "on") { + if (!conf || conf->arg0().empty()) { return SRS_CONF_DEFAULT_HTTP_HEAETBEAT_ENABLED; } - return true; + return SRS_CONF_PERFER_FALSE(conf->arg0()); } int64_t SrsConfig::get_heartbeat_interval() @@ -3287,11 +4041,11 @@ bool SrsConfig::get_heartbeat_summaries() } conf = conf->get("summaries"); - if (!conf || conf->arg0() != "on") { + if (!conf || conf->arg0().empty()) { return SRS_CONF_DEFAULT_HTTP_HEAETBEAT_SUMMARIES; } - return true; + return SRS_CONF_PERFER_FALSE(conf->arg0()); } SrsConfDirective* SrsConfig::get_stats() diff --git a/trunk/src/app/srs_app_config.hpp b/trunk/src/app/srs_app_config.hpp index dd3d685339..6e2a1b3533 100644 --- a/trunk/src/app/srs_app_config.hpp +++ b/trunk/src/app/srs_app_config.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -43,23 +43,37 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define SRS_CONF_DEFAULT_LOG_TANK_CONSOLE "console" #define SRS_CONF_DEFAULT_COFNIG_FILE "conf/srs.conf" #define SRS_CONF_DEFAULT_FF_LOG_DIR "./objs" +#define SRS_CONF_DEFAULT_UTC_TIME false #define SRS_CONF_DEFAULT_MAX_CONNECTIONS 1000 #define SRS_CONF_DEFAULT_HLS_PATH "./objs/nginx/html" +#define SRS_CONF_DEFAULT_HLS_M3U8_FILE "[app]/[stream].m3u8" +#define SRS_CONF_DEFAULT_HLS_TS_FILE "[app]/[stream]-[seq].ts" +#define SRS_CONF_DEFAULT_HLS_TS_FLOOR false #define SRS_CONF_DEFAULT_HLS_FRAGMENT 10 +#define SRS_CONF_DEFAULT_HLS_TD_RATIO 1.5 +#define SRS_CONF_DEFAULT_HLS_AOF_RATIO 2.0 #define SRS_CONF_DEFAULT_HLS_WINDOW 60 #define SRS_CONF_DEFAULT_HLS_ON_ERROR_IGNORE "ignore" #define SRS_CONF_DEFAULT_HLS_ON_ERROR_DISCONNECT "disconnect" #define SRS_CONF_DEFAULT_HLS_ON_ERROR_CONTINUE "continue" #define SRS_CONF_DEFAULT_HLS_ON_ERROR SRS_CONF_DEFAULT_HLS_ON_ERROR_IGNORE -#define SRS_CONF_DEFAULT_DVR_PATH "./objs/nginx/html" +#define SRS_CONF_DEFAULT_HLS_STORAGE "disk" +#define SRS_CONF_DEFAULT_HLS_MOUNT "[vhost]/[app]/[stream].m3u8" +#define SRS_CONF_DEFAULT_HLS_ACODEC "aac" +#define SRS_CONF_DEFAULT_HLS_VCODEC "h264" +#define SRS_CONF_DEFAULT_HLS_CLEANUP true +#define SRS_CONF_DEFAULT_HLS_WAIT_KEYFRAME true +#define SRS_CONF_DEFAULT_HLS_NB_NOTIFY 64 +#define SRS_CONF_DEFAULT_DVR_PATH "./objs/nginx/html/[app]/[stream].[timestamp].flv" #define SRS_CONF_DEFAULT_DVR_PLAN_SESSION "session" #define SRS_CONF_DEFAULT_DVR_PLAN_SEGMENT "segment" +#define SRS_CONF_DEFAULT_DVR_PLAN_APPEND "append" #define SRS_CONF_DEFAULT_DVR_PLAN SRS_CONF_DEFAULT_DVR_PLAN_SESSION #define SRS_CONF_DEFAULT_DVR_DURATION 30 #define SRS_CONF_DEFAULT_TIME_JITTER "full" -// in seconds, the live queue length. -#define SRS_CONF_DEFAULT_QUEUE_LENGTH 30 +#define SRS_CONF_DEFAULT_ATC_AUTO true +#define SRS_CONF_DEFAULT_MIX_CORRECT false // in seconds, the paused queue length. #define SRS_CONF_DEFAULT_PAUSED_LENGTH 10 // the interval in seconds for bandwidth check @@ -67,26 +81,30 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // the interval in seconds for bandwidth check #define SRS_CONF_DEFAULT_BANDWIDTH_LIMIT_KBPS 1000 -#define SRS_CONF_DEFAULT_HTTP_MOUNT "/" +#define SRS_CONF_DEFAULT_HTTP_MOUNT "[vhost]/" +#define SRS_CONF_DEFAULT_HTTP_REMUX_MOUNT "[vhost]/[app]/[stream].flv" #define SRS_CONF_DEFAULT_HTTP_DIR SRS_CONF_DEFAULT_HLS_PATH +#define SRS_CONF_DEFAULT_HTTP_AUDIO_FAST_CACHE 0 -#define SRS_CONF_DEFAULT_HTTP_STREAM_PORT 8080 -#define SRS_CONF_DEFAULT_HTTP_API_PORT 1985 +#define SRS_CONF_DEFAULT_HTTP_STREAM_PORT "8080" +#define SRS_CONF_DEFAULT_HTTP_API_PORT "1985" +#define SRS_CONF_DEFAULT_HTTP_API_CROSSDOMAIN true #define SRS_CONF_DEFAULT_HTTP_HEAETBEAT_ENABLED false #define SRS_CONF_DEFAULT_HTTP_HEAETBEAT_INTERVAL 9.9 #define SRS_CONF_DEFAULT_HTTP_HEAETBEAT_URL "http://"SRS_CONSTS_LOCALHOST":8085/api/v1/servers" #define SRS_CONF_DEFAULT_HTTP_HEAETBEAT_SUMMARIES false +#define SRS_CONF_DEFAULT_SECURITY_ENABLED false + +#define SRS_CONF_DEFAULT_STREAM_CASTER_ENABLED false +#define SRS_CONF_DEFAULT_STREAM_CASTER_MPEGTS_OVER_UDP "mpegts_over_udp" +#define SRS_CONF_DEFAULT_STREAM_CASTER_RTSP "rtsp" +#define SRS_CONF_DEFAULT_STREAM_CASTER_FLV "flv" + #define SRS_CONF_DEFAULT_STATS_NETWORK_DEVICE_INDEX 0 -#define SRS_CONF_DEFAULT_STAGE_PLAY_USER_INTERVAL_MS 10000 -#define SRS_CONF_DEFAULT_STAGE_PUBLISH_USER_INTERVAL_MS 10000 -#define SRS_CONF_DEFAULT_STAGE_FORWARDER_INTERVAL_MS 10000 -#define SRS_CONF_DEFAULT_STAGE_ENCODER_INTERVAL_MS 10000 -#define SRS_CONF_DEFAULT_STAGE_INGESTER_INTERVAL_MS 10000 -#define SRS_CONF_DEFAULT_STAGE_HLS_INTERVAL_MS 10000 -#define SRS_CONF_DEFAULT_STAGE_EDGE_INTERVAL_MS 10000 +#define SRS_CONF_DEFAULT_PITHY_PRINT_MS 10000 #define SRS_CONF_DEFAULT_INGEST_TYPE_FILE "file" #define SRS_CONF_DEFAULT_INGEST_TYPE_STREAM "stream" @@ -94,10 +112,19 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define SRS_CONF_DEFAULT_TRANSCODE_IFORMAT "flv" #define SRS_CONF_DEFAULT_TRANSCODE_OFORMAT "flv" +#define SRS_CONF_DEFAULT_EDGE_MODE false +#define SRS_CONF_DEFAULT_EDGE_TOKEN_TRAVERSE false +#define SRS_CONF_DEFAULT_EDGE_TRANSFORM_VHOST "[vhost]" + +// hds default value +#define SRS_CONF_DEFAULT_HDS_PATH "./objs/nginx/html" +#define SRS_CONF_DEFAULT_HDS_WINDOW (60) +#define SRS_CONF_DEFAULT_HDS_FRAGMENT (10) + namespace _srs_internal { class SrsConfigBuffer; -}; +} /** * the config directive. @@ -186,6 +213,10 @@ class SrsConfDirective * whether current directive is vhost. */ virtual bool is_vhost(); + /** + * whether current directive is stream_caster. + */ + virtual bool is_stream_caster(); // parse utilities public: /** @@ -340,11 +371,11 @@ class SrsConfig * print help and exit. */ virtual void print_help(char** argv); +public: /** * parse the config file, which is specified by cli. */ virtual int parse_file(const char* filename); -public: /** * check the parsed config. */ @@ -394,7 +425,7 @@ class SrsConfig * user can specifies multiple listen ports, * each args of directive is a listen port. */ - virtual std::vector get_listen(); + virtual std::vector get_listens(); /** * get the pid file path. * the pid file is used to save the pid of SRS, @@ -404,46 +435,46 @@ class SrsConfig * user can use different pid file for each process. */ virtual std::string get_pid_file(); -// pithy print -private: - virtual SrsConfDirective* get_pithy_print(); + /** + * get pithy print pulse ms, + * for example, all rtmp connections only print one message + * every this interval in ms. + */ + virtual int get_pithy_print_ms(); + /** + * whether use utc-time to format the time. + */ + virtual bool get_utc_time(); +// stream_caster section public: /** - * get the pithy print interval for publish, in ms, - * the publish(flash/FMLE) message print. + * get all stream_caster in config file. */ - virtual int get_pithy_print_publish(); + virtual std::vector get_stream_casters(); /** - * get the pithy print interval for forwarder, in ms, - * the forwarder message print, for SRS forward stream to other servers. + * get whether the specified stream_caster is enabled. */ - virtual int get_pithy_print_forwarder(); + virtual bool get_stream_caster_enabled(SrsConfDirective* sc); /** - * get the pithy print interval for encoder, in ms, - * the encoder message print, for FFMPEG transcoder. + * get the engine of stream_caster, the caster config. */ - virtual int get_pithy_print_encoder(); + virtual std::string get_stream_caster_engine(SrsConfDirective* sc); /** - * get the pithy print interval for ingester, in ms, - * the ingest used FFMPEG, or your tools, to read and transcode other stream - * to RTMP to SRS. + * get the output rtmp url of stream_caster, the output config. */ - virtual int get_pithy_print_ingester(); + virtual std::string get_stream_caster_output(SrsConfDirective* sc); /** - * get the pithy print interval for HLS, in ms, - * the HLS used for IOS/android/PC, SRS will mux RTMP to HLS. + * get the listen port of stream caster. */ - virtual int get_pithy_print_hls(); + virtual int get_stream_caster_listen(SrsConfDirective* sc); /** - * get the pithy print interval for Play, in ms, - * the play is client or edge playing RTMP stream + * get the min udp port for rtp of stream caster rtsp. */ - virtual int get_pithy_print_play(); + virtual int get_stream_caster_rtp_port_min(SrsConfDirective* sc); /** - * get the pithy print interval for edge, in ms, - * the edge will get stream from upnode. + * get the max udp port for rtp of stream caster rtsp. */ - virtual int get_pithy_print_edge(); + virtual int get_stream_caster_rtp_port_max(SrsConfDirective* sc); // vhost specified section public: /** @@ -478,7 +509,7 @@ class SrsConfig * whether debug_srs_upnode is enabled of vhost. * debug_srs_upnode is very important feature for tracable log, * but some server, for instance, flussonic donot support it. - * @see https://github.com/winlinvip/simple-rtmp-server/issues/160 + * @see https://github.com/simple-rtmp-server/srs/issues/160 * @return true when debug_srs_upnode is ok; otherwise, false. * @remark, default true. */ @@ -503,6 +534,11 @@ class SrsConfig * @remark, default full. */ virtual int get_time_jitter(std::string vhost); + /** + * whether use mix correct algorithm to ensure the timestamp + * monotonically increase. + */ + virtual bool get_mix_correct(std::string vhost); /** * get the cache queue length, in seconds. * when exceed the queue length, drop packet util I frame. @@ -534,6 +570,29 @@ class SrsConfig * @remark, default 60000. */ virtual int get_chunk_size(std::string vhost); + /** + * whether mr is enabled for vhost. + * @param vhost, the vhost to get the mr. + */ + virtual bool get_mr_enabled(std::string vhost); + /** + * get the mr sleep time in ms for vhost. + * @param vhost, the vhost to get the mr sleep time. + */ + // TODO: FIXME: add utest for mr config. + virtual int get_mr_sleep_ms(std::string vhost); + /** + * get the mw sleep time in ms for vhost. + * @param vhost, the vhost to get the mw sleep time. + */ + // TODO: FIXME: add utest for mw config. + virtual int get_mw_sleep_ms(std::string vhost); + /** + * whether min latency mode enabled. + * @param vhost, the vhost to get the min_latency. + */ + // TODO: FIXME: add utest for min_latency. + virtual bool get_realtime_enabled(std::string vhost); private: /** * get the global chunk size. @@ -587,6 +646,21 @@ class SrsConfig * @return the on_stop callback directive, the args is the url to callback. */ virtual SrsConfDirective* get_vhost_on_stop(std::string vhost); + /** + * get the on_dvr callbacks of vhost. + * @return the on_dvr callback directive, the args is the url to callback. + */ + virtual SrsConfDirective* get_vhost_on_dvr(std::string vhost); + /** + * get the on_hls callbacks of vhost. + * @return the on_hls callback directive, the args is the url to callback. + */ + virtual SrsConfDirective* get_vhost_on_hls(std::string vhost); + /** + * get the on_hls_notify callbacks of vhost. + * @return the on_hls_notify callback directive, the args is the url to callback. + */ + virtual SrsConfDirective* get_vhost_on_hls_notify(std::string vhost); // bwct(bandwidth check tool) section public: /** @@ -638,6 +712,21 @@ class SrsConfig * all clients connected to edge must be tranverse to origin to verify. */ virtual bool get_vhost_edge_token_traverse(std::string vhost); + /** + * get the transformed vhost for edge, + * @see https://github.com/simple-rtmp-server/srs/issues/372 + */ + virtual std::string get_vhost_edge_transform_vhost(std::string vhost); +// vhost security section +public: + /** + * whether the secrity of vhost enabled. + */ + virtual bool get_security_enabled(std::string vhost); + /** + * get the security rules. + */ + virtual SrsConfDirective* get_security_rules(std::string vhost); // vhost transcode section public: /** @@ -755,7 +844,7 @@ class SrsConfig * @remark, we will use some variable, for instance, [vhost] to substitude with vhost. */ virtual std::string get_engine_output(SrsConfDirective* engine); -// ingest section +// vhost ingest section public: /** * get the ingest directives of vhost. @@ -816,15 +905,38 @@ class SrsConfig */ virtual bool get_hls_enabled(std::string vhost); /** - * get the HLS ts/m3u8 file store path. + * get the HLS m3u8 list ts segment entry prefix info. */ + virtual std::string get_hls_entry_prefix(std::string vhost); + /** + * get the HLS ts/m3u8 file store path. + */ virtual std::string get_hls_path(std::string vhost); + /** + * get the HLS m3u8 file path template. + */ + virtual std::string get_hls_m3u8_file(std::string vhost); + /** + * get the HLS ts file path template. + */ + virtual std::string get_hls_ts_file(std::string vhost); + /** + * whether enable the floor(timestamp/hls_fragment) for variable timestamp. + */ + virtual bool get_hls_ts_floor(std::string vhost); /** * get the hls fragment time, in seconds. - * a fragment is a ts file. */ virtual double get_hls_fragment(std::string vhost); /** + * get the hls td(target duration) ratio. + */ + virtual double get_hls_td_ratio(std::string vhost); + /** + * get the hls aof(audio overflow) ratio. + */ + virtual double get_hls_aof_ratio(std::string vhost); + /** * get the hls window time, in seconds. * a window is a set of ts, the ts collection in m3u8. * @remark SRS will delete the ts exceed the window. @@ -834,9 +946,63 @@ class SrsConfig * get the hls hls_on_error config. * the ignore will ignore error and disable hls. * the disconnect will disconnect publish connection. - * @see https://github.com/winlinvip/simple-rtmp-server/issues/264 + * @see https://github.com/simple-rtmp-server/srs/issues/264 */ virtual std::string get_hls_on_error(std::string vhost); + /** + * get the HLS storage type. + */ + virtual std::string get_hls_storage(std::string vhost); + /** + * get the HLS mount url for HTTP server. + */ + virtual std::string get_hls_mount(std::string vhost); + /** + * get the HLS default audio codec. + */ + virtual std::string get_hls_acodec(std::string vhost); + /** + * get the HLS default video codec. + */ + virtual std::string get_hls_vcodec(std::string vhost); + /** + * whether cleanup the old ts files. + */ + virtual bool get_hls_cleanup(std::string vhost); + /** + * whether reap the ts when got keyframe. + */ + virtual bool get_hls_wait_keyframe(std::string vhost); + /** + * get the size of bytes to read from cdn network, for the on_hls_notify callback, + * that is, to read max bytes of the bytes from the callback, or timeout or error. + */ + virtual int get_vhost_hls_nb_notify(std::string vhost); +// hds section +private: + /** + * get the hds directive of vhost. + */ + virtual SrsConfDirective* get_hds(const std::string &vhost); +public: + /** + * whether HDS is enabled. + */ + virtual bool get_hds_enabled(const std::string &vhost); + /** + * get the HDS file store path. + */ + virtual std::string get_hds_path(const std::string &vhost); + /** + * get the hds fragment time, in seconds. + */ + virtual double get_hds_fragment(const std::string &vhost); + /** + * get the hds window time, in seconds. + * a window is a set of hds fragments. + */ + virtual double get_hds_window(const std::string &vhost); + // dvr section private: /** @@ -857,11 +1023,11 @@ class SrsConfig */ virtual std::string get_dvr_plan(std::string vhost); /** - * get the duration of dvr flv, for segment plan. + * get the duration of dvr flv. */ virtual int get_dvr_duration(std::string vhost); /** - * whether wait keyframe to reap segment, for segment plan. + * whether wait keyframe to reap segment. */ virtual bool get_dvr_wait_keyframe(std::string vhost); /** @@ -886,7 +1052,11 @@ class SrsConfig /** * get the http api listen port. */ - virtual int get_http_api_listen(); + virtual std::string get_http_api_listen(); + /** + * whether enable crossdomain for http api. + */ + virtual bool get_http_api_crossdomain(); // http stream section private: /** @@ -905,7 +1075,7 @@ class SrsConfig /** * get the http stream listen port. */ - virtual int get_http_stream_listen(); + virtual std::string get_http_stream_listen(); /** * get the http stream root dir. */ @@ -916,20 +1086,34 @@ class SrsConfig */ virtual bool get_vhost_http_enabled(std::string vhost); /** - * get the http mount point for vhost, - * vhost can use sub dir of http. - * for example, http://server/vhost1/live/livestream - * where the vhost1 is mount point for vhost1. + * get the http mount point for vhost. + * for example, http://vhost/live/livestream */ virtual std::string get_vhost_http_mount(std::string vhost); /** * get the http dir for vhost. - * the http dir of vhost will mount to the mount point of vhost. - * for example, http://server/vhost1/live/livestream - * where the vhost1 is mount point for vhost1, - * and vhost1 dir is specified by this http dir. + * the path on disk for mount root of http vhost. */ virtual std::string get_vhost_http_dir(std::string vhost); +// flv live streaming section +public: + /** + * get whether vhost enabled http flv live stream + */ + virtual bool get_vhost_http_remux_enabled(std::string vhost); + /** + * get the fast cache duration for http audio live stream. + */ + virtual double get_vhost_http_remux_fast_cache(std::string vhost); + /** + * get the http flv live stream mount point for vhost. + * used to generate the flv stream mount path. + */ + virtual std::string get_vhost_http_remux_mount(std::string vhost); + /** + * get whether the hstrs(http stream trigger rtmp source) enabled. + */ + virtual bool get_vhost_http_remux_hstrs(std::string vhost); // http heartbeart section private: /** @@ -1021,3 +1205,4 @@ bool srs_directive_equals(SrsConfDirective* a, SrsConfDirective* b); extern SrsConfig* _srs_config; #endif + diff --git a/trunk/src/app/srs_app_conn.cpp b/trunk/src/app/srs_app_conn.cpp index ecaed2168d..4c483ba1c9 100644 --- a/trunk/src/app/srs_app_conn.cpp +++ b/trunk/src/app/srs_app_conn.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -25,19 +25,27 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include -#include #include -SrsConnection::SrsConnection(SrsServer* srs_server, st_netfd_t client_stfd) +IConnectionManager::IConnectionManager() { - server = srs_server; - stfd = client_stfd; +} + +IConnectionManager::~IConnectionManager() +{ +} + +SrsConnection::SrsConnection(IConnectionManager* cm, st_netfd_t c) +{ + id = 0; + manager = cm; + stfd = c; // the client thread should reap itself, // so we never use joinable. // TODO: FIXME: maybe other thread need to stop it. - // @see: https://github.com/winlinvip/simple-rtmp-server/issues/78 - pthread = new SrsThread(this, 0, false); + // @see: https://github.com/simple-rtmp-server/srs/issues/78 + pthread = new SrsThread("conn", this, 0, false); } SrsConnection::~SrsConnection() @@ -55,6 +63,8 @@ int SrsConnection::cycle() int ret = ERROR_SUCCESS; _srs_context->generate_id(); + id = _srs_context->get_id(); + ip = srs_get_peer_ip(st_netfd_fileno(stfd)); ret = do_cycle(); @@ -83,7 +93,12 @@ int SrsConnection::cycle() void SrsConnection::on_thread_stop() { // TODO: FIXME: never remove itself, use isolate thread to do cleanup. - server->remove(this); + manager->remove(this); +} + +int SrsConnection::srs_id() +{ + return id; } void SrsConnection::stop() diff --git a/trunk/src/app/srs_app_conn.hpp b/trunk/src/app/srs_app_conn.hpp index fda7f99bb5..29a6eace76 100644 --- a/trunk/src/app/srs_app_conn.hpp +++ b/trunk/src/app/srs_app_conn.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -36,7 +36,22 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include -class SrsServer; +class SrsConnection; + +/** + * the manager for connection. + */ +class IConnectionManager +{ +public: + IConnectionManager(); + virtual ~IConnectionManager(); +public: + /** + * remove the specified connection. + */ + virtual void remove(SrsConnection* c) = 0; +}; /** * the basic connection of SRS, @@ -51,11 +66,15 @@ class SrsConnection : public virtual ISrsThreadHandler, public virtual IKbpsDelt * when thread stop, the connection will be delete by server. */ SrsThread* pthread; + /** + * the id of connection. + */ + int id; protected: /** - * the server object to manage the connection. + * the manager object to manage the connection. */ - SrsServer* server; + IConnectionManager* manager; /** * the underlayer st fd handler. */ @@ -65,7 +84,7 @@ class SrsConnection : public virtual ISrsThreadHandler, public virtual IKbpsDelt */ std::string ip; public: - SrsConnection(SrsServer* srs_server, st_netfd_t client_stfd); + SrsConnection(IConnectionManager* cm, st_netfd_t c); virtual ~SrsConnection(); public: /** @@ -92,13 +111,9 @@ class SrsConnection : public virtual ISrsThreadHandler, public virtual IKbpsDelt virtual void on_thread_stop(); public: /** - * when server to get the kbps of connection, - * it cannot wait the connection terminated then get the kbps, - * it must sample the kbps every some interval, for instance, 9s to sample all connections kbps, - * all connections will extends from IKbpsDelta which provides the bytes delta, - * while the delta must be update by the sample which invoke by the kbps_resample(). + * get the srs id which identify the client. */ - virtual void kbps_resample() = 0; + virtual int srs_id(); protected: /** * for concrete connection to do the cycle. diff --git a/trunk/src/app/srs_app_dvr.cpp b/trunk/src/app/srs_app_dvr.cpp index d58d642a0c..10725fe752 100644 --- a/trunk/src/app/srs_app_dvr.cpp +++ b/trunk/src/app/srs_app_dvr.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -27,19 +27,35 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include +#include using namespace std; #include -#include +#include #include #include #include #include #include #include +#include +#include +#include +#include -SrsFlvSegment::SrsFlvSegment() +// update the flv duration and filesize every this interval in ms. +#define SRS_DVR_UPDATE_DURATION_INTERVAL 60000 + +SrsFlvSegment::SrsFlvSegment(SrsDvrPlan* p) { + req = NULL; + jitter = NULL; + plan = p; + + fs = new SrsFileWriter(); + enc = new SrsFlvEncoder(); + jitter_algorithm = SrsRtmpJitterAlgorithmOFF; + path = ""; has_keyframe = false; duration = 0; @@ -47,162 +63,215 @@ SrsFlvSegment::SrsFlvSegment() stream_starttime = 0; stream_previous_pkt_time = -1; stream_duration = 0; -} -SrsFlvSegment::~SrsFlvSegment() -{ -} + duration_offset = 0; + filesize_offset = 0; -void SrsFlvSegment::reset() -{ - has_keyframe = false; - starttime = -1; - duration = 0; -} - -SrsDvrPlan::SrsDvrPlan() -{ - _source = NULL; - _req = NULL; - jitter = NULL; - dvr_enabled = false; - fs = new SrsFileWriter(); - enc = new SrsFlvEncoder(); - segment = new SrsFlvSegment(); - jitter_algorithm = SrsRtmpJitterAlgorithmOFF; - _srs_config->subscribe(this); } -SrsDvrPlan::~SrsDvrPlan() +SrsFlvSegment::~SrsFlvSegment() { _srs_config->unsubscribe(this); - + srs_freep(jitter); srs_freep(fs); srs_freep(enc); - srs_freep(segment); } -int SrsDvrPlan::initialize(SrsSource* source, SrsRequest* req) +int SrsFlvSegment::initialize(SrsRequest* r) { int ret = ERROR_SUCCESS; - - _source = source; - _req = req; - - jitter_algorithm = (SrsRtmpJitterAlgorithm)_srs_config->get_dvr_time_jitter(_req->vhost); + + req = r; + jitter_algorithm = (SrsRtmpJitterAlgorithm)_srs_config->get_dvr_time_jitter(req->vhost); return ret; } -int SrsDvrPlan::on_publish() +bool SrsFlvSegment::is_overflow(int64_t max_duration) +{ + return duration >= max_duration; +} + +int SrsFlvSegment::open(bool use_tmp_file) { int ret = ERROR_SUCCESS; - // support multiple publish. - if (dvr_enabled) { + // ignore when already open. + if (fs->is_open()) { return ret; } + + path = generate_path(); + bool fresh_flv_file = !srs_path_exists(path); - SrsRequest* req = _req; + // create dir first. + std::string dir = path.substr(0, path.rfind("/")); + if ((ret = srs_create_dir_recursively(dir)) != ERROR_SUCCESS) { + srs_error("create dir=%s failed. ret=%d", dir.c_str(), ret); + return ret; + } + srs_info("create dir=%s ok", dir.c_str()); - if (!_srs_config->get_dvr_enabled(req->vhost)) { + // create jitter. + if ((ret = create_jitter(!fresh_flv_file)) != ERROR_SUCCESS) { + srs_error("create jitter failed, path=%s, fresh=%d. ret=%d", path.c_str(), fresh_flv_file, ret); return ret; } - // jitter when publish, ensure whole stream start from 0. - srs_freep(jitter); - jitter = new SrsRtmpJitter(); - - // always update time cache. - srs_update_system_time_ms(); - - // when republish, stream starting. - segment->stream_previous_pkt_time = -1; - segment->stream_starttime = srs_get_system_time_ms(); - segment->stream_duration = 0; + // generate the tmp flv path. + if (!fresh_flv_file || !use_tmp_file) { + // when path exists, always append to it. + // so we must use the target flv path as output flv. + tmp_flv_file = path; + } else { + // when path not exists, dvr to tmp file. + tmp_flv_file = path + ".tmp"; + } - if ((ret = open_new_segment()) != ERROR_SUCCESS) { + // open file writer, in append or create mode. + if (!fresh_flv_file) { + if ((ret = fs->open_append(tmp_flv_file)) != ERROR_SUCCESS) { + srs_error("append file stream for file %s failed. ret=%d", path.c_str(), ret); + return ret; + } + srs_trace("dvr: always append to when exists, file=%s.", path.c_str()); + } else { + if ((ret = fs->open(tmp_flv_file)) != ERROR_SUCCESS) { + srs_error("open file stream for file %s failed. ret=%d", path.c_str(), ret); + return ret; + } + } + + // initialize the encoder. + if ((ret = enc->initialize(fs)) != ERROR_SUCCESS) { + srs_error("initialize enc by fs for file %s failed. ret=%d", path.c_str(), ret); return ret; } + // when exists, donot write flv header. + if (fresh_flv_file) { + // write the flv header to writer. + if ((ret = enc->write_header()) != ERROR_SUCCESS) { + srs_error("write flv header failed. ret=%d", ret); + return ret; + } + } + + // update the duration and filesize offset. + duration_offset = 0; + filesize_offset = 0; + + srs_trace("dvr stream %s to file %s", req->stream.c_str(), path.c_str()); + return ret; } -int SrsDvrPlan::open_new_segment() +int SrsFlvSegment::close() { int ret = ERROR_SUCCESS; - SrsRequest* req = _req; - - // new flv file - std::stringstream path; + // ignore when already closed. + if (!fs->is_open()) { + return ret; + } + + // update duration and filesize. + if ((ret = update_flv_metadata()) != ERROR_SUCCESS) { + return ret; + } - path << _srs_config->get_dvr_path(req->vhost) - << "/" << req->app << "/" - << req->stream << "." << srs_get_system_time_ms() << ".flv"; + fs->close(); - if ((ret = flv_open(req->get_stream_url(), path.str())) != ERROR_SUCCESS) { + // when tmp flv file exists, reap it. + if (tmp_flv_file != path) { + if (rename(tmp_flv_file.c_str(), path.c_str()) < 0) { + ret = ERROR_SYSTEM_FILE_RENAME; + srs_error("rename flv file failed, %s => %s. ret=%d", + tmp_flv_file.c_str(), path.c_str(), ret); + return ret; + } + } + + // TODO: FIXME: the http callback is async, which will trigger thread switch, + // so the on_video maybe invoked during the http callback, and error. + if ((ret = plan->on_reap_segment()) != ERROR_SUCCESS) { + srs_error("dvr: notify plan to reap segment failed. ret=%d", ret); return ret; } - dvr_enabled = true; - + return ret; } -int SrsDvrPlan::on_dvr_request_sh() +int SrsFlvSegment::write_metadata(SrsSharedPtrMessage* metadata) { int ret = ERROR_SUCCESS; - - // the dvr is enabled, notice the source to push the data. - if ((ret = _source->on_dvr_request_sh()) != ERROR_SUCCESS) { + + if (duration_offset || filesize_offset) { return ret; } - - return ret; -} -int SrsDvrPlan::on_video_keyframe() -{ - int ret = ERROR_SUCCESS; - return ret; -} + SrsStream stream; + if ((ret = stream.initialize(metadata->payload, metadata->size)) != ERROR_SUCCESS) { + return ret; + } -int64_t SrsDvrPlan::filter_timestamp(int64_t timestamp) -{ - return timestamp; -} + SrsAmf0Any* name = SrsAmf0Any::str(); + SrsAutoFree(SrsAmf0Any, name); + if ((ret = name->read(&stream)) != ERROR_SUCCESS) { + return ret; + } -int SrsDvrPlan::on_meta_data(SrsOnMetaDataPacket* metadata) -{ - int ret = ERROR_SUCCESS; - - if (!dvr_enabled) { + SrsAmf0Object* obj = SrsAmf0Any::object(); + SrsAutoFree(SrsAmf0Object, obj); + if ((ret = obj->read(&stream)) != ERROR_SUCCESS) { return ret; } + + // remove duration and filesize. + obj->set("filesize", NULL); + obj->set("duration", NULL); + + // add properties. + obj->set("service", SrsAmf0Any::str(RTMP_SIG_SRS_SERVER)); + obj->set("filesize", SrsAmf0Any::number(0)); + obj->set("duration", SrsAmf0Any::number(0)); - int size = 0; - char* payload = NULL; - if ((ret = metadata->encode(size, payload)) != ERROR_SUCCESS) { + int size = name->total_size() + obj->total_size(); + char* payload = new char[size]; + SrsAutoFree(char, payload); + + // 11B flv header, 3B object EOF, 8B number value, 1B number flag. + duration_offset = fs->tellg() + size + 11 - SrsAmf0Size::object_eof() - SrsAmf0Size::number(); + // 2B string flag, 8B number value, 8B string 'duration', 1B number flag + filesize_offset = duration_offset - SrsAmf0Size::utf8("duration") - SrsAmf0Size::number(); + + // convert metadata to bytes. + if ((ret = stream.initialize(payload, size)) != ERROR_SUCCESS) { + return ret; + } + if ((ret = name->write(&stream)) != ERROR_SUCCESS) { + return ret; + } + if ((ret = obj->write(&stream)) != ERROR_SUCCESS) { return ret; } - SrsAutoFree(char, payload); - if ((ret = enc->write_metadata(payload, size)) != ERROR_SUCCESS) { + // to flv file. + if ((ret = enc->write_metadata(18, payload, size)) != ERROR_SUCCESS) { return ret; } return ret; } -int SrsDvrPlan::on_audio(SrsSharedPtrMessage* audio) +int SrsFlvSegment::write_audio(SrsSharedPtrMessage* shared_audio) { int ret = ERROR_SUCCESS; - - if (!dvr_enabled) { - return ret; - } + + SrsSharedPtrMessage* audio = shared_audio->copy(); + SrsAutoFree(SrsSharedPtrMessage, audio); if ((jitter->correct(audio, 0, 0, jitter_algorithm)) != ERROR_SUCCESS) { return ret; @@ -210,41 +279,50 @@ int SrsDvrPlan::on_audio(SrsSharedPtrMessage* audio) char* payload = audio->payload; int size = audio->size; - int64_t timestamp = filter_timestamp(audio->header.timestamp); + int64_t timestamp = plan->filter_timestamp(audio->timestamp); if ((ret = enc->write_audio(timestamp, payload, size)) != ERROR_SUCCESS) { return ret; } - - if ((ret = update_duration(audio)) != ERROR_SUCCESS) { + + if ((ret = on_update_duration(audio)) != ERROR_SUCCESS) { return ret; } return ret; } -int SrsDvrPlan::on_video(SrsSharedPtrMessage* video) +int SrsFlvSegment::write_video(SrsSharedPtrMessage* shared_video) { int ret = ERROR_SUCCESS; - - if (!dvr_enabled) { - return ret; - } + + SrsSharedPtrMessage* video = shared_video->copy(); + SrsAutoFree(SrsSharedPtrMessage, video); char* payload = video->payload; int size = video->size; + bool is_sequence_header = SrsFlvCodec::video_is_sequence_header(payload, size); #ifdef SRS_AUTO_HTTP_CALLBACK bool is_key_frame = SrsFlvCodec::video_is_h264(payload, size) - && SrsFlvCodec::video_is_keyframe(payload, size) - && !SrsFlvCodec::video_is_sequence_header(payload, size); + && SrsFlvCodec::video_is_keyframe(payload, size) && !is_sequence_header; if (is_key_frame) { - segment->has_keyframe = true; - if ((ret = on_video_keyframe()) != ERROR_SUCCESS) { + has_keyframe = true; + if ((ret = plan->on_video_keyframe()) != ERROR_SUCCESS) { return ret; } } srs_verbose("dvr video is key: %d", is_key_frame); #endif + + // accept the sequence header here. + // when got no keyframe, ignore when should wait keyframe. + if (!has_keyframe && !is_sequence_header) { + bool wait_keyframe = _srs_config->get_dvr_wait_keyframe(req->vhost); + if (wait_keyframe) { + srs_info("dvr: ignore when wait keyframe."); + return ret; + } + } if ((jitter->correct(video, 0, 0, jitter_algorithm)) != ERROR_SUCCESS) { return ret; @@ -252,11 +330,11 @@ int SrsDvrPlan::on_video(SrsSharedPtrMessage* video) // update segment duration, session plan just update the duration, // the segment plan will reap segment if exceed, this video will write to next segment. - if ((ret = update_duration(video)) != ERROR_SUCCESS) { + if ((ret = on_update_duration(video)) != ERROR_SUCCESS) { return ret; } - int32_t timestamp = filter_timestamp(video->header.timestamp); + int32_t timestamp = plan->filter_timestamp(video->timestamp); if ((ret = enc->write_video(timestamp, payload, size)) != ERROR_SUCCESS) { return ret; } @@ -264,98 +342,299 @@ int SrsDvrPlan::on_video(SrsSharedPtrMessage* video) return ret; } -int SrsDvrPlan::on_reload_vhost_dvr(std::string /*vhost*/) +int SrsFlvSegment::update_flv_metadata() { int ret = ERROR_SUCCESS; + + // no duration or filesize specified. + if (!duration_offset || !filesize_offset) { + return ret; + } + + int64_t cur = fs->tellg(); + + // buffer to write the size. + char* buf = new char[SrsAmf0Size::number()]; + SrsAutoFree(char, buf); + + SrsStream stream; + if ((ret = stream.initialize(buf, SrsAmf0Size::number())) != ERROR_SUCCESS) { + return ret; + } + + // filesize to buf. + SrsAmf0Any* size = SrsAmf0Any::number((double)cur); + SrsAutoFree(SrsAmf0Any, size); + + stream.skip(-1 * stream.pos()); + if ((ret = size->write(&stream)) != ERROR_SUCCESS) { + return ret; + } + + // update the flesize. + fs->lseek(filesize_offset); + if ((ret = fs->write(buf, SrsAmf0Size::number(), NULL)) != ERROR_SUCCESS) { + return ret; + } - jitter_algorithm = (SrsRtmpJitterAlgorithm)_srs_config->get_dvr_time_jitter(_req->vhost); + // duration to buf + SrsAmf0Any* dur = SrsAmf0Any::number((double)duration / 1000.0); + SrsAutoFree(SrsAmf0Any, dur); + stream.skip(-1 * stream.pos()); + if ((ret = dur->write(&stream)) != ERROR_SUCCESS) { + return ret; + } + + // update the duration + fs->lseek(duration_offset); + if ((ret = fs->write(buf, SrsAmf0Size::number(), NULL)) != ERROR_SUCCESS) { + return ret; + } + + // reset the offset. + fs->lseek(cur); + return ret; } -int SrsDvrPlan::flv_open(string stream, string path) +string SrsFlvSegment::get_path() { - int ret = ERROR_SUCCESS; + return path; +} + +string SrsFlvSegment::generate_path() +{ + // the path in config, for example, + // /data/[vhost]/[app]/[stream]/[2006]/[01]/[02]/[15].[04].[05].[999].flv + std::string path_config = _srs_config->get_dvr_path(req->vhost); + + // add [stream].[timestamp].flv as filename for dir + if (path_config.find(".flv") != path_config.length() - 4) { + path_config += "/[stream].[timestamp].flv"; + } - segment->reset(); + // the flv file path + std::string flv_path = path_config; + flv_path = srs_path_build_stream(flv_path, req->vhost, req->app, req->stream); + flv_path = srs_path_build_timestamp(flv_path); + + return flv_path; +} + +int SrsFlvSegment::create_jitter(bool loads_from_flv) +{ + int ret = ERROR_SUCCESS; - std::string tmp_file = path + ".tmp"; - if ((ret = fs->open(tmp_file)) != ERROR_SUCCESS) { - srs_error("open file stream for file %s failed. ret=%d", path.c_str(), ret); + // when path exists, use exists jitter. + if (!loads_from_flv) { + // jitter when publish, ensure whole stream start from 0. + srs_freep(jitter); + jitter = new SrsRtmpJitter(); + + // fresh stream starting. + starttime = -1; + stream_previous_pkt_time = -1; + stream_starttime = srs_update_system_time_ms(); + stream_duration = 0; + + // fresh segment starting. + has_keyframe = false; + duration = 0; + return ret; } - - if ((ret = enc->initialize(fs)) != ERROR_SUCCESS) { - srs_error("initialize enc by fs for file %s failed. ret=%d", path.c_str(), ret); + + // when jitter ok, do nothing. + if (jitter) { return ret; } + + // always ensure the jitter crote. + // for the first time, initialize jitter from exists file. + jitter = new SrsRtmpJitter(); + + // TODO: FIXME: implements it. + + return ret; +} + +int SrsFlvSegment::on_update_duration(SrsSharedPtrMessage* msg) +{ + int ret = ERROR_SUCCESS; + + // we must assumpt that the stream timestamp is monotonically increase, + // that is, always use time jitter to correct the timestamp. + // except the time jitter is disabled in config. + + // set the segment starttime at first time + if (starttime < 0) { + starttime = msg->timestamp; + } - if ((ret = write_flv_header()) != ERROR_SUCCESS) { - return ret; + // no previous packet or timestamp overflow. + if (stream_previous_pkt_time < 0 || stream_previous_pkt_time > msg->timestamp) { + stream_previous_pkt_time = msg->timestamp; } - segment->path = path; + // collect segment and stream duration, timestamp overflow is ok. + duration += msg->timestamp - stream_previous_pkt_time; + stream_duration += msg->timestamp - stream_previous_pkt_time; + + // update previous packet time + stream_previous_pkt_time = msg->timestamp; - srs_trace("dvr stream %s to file %s", stream.c_str(), path.c_str()); return ret; } -int SrsDvrPlan::flv_close() +int SrsFlvSegment::on_reload_vhost_dvr(std::string /*vhost*/) { int ret = ERROR_SUCCESS; - fs->close(); - - std::string tmp_file = segment->path + ".tmp"; - if (rename(tmp_file.c_str(), segment->path.c_str()) < 0) { - ret = ERROR_SYSTEM_FILE_RENAME; - srs_error("rename flv file failed, %s => %s. ret=%d", - tmp_file.c_str(), segment->path.c_str(), ret); - return ret; - } + jitter_algorithm = (SrsRtmpJitterAlgorithm)_srs_config->get_dvr_time_jitter(req->vhost); return ret; } -int SrsDvrPlan::update_duration(SrsSharedPtrMessage* msg) +SrsDvrAsyncCallOnDvr::SrsDvrAsyncCallOnDvr(SrsRequest* r, string p) +{ + req = r; + path = p; +} + +SrsDvrAsyncCallOnDvr::~SrsDvrAsyncCallOnDvr() +{ +} + +int SrsDvrAsyncCallOnDvr::call() { int ret = ERROR_SUCCESS; + +#ifdef SRS_AUTO_HTTP_CALLBACK + // http callback for on_dvr in config. + if (_srs_config->get_vhost_http_hooks_enabled(req->vhost)) { + // HTTP: on_dvr + SrsConfDirective* on_dvr = _srs_config->get_vhost_on_dvr(req->vhost); + if (!on_dvr) { + srs_info("ignore the empty http callback: on_dvr"); + return ret; + } + + std::string file = path; + for (int i = 0; i < (int)on_dvr->args.size(); i++) { + std::string url = on_dvr->args.at(i); + if ((ret = SrsHttpHooks::on_dvr(url, req, file)) != ERROR_SUCCESS) { + srs_error("hook client on_dvr failed. url=%s, ret=%d", url.c_str(), ret); + return ret; + } + } + } +#endif - // we must assumpt that the stream timestamp is monotonically increase, - // that is, always use time jitter to correct the timestamp. + return ret; +} + +string SrsDvrAsyncCallOnDvr::to_string() +{ + std::stringstream ss; + ss << "vhost=" << req->vhost << ", file=" << path; + return ss.str(); +} + +SrsDvrPlan::SrsDvrPlan() +{ + req = NULL; + + dvr_enabled = false; + segment = new SrsFlvSegment(this); + async = new SrsDvrAsyncCallThread(); +} + +SrsDvrPlan::~SrsDvrPlan() +{ + srs_freep(segment); + srs_freep(async); +} + +int SrsDvrPlan::initialize(SrsRequest* r) +{ + int ret = ERROR_SUCCESS; - // set the segment starttime at first time - if (segment->starttime < 0) { - segment->starttime = msg->header.timestamp; + req = r; + + if ((ret = segment->initialize(r)) != ERROR_SUCCESS) { + return ret; + } + + if ((ret = async->start()) != ERROR_SUCCESS) { + return ret; } + + return ret; +} + +int SrsDvrPlan::on_video_keyframe() +{ + return ERROR_SUCCESS; +} + +int64_t SrsDvrPlan::filter_timestamp(int64_t timestamp) +{ + return timestamp; +} + +int SrsDvrPlan::on_meta_data(SrsSharedPtrMessage* shared_metadata) +{ + int ret = ERROR_SUCCESS; - // no previous packet or timestamp overflow. - if (segment->stream_previous_pkt_time < 0 || segment->stream_previous_pkt_time > msg->header.timestamp) { - segment->stream_previous_pkt_time = msg->header.timestamp; + if (!dvr_enabled) { + return ret; } - // collect segment and stream duration, timestamp overflow is ok. - segment->duration += msg->header.timestamp - segment->stream_previous_pkt_time; - segment->stream_duration += msg->header.timestamp - segment->stream_previous_pkt_time; + return segment->write_metadata(shared_metadata); +} + +int SrsDvrPlan::on_audio(SrsSharedPtrMessage* shared_audio) +{ + int ret = ERROR_SUCCESS; - // update previous packet time - segment->stream_previous_pkt_time = msg->header.timestamp; + if (!dvr_enabled) { + return ret; + } + + if ((ret = segment->write_audio(shared_audio)) != ERROR_SUCCESS) { + return ret; + } return ret; } -int SrsDvrPlan::write_flv_header() +int SrsDvrPlan::on_video(SrsSharedPtrMessage* shared_video) { int ret = ERROR_SUCCESS; - if ((ret = enc->write_header()) != ERROR_SUCCESS) { - srs_error("write flv header failed. ret=%d", ret); + if (!dvr_enabled) { + return ret; + } + + if ((ret = segment->write_video(shared_video)) != ERROR_SUCCESS) { return ret; } return ret; } +int SrsDvrPlan::on_reap_segment() +{ + int ret = ERROR_SUCCESS; + + if ((ret = async->call(new SrsDvrAsyncCallOnDvr(req, segment->get_path()))) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + SrsDvrPlan* SrsDvrPlan::create_plan(string vhost) { std::string plan = _srs_config->get_dvr_plan(vhost); @@ -363,8 +642,11 @@ SrsDvrPlan* SrsDvrPlan::create_plan(string vhost) return new SrsDvrSegmentPlan(); } else if (plan == SRS_CONF_DEFAULT_DVR_PLAN_SESSION) { return new SrsDvrSessionPlan(); + } else if (plan == SRS_CONF_DEFAULT_DVR_PLAN_APPEND) { + return new SrsDvrAppendPlan(); } else { - return new SrsDvrSessionPlan(); + srs_error("invalid dvr plan=%s, vhost=%s", plan.c_str(), vhost.c_str()); + srs_assert(false); } } @@ -376,6 +658,32 @@ SrsDvrSessionPlan::~SrsDvrSessionPlan() { } +int SrsDvrSessionPlan::on_publish() +{ + int ret = ERROR_SUCCESS; + + // support multiple publish. + if (dvr_enabled) { + return ret; + } + + if (!_srs_config->get_dvr_enabled(req->vhost)) { + return ret; + } + + if ((ret = segment->close()) != ERROR_SUCCESS) { + return ret; + } + + if ((ret = segment->open()) != ERROR_SUCCESS) { + return ret; + } + + dvr_enabled = true; + + return ret; +} + void SrsDvrSessionPlan::on_unpublish() { // support multiple publish. @@ -384,7 +692,7 @@ void SrsDvrSessionPlan::on_unpublish() } // ignore error. - int ret = flv_close(); + int ret = segment->close(); if (ret != ERROR_SUCCESS) { srs_warn("ignore flv close error. ret=%d", ret); } @@ -392,23 +700,116 @@ void SrsDvrSessionPlan::on_unpublish() dvr_enabled = false; } +SrsDvrAppendPlan::SrsDvrAppendPlan() +{ + last_update_time = 0; +} + +SrsDvrAppendPlan::~SrsDvrAppendPlan() +{ +} + +int SrsDvrAppendPlan::on_publish() +{ + int ret = ERROR_SUCCESS; + + // support multiple publish. + if (dvr_enabled) { + return ret; + } + + if (!_srs_config->get_dvr_enabled(req->vhost)) { + return ret; + } + + if ((ret = segment->open(false)) != ERROR_SUCCESS) { + return ret; + } + + dvr_enabled = true; + + return ret; +} + +void SrsDvrAppendPlan::on_unpublish() +{ +} + +int SrsDvrAppendPlan::on_audio(SrsSharedPtrMessage* shared_audio) +{ + int ret = ERROR_SUCCESS; + + if ((ret = update_duration(shared_audio)) != ERROR_SUCCESS) { + return ret; + } + + if ((ret = SrsDvrPlan::on_audio(shared_audio)) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +int SrsDvrAppendPlan::on_video(SrsSharedPtrMessage* shared_video) +{ + int ret = ERROR_SUCCESS; + + if ((ret = update_duration(shared_video)) != ERROR_SUCCESS) { + return ret; + } + + if ((ret = SrsDvrPlan::on_video(shared_video)) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +int SrsDvrAppendPlan::update_duration(SrsSharedPtrMessage* msg) +{ + int ret = ERROR_SUCCESS; + + if (last_update_time <= 0) { + last_update_time = msg->timestamp; + return ret; + } + + if (msg->timestamp < last_update_time) { + last_update_time = msg->timestamp; + return ret; + } + + if (SRS_DVR_UPDATE_DURATION_INTERVAL > msg->timestamp - last_update_time) { + return ret; + } + last_update_time = msg->timestamp; + + srs_assert(segment); + if (!segment->update_flv_metadata()) { + return ret; + } + + return ret; +} + SrsDvrSegmentPlan::SrsDvrSegmentPlan() { segment_duration = -1; - sh_video = sh_audio = NULL; + metadata = sh_video = sh_audio = NULL; } SrsDvrSegmentPlan::~SrsDvrSegmentPlan() { srs_freep(sh_video); srs_freep(sh_audio); + srs_freep(metadata); } -int SrsDvrSegmentPlan::initialize(SrsSource* source, SrsRequest* req) +int SrsDvrSegmentPlan::initialize(SrsRequest* req) { int ret = ERROR_SUCCESS; - if ((ret = SrsDvrPlan::initialize(source, req)) != ERROR_SUCCESS) { + if ((ret = SrsDvrPlan::initialize(req)) != ERROR_SUCCESS) { return ret; } @@ -423,66 +824,101 @@ int SrsDvrSegmentPlan::on_publish() { int ret = ERROR_SUCCESS; - // if already opened, continue to dvr. - // the segment plan maybe keep running longer than the encoder. - // for example, segment running, encoder restart, - // the segment plan will just continue going and donot open new segment. - if (fs->is_open()) { - dvr_enabled = true; + // support multiple publish. + if (dvr_enabled) { return ret; } - - return SrsDvrPlan::on_publish(); + + if (!_srs_config->get_dvr_enabled(req->vhost)) { + return ret; + } + + if ((ret = segment->close()) != ERROR_SUCCESS) { + return ret; + } + + if ((ret = segment->open()) != ERROR_SUCCESS) { + return ret; + } + + dvr_enabled = true; + + return ret; } void SrsDvrSegmentPlan::on_unpublish() { - // support multiple publish. - if (!dvr_enabled) { - return; +} + +int SrsDvrSegmentPlan::on_meta_data(SrsSharedPtrMessage* shared_metadata) +{ + int ret = ERROR_SUCCESS; + + srs_freep(metadata); + metadata = shared_metadata->copy(); + + if ((ret = SrsDvrPlan::on_meta_data(shared_metadata)) != ERROR_SUCCESS) { + return ret; } - dvr_enabled = false; + + return ret; } -int SrsDvrSegmentPlan::on_audio(SrsSharedPtrMessage* audio) +int SrsDvrSegmentPlan::on_audio(SrsSharedPtrMessage* shared_audio) { - if (SrsFlvCodec::audio_is_sequence_header(audio->payload, audio->size)) { + int ret = ERROR_SUCCESS; + + if (SrsFlvCodec::audio_is_sequence_header(shared_audio->payload, shared_audio->size)) { srs_freep(sh_audio); - sh_audio = audio->copy(); + sh_audio = shared_audio->copy(); + } + + if ((ret = update_duration(shared_audio)) != ERROR_SUCCESS) { + return ret; } - return SrsDvrPlan::on_audio(audio); + if ((ret = SrsDvrPlan::on_audio(shared_audio)) != ERROR_SUCCESS) { + return ret; + } + + return ret; } -int SrsDvrSegmentPlan::on_video(SrsSharedPtrMessage* video) +int SrsDvrSegmentPlan::on_video(SrsSharedPtrMessage* shared_video) { - if (SrsFlvCodec::video_is_sequence_header(video->payload, video->size)) { + int ret = ERROR_SUCCESS; + + if (SrsFlvCodec::video_is_sequence_header(shared_video->payload, shared_video->size)) { srs_freep(sh_video); - sh_video = video->copy(); + sh_video = shared_video->copy(); + } + + if ((ret = update_duration(shared_video)) != ERROR_SUCCESS) { + return ret; } - return SrsDvrPlan::on_video(video); + if ((ret = SrsDvrPlan::on_video(shared_video)) != ERROR_SUCCESS) { + return ret; + } + + return ret; } int SrsDvrSegmentPlan::update_duration(SrsSharedPtrMessage* msg) { int ret = ERROR_SUCCESS; - if ((ret = SrsDvrPlan::update_duration(msg)) != ERROR_SUCCESS) { - return ret; - } - srs_assert(segment); // ignore if duration ok. - if (segment_duration <= 0 || segment->duration < segment_duration) { + if (segment_duration <= 0 || !segment->is_overflow(segment_duration)) { return ret; } // when wait keyframe, ignore if no frame arrived. - // @see https://github.com/winlinvip/simple-rtmp-server/issues/177 - if (_srs_config->get_dvr_wait_keyframe(_req->vhost)) { - if (!msg->header.is_video()) { + // @see https://github.com/simple-rtmp-server/srs/issues/177 + if (_srs_config->get_dvr_wait_keyframe(req->vhost)) { + if (!msg->is_video()) { return ret; } @@ -497,18 +933,19 @@ int SrsDvrSegmentPlan::update_duration(SrsSharedPtrMessage* msg) } // reap segment - if ((ret = flv_close()) != ERROR_SUCCESS) { - segment->reset(); + if ((ret = segment->close()) != ERROR_SUCCESS) { return ret; } - on_unpublish(); // open new flv file - if ((ret = open_new_segment()) != ERROR_SUCCESS) { + if ((ret = segment->open()) != ERROR_SUCCESS) { return ret; } // update sequence header + if (metadata && (ret = SrsDvrPlan::on_meta_data(metadata)) != ERROR_SUCCESS) { + return ret; + } if (sh_video && (ret = SrsDvrPlan::on_video(sh_video)) != ERROR_SUCCESS) { return ret; } @@ -519,9 +956,9 @@ int SrsDvrSegmentPlan::update_duration(SrsSharedPtrMessage* msg) return ret; } -SrsDvr::SrsDvr(SrsSource* source) +SrsDvr::SrsDvr() { - _source = source; + source = NULL; plan = NULL; } @@ -530,21 +967,27 @@ SrsDvr::~SrsDvr() srs_freep(plan); } -int SrsDvr::initialize(SrsRequest* req) +int SrsDvr::initialize(SrsSource* s, SrsRequest* r) { int ret = ERROR_SUCCESS; + + source = s; srs_freep(plan); - plan = SrsDvrPlan::create_plan(req->vhost); + plan = SrsDvrPlan::create_plan(r->vhost); - if ((ret = plan->initialize(_source, req)) != ERROR_SUCCESS) { + if ((ret = plan->initialize(r)) != ERROR_SUCCESS) { + return ret; + } + + if ((ret = source->on_dvr_request_sh()) != ERROR_SUCCESS) { return ret; } return ret; } -int SrsDvr::on_publish(SrsRequest* /*req*/) +int SrsDvr::on_publish(SrsRequest* /*r*/) { int ret = ERROR_SUCCESS; @@ -560,43 +1003,39 @@ void SrsDvr::on_unpublish() plan->on_unpublish(); } -int SrsDvr::on_meta_data(SrsOnMetaDataPacket* metadata) +// TODO: FIXME: source should use shared message instead. +int SrsDvr::on_meta_data(SrsOnMetaDataPacket* m) { int ret = ERROR_SUCCESS; - - if ((ret = plan->on_meta_data(metadata)) != ERROR_SUCCESS) { + + int size = 0; + char* payload = NULL; + if ((ret = m->encode(size, payload)) != ERROR_SUCCESS) { return ret; } - - return ret; -} -int SrsDvr::on_audio(SrsSharedPtrMessage* audio) -{ - int ret = ERROR_SUCCESS; - - SrsAutoFree(SrsSharedPtrMessage, audio); - - if ((ret = plan->on_audio(audio)) != ERROR_SUCCESS) { + SrsSharedPtrMessage metadata; + if ((ret = metadata.create(NULL, payload, size)) != ERROR_SUCCESS) { return ret; } - - return ret; -} -int SrsDvr::on_video(SrsSharedPtrMessage* video) -{ - int ret = ERROR_SUCCESS; - - SrsAutoFree(SrsSharedPtrMessage, video); - - if ((ret = plan->on_video(video)) != ERROR_SUCCESS) { + if ((ret = plan->on_meta_data(&metadata)) != ERROR_SUCCESS) { return ret; } return ret; } +int SrsDvr::on_audio(SrsSharedPtrMessage* shared_audio) +{ + return plan->on_audio(shared_audio); +} + +int SrsDvr::on_video(SrsSharedPtrMessage* shared_video) +{ + return plan->on_video(shared_video); +} + #endif diff --git a/trunk/src/app/srs_app_dvr.hpp b/trunk/src/app/srs_app_dvr.hpp index edea5cc75d..4fb653a202 100644 --- a/trunk/src/app/srs_app_dvr.hpp +++ b/trunk/src/app/srs_app_dvr.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -30,6 +30,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include +#include #ifdef SRS_AUTO_DVR @@ -41,16 +42,48 @@ class SrsOnMetaDataPacket; class SrsSharedPtrMessage; class SrsFileWriter; class SrsFlvEncoder; +class SrsDvrPlan; +class SrsJsonAny; +class SrsJsonObject; +class SrsThread; #include #include +#include /** * a piece of flv segment. +* when open segment, support start at 0 or not. */ -class SrsFlvSegment +class SrsFlvSegment : public ISrsReloadHandler { -public: +private: + SrsRequest* req; + SrsDvrPlan* plan; +private: + /** + * the underlayer dvr stream. + * if close, the flv is reap and closed. + * if open, new flv file is crote. + */ + SrsFlvEncoder* enc; + SrsRtmpJitter* jitter; + SrsRtmpJitterAlgorithm jitter_algorithm; + SrsFileWriter* fs; +private: + /** + * the offset of file for duration value. + * the next 8 bytes is the double value. + */ + int64_t duration_offset; + /** + * the offset of file for filesize value. + * the next 8 bytes is the double value. + */ + int64_t filesize_offset; +private: + std::string tmp_flv_file; +private: /** * current segment flv file path. */ @@ -81,10 +114,81 @@ class SrsFlvSegment */ int64_t stream_previous_pkt_time; public: - SrsFlvSegment(); + SrsFlvSegment(SrsDvrPlan* p); virtual ~SrsFlvSegment(); public: - virtual void reset(); + /** + * initialize the segment. + */ + virtual int initialize(SrsRequest* r); + /** + * whether segment is overflow. + */ + virtual bool is_overflow(int64_t max_duration); + /** + * open new segment file, timestamp start at 0 for fresh flv file. + * @remark ignore when already open. + * @param use_tmp_file whether use tmp file if possible. + */ + virtual int open(bool use_tmp_file = true); + /** + * close current segment. + * @remark ignore when already closed. + */ + virtual int close(); + /** + * write the metadata to segment. + */ + virtual int write_metadata(SrsSharedPtrMessage* metadata); + /** + * @param shared_audio, directly ptr, copy it if need to save it. + */ + virtual int write_audio(SrsSharedPtrMessage* shared_audio); + /** + * @param shared_video, directly ptr, copy it if need to save it. + */ + virtual int write_video(SrsSharedPtrMessage* shared_video); + /** + * update the flv metadata. + */ + virtual int update_flv_metadata(); + /** + * get the current dvr path. + */ + virtual std::string get_path(); +private: + /** + * generate the flv segment path. + */ + virtual std::string generate_path(); + /** + * create flv jitter. load jitter when flv exists. + * @param loads_from_flv whether loads the jitter from exists flv file. + */ + virtual int create_jitter(bool loads_from_flv); + /** + * when update the duration of segment by rtmp msg. + */ + virtual int on_update_duration(SrsSharedPtrMessage* msg); +// interface ISrsReloadHandler +public: + virtual int on_reload_vhost_dvr(std::string vhost); +}; + +/** +* the dvr async call. +*/ +class SrsDvrAsyncCallOnDvr : public ISrsDvrAsyncCall +{ +private: + std::string path; + SrsRequest* req; +public: + SrsDvrAsyncCallOnDvr(SrsRequest* r, std::string p); + virtual ~SrsDvrAsyncCallOnDvr(); +public: + virtual int call(); + virtual std::string to_string(); }; /** @@ -94,43 +198,37 @@ class SrsFlvSegment * 2. reap flv: when to reap the flv and start new piece. */ // TODO: FIXME: the plan is too fat, refine me. -class SrsDvrPlan : public ISrsReloadHandler +class SrsDvrPlan { -private: - /** - * the underlayer dvr stream. - * if close, the flv is reap and closed. - * if open, new flv file is crote. - */ - SrsFlvEncoder* enc; - SrsSource* _source; - SrsRtmpJitter* jitter; - SrsRtmpJitterAlgorithm jitter_algorithm; +public: + friend class SrsFlvSegment; +public: + SrsRequest* req; protected: SrsFlvSegment* segment; - SrsRequest* _req; + SrsDvrAsyncCallThread* async; bool dvr_enabled; - SrsFileWriter* fs; public: SrsDvrPlan(); virtual ~SrsDvrPlan(); public: - virtual int initialize(SrsSource* source, SrsRequest* req); - virtual int on_publish(); + virtual int initialize(SrsRequest* r); + virtual int on_publish() = 0; virtual void on_unpublish() = 0; - virtual int on_meta_data(SrsOnMetaDataPacket* metadata); - virtual int on_audio(SrsSharedPtrMessage* audio); - virtual int on_video(SrsSharedPtrMessage* video); -// interface ISrsReloadHandler -public: - virtual int on_reload_vhost_dvr(std::string vhost); + /** + * when got metadata. + */ + virtual int on_meta_data(SrsSharedPtrMessage* shared_metadata); + /** + * @param shared_audio, directly ptr, copy it if need to save it. + */ + virtual int on_audio(SrsSharedPtrMessage* shared_audio); + /** + * @param shared_video, directly ptr, copy it if need to save it. + */ + virtual int on_video(SrsSharedPtrMessage* shared_video); protected: - virtual int flv_open(std::string stream, std::string path); - virtual int flv_close(); - virtual int open_new_segment(); - virtual int update_duration(SrsSharedPtrMessage* msg); - virtual int write_flv_header(); - virtual int on_dvr_request_sh(); + virtual int on_reap_segment(); virtual int on_video_keyframe(); virtual int64_t filter_timestamp(int64_t timestamp); public: @@ -146,9 +244,29 @@ class SrsDvrSessionPlan : public SrsDvrPlan SrsDvrSessionPlan(); virtual ~SrsDvrSessionPlan(); public: + virtual int on_publish(); virtual void on_unpublish(); }; +/** +* always append to flv file, never reap it. +*/ +class SrsDvrAppendPlan : public SrsDvrPlan +{ +private: + int64_t last_update_time; +public: + SrsDvrAppendPlan(); + virtual ~SrsDvrAppendPlan(); +public: + virtual int on_publish(); + virtual void on_unpublish(); + virtual int on_audio(SrsSharedPtrMessage* shared_audio); + virtual int on_video(SrsSharedPtrMessage* shared_video); +private: + virtual int update_duration(SrsSharedPtrMessage* msg); +}; + /** * segment plan: reap flv when duration exceed. */ @@ -159,15 +277,17 @@ class SrsDvrSegmentPlan : public SrsDvrPlan int segment_duration; SrsSharedPtrMessage* sh_audio; SrsSharedPtrMessage* sh_video; + SrsSharedPtrMessage* metadata; public: SrsDvrSegmentPlan(); virtual ~SrsDvrSegmentPlan(); public: - virtual int initialize(SrsSource* source, SrsRequest* req); + virtual int initialize(SrsRequest* req); virtual int on_publish(); virtual void on_unpublish(); - virtual int on_audio(SrsSharedPtrMessage* audio); - virtual int on_video(SrsSharedPtrMessage* video); + virtual int on_meta_data(SrsSharedPtrMessage* shared_metadata); + virtual int on_audio(SrsSharedPtrMessage* shared_audio); + virtual int on_video(SrsSharedPtrMessage* shared_video); private: virtual int update_duration(SrsSharedPtrMessage* msg); }; @@ -179,11 +299,11 @@ class SrsDvrSegmentPlan : public SrsDvrPlan class SrsDvr { private: - SrsSource* _source; + SrsSource* source; private: SrsDvrPlan* plan; public: - SrsDvr(SrsSource* source); + SrsDvr(); virtual ~SrsDvr(); public: /** @@ -191,12 +311,12 @@ class SrsDvr * when system initialize(encoder publish at first time, or reload), * initialize the dvr will reinitialize the plan, the whole dvr framework. */ - virtual int initialize(SrsRequest* req); + virtual int initialize(SrsSource* s, SrsRequest* r); /** * publish stream event, * when encoder start to publish RTMP stream. */ - virtual int on_publish(SrsRequest* req); + virtual int on_publish(SrsRequest* r); /** * the unpublish event., * when encoder stop(unpublish) to publish RTMP stream. @@ -205,15 +325,17 @@ class SrsDvr /** * get some information from metadata, it's optinal. */ - virtual int on_meta_data(SrsOnMetaDataPacket* metadata); + virtual int on_meta_data(SrsOnMetaDataPacket* m); /** * mux the audio packets to dvr. + * @param shared_audio, directly ptr, copy it if need to save it. */ - virtual int on_audio(SrsSharedPtrMessage* audio); + virtual int on_audio(SrsSharedPtrMessage* shared_audio); /** * mux the video packets to dvr. + * @param shared_video, directly ptr, copy it if need to save it. */ - virtual int on_video(SrsSharedPtrMessage* video); + virtual int on_video(SrsSharedPtrMessage* shared_video); }; #endif diff --git a/trunk/src/app/srs_app_edge.cpp b/trunk/src/app/srs_app_edge.cpp index 02ee571da9..3a8483be96 100644 --- a/trunk/src/app/srs_app_edge.cpp +++ b/trunk/src/app/srs_app_edge.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -31,18 +31,19 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using namespace std; #include -#include -#include +#include +#include #include -#include +#include #include #include #include #include #include -#include +#include #include -#include +#include +#include // when error, edge ingester sleep for a while and retry. #define SRS_EDGE_INGESTER_SLEEP_US (int64_t)(1*1000*1000LL) @@ -69,7 +70,7 @@ SrsEdgeIngester::SrsEdgeIngester() origin_index = 0; stream_id = 0; stfd = NULL; - pthread = new SrsThread(this, SRS_EDGE_INGESTER_SLEEP_US, true); + pthread = new SrsThread("edge-igs", this, SRS_EDGE_INGESTER_SLEEP_US, true); } SrsEdgeIngester::~SrsEdgeIngester() @@ -167,26 +168,24 @@ int SrsEdgeIngester::ingest() client->set_recv_timeout(SRS_EDGE_INGESTER_TIMEOUT_US); - SrsPithyPrint pithy_print(SRS_CONSTS_STAGE_EDGE); + SrsPithyPrint* pprint = SrsPithyPrint::create_edge(); + SrsAutoFree(SrsPithyPrint, pprint); while (pthread->can_loop()) { - // switch to other st-threads. - st_usleep(0); - - pithy_print.elapse(); + pprint->elapse(); // pithy print - if (pithy_print.can_print()) { + if (pprint->can_print()) { kbps->sample(); srs_trace("<- "SRS_CONSTS_LOG_EDGE_PLAY " time=%"PRId64", okbps=%d,%d,%d, ikbps=%d,%d,%d", - pithy_print.age(), + pprint->age(), kbps->get_send_kbps(), kbps->get_send_kbps_30s(), kbps->get_send_kbps_5m(), kbps->get_recv_kbps(), kbps->get_recv_kbps_30s(), kbps->get_recv_kbps_5m()); } // read from client. - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; if ((ret = client->recv_message(&msg)) != ERROR_SUCCESS) { if (!srs_is_client_gracefully_close(ret)) { srs_error("pull origin server message failed. ret=%d", ret); @@ -196,7 +195,7 @@ int SrsEdgeIngester::ingest() srs_verbose("edge loop recv message. ret=%d", ret); srs_assert(msg); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); if ((ret = process_publish_message(msg)) != ERROR_SUCCESS) { return ret; @@ -219,7 +218,7 @@ int SrsEdgeIngester::connect_app(string ep_server, string ep_port) } // notify server the edge identity, - // @see https://github.com/winlinvip/simple-rtmp-server/issues/147 + // @see https://github.com/simple-rtmp-server/srs/issues/147 SrsAmf0Object* data = req->args; data->set("srs_sig", SrsAmf0Any::str(RTMP_SIG_SRS_KEY)); data->set("srs_server", SrsAmf0Any::str(RTMP_SIG_SRS_SERVER)); @@ -242,12 +241,21 @@ int SrsEdgeIngester::connect_app(string ep_server, string ep_port) std::string local_ip = ips[_srs_config->get_stats_network()]; data->set("srs_server_ip", SrsAmf0Any::str(local_ip.c_str())); + // support vhost tranform for edge, + // @see https://github.com/simple-rtmp-server/srs/issues/372 + std::string vhost = _srs_config->get_vhost_edge_transform_vhost(req->vhost); + vhost = srs_string_replace(vhost, "[vhost]", req->vhost); // generate the tcUrl std::string param = ""; - std::string tc_url = srs_generate_tc_url(ep_server, req->vhost, req->app, ep_port, param); + std::string tc_url = srs_generate_tc_url(ep_server, vhost, req->app, ep_port, param); + srs_trace("edge ingest from %s:%s at %s", ep_server.c_str(), ep_port.c_str(), tc_url.c_str()); + + // replace the tcUrl in request, + // which will replace the tc_url in client.connect_app(). + req->tcUrl = tc_url; // upnode server identity will show in the connect_app of client. - // @see https://github.com/winlinvip/simple-rtmp-server/issues/160 + // @see https://github.com/simple-rtmp-server/srs/issues/160 // the debug_srs_upnode is config in vhost and default to true. bool debug_srs_upnode = _srs_config->get_debug_srs_upnode(req->vhost); if ((ret = client->connect_app(req->app, tc_url, req, debug_srs_upnode)) != ERROR_SUCCESS) { @@ -259,7 +267,7 @@ int SrsEdgeIngester::connect_app(string ep_server, string ep_port) return ret; } -int SrsEdgeIngester::process_publish_message(SrsMessage* msg) +int SrsEdgeIngester::process_publish_message(SrsCommonMessage* msg) { int ret = ERROR_SUCCESS; @@ -330,7 +338,7 @@ int SrsEdgeIngester::connect_server(string& ep_server, string& ep_port) SrsConfDirective* conf = _srs_config->get_vhost_edge_origin(_req->vhost); - // @see https://github.com/winlinvip/simple-rtmp-server/issues/79 + // @see https://github.com/simple-rtmp-server/srs/issues/79 // when origin is error, for instance, server is shutdown, // then user remove the vhost then reload, the conf is empty. if (!conf) { @@ -389,7 +397,7 @@ SrsEdgeForwarder::SrsEdgeForwarder() origin_index = 0; stream_id = 0; stfd = NULL; - pthread = new SrsThread(this, SRS_EDGE_FORWARDER_SLEEP_US, true); + pthread = new SrsThread("edge-fwr", this, SRS_EDGE_FORWARDER_SLEEP_US, true); queue = new SrsMessageQueue(); send_error_code = ERROR_SUCCESS; } @@ -450,7 +458,7 @@ int SrsEdgeForwarder::start() } if ((ret = client->publish(req->stream, stream_id)) != ERROR_SUCCESS) { - srs_error("connect with server failed, stream=%s, stream_id=%d. ret=%d", + srs_error("publish failed, stream=%s, stream_id=%d. ret=%d", req->stream.c_str(), stream_id, ret); return ret; } @@ -476,14 +484,12 @@ int SrsEdgeForwarder::cycle() client->set_recv_timeout(SRS_CONSTS_RTMP_PULSE_TIMEOUT_US); - SrsPithyPrint pithy_print(SRS_CONSTS_STAGE_EDGE); + SrsPithyPrint* pprint = SrsPithyPrint::create_edge(); + SrsAutoFree(SrsPithyPrint, pprint); - SrsSharedPtrMessageArray msgs(SYS_MAX_EDGE_SEND_MSGS); + SrsMessageArray msgs(SYS_MAX_EDGE_SEND_MSGS); while (pthread->can_loop()) { - // switch to other st-threads. - st_usleep(0); - if (send_error_code != ERROR_SUCCESS) { st_usleep(SRS_EDGE_FORWARDER_ERROR_US); continue; @@ -491,7 +497,7 @@ int SrsEdgeForwarder::cycle() // read from client. if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ret = client->recv_message(&msg); srs_verbose("edge loop recv message. ret=%d", ret); @@ -505,20 +511,21 @@ int SrsEdgeForwarder::cycle() } // forward all messages. + // each msg in msgs.msgs must be free, for the SrsMessageArray never free them. int count = 0; - if ((ret = queue->dump_packets(msgs.size, msgs.msgs, count)) != ERROR_SUCCESS) { + if ((ret = queue->dump_packets(msgs.max, msgs.msgs, count)) != ERROR_SUCCESS) { srs_error("get message to push to origin failed. ret=%d", ret); return ret; } - pithy_print.elapse(); + pprint->elapse(); // pithy print - if (pithy_print.can_print()) { + if (pprint->can_print()) { kbps->sample(); srs_trace("-> "SRS_CONSTS_LOG_EDGE_PUBLISH " time=%"PRId64", msgs=%d, okbps=%d,%d,%d, ikbps=%d,%d,%d", - pithy_print.age(), count, + pprint->age(), count, kbps->get_send_kbps(), kbps->get_send_kbps_30s(), kbps->get_send_kbps_5m(), kbps->get_recv_kbps(), kbps->get_recv_kbps_30s(), kbps->get_recv_kbps_5m()); } @@ -529,26 +536,17 @@ int SrsEdgeForwarder::cycle() continue; } - // all msgs to forward to origin. - // @remark, becareful, all msgs must be free explicitly, - // free by send_and_free_message or srs_freep. - for (int i = 0; i < count; i++) { - SrsSharedPtrMessage* msg = msgs.msgs[i]; - - srs_assert(msg); - msgs.msgs[i] = NULL; - - if ((ret = client->send_and_free_message(msg, stream_id)) != ERROR_SUCCESS) { - srs_error("edge publish push message to server failed. ret=%d", ret); - return ret; - } + // sendout messages, all messages are freed by send_and_free_messages(). + if ((ret = client->send_and_free_messages(msgs.msgs, count, stream_id)) != ERROR_SUCCESS) { + srs_error("edge publish push message to server failed. ret=%d", ret); + return ret; } } return ret; } -int SrsEdgeForwarder::proxy(SrsMessage* msg) +int SrsEdgeForwarder::proxy(SrsCommonMessage* msg) { int ret = ERROR_SUCCESS; @@ -574,7 +572,7 @@ int SrsEdgeForwarder::proxy(SrsMessage* msg) } srs_verbose("initialize shared ptr msg success."); - copy.header.stream_id = stream_id; + copy.stream_id = stream_id; if ((ret = queue->enqueue(copy.copy())) != ERROR_SUCCESS) { srs_error("enqueue edge publish msg failed. ret=%d", ret); } @@ -651,7 +649,7 @@ int SrsEdgeForwarder::connect_app(string ep_server, string ep_port) } // notify server the edge identity, - // @see https://github.com/winlinvip/simple-rtmp-server/issues/147 + // @see https://github.com/simple-rtmp-server/srs/issues/147 SrsAmf0Object* data = req->args; data->set("srs_sig", SrsAmf0Any::str(RTMP_SIG_SRS_KEY)); data->set("srs_server", SrsAmf0Any::str(RTMP_SIG_SRS_SERVER)); @@ -674,12 +672,21 @@ int SrsEdgeForwarder::connect_app(string ep_server, string ep_port) std::string local_ip = ips[_srs_config->get_stats_network()]; data->set("srs_server_ip", SrsAmf0Any::str(local_ip.c_str())); + // support vhost tranform for edge, + // @see https://github.com/simple-rtmp-server/srs/issues/372 + std::string vhost = _srs_config->get_vhost_edge_transform_vhost(req->vhost); + vhost = srs_string_replace(vhost, "[vhost]", req->vhost); // generate the tcUrl std::string param = ""; - std::string tc_url = srs_generate_tc_url(ep_server, req->vhost, req->app, ep_port, param); + std::string tc_url = srs_generate_tc_url(ep_server, vhost, req->app, ep_port, param); + srs_trace("edge forward to %s:%s at %s", ep_server.c_str(), ep_port.c_str(), tc_url.c_str()); + + // replace the tcUrl in request, + // which will replace the tc_url in client.connect_app(). + req->tcUrl = tc_url; // upnode server identity will show in the connect_app of client. - // @see https://github.com/winlinvip/simple-rtmp-server/issues/160 + // @see https://github.com/simple-rtmp-server/srs/issues/160 // the debug_srs_upnode is config in vhost and default to true. bool debug_srs_upnode = _srs_config->get_debug_srs_upnode(req->vhost); if ((ret = client->connect_app(req->app, tc_url, req, debug_srs_upnode)) != ERROR_SUCCESS) { @@ -816,7 +823,7 @@ int SrsPublishEdge::on_client_publish() return ret; } - // @see https://github.com/winlinvip/simple-rtmp-server/issues/180 + // @see https://github.com/simple-rtmp-server/srs/issues/180 // to avoid multiple publish the same stream on the same edge, // directly enter the publish stage. if (true) { @@ -828,7 +835,7 @@ int SrsPublishEdge::on_client_publish() // start to forward stream to origin. ret = forwarder->start(); - // @see https://github.com/winlinvip/simple-rtmp-server/issues/180 + // @see https://github.com/simple-rtmp-server/srs/issues/180 // when failed, revert to init if (ret != ERROR_SUCCESS) { SrsEdgeState pstate = state; @@ -839,7 +846,7 @@ int SrsPublishEdge::on_client_publish() return ret; } -int SrsPublishEdge::on_proxy_publish(SrsMessage* msg) +int SrsPublishEdge::on_proxy_publish(SrsCommonMessage* msg) { return forwarder->proxy(msg); } diff --git a/trunk/src/app/srs_app_edge.hpp b/trunk/src/app/srs_app_edge.hpp index d81e09723a..99147e62ce 100644 --- a/trunk/src/app/srs_app_edge.hpp +++ b/trunk/src/app/srs_app_edge.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -42,7 +42,7 @@ class SrsRequest; class SrsPlayEdge; class SrsPublishEdge; class SrsRtmpClient; -class SrsMessage; +class SrsCommonMessage; class SrsMessageQueue; class ISrsProtocolReaderWriter; class SrsKbps; @@ -104,7 +104,7 @@ class SrsEdgeIngester : public ISrsThreadHandler virtual void close_underlayer_socket(); virtual int connect_server(std::string& ep_server, std::string& ep_port); virtual int connect_app(std::string ep_server, std::string ep_port); - virtual int process_publish_message(SrsMessage* msg); + virtual int process_publish_message(SrsCommonMessage* msg); }; /** @@ -148,7 +148,7 @@ class SrsEdgeForwarder : public ISrsThreadHandler public: virtual int cycle(); public: - virtual int proxy(SrsMessage* msg); + virtual int proxy(SrsCommonMessage* msg); private: virtual void close_underlayer_socket(); virtual int connect_server(std::string& ep_server, std::string& ep_port); @@ -214,7 +214,7 @@ class SrsPublishEdge /** * proxy publish stream to edge */ - virtual int on_proxy_publish(SrsMessage* msg); + virtual int on_proxy_publish(SrsCommonMessage* msg); /** * proxy unpublish stream to edge. */ diff --git a/trunk/src/app/srs_app_empty.cpp b/trunk/src/app/srs_app_empty.cpp index 0b95a3605e..fb80ecf4eb 100644 --- a/trunk/src/app/srs_app_empty.cpp +++ b/trunk/src/app/srs_app_empty.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/trunk/src/app/srs_app_empty.hpp b/trunk/src/app/srs_app_empty.hpp index 73c79da795..5e0a6a165b 100644 --- a/trunk/src/app/srs_app_empty.hpp +++ b/trunk/src/app/srs_app_empty.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/trunk/src/app/srs_app_encoder.cpp b/trunk/src/app/srs_app_encoder.cpp index 6aedcd1528..3bcbc230c0 100644 --- a/trunk/src/app/srs_app_encoder.cpp +++ b/trunk/src/app/srs_app_encoder.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -29,7 +29,7 @@ using namespace std; #include #include #include -#include +#include #include #include #include @@ -44,8 +44,8 @@ static std::vector _transcoded_url; SrsEncoder::SrsEncoder() { - pthread = new SrsThread(this, SRS_RTMP_ENCODER_SLEEP_US, true); - pithy_print = new SrsPithyPrint(SRS_CONSTS_STAGE_ENCODER); + pthread = new SrsThread("encoder", this, SRS_RTMP_ENCODER_SLEEP_US, true); + pprint = SrsPithyPrint::create_encoder(); } SrsEncoder::~SrsEncoder() @@ -53,7 +53,7 @@ SrsEncoder::~SrsEncoder() on_unpublish(); srs_freep(pthread); - srs_freep(pithy_print); + srs_freep(pprint); } int SrsEncoder::on_publish(SrsRequest* req) @@ -113,8 +113,7 @@ int SrsEncoder::cycle() } // pithy print - encoder(); - pithy_print->elapse(); + show_encode_log_message(); return ret; } @@ -324,13 +323,15 @@ int SrsEncoder::initialize_ffmpeg(SrsFFMPEG* ffmpeg, SrsRequest* req, SrsConfDir return ret; } -void SrsEncoder::encoder() +void SrsEncoder::show_encode_log_message() { + pprint->elapse(); + // reportable - if (pithy_print->can_print()) { + if (pprint->can_print()) { // TODO: FIXME: show more info. srs_trace("-> "SRS_CONSTS_LOG_ENCODER" time=%"PRId64", encoders=%d, input=%s", - pithy_print->age(), (int)ffmpegs.size(), input_stream_name.c_str()); + pprint->age(), (int)ffmpegs.size(), input_stream_name.c_str()); } } diff --git a/trunk/src/app/srs_app_encoder.hpp b/trunk/src/app/srs_app_encoder.hpp index 00209c31f3..f728e7579b 100644 --- a/trunk/src/app/srs_app_encoder.hpp +++ b/trunk/src/app/srs_app_encoder.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -52,7 +52,7 @@ class SrsEncoder : public ISrsThreadHandler std::vector ffmpegs; private: SrsThread* pthread; - SrsPithyPrint* pithy_print; + SrsPithyPrint* pprint; public: SrsEncoder(); virtual ~SrsEncoder(); @@ -69,7 +69,7 @@ class SrsEncoder : public ISrsThreadHandler virtual int parse_scope_engines(SrsRequest* req); virtual int parse_ffmpeg(SrsRequest* req, SrsConfDirective* conf); virtual int initialize_ffmpeg(SrsFFMPEG* ffmpeg, SrsRequest* req, SrsConfDirective* engine); - virtual void encoder(); + virtual void show_encode_log_message(); }; #endif diff --git a/trunk/src/app/srs_app_ffmpeg.cpp b/trunk/src/app/srs_app_ffmpeg.cpp index f5a87fd02b..373a1a004d 100644 --- a/trunk/src/app/srs_app_ffmpeg.cpp +++ b/trunk/src/app/srs_app_ffmpeg.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -171,7 +171,7 @@ int SrsFFMPEG::initialize_transcode(SrsConfDirective* engine) } } - // @see, https://github.com/winlinvip/simple-rtmp-server/issues/145 + // @see, https://github.com/simple-rtmp-server/srs/issues/145 if (acodec == SRS_RTMP_ENCODER_LIBAACPLUS) { if (abitrate < 16 || abitrate > 72) { ret = ERROR_ENCODER_ABITRATE; @@ -181,12 +181,6 @@ int SrsFFMPEG::initialize_transcode(SrsConfDirective* engine) } if (acodec != SRS_RTMP_ENCODER_COPY && acodec != SRS_RTMP_ENCODER_NO_AUDIO) { - if (acodec.find(SRS_RTMP_ENCODER_ACODEC) == std::string::npos) { - ret = ERROR_ENCODER_ACODEC; - srs_error("invalid acodec, must be %s, actual %s, ret=%d", - SRS_RTMP_ENCODER_ACODEC, acodec.c_str(), ret); - return ret; - } if (abitrate <= 0) { ret = ERROR_ENCODER_ABITRATE; srs_error("invalid abitrate: %d, ret=%d", abitrate, ret); @@ -211,7 +205,7 @@ int SrsFFMPEG::initialize_transcode(SrsConfDirective* engine) // for not rtmp input, donot append the iformat, // for example, "-f flv" before "-i udp://192.168.1.252:2222" - // @see https://github.com/winlinvip/simple-rtmp-server/issues/290 + // @see https://github.com/simple-rtmp-server/srs/issues/290 if (input.find("rtmp://") != 0) { iformat = ""; } @@ -336,21 +330,21 @@ int SrsFFMPEG::start() } // the codec params is disabled when copy - if (acodec != SRS_RTMP_ENCODER_COPY && acodec != SRS_RTMP_ENCODER_NO_AUDIO) { - params.push_back("-b:a"); - snprintf(tmp, sizeof(tmp), "%d", abitrate * 1000); - params.push_back(tmp); - - params.push_back("-ar"); - snprintf(tmp, sizeof(tmp), "%d", asample_rate); - params.push_back(tmp); - - params.push_back("-ac"); - snprintf(tmp, sizeof(tmp), "%d", achannels); - params.push_back(tmp); - - // aparams - if (!aparams.empty()) { + if (acodec != SRS_RTMP_ENCODER_NO_AUDIO) { + if (acodec != SRS_RTMP_ENCODER_COPY) { + params.push_back("-b:a"); + snprintf(tmp, sizeof(tmp), "%d", abitrate * 1000); + params.push_back(tmp); + + params.push_back("-ar"); + snprintf(tmp, sizeof(tmp), "%d", asample_rate); + params.push_back(tmp); + + params.push_back("-ac"); + snprintf(tmp, sizeof(tmp), "%d", achannels); + params.push_back(tmp); + + // aparams std::vector::iterator it; for (it = aparams.begin(); it != aparams.end(); ++it) { std::string p = *it; @@ -358,6 +352,20 @@ int SrsFFMPEG::start() params.push_back(p); } } + } else { + // for audio copy. + for (int i = 0; i < (int)aparams.size();) { + std::string pn = aparams[i++]; + + // aparams, the adts to asc filter "-bsf:a aac_adtstoasc" + if (pn == "-bsf:a" && i < (int)aparams.size()) { + std::string pv = aparams[i++]; + if (pv == "aac_adtstoasc") { + params.push_back(pn); + params.push_back(pv); + } + } + } } } @@ -438,8 +446,8 @@ int SrsFFMPEG::start() // memory leak in child process, it's ok. char** charpv_params = new char*[params.size() + 1]; for (int i = 0; i < (int)params.size(); i++) { - std::string p = params[i]; - charpv_params[i] = (char*)p.c_str(); + std::string& p = params[i]; + charpv_params[i] = (char*)p.data(); } // EOF: NULL charpv_params[params.size()] = NULL; diff --git a/trunk/src/app/srs_app_ffmpeg.hpp b/trunk/src/app/srs_app_ffmpeg.hpp index 60a92d20d3..7c075c4771 100644 --- a/trunk/src/app/srs_app_ffmpeg.hpp +++ b/trunk/src/app/srs_app_ffmpeg.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/trunk/src/app/srs_app_forward.cpp b/trunk/src/app/srs_app_forward.cpp index d819061b73..1d77d88542 100644 --- a/trunk/src/app/srs_app_forward.cpp +++ b/trunk/src/app/srs_app_forward.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -36,13 +36,14 @@ using namespace std; #include #include #include -#include -#include +#include +#include #include -#include +#include #include -#include +#include #include +#include // when error, forwarder sleep for a while and retry. #define SRS_FORWARDER_SLEEP_US (int64_t)(3*1000*1000LL) @@ -58,7 +59,7 @@ SrsForwarder::SrsForwarder(SrsSource* _source) kbps = new SrsKbps(); stream_id = 0; - pthread = new SrsThread(this, SRS_FORWARDER_SLEEP_US, true); + pthread = new SrsThread("forward", this, SRS_FORWARDER_SLEEP_US, true); queue = new SrsMessageQueue(); jitter = new SrsRtmpJitter(); @@ -156,9 +157,11 @@ void SrsForwarder::on_unpublish() kbps->set_io(NULL, NULL); } -int SrsForwarder::on_meta_data(SrsSharedPtrMessage* metadata) +int SrsForwarder::on_meta_data(SrsSharedPtrMessage* shared_metadata) { int ret = ERROR_SUCCESS; + + SrsSharedPtrMessage* metadata = shared_metadata->copy(); if ((ret = jitter->correct(metadata, 0, 0, SrsRtmpJitterAlgorithmFULL)) != ERROR_SUCCESS) { srs_freep(metadata); @@ -172,10 +175,12 @@ int SrsForwarder::on_meta_data(SrsSharedPtrMessage* metadata) return ret; } -int SrsForwarder::on_audio(SrsSharedPtrMessage* msg) +int SrsForwarder::on_audio(SrsSharedPtrMessage* shared_audio) { int ret = ERROR_SUCCESS; + SrsSharedPtrMessage* msg = shared_audio->copy(); + if ((ret = jitter->correct(msg, 0, 0, SrsRtmpJitterAlgorithmFULL)) != ERROR_SUCCESS) { srs_freep(msg); return ret; @@ -193,9 +198,11 @@ int SrsForwarder::on_audio(SrsSharedPtrMessage* msg) return ret; } -int SrsForwarder::on_video(SrsSharedPtrMessage* msg) +int SrsForwarder::on_video(SrsSharedPtrMessage* shared_video) { int ret = ERROR_SUCCESS; + + SrsSharedPtrMessage* msg = shared_video->copy(); if ((ret = jitter->correct(msg, 0, 0, SrsRtmpJitterAlgorithmFULL)) != ERROR_SUCCESS) { srs_freep(msg); @@ -333,7 +340,7 @@ int SrsForwarder::connect_app(string ep_server, string ep_port) } // notify server the edge identity, - // @see https://github.com/winlinvip/simple-rtmp-server/issues/147 + // @see https://github.com/simple-rtmp-server/srs/issues/147 SrsAmf0Object* data = req->args; data->set("srs_sig", SrsAmf0Any::str(RTMP_SIG_SRS_KEY)); data->set("srs_server", SrsAmf0Any::str(RTMP_SIG_SRS_SERVER)); @@ -361,7 +368,7 @@ int SrsForwarder::connect_app(string ep_server, string ep_port) std::string tc_url = srs_generate_tc_url(ep_server, req->vhost, req->app, ep_port, param); // upnode server identity will show in the connect_app of client. - // @see https://github.com/winlinvip/simple-rtmp-server/issues/160 + // @see https://github.com/simple-rtmp-server/srs/issues/160 // the debug_srs_upnode is config in vhost and default to true. bool debug_srs_upnode = _srs_config->get_debug_srs_upnode(req->vhost); if ((ret = client->connect_app(req->app, tc_url, req, debug_srs_upnode)) != ERROR_SUCCESS) { @@ -380,9 +387,10 @@ int SrsForwarder::forward() client->set_recv_timeout(SRS_CONSTS_RTMP_PULSE_TIMEOUT_US); - SrsPithyPrint pithy_print(SRS_CONSTS_STAGE_FORWARDER); + SrsPithyPrint* pprint = SrsPithyPrint::create_forwarder(); + SrsAutoFree(SrsPithyPrint, pprint); - SrsSharedPtrMessageArray msgs(SYS_MAX_FORWARD_SEND_MSGS); + SrsMessageArray msgs(SYS_MAX_FORWARD_SEND_MSGS); // update sequence header // TODO: FIXME: maybe need to zero the sequence header timestamp. @@ -400,14 +408,11 @@ int SrsForwarder::forward() } while (pthread->can_loop()) { - // switch to other st-threads. - st_usleep(0); - - pithy_print.elapse(); + pprint->elapse(); // read from client. if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ret = client->recv_message(&msg); srs_verbose("play loop recv message. ret=%d", ret); @@ -420,18 +425,19 @@ int SrsForwarder::forward() } // forward all messages. + // each msg in msgs.msgs must be free, for the SrsMessageArray never free them. int count = 0; - if ((ret = queue->dump_packets(msgs.size, msgs.msgs, count)) != ERROR_SUCCESS) { + if ((ret = queue->dump_packets(msgs.max, msgs.msgs, count)) != ERROR_SUCCESS) { srs_error("get message to forward failed. ret=%d", ret); return ret; } // pithy print - if (pithy_print.can_print()) { + if (pprint->can_print()) { kbps->sample(); srs_trace("-> "SRS_CONSTS_LOG_FOWARDER " time=%"PRId64", msgs=%d, okbps=%d,%d,%d, ikbps=%d,%d,%d", - pithy_print.age(), count, + pprint->age(), count, kbps->get_send_kbps(), kbps->get_send_kbps_30s(), kbps->get_send_kbps_5m(), kbps->get_recv_kbps(), kbps->get_recv_kbps_30s(), kbps->get_recv_kbps_5m()); } @@ -442,19 +448,10 @@ int SrsForwarder::forward() continue; } - // all msgs to forward. - // @remark, becareful, all msgs must be free explicitly, - // free by send_and_free_message or srs_freep. - for (int i = 0; i < count; i++) { - SrsSharedPtrMessage* msg = msgs.msgs[i]; - - srs_assert(msg); - msgs.msgs[i] = NULL; - - if ((ret = client->send_and_free_message(msg, stream_id)) != ERROR_SUCCESS) { - srs_error("forwarder send message to server failed. ret=%d", ret); - return ret; - } + // sendout messages, all messages are freed by send_and_free_messages(). + if ((ret = client->send_and_free_messages(msgs.msgs, count, stream_id)) != ERROR_SUCCESS) { + srs_error("forwarder messages to server failed. ret=%d", ret); + return ret; } } diff --git a/trunk/src/app/srs_app_forward.hpp b/trunk/src/app/srs_app_forward.hpp index c2fdb6f570..dbad1c8278 100644 --- a/trunk/src/app/srs_app_forward.hpp +++ b/trunk/src/app/srs_app_forward.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -67,7 +67,7 @@ class SrsForwarder : public ISrsThreadHandler SrsMessageQueue* queue; /** * cache the sequence header for retry when slave is failed. - * @see https://github.com/winlinvip/simple-rtmp-server/issues/150 + * @see https://github.com/simple-rtmp-server/srs/issues/150 */ SrsSharedPtrMessage* sh_audio; SrsSharedPtrMessage* sh_video; @@ -80,9 +80,21 @@ class SrsForwarder : public ISrsThreadHandler public: virtual int on_publish(); virtual void on_unpublish(); - virtual int on_meta_data(SrsSharedPtrMessage* metadata); - virtual int on_audio(SrsSharedPtrMessage* msg); - virtual int on_video(SrsSharedPtrMessage* msg); + /** + * forward the audio packet. + * @param shared_metadata, directly ptr, copy it if need to save it. + */ + virtual int on_meta_data(SrsSharedPtrMessage* shared_metadata); + /** + * forward the audio packet. + * @param shared_audio, directly ptr, copy it if need to save it. + */ + virtual int on_audio(SrsSharedPtrMessage* shared_audio); + /** + * forward the video packet. + * @param shared_video, directly ptr, copy it if need to save it. + */ + virtual int on_video(SrsSharedPtrMessage* shared_video); // interface ISrsThreadHandler. public: virtual int cycle(); diff --git a/trunk/src/app/srs_app_hds.cpp b/trunk/src/app/srs_app_hds.cpp new file mode 100644 index 0000000000..7e05c7aa31 --- /dev/null +++ b/trunk/src/app/srs_app_hds.cpp @@ -0,0 +1,750 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#include + +#ifdef SRS_AUTO_HDS + +#include +#include +#include +#include +#include +#include +using namespace std; + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void update_box(char *start, int size) +{ + char *p_size = (char*)&size; + start[0] = p_size[3]; + start[1] = p_size[2]; + start[2] = p_size[1]; + start[3] = p_size[0]; +} + +char flv_header[] = {'F', 'L', 'V', + 0x01, 0x05, 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00}; + +string serialFlv(SrsSharedPtrMessage *msg) +{ + SrsStream *stream = new SrsStream; + + int size = 15 + msg->size; + char *byte = new char[size]; + stream->initialize(byte, size); + + // tag header + long long dts = msg->timestamp; + char type = msg->is_video() ? 0x09 : 0x08; + + stream->write_1bytes(type); + stream->write_3bytes(msg->size); + stream->write_3bytes(dts); + stream->write_1bytes(dts >> 24 & 0xFF); + stream->write_3bytes(0); + stream->write_bytes(msg->payload, msg->size); + + // pre tag size + int preTagSize = msg->size + 11; + stream->write_4bytes(preTagSize); + + string ret(stream->data(), stream->size()); + + delete stream; + delete [] byte; + + return ret; +} + +class SrsHdsFragment +{ +public: + SrsHdsFragment(SrsRequest *r) + : req(r) + , index(-1) + , start_time(0) + , videoSh(NULL) + , audioSh(NULL) + { + + } + + ~SrsHdsFragment() + { + srs_freep(videoSh); + srs_freep(audioSh); + + // clean msgs + list::iterator iter; + for (iter = msgs.begin(); iter != msgs.end(); ++iter) { + SrsSharedPtrMessage *msg = *iter; + srs_freep(msg); + } + } + + void on_video(SrsSharedPtrMessage *msg) + { + SrsSharedPtrMessage *_msg = msg->copy(); + msgs.push_back(_msg); + } + + void on_audio(SrsSharedPtrMessage *msg) + { + SrsSharedPtrMessage *_msg = msg->copy(); + msgs.push_back(_msg); + } + + /*! + flush data to disk. + */ + int flush() + { + string data; + if (videoSh) { + videoSh->timestamp = start_time; + data.append(serialFlv(videoSh)); + } + + if (audioSh) { + audioSh->timestamp = start_time; + data.append(serialFlv(audioSh)); + } + + list::iterator iter; + for (iter = msgs.begin(); iter != msgs.end(); ++iter) { + SrsSharedPtrMessage *msg = *iter; + data.append(serialFlv(msg)); + } + + char box_header[8]; + SrsStream ss; + ss.initialize(box_header, 8); + ss.write_4bytes(8 + data.size()); + ss.write_string("mdat"); + + data = string(ss.data(), ss.size()) + data; + + const char *file_path = path.c_str(); + int fd = open(file_path, O_WRONLY | O_CREAT, S_IRWXU | S_IRGRP | S_IROTH); + if (fd < 0) { + srs_error("open fragment file failed, path=%s", file_path); + return -1; + } + + if (write(fd, data.data(), data.size()) != (int)data.size()) { + srs_error("write fragment file failed, path=", file_path); + close(fd); + return -1; + } + close(fd); + + srs_trace("build fragment success=%s", file_path); + + return ERROR_SUCCESS; + } + + /*! + calc the segment duration in milliseconds. + @return 0 if no msgs + or the last msg dts minus the first msg dts. + */ + int duration() + { + int duration_ms = 0; + long long first_msg_ts = 0; + long long last_msg_ts = 0; + + if (msgs.size() >= 2) { + SrsSharedPtrMessage *first_msg = msgs.front(); + first_msg_ts = first_msg->timestamp; + + SrsSharedPtrMessage *last_msg = msgs.back(); + last_msg_ts = last_msg->timestamp; + + duration_ms = last_msg_ts - first_msg_ts; + } + + return duration_ms; + } + + /*! + set/get index + */ + inline void set_index(int idx) + { + char file_path[1024] = {0}; + sprintf(file_path, "%s/%s/%sSeg1-Frag%d", _srs_config->get_hds_path(req->vhost).c_str() + , req->app.c_str(), req->stream.c_str(), idx); + + path = file_path; + index = idx; + } + + inline int get_index() + { + return index; + } + + /*! + set/get start time + */ + inline void set_start_time(long long st) + { + start_time = st; + } + + inline long long get_start_time() + { + return start_time; + } + + void set_video_sh(SrsSharedPtrMessage *msg) + { + srs_freep(videoSh); + videoSh = msg->copy(); + } + + void set_audio_sh(SrsSharedPtrMessage *msg) + { + srs_freep(audioSh); + audioSh = msg->copy(); + } + + string fragment_path() + { + return path; + } + +private: + SrsRequest *req; + list msgs; + + /*! + the index of this fragment + */ + int index; + long long start_time; + + SrsSharedPtrMessage *videoSh; + SrsSharedPtrMessage *audioSh; + string path; +}; + +SrsHds::SrsHds(SrsSource *s) + : currentSegment(NULL) + , fragment_index(1) + , video_sh(NULL) + , audio_sh(NULL) + , hds_req(NULL) + , hds_enabled(false) +{ + +} + +SrsHds::~SrsHds() +{ + +} + +int SrsHds::on_publish(SrsRequest *req) +{ + int ret = ERROR_SUCCESS; + if (hds_enabled) { + return ret; + } + + std::string vhost = req->vhost; + if (!_srs_config->get_hds_enabled(vhost)) { + hds_enabled = false; + return ret; + } + hds_enabled = true; + + hds_req = req->copy(); + + return flush_mainfest(); +} + +int SrsHds::on_unpublish() +{ + int ret = ERROR_SUCCESS; + + if (!hds_enabled) { + return ret; + } + + hds_enabled = false; + + srs_freep(video_sh); + srs_freep(audio_sh); + srs_freep(hds_req); + + // clean fragments + list::iterator iter; + for (iter = fragments.begin(); iter != fragments.end(); ++iter) { + SrsHdsFragment *st = *iter; + srs_freep(st); + } + fragments.clear(); + + srs_freep(currentSegment); + + srs_trace("HDS un-published"); + + return ret; +} + +int SrsHds::on_video(SrsSharedPtrMessage* msg) +{ + int ret = ERROR_SUCCESS; + + if (!hds_enabled) { + return ret; + } + + if (SrsFlvCodec::video_is_sequence_header(msg->payload, msg->size)) { + srs_freep(video_sh); + video_sh = msg->copy(); + } + + if (!currentSegment) { + currentSegment = new SrsHdsFragment(hds_req); + currentSegment->set_index(fragment_index++); + currentSegment->set_start_time(msg->timestamp); + + if (video_sh) + currentSegment->set_video_sh(video_sh); + + if (audio_sh) + currentSegment->set_audio_sh(audio_sh); + } + + currentSegment->on_video(msg); + + double fragment_duration = _srs_config->get_hds_fragment(hds_req->vhost) * 1000; + if (currentSegment->duration() >= fragment_duration) { + // flush segment + if ((ret = currentSegment->flush()) != ERROR_SUCCESS) { + srs_error("flush segment failed."); + return ret; + } + + srs_trace("flush Segment success."); + fragments.push_back(currentSegment); + currentSegment = NULL; + adjust_windows(); + + // flush bootstrap + if ((ret = flush_bootstrap()) != ERROR_SUCCESS) { + srs_error("flush bootstrap failed."); + return ret; + } + + srs_trace("flush BootStrap success."); + } + + return ret; +} + +int SrsHds::on_audio(SrsSharedPtrMessage* msg) +{ + int ret = ERROR_SUCCESS; + + if (!hds_enabled) { + return ret; + } + + if (SrsFlvCodec::audio_is_sequence_header(msg->payload, msg->size)) { + srs_freep(audio_sh); + audio_sh = msg->copy(); + } + + if (!currentSegment) { + currentSegment = new SrsHdsFragment(hds_req); + currentSegment->set_index(fragment_index++); + currentSegment->set_start_time(msg->timestamp); + + if (video_sh) + currentSegment->set_video_sh(video_sh); + + if (audio_sh) + currentSegment->set_audio_sh(audio_sh); + } + + currentSegment->on_audio(msg); + + double fragment_duration = _srs_config->get_hds_fragment(hds_req->vhost) * 1000; + if (currentSegment->duration() >= fragment_duration) { + // flush segment + if ((ret = currentSegment->flush()) != ERROR_SUCCESS) { + srs_error("flush segment failed."); + return ret; + } + + srs_info("flush Segment success."); + + // reset the current segment + fragments.push_back(currentSegment); + currentSegment = NULL; + adjust_windows(); + + // flush bootstrap + if ((ret = flush_bootstrap()) != ERROR_SUCCESS) { + srs_error("flush bootstrap failed."); + return ret; + } + + srs_info("flush BootStrap success."); + } + + return ret; +} + +int SrsHds::flush_mainfest() +{ + int ret = ERROR_SUCCESS; + + char buf[1024] = {0}; + sprintf(buf, "\n" + "\n\t" + "%s.f4m\n\t" + "live\n\t" + "streaming\n\t" + "\n\t" + "\n" + "" + , hds_req->stream.c_str(), hds_req->stream.c_str(), hds_req->stream.c_str()); + + string dir = _srs_config->get_hds_path(hds_req->vhost) + "/" + hds_req->app; + if ((ret = srs_create_dir_recursively(dir)) != ERROR_SUCCESS) { + srs_error("hds create dir failed. ret=%d", ret); + return ret; + } + string path = dir + "/" + hds_req->stream + ".f4m"; + + int fd = open(path.c_str(), O_WRONLY | O_CREAT, S_IRWXU | S_IRGRP | S_IROTH); + if (fd < 0) { + srs_error("open manifest file failed, path=%s", path.c_str()); + ret = ERROR_HDS_OPEN_F4M_FAILED; + return ret; + } + + int f4m_size = strlen(buf); + if (write(fd, buf, f4m_size) != f4m_size) { + srs_error("write manifest file failed, path=", path.c_str()); + close(fd); + ret = ERROR_HDS_WRITE_F4M_FAILED; + return ret; + } + close(fd); + + srs_trace("build manifest success=%s", path.c_str()); + + return ERROR_SUCCESS; +} + +int SrsHds::flush_bootstrap() +{ + int ret = ERROR_SUCCESS; + + SrsStream abst; + + int size = 1024*100; + + char *start_abst = new char[1024*100]; + SrsAutoFree(char, start_abst); + + int size_abst = 0; + char *start_asrt = NULL; + int size_asrt = 0; + char *start_afrt = NULL; + int size_afrt = 0; + + if ((ret = abst.initialize(start_abst, size)) != ERROR_SUCCESS) { + return ret; + } + + // @see video_file_format_spec_v10_1 + // page: 46 + abst.write_4bytes(0); + abst.write_string("abst"); + abst.write_1bytes(0x00); // Either 0 or 1 + abst.write_3bytes(0x00); // Flags always 0 + size_abst += 12; + /*! + @BootstrapinfoVersion UI32 + The version number of the bootstrap information. + When the Update field is set, BootstrapinfoVersion + indicates the version number that is being updated. + we assume this is the last. + */ + abst.write_4bytes(fragment_index - 1); // BootstrapinfoVersion + + abst.write_1bytes(0x20); // profile, live, update + abst.write_4bytes(1000); // TimeScale Typically, the value is 1000, for a unit of milliseconds + size_abst += 9; + /*! + The timestamp in TimeScale units of the latest available Fragment in the media presentation. + This timestamp is used to request the right fragment number. + The CurrentMedia Time can be the total duration. + For media presentations that are not live, CurrentMediaTime can be 0. + */ + SrsHdsFragment *st = fragments.back(); + abst.write_8bytes(st->get_start_time()); + + // SmpteTimeCodeOffset + abst.write_8bytes(0); + size_abst += 16; + + /*! + @MovieIdentifier STRING + The identifier of this presentation. + we write null string. + */ + abst.write_1bytes(0); + size_abst += 1; + /*! + @ServerEntryCount UI8 + The number of ServerEntryTable entries. + The minimum value is 0. + */ + abst.write_1bytes(0); + size_abst += 1; + /*! + @ServerEntryTable + because we write 0 of ServerEntryCount, so this feild is ignored. + */ + + /*! + @QualityEntryCount UI8 + The number of QualityEntryTable entries, which is + also the number of available quality levels. The + minimum value is 0. Available quality levels are for, + for example, multi bit rate files or trick files. + */ + abst.write_1bytes(0); + size_abst += 1; + /*! + @QualityEntryTable + because we write 0 of QualityEntryCount, so this feild is ignored. + */ + + /*! + @DrmData STRING + Null or null-terminated UTF-8 string. This string + holds Digital Rights Management metadata. + Encrypted files use this metadata to get the + necessary keys and licenses for decryption and play back. + we write null string. + */ + abst.write_1bytes(0); + size_abst += 1; + /*! + @MetaData STRING + Null or null-terminated UTF - 8 string that holds metadata. + we write null string. + */ + abst.write_1bytes(0); + size_abst += 1; + /*! + @SegmentRunTableCount UI8 + The number of entries in SegmentRunTableEntries. + The minimum value is 1. Typically, one table + contains all segment runs. However, this count + provides the flexibility to define the segment runs + individually for each quality level (or trick file). + */ + abst.write_1bytes(1); + size_abst += 1; + + start_asrt = start_abst + size_abst; + + // follows by asrt + abst.write_4bytes(0); + abst.write_string("asrt"); + size_asrt += 8; + /*! + @Version UI8 + @Flags UI24 + */ + abst.write_4bytes(0); + size_asrt += 4; + /*! + @QualityEntryCount UI8 + The number of QualitySegmen tUrlModifiers + (quality level references) that follow. If 0, this + Segment Run Table applies to all quality levels, + and there shall be only one Segment Run Table + box in the Bootstrap Info box. + */ + abst.write_1bytes(0); + size_asrt += 1; + + /*! + @QualitySegmentUrlModifiers + ignored. + */ + + /*! + @SegmentRunEntryCount + The number of items in this + SegmentRunEn tryTable. The minimum value is 1. + */ + abst.write_4bytes(1); + size_asrt += 4; + /*! + @SegmentRunEntryTable + */ + for (int i = 0; i < 1; ++i) { + /*! + @FirstSegment UI32 + The identifying number of the first segment in the run of + segments containing the same number of fragments. + The segment corresponding to the FirstSegment in the next + SEGMENTRUNENTRY will terminate this run. + */ + abst.write_4bytes(1); + + /*! + @FragmentsPerSegment UI32 + The number of fragments in each segment in this run. + */ + abst.write_4bytes(fragment_index - 1); + size_asrt += 8; + } + + update_box(start_asrt, size_asrt); + size_abst += size_asrt; + + /*! + @FragmentRunTableCount UI8 + The number of entries in FragmentRunTable-Entries. + The min i mum value is 1. + */ + abst.write_1bytes(1); + size_abst += 1; + + // follows by afrt + start_afrt = start_abst + size_abst; + + abst.write_4bytes(0); + abst.write_string("afrt"); + size_afrt += 8; + + /*! + @Version UI8 + @Flags UI24 + */ + abst.write_4bytes(0); + size_afrt += 4; + /*! + @TimeScale UI32 + The number of time units per second, used in the FirstFragmentTime stamp and + Fragment Duration fields. + Typically, the value is 1000. + */ + abst.write_4bytes(1000); + size_afrt += 4; + /*! + @QualityEntryCount UI8 + The number of QualitySegment Url Modifiers + (quality level references) that follow. + If 0, this Fragment Run Table applies to all quality levels, + and there shall be only one Fragment Run Table + box in the Bootstrap Info box. + */ + abst.write_1bytes(0); + size_afrt += 1; + + /*! + @FragmentRunEntryCount UI32 + The number of items in this FragmentRunEntryTable. + The minimum value is 1. + */ + abst.write_4bytes(fragments.size()); + size_afrt += 4; + + list::iterator iter; + for (iter = fragments.begin(); iter != fragments.end(); ++iter) { + SrsHdsFragment *st = *iter; + abst.write_4bytes(st->get_index()); + abst.write_8bytes(st->get_start_time()); + abst.write_4bytes(st->duration()); + size_afrt += 16; + } + + update_box(start_afrt, size_afrt); + size_abst += size_afrt; + update_box(start_abst, size_abst); + + string path = _srs_config->get_hds_path(hds_req->vhost) + "/" + hds_req->app + "/" + hds_req->stream +".abst"; + + int fd = open(path.c_str(), O_WRONLY | O_CREAT, S_IRWXU | S_IRGRP | S_IROTH); + if (fd < 0) { + srs_error("open bootstrap file failed, path=%s", path.c_str()); + ret = ERROR_HDS_OPEN_BOOTSTRAP_FAILED; + return ret; + } + + if (write(fd, start_abst, size_abst) != size_abst) { + srs_error("write bootstrap file failed, path=", path.c_str()); + close(fd); + ret = ERROR_HDS_WRITE_BOOTSTRAP_FAILED; + return ret; + } + close(fd); + + srs_trace("build bootstrap success=%s", path.c_str()); + + return ERROR_SUCCESS; +} + +void SrsHds::adjust_windows() +{ + int windows_size = 0; + list::iterator iter; + for (iter = fragments.begin(); iter != fragments.end(); ++iter) { + SrsHdsFragment *fragment = *iter; + windows_size += fragment->duration(); + } + + double windows_size_limit = _srs_config->get_hds_window(hds_req->vhost) * 1000; + if (windows_size > windows_size_limit ) { + SrsHdsFragment *fragment = fragments.front(); + unlink(fragment->fragment_path().c_str()); + fragments.erase(fragments.begin()); + srs_freep(fragment); + } +} + +#endif diff --git a/trunk/src/app/srs_app_hds.hpp b/trunk/src/app/srs_app_hds.hpp new file mode 100644 index 0000000000..17030bc4e5 --- /dev/null +++ b/trunk/src/app/srs_app_hds.hpp @@ -0,0 +1,68 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef SRS_APP_HDS_HPP +#define SRS_APP_HDS_HPP + +#include + +#ifdef SRS_AUTO_HDS + +#include + +class SrsRequest; +class SrsSharedPtrMessage; +class SrsHdsFragment; +class SrsSource; + +class SrsHds +{ +public: + SrsHds(SrsSource* s); + ~SrsHds(); + + int on_publish(SrsRequest* req); + int on_unpublish(); + + int on_video(SrsSharedPtrMessage* msg); + int on_audio(SrsSharedPtrMessage* msg); + +private: + int flush_mainfest(); + int flush_bootstrap(); + void adjust_windows(); + +private: + std::list fragments; + SrsHdsFragment *currentSegment; + int fragment_index; + SrsSharedPtrMessage *video_sh; + SrsSharedPtrMessage *audio_sh; + + SrsRequest *hds_req; + bool hds_enabled; +}; + +#endif + +#endif diff --git a/trunk/src/app/srs_app_heartbeat.cpp b/trunk/src/app/srs_app_heartbeat.cpp index da48b8e2c8..ecb9b91963 100644 --- a/trunk/src/app/srs_app_heartbeat.cpp +++ b/trunk/src/app/srs_app_heartbeat.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -35,6 +35,7 @@ using namespace std; #include #include #include +#include SrsHttpHeartbeat::SrsHttpHeartbeat() { @@ -65,28 +66,39 @@ void SrsHttpHeartbeat::heartbeat() } std::stringstream ss; - ss << __SRS_JOBJECT_START - << __SRS_JFIELD_STR("device_id", device_id) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("ip", ip); + ss << SRS_JOBJECT_START + << SRS_JFIELD_STR("device_id", device_id) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("ip", ip); if (_srs_config->get_heartbeat_summaries()) { - ss << __SRS_JFIELD_CONT << __SRS_JFIELD_ORG("summaries", ""); + ss << SRS_JFIELD_CONT << SRS_JFIELD_ORG("summaries", ""); srs_api_dump_summaries(ss); } - ss << __SRS_JOBJECT_END; - std::string data = ss.str(); - std::string res; + ss << SRS_JOBJECT_END; + + std::string req = ss.str(); SrsHttpClient http; - if ((ret = http.post(&uri, data, res)) != ERROR_SUCCESS) { + if ((ret = http.initialize(uri.get_host(), uri.get_port())) != ERROR_SUCCESS) { + return; + } + + SrsHttpMessage* msg = NULL; + if ((ret = http.post(uri.get_path(), req, &msg)) != ERROR_SUCCESS) { srs_info("http post hartbeart uri failed. " "url=%s, request=%s, response=%s, ret=%d", - url.c_str(), data.c_str(), res.c_str(), ret); + url.c_str(), req.c_str(), res.c_str(), ret); + return; + } + SrsAutoFree(SrsHttpMessage, msg); + + std::string res; + if ((ret = msg->body_read_all(res)) != ERROR_SUCCESS) { return; } srs_info("http hook hartbeart success. " - "url=%s, request=%s, response=%s, ret=%d", - url.c_str(), data.c_str(), res.c_str(), ret); + "url=%s, request=%s, status_code=%d, response=%s, ret=%d", + url.c_str(), req.c_str(), status_code, res.c_str(), ret); return; } diff --git a/trunk/src/app/srs_app_heartbeat.hpp b/trunk/src/app/srs_app_heartbeat.hpp index 1bc96c1339..6aad01b675 100644 --- a/trunk/src/app/srs_app_heartbeat.hpp +++ b/trunk/src/app/srs_app_heartbeat.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/trunk/src/app/srs_app_hls.cpp b/trunk/src/app/srs_app_hls.cpp index be90a12199..cf322b22b1 100644 --- a/trunk/src/app/srs_app_hls.cpp +++ b/trunk/src/app/srs_app_hls.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -23,25 +23,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include -/** -* the public data, event HLS disable, others can use it. -*/ -// 0 = 5.5 kHz = 5512 Hz -// 1 = 11 kHz = 11025 Hz -// 2 = 22 kHz = 22050 Hz -// 3 = 44 kHz = 44100 Hz -int flv_sample_rates[] = {5512, 11025, 22050, 44100}; - -// the sample rates in the codec, -// in the sequence header. -int aac_sample_rates[] = -{ - 96000, 88200, 64000, 48000, - 44100, 32000, 24000, 22050, - 16000, 12000, 11025, 8000, - 7350, 0, 0, 0 -}; - /** * the HLS section, only available when HLS enabled. */ @@ -52,484 +33,125 @@ int aac_sample_rates[] = #include #include #include +#include #include +#include using namespace std; #include #include -#include -#include +#include +#include #include #include #include -#include +#include #include #include -#include +#include #include -#include - -// max PES packets size to flush the video. -#define SRS_AUTO_HLS_AUDIO_CACHE_SIZE 1024 * 1024 +#include +#include +#include +#include // drop the segment when duration of ts too small. #define SRS_AUTO_HLS_SEGMENT_MIN_DURATION_MS 100 -// @see: NGX_RTMP_HLS_DELAY, -// 63000: 700ms, ts_tbn=90000 -// 72000: 800ms, ts_tbn=90000 -// @see https://github.com/winlinvip/simple-rtmp-server/issues/151#issuecomment-71352511 -#define SRS_AUTO_HLS_DELAY 72000 - -// the mpegts header specifed the video/audio pid. -#define TS_VIDEO_PID 256 -#define TS_AUDIO_PID 257 - -// ts aac stream id. -#define TS_AUDIO_AAC 0xc0 -// ts avc stream id. -#define TS_VIDEO_AVC 0xe0 - -// @see: ngx_rtmp_hls_audio -/* We assume here AAC frame size is 1024 - * Need to handle AAC frames with frame size of 960 */ -#define _SRS_AAC_SAMPLE_SIZE 1024 - -// in ms, for HLS aac sync time. -#define SRS_CONF_DEFAULT_AAC_SYNC 100 -// in ms, for HLS aac flush the audio -#define SRS_CONF_DEFAULT_AAC_DELAY 100 - -// @see: ngx_rtmp_mpegts_header -u_int8_t mpegts_header[] = { - /* TS */ - 0x47, 0x40, 0x00, 0x10, 0x00, - /* PSI */ - 0x00, 0xb0, 0x0d, 0x00, 0x01, 0xc1, 0x00, 0x00, - /* PAT */ - 0x00, 0x01, 0xf0, 0x01, - /* CRC */ - 0x2e, 0x70, 0x19, 0x05, - /* stuffing 167 bytes */ - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - - /* TS */ - 0x47, 0x50, 0x01, 0x10, 0x00, - /* PSI */ - 0x02, 0xb0, 0x17, 0x00, 0x01, 0xc1, 0x00, 0x00, - /* PMT */ - 0xe1, 0x00, - 0xf0, 0x00, - // must generate header with/without video, @see: - // https://github.com/winlinvip/simple-rtmp-server/issues/40 - 0x1b, 0xe1, 0x00, 0xf0, 0x00, /* h264, pid=0x100=256 */ - 0x0f, 0xe1, 0x01, 0xf0, 0x00, /* aac, pid=0x101=257 */ - /*0x03, 0xe1, 0x01, 0xf0, 0x00,*/ /* mp3 */ - /* CRC */ - 0x2f, 0x44, 0xb9, 0x9b, /* crc for aac */ - /*0x4e, 0x59, 0x3d, 0x1e,*/ /* crc for mp3 */ - /* stuffing 157 bytes */ - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff -}; - -// @see: ngx_rtmp_SrsMpegtsFrame_t -class SrsMpegtsFrame -{ -public: - int64_t pts; - int64_t dts; - int pid; - int sid; - int cc; - bool key; - - SrsMpegtsFrame() - { - pts = dts = 0; - pid = sid = cc = 0; - key = false; - } -}; - -// @see: ngx_rtmp_mpegts.c -// TODO: support full mpegts feature in future. -class SrsMpegtsWriter -{ -public: - static int write_header(SrsFileWriter* writer) - { - int ret = ERROR_SUCCESS; - - if ((ret = writer->write(mpegts_header, sizeof(mpegts_header), NULL)) != ERROR_SUCCESS) { - ret = ERROR_HLS_WRITE_FAILED; - srs_error("write ts file header failed. ret=%d", ret); - return ret; - } - - return ret; - } - static int write_frame(SrsFileWriter* writer, SrsMpegtsFrame* frame, SrsBuffer* buffer) - { - int ret = ERROR_SUCCESS; - - if (!buffer->bytes() || buffer->length() <= 0) { - return ret; - } - - char* last = buffer->bytes() + buffer->length(); - char* pos = buffer->bytes(); - - bool first = true; - while (pos < last) { - static char packet[188]; - char* p = packet; - - frame->cc++; - - // sync_byte; //8bits - *p++ = 0x47; - // pid; //13bits - *p++ = (frame->pid >> 8) & 0x1f; - // payload_unit_start_indicator; //1bit - if (first) { - p[-1] |= 0x40; - } - *p++ = frame->pid; - - // transport_scrambling_control; //2bits - // adaption_field_control; //2bits, 0x01: PayloadOnly - // continuity_counter; //4bits - *p++ = 0x10 | (frame->cc & 0x0f); - - if (first) { - first = false; - if (frame->key) { - p[-1] |= 0x20; // Both Adaption and Payload - *p++ = 7; // size - *p++ = 0x50; // random access + PCR - // about the pcr, read https://github.com/winlinvip/simple-rtmp-server/issues/151#issuecomment-71352511 - p = write_pcr(p, frame->dts); - } - - // PES header - // packet_start_code_prefix; //24bits, '00 00 01' - *p++ = 0x00; - *p++ = 0x00; - *p++ = 0x01; - //8bits - *p++ = frame->sid; - - // pts(33bits) need 5bytes. - u_int8_t header_size = 5; - u_int8_t flags = 0x80; // pts - - // dts(33bits) need 5bytes also - if (frame->dts != frame->pts) { - header_size += 5; - flags |= 0x40; // dts - } - - // 3bytes: flag fields from PES_packet_length to PES_header_data_length - int pes_size = (last - pos) + header_size + 3; - if (pes_size > 0xffff) { - /** - * when actual packet length > 0xffff(65535), - * which exceed the max u_int16_t packet length, - * use 0 packet length, the next unit start indicates the end of packet. - */ - pes_size = 0; - } - - // PES_packet_length; //16bits - *p++ = (pes_size >> 8); - *p++ = pes_size; - - // PES_scrambling_control; //2bits, '10' - // PES_priority; //1bit - // data_alignment_indicator; //1bit - // copyright; //1bit - // original_or_copy; //1bit - *p++ = 0x80; /* H222 */ - - // PTS_DTS_flags; //2bits - // ESCR_flag; //1bit - // ES_rate_flag; //1bit - // DSM_trick_mode_flag; //1bit - // additional_copy_info_flag; //1bit - // PES_CRC_flag; //1bit - // PES_extension_flag; //1bit - *p++ = flags; - - // PES_header_data_length; //8bits - *p++ = header_size; - - // pts; // 33bits - p = write_pts(p, flags >> 6, frame->pts + SRS_AUTO_HLS_DELAY); - - // dts; // 33bits - if (frame->dts != frame->pts) { - p = write_pts(p, 1, frame->dts + SRS_AUTO_HLS_DELAY); - } - } - - int body_size = sizeof(packet) - (p - packet); - int in_size = last - pos; - - if (body_size <= in_size) { - memcpy(p, pos, body_size); - pos += body_size; - } else { - p = fill_stuff(p, packet, body_size, in_size); - memcpy(p, pos, in_size); - pos = last; - } - - // write ts packet - if ((ret = writer->write(packet, sizeof(packet), NULL)) != ERROR_SUCCESS) { - ret = ERROR_HLS_WRITE_FAILED; - srs_error("write ts file failed. ret=%d", ret); - return ret; - } - } - - return ret; - } -private: - static char* fill_stuff(char* pes_body_end, char* packet, int body_size, int in_size) - { - char* p = pes_body_end; - - // insert the stuff bytes before PES body - int stuff_size = (body_size - in_size); - - // adaption_field_control; //2bits - if (packet[3] & 0x20) { - // has adaptation - // packet[4]: adaption_field_length - // packet[5]: adaption field data - // base: start of PES body - char* base = &packet[5] + packet[4]; - int len = p - base; - p = (char*)memmove(base + stuff_size, base, len) + len; - // increase the adaption field size. - packet[4] += stuff_size; - - return p; - } - - // create adaption field. - // adaption_field_control; //2bits - packet[3] |= 0x20; - // base: start of PES body - char* base = &packet[4]; - int len = p - base; - p = (char*)memmove(base + stuff_size, base, len) + len; - // adaption_field_length; //8bits - packet[4] = (stuff_size - 1); - if (stuff_size >= 2) { - // adaption field flags. - packet[5] = 0; - // adaption data. - if (stuff_size > 2) { - memset(&packet[6], 0xff, stuff_size - 2); - } - } - - return p; - } - static char* write_pcr(char* p, int64_t pcr) - { - // the pcr=dts-delay, where dts = frame->dts + delay - // and the pcr should never be negative - // @see https://github.com/winlinvip/simple-rtmp-server/issues/268 - srs_assert(pcr >= 0); - - int64_t v = pcr; - - *p++ = (char) (v >> 25); - *p++ = (char) (v >> 17); - *p++ = (char) (v >> 9); - *p++ = (char) (v >> 1); - *p++ = (char) (v << 7 | 0x7e); - *p++ = 0; - - return p; - } - static char* write_pts(char* p, u_int8_t fb, int64_t pts) - { - int32_t val; - - val = fb << 4 | (((pts >> 30) & 0x07) << 1) | 1; - *p++ = val; - - val = (((pts >> 15) & 0x7fff) << 1) | 1; - *p++ = (val >> 8); - *p++ = val; - - val = (((pts) & 0x7fff) << 1) | 1; - *p++ = (val >> 8); - *p++ = val; - - return p; - } -}; +// fragment plus the deviation percent. +#define SRS_HLS_FLOOR_REAP_PERCENT 0.3 +// reset the piece id when deviation overflow this. +#define SRS_JUMP_WHEN_PIECE_DEVIATION 20 -SrsHlsAacJitter::~SrsHlsAacJitter() +ISrsHlsHandler::ISrsHlsHandler() { } -int64_t SrsHlsAacJitter::on_buffer_start(int64_t flv_pts, int sample_rate, int aac_sample_rate) +ISrsHlsHandler::~ISrsHlsHandler() { - // use sample rate in flv/RTMP. - int flv_sample_rate = flv_sample_rates[sample_rate & 0x03]; - - // override the sample rate by sequence header - if (aac_sample_rate != __SRS_AAC_SAMPLE_RATE_UNSET) { - flv_sample_rate = aac_sample_rates[aac_sample_rate]; - } - - // sync time set to 0, donot adjust the aac timestamp. - if (!sync_ms) { - return flv_pts; - } - - // @see: ngx_rtmp_hls_audio - // drop the rtmp audio packet timestamp, re-calc it by sample rate. - // - // resample for the tbn of ts is 90000, flv is 1000, - // we will lost timestamp if use audio packet timestamp, - // so we must resample. or audio will corupt in IOS. - int64_t est_pts = base_pts + nb_samples * 90000LL * _SRS_AAC_SAMPLE_SIZE / flv_sample_rate; - int64_t dpts = (int64_t) (est_pts - flv_pts); - - if (dpts <= (int64_t) sync_ms * 90 && dpts >= (int64_t) sync_ms * -90) { - srs_info("HLS correct aac pts " - "from %"PRId64" to %"PRId64", base=%"PRId64", nb_samples=%d, sample_rate=%d", - flv_pts, est_pts, nb_samples, flv_sample_rate, base_pts); - - nb_samples++; - - return est_pts; - } - - // resync - srs_trace("HLS aac resync, dpts=%"PRId64", pts=%"PRId64 - ", base=%"PRId64", nb_samples=%"PRId64", sample_rate=%d", - dpts, flv_pts, base_pts, nb_samples, flv_sample_rate); - - base_pts = flv_pts; - nb_samples = 1; - - return flv_pts; } -void SrsHlsAacJitter::on_buffer_continue() +SrsHlsCacheWriter::SrsHlsCacheWriter(bool write_cache, bool write_file) { - nb_samples++; + should_write_cache = write_cache; + should_write_file = write_file; } -SrsTSMuxer::SrsTSMuxer() +SrsHlsCacheWriter::~SrsHlsCacheWriter() { - writer = new SrsFileWriter(); } -SrsTSMuxer::~SrsTSMuxer() +int SrsHlsCacheWriter::open(string file) { - close(); - srs_freep(writer); + if (!should_write_file) { + return ERROR_SUCCESS; + } + + return impl.open(file); } -int SrsTSMuxer::open(string _path) +void SrsHlsCacheWriter::close() { - int ret = ERROR_SUCCESS; - - path = _path; - - close(); - - if ((ret = writer->open(path)) != ERROR_SUCCESS) { - return ret; + if (!should_write_file) { + return; } - // write mpegts header - if ((ret = SrsMpegtsWriter::write_header(writer)) != ERROR_SUCCESS) { - return ret; + impl.close(); +} + +bool SrsHlsCacheWriter::is_open() +{ + if (!should_write_file) { + return true; } - - return ret; + + return impl.is_open(); } -int SrsTSMuxer::write_audio(SrsMpegtsFrame* af, SrsBuffer* ab) +int64_t SrsHlsCacheWriter::tellg() { - int ret = ERROR_SUCCESS; - - if ((ret = SrsMpegtsWriter::write_frame(writer, af, ab)) != ERROR_SUCCESS) { - return ret; + if (!should_write_file) { + return 0; } - - return ret; + + return impl.tellg(); } -int SrsTSMuxer::write_video(SrsMpegtsFrame* vf, SrsBuffer* vb) +int SrsHlsCacheWriter::write(void* buf, size_t count, ssize_t* pnwrite) { - int ret = ERROR_SUCCESS; - - if ((ret = SrsMpegtsWriter::write_frame(writer, vf, vb)) != ERROR_SUCCESS) { - return ret; + if (should_write_cache) { + if (count > 0) { + data.append((char*)buf, count); + } } - - return ret; + + if (should_write_file) { + return impl.write(buf, count, pnwrite); + } + + return ERROR_SUCCESS; } -void SrsTSMuxer::close() +string SrsHlsCacheWriter::cache() { - writer->close(); + return data; } -SrsHlsSegment::SrsHlsSegment() +SrsHlsSegment::SrsHlsSegment(SrsTsContext* c, bool write_cache, bool write_file, SrsCodecAudio ac, SrsCodecVideo vc) { duration = 0; sequence_no = 0; - muxer = new SrsTSMuxer(); segment_start_dts = 0; is_sequence_header = false; + writer = new SrsHlsCacheWriter(write_cache, write_file); + muxer = new SrsTSMuxer(writer, c, ac, vc); } SrsHlsSegment::~SrsHlsSegment() { srs_freep(muxer); + srs_freep(writer); } void SrsHlsSegment::update_duration(int64_t current_frame_dts) @@ -548,20 +170,122 @@ void SrsHlsSegment::update_duration(int64_t current_frame_dts) return; } -SrsHlsAacJitter::SrsHlsAacJitter() +SrsDvrAsyncCallOnHls::SrsDvrAsyncCallOnHls(SrsRequest* r, string p, string t, string m, string mu, int s, double d) +{ + req = r; + path = p; + ts_url = t; + m3u8 = m; + m3u8_url = mu; + seq_no = s; + duration = d; +} + +SrsDvrAsyncCallOnHls::~SrsDvrAsyncCallOnHls() +{ +} + +int SrsDvrAsyncCallOnHls::call() +{ + int ret = ERROR_SUCCESS; + +#ifdef SRS_AUTO_HTTP_CALLBACK + // http callback for on_hls in config. + if (_srs_config->get_vhost_http_hooks_enabled(req->vhost)) { + // HTTP: on_hls + SrsConfDirective* on_hls = _srs_config->get_vhost_on_hls(req->vhost); + if (!on_hls) { + srs_info("ignore the empty http callback: on_hls"); + return ret; + } + + std::string file = path; + int sn = seq_no; + for (int i = 0; i < (int)on_hls->args.size(); i++) { + std::string url = on_hls->args.at(i); + if ((ret = SrsHttpHooks::on_hls(url, req, file, ts_url, m3u8, m3u8_url, sn, duration)) != ERROR_SUCCESS) { + srs_error("hook client on_hls failed. url=%s, ret=%d", url.c_str(), ret); + return ret; + } + } + } +#endif + + return ret; +} + +string SrsDvrAsyncCallOnHls::to_string() +{ + return "on_hls: " + path; +} + +SrsDvrAsyncCallOnHlsNotify::SrsDvrAsyncCallOnHlsNotify(SrsRequest* r, string u) { - base_pts = 0; - nb_samples = 0; + req = r; + ts_url = u; +} - // TODO: config it, 0 means no adjust - sync_ms = SRS_CONF_DEFAULT_AAC_SYNC; +SrsDvrAsyncCallOnHlsNotify::~SrsDvrAsyncCallOnHlsNotify() +{ +} + +int SrsDvrAsyncCallOnHlsNotify::call() +{ + int ret = ERROR_SUCCESS; + +#ifdef SRS_AUTO_HTTP_CALLBACK + // http callback for on_hls_notify in config. + if (_srs_config->get_vhost_http_hooks_enabled(req->vhost)) { + // HTTP: on_hls + SrsConfDirective* on_hls = _srs_config->get_vhost_on_hls_notify(req->vhost); + if (!on_hls) { + srs_info("ignore the empty http callback: on_hls_notify"); + return ret; + } + + std::string url; + if (true) { + static u_int32_t nb_call = 0; + int index = nb_call++ % on_hls->args.size(); + url = on_hls->args.at(index); + } + + int nb_notify = _srs_config->get_vhost_hls_nb_notify(req->vhost); + if ((ret = SrsHttpHooks::on_hls_notify(url, req, ts_url, nb_notify)) != ERROR_SUCCESS) { + srs_error("hook client on_hls_notify failed. url=%s, ts=%s, ret=%d", url.c_str(), ts_url.c_str(), ret); + return ret; + } + } +#endif + + return ret; +} + +string SrsDvrAsyncCallOnHlsNotify::to_string() +{ + return "on_hls_notify: " + ts_url; } SrsHlsMuxer::SrsHlsMuxer() { + req = NULL; + handler = NULL; hls_fragment = hls_window = 0; + hls_aof_ratio = 1.0; + deviation_ts = 0; + hls_cleanup = true; + hls_wait_keyframe = true; + previous_floor_ts = 0; + accept_floor_ts = 0; + hls_ts_floor = false; + target_duration = 0; _sequence_no = 0; current = NULL; + acodec = SrsCodecAudioReserved1; + should_write_cache = false; + should_write_file = true; + async = new SrsDvrAsyncCallThread(); + context = new SrsTsContext(); } SrsHlsMuxer::~SrsHlsMuxer() @@ -574,6 +298,9 @@ SrsHlsMuxer::~SrsHlsMuxer() segments.clear(); srs_freep(current); + srs_freep(req); + srs_freep(async); + srs_freep(context); } int SrsHlsMuxer::sequence_no() @@ -581,16 +308,90 @@ int SrsHlsMuxer::sequence_no() return _sequence_no; } -int SrsHlsMuxer::update_config( - string _app, string _stream, string path, int fragment, int window +string SrsHlsMuxer::ts_url() +{ + return current? current->uri:""; +} + +double SrsHlsMuxer::duration() +{ + return current? current->duration:0; +} + +int SrsHlsMuxer::deviation() +{ + // no floor, no deviation. + if (!hls_ts_floor) { + return 0; + } + + return deviation_ts; +} + +int SrsHlsMuxer::initialize(ISrsHlsHandler* h) +{ + int ret = ERROR_SUCCESS; + + handler = h; + + if ((ret = async->start()) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +int SrsHlsMuxer::update_config(SrsRequest* r, string entry_prefix, + string path, string m3u8_file, string ts_file, double fragment, double window, + bool ts_floor, double aof_ratio, bool cleanup, bool wait_keyframe ) { int ret = ERROR_SUCCESS; - app = _app; - stream = _stream; + srs_freep(req); + req = r->copy(); + + hls_entry_prefix = entry_prefix; hls_path = path; + hls_ts_file = ts_file; hls_fragment = fragment; + hls_aof_ratio = aof_ratio; + hls_ts_floor = ts_floor; + hls_cleanup = cleanup; + hls_wait_keyframe = wait_keyframe; + previous_floor_ts = 0; + accept_floor_ts = 0; hls_window = window; + deviation_ts = 0; + + // generate the m3u8 dir and path. + m3u8_url = srs_path_build_stream(m3u8_file, req->vhost, req->app, req->stream); + m3u8 = path + "/" + m3u8_url; + + // we always keep the target duration increasing. + int max_td = srs_max(target_duration, (int)(fragment * _srs_config->get_hls_td_ratio(r->vhost))); + srs_info("hls update target duration %d=>%d, aof=%.2f", target_duration, max_td, aof_ratio); + target_duration = max_td; + + std::string storage = _srs_config->get_hls_storage(r->vhost); + if (storage == "ram") { + should_write_cache = true; + should_write_file = false; + } else if (storage == "disk") { + should_write_cache = false; + should_write_file = true; + } else { + srs_assert(storage == "both"); + should_write_cache = true; + should_write_file = true; + } + + // create m3u8 dir once. + m3u8_dir = srs_path_dirname(m3u8); + if (should_write_file && (ret = srs_create_dir_recursively(m3u8_dir)) != ERROR_SUCCESS) { + srs_error("create app dir %s failed. ret=%d", m3u8_dir.c_str(), ret); + return ret; + } + srs_info("create m3u8 dir %s ok", m3u8_dir.c_str()); return ret; } @@ -604,42 +405,125 @@ int SrsHlsMuxer::segment_open(int64_t segment_start_dts) return ret; } - // TODO: create all parents dirs. - // create dir for app. - if ((ret = create_dir()) != ERROR_SUCCESS) { - return ret; - } - // when segment open, the current segment must be NULL. srs_assert(!current); + + // load the default acodec from config. + SrsCodecAudio default_acodec = SrsCodecAudioAAC; + if (true) { + std::string default_acodec_str = _srs_config->get_hls_acodec(req->vhost); + if (default_acodec_str == "mp3") { + default_acodec = SrsCodecAudioMP3; + srs_info("hls: use default mp3 acodec"); + } else if (default_acodec_str == "aac") { + default_acodec = SrsCodecAudioAAC; + srs_info("hls: use default aac acodec"); + } else { + srs_warn("hls: use aac for other codec=%s", default_acodec_str.c_str()); + } + } + + // load the default vcodec from config. + SrsCodecVideo default_vcodec = SrsCodecVideoAVC; + if (true) { + std::string default_vcodec_str = _srs_config->get_hls_vcodec(req->vhost); + if (default_vcodec_str == "h264") { + default_vcodec = SrsCodecVideoAVC; + srs_info("hls: use default h264 vcodec"); + } else if (default_vcodec_str == "vn") { + default_vcodec = SrsCodecVideoDisabled; + srs_info("hls: use default vn vcodec for pure audio"); + } else { + srs_warn("hls: use h264 for other codec=%s", default_vcodec_str.c_str()); + } + } // new segment. - current = new SrsHlsSegment(); + current = new SrsHlsSegment(context, should_write_cache, should_write_file, default_acodec, default_vcodec); current->sequence_no = _sequence_no++; current->segment_start_dts = segment_start_dts; // generate filename. - char filename[128]; - snprintf(filename, sizeof(filename), - "%s-%d.ts", stream.c_str(), current->sequence_no); + std::string ts_file = hls_ts_file; + ts_file = srs_path_build_stream(ts_file, req->vhost, req->app, req->stream); + if (hls_ts_floor) { + // accept the floor ts for the first piece. + int64_t current_floor_ts = (int64_t)(srs_update_system_time_ms() / (1000 * hls_fragment)); + if (!accept_floor_ts) { + accept_floor_ts = current_floor_ts - 1; + } else { + accept_floor_ts++; + } + + // jump when deviation more than 10p + if (accept_floor_ts - current_floor_ts > SRS_JUMP_WHEN_PIECE_DEVIATION) { + srs_warn("hls: jmp for ts deviation, current=%"PRId64", accept=%"PRId64, current_floor_ts, accept_floor_ts); + accept_floor_ts = current_floor_ts - 1; + } + + // when reap ts, adjust the deviation. + deviation_ts = (int)(accept_floor_ts - current_floor_ts); + + // dup/jmp detect for ts in floor mode. + if (previous_floor_ts && previous_floor_ts != current_floor_ts - 1) { + srs_warn("hls: dup/jmp ts, previous=%"PRId64", current=%"PRId64", accept=%"PRId64", deviation=%d", + previous_floor_ts, current_floor_ts, accept_floor_ts, deviation_ts); + } + previous_floor_ts = current_floor_ts; + + // we always ensure the piece is increase one by one. + std::stringstream ts_floor; + ts_floor << accept_floor_ts; + ts_file = srs_string_replace(ts_file, "[timestamp]", ts_floor.str()); + + // TODO: FIMXE: we must use the accept ts floor time to generate the hour variable. + ts_file = srs_path_build_timestamp(ts_file); + } else { + ts_file = srs_path_build_timestamp(ts_file); + } + if (true) { + std::stringstream ss; + ss << current->sequence_no; + ts_file = srs_string_replace(ts_file, "[seq]", ss.str()); + } + current->full_path = hls_path + "/" + ts_file; + srs_info("hls: generate ts path %s, tmpl=%s, floor=%d", ts_file.c_str(), hls_ts_file.c_str(), hls_ts_floor); - // TODO: use temp file and rename it. - current->full_path = hls_path; - current->full_path += "/"; - current->full_path += app; - current->full_path += "/"; - current->full_path += filename; + // the ts url, relative or absolute url. + std::string ts_url = current->full_path; + if (srs_string_starts_with(ts_url, m3u8_dir)) { + ts_url = ts_url.substr(m3u8_dir.length()); + } + while (srs_string_starts_with(ts_url, "/")) { + ts_url = ts_url.substr(1); + } + current->uri += hls_entry_prefix; + if (!hls_entry_prefix.empty() && !srs_string_ends_with(hls_entry_prefix, "/")) { + current->uri += "/"; + } + current->uri += ts_url; - // TODO: support base url, and so on. - current->uri = filename; + // create dir recursively for hls. + std::string ts_dir = srs_path_dirname(current->full_path); + if (should_write_file && (ret = srs_create_dir_recursively(ts_dir)) != ERROR_SUCCESS) { + srs_error("create app dir %s failed. ret=%d", ts_dir.c_str(), ret); + return ret; + } + srs_info("create ts dir %s ok", ts_dir.c_str()); + // open temp ts file. std::string tmp_file = current->full_path + ".tmp"; if ((ret = current->muxer->open(tmp_file.c_str())) != ERROR_SUCCESS) { srs_error("open hls muxer failed. ret=%d", ret); return ret; } - srs_info("open HLS muxer success. path=%s, tmp=%s", + srs_info("open HLS muxer success. path=%s, tmp=%s", current->full_path.c_str(), tmp_file.c_str()); + + // set the segment muxer audio codec. + if (acodec != SrsCodecAudioReserved1) { + current->muxer->update_acodec(acodec); + } return ret; } @@ -660,16 +544,52 @@ int SrsHlsMuxer::on_sequence_header() bool SrsHlsMuxer::is_segment_overflow() { srs_assert(current); - return current->duration >= hls_fragment; + + // to prevent very small segment. + if (current->duration * 1000 < 2 * SRS_AUTO_HLS_SEGMENT_MIN_DURATION_MS) { + return false; + } + + // use N% deviation, to smoother. + double deviation = hls_ts_floor? SRS_HLS_FLOOR_REAP_PERCENT * deviation_ts * hls_fragment : 0.0; + srs_info("hls: dur=%.2f, tar=%.2f, dev=%.2fms/%dp, frag=%.2f", + current->duration, hls_fragment + deviation, deviation, deviation_ts, hls_fragment); + + return current->duration >= hls_fragment + deviation; +} + +bool SrsHlsMuxer::wait_keyframe() +{ + return hls_wait_keyframe; } bool SrsHlsMuxer::is_segment_absolutely_overflow() +{ + // @see https://github.com/simple-rtmp-server/srs/issues/151#issuecomment-83553950 + srs_assert(current); + + // to prevent very small segment. + if (current->duration * 1000 < 2 * SRS_AUTO_HLS_SEGMENT_MIN_DURATION_MS) { + return false; + } + + // use N% deviation, to smoother. + double deviation = hls_ts_floor? SRS_HLS_FLOOR_REAP_PERCENT * deviation_ts * hls_fragment : 0.0; + srs_info("hls: dur=%.2f, tar=%.2f, dev=%.2fms/%dp, frag=%.2f", + current->duration, hls_fragment + deviation, deviation, deviation_ts, hls_fragment); + + return current->duration >= hls_aof_ratio * hls_fragment + deviation; +} + +int SrsHlsMuxer::update_acodec(SrsCodecAudio ac) { srs_assert(current); - return current->duration >= 2 * hls_fragment; + srs_assert(current->muxer); + acodec = ac; + return current->muxer->update_acodec(ac); } -int SrsHlsMuxer::flush_audio(SrsMpegtsFrame* af, SrsBuffer* ab) +int SrsHlsMuxer::flush_audio(SrsTsCache* cache) { int ret = ERROR_SUCCESS; @@ -679,24 +599,24 @@ int SrsHlsMuxer::flush_audio(SrsMpegtsFrame* af, SrsBuffer* ab) return ret; } - if (ab->length() <= 0) { + if (!cache->audio || cache->audio->payload->length() <= 0) { return ret; } // update the duration of segment. - current->update_duration(af->pts); + current->update_duration(cache->audio->pts); - if ((ret = current->muxer->write_audio(af, ab)) != ERROR_SUCCESS) { + if ((ret = current->muxer->write_audio(cache->audio)) != ERROR_SUCCESS) { return ret; } - // write success, clear and free the buffer - ab->erase(ab->length()); + // write success, clear and free the msg + srs_freep(cache->audio); return ret; } -int SrsHlsMuxer::flush_video(SrsMpegtsFrame* /*af*/, SrsBuffer* /*ab*/, SrsMpegtsFrame* vf, SrsBuffer* vb) +int SrsHlsMuxer::flush_video(SrsTsCache* cache) { int ret = ERROR_SUCCESS; @@ -706,17 +626,21 @@ int SrsHlsMuxer::flush_video(SrsMpegtsFrame* /*af*/, SrsBuffer* /*ab*/, SrsMpegt return ret; } + if (!cache->video || cache->video->payload->length() <= 0) { + return ret; + } + srs_assert(current); // update the duration of segment. - current->update_duration(vf->dts); + current->update_duration(cache->video->dts); - if ((ret = current->muxer->write_video(vf, vb)) != ERROR_SUCCESS) { + if ((ret = current->muxer->write_video(cache->video)) != ERROR_SUCCESS) { return ret; } - // write success, clear and free the buffer - vb->erase(vb->length()); + // write success, clear and free the msg + srs_freep(cache->video); return ret; } @@ -741,10 +665,30 @@ int SrsHlsMuxer::segment_close(string log_desc) // valid, add to segments if segment duration is ok if (current->duration * 1000 >= SRS_AUTO_HLS_SEGMENT_MIN_DURATION_MS) { segments.push_back(current); + + // use async to call the http hooks, for it will cause thread switch. + if ((ret = async->call(new SrsDvrAsyncCallOnHls(req, + current->full_path, current->uri, m3u8, m3u8_url, + current->sequence_no, current->duration))) != ERROR_SUCCESS) + { + return ret; + } + + // use async to call the http hooks, for it will cause thread switch. + if ((ret = async->call(new SrsDvrAsyncCallOnHlsNotify(req, current->uri))) != ERROR_SUCCESS) { + return ret; + } - srs_info("%s reap ts segment, sequence_no=%d, uri=%s, duration=%.2f, start=%"PRId64"", + srs_info("%s reap ts segment, sequence_no=%d, uri=%s, duration=%.2f, start=%"PRId64, log_desc.c_str(), current->sequence_no, current->uri.c_str(), current->duration, current->segment_start_dts); + + // notify handler for update ts. + srs_assert(current->writer); + if (handler && (ret = handler->on_update_ts(req, current->uri, current->writer->cache())) != ERROR_SUCCESS) { + srs_error("notify handler for update ts failed. ret=%d", ret); + return ret; + } // close the muxer of finished segment. srs_freep(current->muxer); @@ -753,7 +697,7 @@ int SrsHlsMuxer::segment_close(string log_desc) // rename from tmp to real path std::string tmp_file = full_path + ".tmp"; - if (rename(tmp_file.c_str(), full_path.c_str()) < 0) { + if (should_write_file && rename(tmp_file.c_str(), full_path.c_str()) < 0) { ret = ERROR_HLS_WRITE_FAILED; srs_error("rename ts file failed, %s => %s. ret=%d", tmp_file.c_str(), full_path.c_str(), ret); @@ -769,7 +713,9 @@ int SrsHlsMuxer::segment_close(string log_desc) // rename from tmp to real path std::string tmp_file = current->full_path + ".tmp"; - unlink(tmp_file.c_str()); + if (should_write_file) { + unlink(tmp_file.c_str()); + } srs_freep(current); } @@ -780,7 +726,7 @@ int SrsHlsMuxer::segment_close(string log_desc) // shrink the segments. double duration = 0; int remove_index = -1; - for (int i = segments.size() - 1; i >= 0; i--) { + for (int i = (int)segments.size() - 1; i >= 0; i--) { SrsHlsSegment* segment = segments[i]; duration += segment->duration; @@ -801,7 +747,11 @@ int SrsHlsMuxer::segment_close(string log_desc) // remove the ts file. for (int i = 0; i < (int)segment_to_remove.size(); i++) { SrsHlsSegment* segment = segment_to_remove[i]; - unlink(segment->full_path.c_str()); + + if (hls_cleanup) { + unlink(segment->full_path.c_str()); + } + srs_freep(segment); } segment_to_remove.clear(); @@ -819,34 +769,21 @@ int SrsHlsMuxer::refresh_m3u8() { int ret = ERROR_SUCCESS; - std::string m3u8_file = hls_path; - m3u8_file += "/"; - m3u8_file += app; - m3u8_file += "/"; - m3u8_file += stream; - m3u8_file += ".m3u8"; - - m3u8 = m3u8_file; - m3u8_file += ".temp"; - - int fd = -1; - ret = _refresh_m3u8(fd, m3u8_file); - if (fd >= 0) { - close(fd); - if (rename(m3u8_file.c_str(), m3u8.c_str()) < 0) { + std::string temp_m3u8 = m3u8 + ".temp"; + if ((ret = _refresh_m3u8(temp_m3u8)) == ERROR_SUCCESS) { + if (should_write_file && rename(temp_m3u8.c_str(), m3u8.c_str()) < 0) { ret = ERROR_HLS_WRITE_FAILED; - srs_error("rename m3u8 file failed. " - "%s => %s, ret=%d", m3u8_file.c_str(), m3u8.c_str(), ret); + srs_error("rename m3u8 file failed. %s => %s, ret=%d", temp_m3u8.c_str(), m3u8.c_str(), ret); } } // remove the temp file. - unlink(m3u8_file.c_str()); + unlink(temp_m3u8.c_str()); return ret; } -int SrsHlsMuxer::_refresh_m3u8(int& fd, string m3u8_file) +int SrsHlsMuxer::_refresh_m3u8(string m3u8_file) { int ret = ERROR_SUCCESS; @@ -854,61 +791,44 @@ int SrsHlsMuxer::_refresh_m3u8(int& fd, string m3u8_file) if (segments.size() == 0) { return ret; } - - int flags = O_CREAT|O_WRONLY|O_TRUNC; - mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH; - if ((fd = ::open(m3u8_file.c_str(), flags, mode)) < 0) { - ret = ERROR_HLS_OPEN_FAILED; + + SrsHlsCacheWriter writer(should_write_cache, should_write_file); + if ((ret = writer.open(m3u8_file)) != ERROR_SUCCESS) { srs_error("open m3u8 file %s failed. ret=%d", m3u8_file.c_str(), ret); return ret; } srs_info("open m3u8 file %s success.", m3u8_file.c_str()); - // #EXTM3U\n#EXT-X-VERSION:3\n - char header[] = { - // #EXTM3U\n - 0x23, 0x45, 0x58, 0x54, 0x4d, 0x33, 0x55, 0xa, - // #EXT-X-VERSION:3\n - 0x23, 0x45, 0x58, 0x54, 0x2d, 0x58, 0x2d, 0x56, 0x45, 0x52, - 0x53, 0x49, 0x4f, 0x4e, 0x3a, 0x33, 0xa, - // #EXT-X-ALLOW-CACHE:NO - 0x23, 0x45, 0x58, 0x54, 0x2d, 0x58, 0x2d, 0x41, 0x4c, 0x4c, - 0x4f, 0x57, 0x2d, 0x43, 0x41, 0x43, 0x48, 0x45, 0x3a, 0x4e, 0x4f, 0x0a - }; - if (::write(fd, header, sizeof(header)) != sizeof(header)) { - ret = ERROR_HLS_WRITE_FAILED; - srs_error("write m3u8 header failed. ret=%d", ret); - return ret; - } + // #EXTM3U\n + // #EXT-X-VERSION:3\n + // #EXT-X-ALLOW-CACHE:YES\n + std::stringstream ss; + ss << "#EXTM3U" << SRS_CONSTS_LF + << "#EXT-X-VERSION:3" << SRS_CONSTS_LF + << "#EXT-X-ALLOW-CACHE:YES" << SRS_CONSTS_LF; srs_verbose("write m3u8 header success."); // #EXT-X-MEDIA-SEQUENCE:4294967295\n SrsHlsSegment* first = *segments.begin(); - char sequence[34] = {}; - int len = snprintf(sequence, sizeof(sequence), "#EXT-X-MEDIA-SEQUENCE:%d\n", first->sequence_no); - if (::write(fd, sequence, len) != len) { - ret = ERROR_HLS_WRITE_FAILED; - srs_error("write m3u8 sequence failed. ret=%d", ret); - return ret; - } + ss << "#EXT-X-MEDIA-SEQUENCE:" << first->sequence_no << SRS_CONSTS_LF; srs_verbose("write m3u8 sequence success."); // #EXT-X-TARGETDURATION:4294967295\n - int target_duration = 0; + /** + * @see hls-m3u8-draft-pantos-http-live-streaming-12.pdf, page 25 + * The Media Playlist file MUST contain an EXT-X-TARGETDURATION tag. + * Its value MUST be equal to or greater than the EXTINF duration of any + * media segment that appears or will appear in the Playlist file, + * rounded to the nearest integer. Its value MUST NOT change. A + * typical target duration is 10 seconds. + */ + // @see https://github.com/simple-rtmp-server/srs/issues/304#issuecomment-74000081 std::vector::iterator it; for (it = segments.begin(); it != segments.end(); ++it) { SrsHlsSegment* segment = *it; - target_duration = srs_max(target_duration, (int)segment->duration); - } - // TODO: maybe need to take an around value - target_duration += 1; - char duration[34]; // 23+10+1 - len = snprintf(duration, sizeof(duration), "#EXT-X-TARGETDURATION:%d\n", target_duration); - if (::write(fd, duration, len) != len) { - ret = ERROR_HLS_WRITE_FAILED; - srs_error("write m3u8 duration failed. ret=%d", ret); - return ret; + target_duration = srs_max(target_duration, (int)ceil(segment->duration)); } + ss << "#EXT-X-TARGETDURATION:" << target_duration << SRS_CONSTS_LF; srs_verbose("write m3u8 duration success."); // write all segments @@ -917,87 +837,46 @@ int SrsHlsMuxer::_refresh_m3u8(int& fd, string m3u8_file) if (segment->is_sequence_header) { // #EXT-X-DISCONTINUITY\n - char ext_discon[22]; // 21+1 - len = snprintf(ext_discon, sizeof(ext_discon), "#EXT-X-DISCONTINUITY\n"); - if (::write(fd, ext_discon, len) != len) { - ret = ERROR_HLS_WRITE_FAILED; - srs_error("write m3u8 segment discontinuity failed. ret=%d", ret); - return ret; - } + ss << "#EXT-X-DISCONTINUITY" << SRS_CONSTS_LF; srs_verbose("write m3u8 segment discontinuity success."); } // "#EXTINF:4294967295.208,\n" - char ext_info[25]; // 14+10+1 - len = snprintf(ext_info, sizeof(ext_info), "#EXTINF:%.3f\n", segment->duration); - if (::write(fd, ext_info, len) != len) { - ret = ERROR_HLS_WRITE_FAILED; - srs_error("write m3u8 segment info failed. ret=%d", ret); - return ret; - } + ss.precision(3); + ss.setf(std::ios::fixed, std::ios::floatfield); + ss << "#EXTINF:" << segment->duration << ", no desc" << SRS_CONSTS_LF; srs_verbose("write m3u8 segment info success."); - // file name - std::string filename = segment->uri; - filename += "\n"; - if (::write(fd, filename.c_str(), filename.length()) != (int)filename.length()) { - ret = ERROR_HLS_WRITE_FAILED; - srs_error("write m3u8 segment uri failed. ret=%d", ret); - return ret; - } + // {file name}\n + ss << segment->uri << SRS_CONSTS_LF; srs_verbose("write m3u8 segment uri success."); } - srs_info("write m3u8 %s success.", m3u8_file.c_str()); - - return ret; -} - -int SrsHlsMuxer::create_dir() -{ - int ret = ERROR_SUCCESS; - - std::string app_dir = hls_path; - app_dir += "/"; - app_dir += app; - - // TODO: cleanup the dir when startup. - mode_t mode = S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IXOTH; - if (::mkdir(app_dir.c_str(), mode) < 0) { - if (errno != EEXIST) { - ret = ERROR_HLS_CREATE_DIR; - srs_error("create app dir %s failed. ret=%d", app_dir.c_str(), ret); - return ret; - } + // write m3u8 to writer. + std::string m3u8 = ss.str(); + if ((ret = writer.write((char*)m3u8.c_str(), (int)m3u8.length(), NULL)) != ERROR_SUCCESS) { + srs_error("write m3u8 failed. ret=%d", ret); + return ret; } - srs_info("create app dir %s success.", app_dir.c_str()); + srs_info("write m3u8 %s success.", m3u8_file.c_str()); + // notify handler for update m3u8. + if (handler && (ret = handler->on_update_m3u8(req, writer.cache())) != ERROR_SUCCESS) { + srs_error("notify handler for update m3u8 failed. ret=%d", ret); + return ret; + } + return ret; } SrsHlsCache::SrsHlsCache() { - aac_jitter = new SrsHlsAacJitter(); - - ab = new SrsBuffer(); - vb = new SrsBuffer(); - - af = new SrsMpegtsFrame(); - vf = new SrsMpegtsFrame(); + cache = new SrsTsCache(); } SrsHlsCache::~SrsHlsCache() { - srs_freep(aac_jitter); - - ab->erase(ab->length()); - vb->erase(vb->length()); - - srs_freep(ab); - srs_freep(vb); - - srs_freep(af); - srs_freep(vf); + srs_freep(cache); } int SrsHlsCache::on_publish(SrsHlsMuxer* muxer, SrsRequest* req, int64_t segment_start_dts) @@ -1008,17 +887,30 @@ int SrsHlsCache::on_publish(SrsHlsMuxer* muxer, SrsRequest* req, int64_t segment std::string stream = req->stream; std::string app = req->app; - int hls_fragment = (int)_srs_config->get_hls_fragment(vhost); - int hls_window = (int)_srs_config->get_hls_window(vhost); + double hls_fragment = _srs_config->get_hls_fragment(vhost); + double hls_window = _srs_config->get_hls_window(vhost); + // get the hls m3u8 ts list entry prefix config + std::string entry_prefix = _srs_config->get_hls_entry_prefix(vhost); // get the hls path config - std::string hls_path = _srs_config->get_hls_path(vhost); + std::string path = _srs_config->get_hls_path(vhost); + std::string m3u8_file = _srs_config->get_hls_m3u8_file(vhost); + std::string ts_file = _srs_config->get_hls_ts_file(vhost); + bool cleanup = _srs_config->get_hls_cleanup(vhost); + bool wait_keyframe = _srs_config->get_hls_wait_keyframe(vhost); + // the audio overflow, for pure audio to reap segment. + double hls_aof_ratio = _srs_config->get_hls_aof_ratio(vhost); + // whether use floor(timestamp/hls_fragment) for variable timestamp + bool ts_floor = _srs_config->get_hls_ts_floor(vhost); // TODO: FIXME: support load exists m3u8, to continue publish stream. // for the HLS donot requires the EXT-X-MEDIA-SEQUENCE be monotonically increase. // open muxer - if ((ret = muxer->update_config(app, stream, hls_path, hls_fragment, hls_window)) != ERROR_SUCCESS) { + if ((ret = muxer->update_config(req, entry_prefix, + path, m3u8_file, ts_file, hls_fragment, hls_window, ts_floor, hls_aof_ratio, + cleanup, wait_keyframe)) != ERROR_SUCCESS + ) { srs_error("m3u8 muxer update config failed. ret=%d", ret); return ret; } @@ -1027,6 +919,9 @@ int SrsHlsCache::on_publish(SrsHlsMuxer* muxer, SrsRequest* req, int64_t segment srs_error("m3u8 muxer open segment failed. ret=%d", ret); return ret; } + srs_trace("hls: win=%.2f, frag=%.2f, prefix=%s, path=%s, m3u8=%s, ts=%s, aof=%.2f, floor=%d, clean=%d, waitk=%d", + hls_window, hls_fragment, entry_prefix.c_str(), path.c_str(), m3u8_file.c_str(), + ts_file.c_str(), hls_aof_ratio, ts_floor, cleanup, wait_keyframe); return ret; } @@ -1035,7 +930,7 @@ int SrsHlsCache::on_unpublish(SrsHlsMuxer* muxer) { int ret = ERROR_SUCCESS; - if ((ret = muxer->flush_audio(af, ab)) != ERROR_SUCCESS) { + if ((ret = muxer->flush_audio(cache)) != ERROR_SUCCESS) { srs_error("m3u8 muxer flush audio failed. ret=%d", ret); return ret; } @@ -1062,34 +957,26 @@ int SrsHlsCache::write_audio(SrsAvcAacCodec* codec, SrsHlsMuxer* muxer, int64_t { int ret = ERROR_SUCCESS; - // start buffer, set the af - if (ab->length() == 0) { - pts = aac_jitter->on_buffer_start(pts, sample->sound_rate, codec->aac_sample_rate); - - af->dts = af->pts = audio_buffer_start_pts = pts; - af->pid = TS_AUDIO_PID; - af->sid = TS_AUDIO_AAC; - } else { - aac_jitter->on_buffer_continue(); - } - // write audio to cache. - if ((ret = cache_audio(codec, sample)) != ERROR_SUCCESS) { + if ((ret = cache->cache_audio(codec, pts, sample)) != ERROR_SUCCESS) { return ret; } // flush if buffer exceed max size. - if (ab->length() > SRS_AUTO_HLS_AUDIO_CACHE_SIZE) { - if ((ret = muxer->flush_audio(af, ab)) != ERROR_SUCCESS) { + if (cache->audio->payload->length() > SRS_AUTO_HLS_AUDIO_CACHE_SIZE) { + if ((ret = muxer->flush_audio(cache)) != ERROR_SUCCESS) { return ret; } } + // TODO: config it. // in ms, audio delay to flush the audios. int64_t audio_delay = SRS_CONF_DEFAULT_AAC_DELAY; // flush if audio delay exceed - if (pts - audio_buffer_start_pts > audio_delay * 90) { - if ((ret = muxer->flush_audio(af, ab)) != ERROR_SUCCESS) { + // cache->audio will be free in flush_audio + // so we must check whether it's null ptr. + if (cache->audio && pts - cache->audio->start_pts > audio_delay * 90) { + if ((ret = muxer->flush_audio(cache)) != ERROR_SUCCESS) { return ret; } } @@ -1099,11 +986,12 @@ int SrsHlsCache::write_audio(SrsAvcAacCodec* codec, SrsHlsMuxer* muxer, int64_t // for example, pure audio when start, audio/video when publishing, // pure audio again for audio disabled. // so we reap event when the audio incoming when segment overflow. - // @see https://github.com/winlinvip/simple-rtmp-server/issues/151 + // @see https://github.com/simple-rtmp-server/srs/issues/151 // we use absolutely overflow of segment to make jwplayer/ffplay happy - // @see https://github.com/winlinvip/simple-rtmp-server/issues/151#issuecomment-71155184 - if (muxer->is_segment_absolutely_overflow()) { - if ((ret = reap_segment("audio", muxer, af->pts)) != ERROR_SUCCESS) { + // @see https://github.com/simple-rtmp-server/srs/issues/151#issuecomment-71155184 + if (cache->audio && muxer->is_segment_absolutely_overflow()) { + srs_info("hls: absolute audio reap segment."); + if ((ret = reap_segment("audio", muxer, cache->audio->pts)) != ERROR_SUCCESS) { return ret; } } @@ -1111,33 +999,38 @@ int SrsHlsCache::write_audio(SrsAvcAacCodec* codec, SrsHlsMuxer* muxer, int64_t return ret; } -int SrsHlsCache::write_video( - SrsAvcAacCodec* codec, SrsHlsMuxer* muxer, int64_t dts, SrsCodecSample* sample) +int SrsHlsCache::write_video(SrsAvcAacCodec* codec, SrsHlsMuxer* muxer, int64_t dts, SrsCodecSample* sample) { int ret = ERROR_SUCCESS; // write video to cache. - if ((ret = cache_video(codec, sample)) != ERROR_SUCCESS) { + if ((ret = cache->cache_video(codec, dts, sample)) != ERROR_SUCCESS) { return ret; } - vf->dts = dts; - vf->pts = vf->dts + sample->cts * 90; - vf->pid = TS_VIDEO_PID; - vf->sid = TS_VIDEO_AVC; - vf->key = sample->frame_type == SrsCodecVideoAVCFrameKeyFrame; - - // new segment when: - // 1. base on gop. - // 2. some gops duration overflow. - if (vf->key && muxer->is_segment_overflow()) { - if ((ret = reap_segment("video", muxer, vf->dts)) != ERROR_SUCCESS) { + // when segment overflow, reap if possible. + if (muxer->is_segment_overflow()) { + // do reap ts if any of: + // a. wait keyframe and got keyframe. + // b. always reap when not wait keyframe. + if (!muxer->wait_keyframe()|| sample->frame_type == SrsCodecVideoAVCFrameKeyFrame) { + // when wait keyframe, there must exists idr frame in sample. + if (!sample->has_idr && muxer->wait_keyframe()) { + srs_warn("hls: ts starts without IDR, first nalu=%d, idr=%d", sample->first_nalu_type, sample->has_idr); + } + + // reap the segment, which will also flush the video. + if ((ret = reap_segment("video", muxer, cache->video->dts)) != ERROR_SUCCESS) { + return ret; + } + + // the video must be flushed, just return. return ret; } } // flush video when got one - if ((ret = muxer->flush_video(af, ab, vf, vb)) != ERROR_SUCCESS) { + if ((ret = muxer->flush_video(cache)) != ERROR_SUCCESS) { srs_error("m3u8 muxer flush video failed. ret=%d", ret); return ret; } @@ -1148,22 +1041,32 @@ int SrsHlsCache::write_video( int SrsHlsCache::reap_segment(string log_desc, SrsHlsMuxer* muxer, int64_t segment_start_dts) { int ret = ERROR_SUCCESS; + + // TODO: flush audio before or after segment? + // TODO: fresh segment begin with audio or video? + // close current ts. if ((ret = muxer->segment_close(log_desc)) != ERROR_SUCCESS) { srs_error("m3u8 muxer close segment failed. ret=%d", ret); return ret; } + // open new ts. if ((ret = muxer->segment_open(segment_start_dts)) != ERROR_SUCCESS) { srs_error("m3u8 muxer open segment failed. ret=%d", ret); return ret; } - - // TODO: flush audio before or after segment? + + // segment open, flush video first. + if ((ret = muxer->flush_video(cache)) != ERROR_SUCCESS) { + srs_error("m3u8 muxer flush video failed. ret=%d", ret); + return ret; + } + // segment open, flush the audio. // @see: ngx_rtmp_hls_open_fragment /* start fragment with audio to make iPhone happy */ - if ((ret = muxer->flush_audio(af, ab)) != ERROR_SUCCESS) { + if ((ret = muxer->flush_audio(cache)) != ERROR_SUCCESS) { srs_error("m3u8 muxer flush audio failed. ret=%d", ret); return ret; } @@ -1171,190 +1074,13 @@ int SrsHlsCache::reap_segment(string log_desc, SrsHlsMuxer* muxer, int64_t segme return ret; } -int SrsHlsCache::cache_audio(SrsAvcAacCodec* codec, SrsCodecSample* sample) -{ - int ret = ERROR_SUCCESS; - - for (int i = 0; i < sample->nb_sample_units; i++) { - SrsCodecSampleUnit* sample_unit = &sample->sample_units[i]; - int32_t size = sample_unit->size; - - if (!sample_unit->bytes || size <= 0 || size > 0x1fff) { - ret = ERROR_HLS_AAC_FRAME_LENGTH; - srs_error("invalid aac frame length=%d, ret=%d", size, ret); - return ret; - } - - // the frame length is the AAC raw data plus the adts header size. - int32_t frame_length = size + 7; - - // AAC-ADTS - // 6.2 Audio Data Transport Stream, ADTS - // in aac-iso-13818-7.pdf, page 26. - // fixed 7bytes header - static u_int8_t adts_header[7] = {0xff, 0xf1, 0x00, 0x00, 0x00, 0x0f, 0xfc}; - /* - // adts_fixed_header - // 2B, 16bits - int16_t syncword; //12bits, '1111 1111 1111' - int8_t ID; //1bit, '0' - int8_t layer; //2bits, '00' - int8_t protection_absent; //1bit, can be '1' - // 12bits - int8_t profile; //2bit, 7.1 Profiles, page 40 - TSAacSampleFrequency sampling_frequency_index; //4bits, Table 35, page 46 - int8_t private_bit; //1bit, can be '0' - int8_t channel_configuration; //3bits, Table 8 - int8_t original_or_copy; //1bit, can be '0' - int8_t home; //1bit, can be '0' - - // adts_variable_header - // 28bits - int8_t copyright_identification_bit; //1bit, can be '0' - int8_t copyright_identification_start; //1bit, can be '0' - int16_t frame_length; //13bits - int16_t adts_buffer_fullness; //11bits, 7FF signals that the bitstream is a variable rate bitstream. - int8_t number_of_raw_data_blocks_in_frame; //2bits, 0 indicating 1 raw_data_block() - */ - // profile, 2bits - adts_header[2] = (codec->aac_profile << 6) & 0xc0; - // sampling_frequency_index 4bits - adts_header[2] |= (codec->aac_sample_rate << 2) & 0x3c; - // channel_configuration 3bits - adts_header[2] |= (codec->aac_channels >> 2) & 0x01; - adts_header[3] = (codec->aac_channels << 6) & 0xc0; - // frame_length 13bits - adts_header[3] |= (frame_length >> 11) & 0x03; - adts_header[4] = (frame_length >> 3) & 0xff; - adts_header[5] = ((frame_length << 5) & 0xe0); - // adts_buffer_fullness; //11bits - adts_header[5] |= 0x1f; - - // copy to audio buffer - ab->append((const char*)adts_header, sizeof(adts_header)); - ab->append(sample_unit->bytes, sample_unit->size); - } - - return ret; -} - -int SrsHlsCache::cache_video(SrsAvcAacCodec* codec, SrsCodecSample* sample) +SrsHls::SrsHls() { - int ret = ERROR_SUCCESS; - - // for type1/5/6, insert aud packet. - static u_int8_t aud_nal[] = { 0x00, 0x00, 0x00, 0x01, 0x09, 0xf0 }; + source = NULL; + handler = NULL; - bool sps_pps_sent = false; - bool aud_sent = false; - /** - * a ts sample is format as: - * 00 00 00 01 // header - * xxxxxxx // data bytes - * 00 00 01 // continue header - * xxxxxxx // data bytes. - * so, for each sample, we append header in aud_nal, then appends the bytes in sample. - */ - for (int i = 0; i < sample->nb_sample_units; i++) { - SrsCodecSampleUnit* sample_unit = &sample->sample_units[i]; - int32_t size = sample_unit->size; - - if (!sample_unit->bytes || size <= 0) { - ret = ERROR_HLS_AVC_SAMPLE_SIZE; - srs_error("invalid avc sample length=%d, ret=%d", size, ret); - return ret; - } - - /** - * step 1: - * first, before each "real" sample, - * we add some packets according to the nal_unit_type, - * for example, when got nal_unit_type=5, insert SPS/PPS before sample. - */ - - // 5bits, 7.3.1 NAL unit syntax, - // H.264-AVC-ISO_IEC_14496-10.pdf, page 44. - u_int8_t nal_unit_type; - nal_unit_type = *sample_unit->bytes; - nal_unit_type &= 0x1f; - - // @see: ngx_rtmp_hls_video - // Table 7-1 – NAL unit type codes, page 61 - // 1: Coded slice - if (nal_unit_type == 1) { - sps_pps_sent = false; - } - - // 6: Supplemental enhancement information (SEI) sei_rbsp( ), page 61 - // @see: ngx_rtmp_hls_append_aud - if (!aud_sent) { - // @remark, when got type 9, we donot send aud_nal, but it will make - // ios unhappy, so we remove it. - // @see https://github.com/winlinvip/simple-rtmp-server/issues/281 - /*if (nal_unit_type == 9) { - aud_sent = true; - }*/ - - if (nal_unit_type == 1 || nal_unit_type == 5 || nal_unit_type == 6) { - // for type 6, append a aud with type 9. - vb->append((const char*)aud_nal, sizeof(aud_nal)); - aud_sent = true; - } - } - - // 5: Coded slice of an IDR picture. - // insert sps/pps before IDR or key frame is ok. - if (nal_unit_type == 5 && !sps_pps_sent) { - sps_pps_sent = true; - - // @see: ngx_rtmp_hls_append_sps_pps - if (codec->sequenceParameterSetLength > 0) { - // AnnexB prefix, for sps always 4 bytes header - vb->append((const char*)aud_nal, 4); - // sps - vb->append(codec->sequenceParameterSetNALUnit, codec->sequenceParameterSetLength); - } - if (codec->pictureParameterSetLength > 0) { - // AnnexB prefix, for pps always 4 bytes header - vb->append((const char*)aud_nal, 4); - // pps - vb->append(codec->pictureParameterSetNALUnit, codec->pictureParameterSetLength); - } - } - - // 7-9, ignore, @see: ngx_rtmp_hls_video - if (nal_unit_type >= 7 && nal_unit_type <= 9) { - continue; - } - - /** - * step 2: - * output the "real" sample, in buf. - * when we output some special assist packets according to nal_unit_type - */ - - // sample start prefix, '00 00 00 01' or '00 00 01' - u_int8_t* p = aud_nal + 1; - u_int8_t* end = p + 3; - - // first AnnexB prefix is long (4 bytes) - if (vb->length() == 0) { - p = aud_nal; - } - vb->append((const char*)p, end - p); - - // sample data - vb->append(sample_unit->bytes, sample_unit->size); - } - - return ret; -} - -SrsHls::SrsHls(SrsSource* _source) -{ hls_enabled = false; - - source = _source; + codec = new SrsAvcAacCodec(); sample = new SrsCodecSample(); jitter = new SrsRtmpJitter(); @@ -1362,7 +1088,7 @@ SrsHls::SrsHls(SrsSource* _source) muxer = new SrsHlsMuxer(); hls_cache = new SrsHlsCache(); - pithy_print = new SrsPithyPrint(SRS_CONSTS_STAGE_HLS); + pprint = SrsPithyPrint::create_hls(); stream_dts = 0; } @@ -1375,7 +1101,21 @@ SrsHls::~SrsHls() srs_freep(muxer); srs_freep(hls_cache); - srs_freep(pithy_print); + srs_freep(pprint); +} + +int SrsHls::initialize(SrsSource* s, ISrsHlsHandler* h) +{ + int ret = ERROR_SUCCESS; + + source = s; + handler = h; + + if ((ret = muxer->initialize(h)) != ERROR_SUCCESS) { + return ret; + } + + return ret; } int SrsHls::on_publish(SrsRequest* req) @@ -1440,35 +1180,48 @@ int SrsHls::on_meta_data(SrsAmf0Object* metadata) return ret; } - if ((ret = codec->metadata_demux(metadata)) != ERROR_SUCCESS) { - return ret; - } - return ret; } -int SrsHls::on_audio(SrsSharedPtrMessage* audio) +int SrsHls::on_audio(SrsSharedPtrMessage* shared_audio) { int ret = ERROR_SUCCESS; - SrsAutoFree(SrsSharedPtrMessage, audio); - if (!hls_enabled) { return ret; } + + SrsSharedPtrMessage* audio = shared_audio->copy(); + SrsAutoFree(SrsSharedPtrMessage, audio); sample->clear(); if ((ret = codec->audio_aac_demux(audio->payload, audio->size, sample)) != ERROR_SUCCESS) { - srs_error("hls codec demux audio failed. ret=%d", ret); - return ret; + if (ret != ERROR_HLS_TRY_MP3) { + srs_error("hls aac demux audio failed. ret=%d", ret); + return ret; + } + if ((ret = codec->audio_mp3_demux(audio->payload, audio->size, sample)) != ERROR_SUCCESS) { + srs_error("hls mp3 demux audio failed. ret=%d", ret); + return ret; + } } + srs_info("audio decoded, type=%d, codec=%d, cts=%d, size=%d, time=%"PRId64, + sample->frame_type, codec->audio_codec_id, sample->cts, audio->size, audio->timestamp); + SrsCodecAudio acodec = (SrsCodecAudio)codec->audio_codec_id; - if (codec->audio_codec_id != SrsCodecAudioAAC) { + // ts support audio codec: aac/mp3 + if (acodec != SrsCodecAudioAAC && acodec != SrsCodecAudioMP3) { + return ret; + } + + // when codec changed, write new header. + if ((ret = muxer->update_acodec(acodec)) != ERROR_SUCCESS) { + srs_error("http: ts audio write header failed. ret=%d", ret); return ret; } // ignore sequence header - if (sample->aac_packet_type == SrsCodecAudioTypeSequenceHeader) { + if (acodec == SrsCodecAudioAAC && sample->aac_packet_type == SrsCodecAudioTypeSequenceHeader) { return hls_cache->on_sequence_header(muxer); } @@ -1477,13 +1230,13 @@ int SrsHls::on_audio(SrsSharedPtrMessage* audio) return ret; } - // the pts calc from rtmp/flv header. - int64_t pts = audio->header.timestamp * 90; + // the dts calc from rtmp/flv header. + int64_t dts = audio->timestamp * 90; // for pure audio, we need to update the stream dts also. - stream_dts = pts; + stream_dts = dts; - if ((ret = hls_cache->write_audio(codec, muxer, pts, sample)) != ERROR_SUCCESS) { + if ((ret = hls_cache->write_audio(codec, muxer, dts, sample)) != ERROR_SUCCESS) { srs_error("hls cache write audio failed. ret=%d", ret); return ret; } @@ -1491,24 +1244,27 @@ int SrsHls::on_audio(SrsSharedPtrMessage* audio) return ret; } -int SrsHls::on_video(SrsSharedPtrMessage* video) +int SrsHls::on_video(SrsSharedPtrMessage* shared_video) { int ret = ERROR_SUCCESS; - SrsAutoFree(SrsSharedPtrMessage, video); - if (!hls_enabled) { return ret; } + + SrsSharedPtrMessage* video = shared_video->copy(); + SrsAutoFree(SrsSharedPtrMessage, video); sample->clear(); if ((ret = codec->video_avc_demux(video->payload, video->size, sample)) != ERROR_SUCCESS) { srs_error("hls codec demux video failed. ret=%d", ret); return ret; } + srs_info("video decoded, type=%d, codec=%d, avc=%d, cts=%d, size=%d, time=%"PRId64, + sample->frame_type, codec->video_codec_id, sample->avc_packet_type, sample->cts, video->size, video->timestamp); // ignore info frame, - // @see https://github.com/winlinvip/simple-rtmp-server/issues/288#issuecomment-69863909 + // @see https://github.com/simple-rtmp-server/srs/issues/288#issuecomment-69863909 if (sample->frame_type == SrsCodecVideoAVCFrameVideoInfoFrame) { return ret; } @@ -1528,31 +1284,32 @@ int SrsHls::on_video(SrsSharedPtrMessage* video) return ret; } - int64_t dts = video->header.timestamp * 90; + int64_t dts = video->timestamp * 90; stream_dts = dts; if ((ret = hls_cache->write_video(codec, muxer, dts, sample)) != ERROR_SUCCESS) { srs_error("hls cache write video failed. ret=%d", ret); return ret; } - hls_mux(); + // pithy print message. + hls_show_mux_log(); return ret; } -void SrsHls::hls_mux() +void SrsHls::hls_show_mux_log() { + pprint->elapse(); + // reportable - if (pithy_print->can_print()) { + if (pprint->can_print()) { // the run time is not equals to stream time, - // @see: https://github.com/winlinvip/simple-rtmp-server/issues/81#issuecomment-48100994 + // @see: https://github.com/simple-rtmp-server/srs/issues/81#issuecomment-48100994 // it's ok. - srs_trace("-> "SRS_CONSTS_LOG_HLS - " time=%"PRId64", stream dts=%"PRId64"(%"PRId64"ms), sequence_no=%d", - pithy_print->age(), stream_dts, stream_dts / 90, muxer->sequence_no()); + srs_trace("-> "SRS_CONSTS_LOG_HLS" time=%"PRId64", stream dts=%"PRId64"(%"PRId64"ms), sno=%d, ts=%s, dur=%.2f, dva=%dp", + pprint->age(), stream_dts, stream_dts / 90, muxer->sequence_no(), muxer->ts_url().c_str(), + muxer->duration(), muxer->deviation()); } - - pithy_print->elapse(); } #endif diff --git a/trunk/src/app/srs_app_hls.hpp b/trunk/src/app/srs_app_hls.hpp index fff25c90bc..a79872a108 100644 --- a/trunk/src/app/srs_app_hls.hpp +++ b/trunk/src/app/srs_app_hls.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -29,19 +29,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include -/** -* the public data, event HLS disable, others can use it. -*/ -/** -* the flv sample rate map -*/ -extern int flv_sample_rates[]; - -/** -* the aac sample rate map -*/ -extern int aac_sample_rates[]; - /** * the HLS section, only available when HLS enabled. */ @@ -50,10 +37,12 @@ extern int aac_sample_rates[]; #include #include -class SrsBuffer; +#include +#include +#include + class SrsSharedPtrMessage; class SrsCodecSample; -class SrsMpegtsFrame; class SrsAmf0Object; class SrsRtmpJitter; class SrsTSMuxer; @@ -62,56 +51,74 @@ class SrsRequest; class SrsPithyPrint; class SrsSource; class SrsFileWriter; +class SrsSimpleBuffer; +class SrsTsAacJitter; +class SrsTsCache; +class SrsHlsSegment; +class SrsTsCache; +class SrsTsContext; /** -* jitter correct for audio, -* the sample rate 44100/32000 will lost precise, -* when mp4/ts(tbn=90000) covert to flv/rtmp(1000), -* so the Hls on ipad or iphone will corrupt, -* @see nginx-rtmp: est_pts +* the handler for hls event. +* for example, we use memory only hls for */ -class SrsHlsAacJitter +class ISrsHlsHandler { -private: - int64_t base_pts; - int64_t nb_samples; - int sync_ms; public: - SrsHlsAacJitter(); - virtual ~SrsHlsAacJitter(); + ISrsHlsHandler(); + virtual ~ISrsHlsHandler(); +public: + /** + * when publish stream + */ + virtual int on_hls_publish(SrsRequest* req) = 0; + /** + * when update the m3u8 file. + */ + virtual int on_update_m3u8(SrsRequest* r, std::string m3u8) = 0; /** - * when buffer start, calc the "correct" pts for ts, - * @param flv_pts, the flv pts calc from flv header timestamp, - * @param sample_rate, the sample rate in format(flv/RTMP packet header). - * @param aac_sample_rate, the sample rate in codec(sequence header). - * @return the calc correct pts. + * when reap new ts file. */ - virtual int64_t on_buffer_start(int64_t flv_pts, int sample_rate, int aac_sample_rate); + virtual int on_update_ts(SrsRequest* r, std::string uri, std::string ts) = 0; /** - * when buffer continue, muxer donot write to file, - * the audio buffer continue grow and donot need a pts, - * for the ts audio PES packet only has one pts at the first time. + * when unpublish stream */ - virtual void on_buffer_continue(); + virtual int on_hls_unpublish(SrsRequest* req) = 0; }; /** -* write data from frame(header info) and buffer(data) to ts file. -* it's a simple object wrapper for utility from nginx-rtmp: SrsMpegtsWriter +* write to file and cache. */ -class SrsTSMuxer +class SrsHlsCacheWriter : public SrsFileWriter { private: - SrsFileWriter* writer; - std::string path; + SrsFileWriter impl; + std::string data; + bool should_write_cache; + bool should_write_file; public: - SrsTSMuxer(); - virtual ~SrsTSMuxer(); + SrsHlsCacheWriter(bool write_cache, bool write_file); + virtual ~SrsHlsCacheWriter(); public: - virtual int open(std::string _path); - virtual int write_audio(SrsMpegtsFrame* af, SrsBuffer* ab); - virtual int write_video(SrsMpegtsFrame* vf, SrsBuffer* vb); + /** + * open file writer, can open then close then open... + */ + virtual int open(std::string file); virtual void close(); +public: + virtual bool is_open(); + virtual int64_t tellg(); +public: + /** + * write to file. + * @param pnwrite the output nb_write, NULL to ignore. + */ + virtual int write(void* buf, size_t count, ssize_t* pnwrite); +public: + /** + * get the string cache. + */ + virtual std::string cache(); }; /** @@ -132,15 +139,16 @@ class SrsHlsSegment // ts full file to write. std::string full_path; // the muxer to write ts. + SrsHlsCacheWriter* writer; SrsTSMuxer* muxer; // current segment start dts for m3u8 int64_t segment_start_dts; // whether current segement is sequence header. bool is_sequence_header; - - SrsHlsSegment(); +public: + SrsHlsSegment(SrsTsContext* c, bool write_cache, bool write_file, SrsCodecAudio ac, SrsCodecVideo vc); virtual ~SrsHlsSegment(); - +public: /** * update the segment duration. * @current_frame_dts the dts of frame, in tbn of ts. @@ -148,6 +156,43 @@ class SrsHlsSegment virtual void update_duration(int64_t current_frame_dts); }; +/** + * the hls async call: on_hls + */ +class SrsDvrAsyncCallOnHls : public ISrsDvrAsyncCall +{ +private: + std::string path; + std::string ts_url; + std::string m3u8; + std::string m3u8_url; + int seq_no; + SrsRequest* req; + double duration; +public: + SrsDvrAsyncCallOnHls(SrsRequest* r, std::string p, std::string t, std::string m, std::string mu, int s, double d); + virtual ~SrsDvrAsyncCallOnHls(); +public: + virtual int call(); + virtual std::string to_string(); +}; + +/** + * the hls async call: on_hls_notify + */ +class SrsDvrAsyncCallOnHlsNotify : public ISrsDvrAsyncCall +{ +private: + std::string ts_url; + SrsRequest* req; +public: + SrsDvrAsyncCallOnHlsNotify(SrsRequest* r, std::string u); + virtual ~SrsDvrAsyncCallOnHlsNotify(); +public: + virtual int call(); + virtual std::string to_string(); +}; + /** * muxer the HLS stream(m3u8 and ts files). * generally, the m3u8 muxer only provides methods to open/close segments, @@ -159,15 +204,38 @@ class SrsHlsSegment class SrsHlsMuxer { private: - std::string app; - std::string stream; + SrsRequest* req; private: + std::string hls_entry_prefix; std::string hls_path; - int hls_fragment; - int hls_window; + std::string hls_ts_file; + bool hls_cleanup; + bool hls_wait_keyframe; + std::string m3u8_dir; + double hls_aof_ratio; + double hls_fragment; + double hls_window; + SrsDvrAsyncCallThread* async; +private: + // whether use floor algorithm for timestamp. + bool hls_ts_floor; + // the deviation in piece to adjust the fragment to be more + // bigger or smaller. + int deviation_ts; + // the previous reap floor timestamp, + // used to detect the dup or jmp or ts. + int64_t accept_floor_ts; + int64_t previous_floor_ts; private: int _sequence_no; + int target_duration; std::string m3u8; + std::string m3u8_url; +private: + ISrsHlsHandler* handler; + // TODO: FIXME: supports reload. + bool should_write_cache; + bool should_write_file; private: /** * m3u8 segments. @@ -177,13 +245,37 @@ class SrsHlsMuxer * current writing segment. */ SrsHlsSegment* current; + /** + * the current audio codec, when open new muxer, + * set the muxer audio codec. + * @see https://github.com/simple-rtmp-server/srs/issues/301 + */ + SrsCodecAudio acodec; + /** + * the ts context, to keep cc continous between ts. + * @see https://github.com/simple-rtmp-server/srs/issues/375 + */ + SrsTsContext* context; public: SrsHlsMuxer(); virtual ~SrsHlsMuxer(); public: virtual int sequence_no(); + virtual std::string ts_url(); + virtual double duration(); + virtual int deviation(); public: - virtual int update_config(std::string _app, std::string _stream, std::string path, int fragment, int window); + /** + * initialize the hls muxer. + */ + virtual int initialize(ISrsHlsHandler* h); + /** + * when publish, update the config for muxer. + */ + virtual int update_config(SrsRequest* r, std::string entry_prefix, + std::string path, std::string m3u8_file, std::string ts_file, + double fragment, double window, bool ts_floor, double aof_ratio, + bool cleanup, bool wait_keyframe); /** * open a new segment(a new ts file), * @param segment_start_dts use to calc the segment duration, @@ -196,14 +288,20 @@ class SrsHlsMuxer * that is whether the current segment duration>=(the segment in config) */ virtual bool is_segment_overflow(); + /** + * whether wait keyframe to reap the ts. + */ + virtual bool wait_keyframe(); /** * whether segment absolutely overflow, for pure audio to reap segment, * that is whether the current segment duration>=2*(the segment in config) - * @see https://github.com/winlinvip/simple-rtmp-server/issues/151#issuecomment-71155184 + * @see https://github.com/simple-rtmp-server/srs/issues/151#issuecomment-71155184 */ virtual bool is_segment_absolutely_overflow(); - virtual int flush_audio(SrsMpegtsFrame* af, SrsBuffer* ab); - virtual int flush_video(SrsMpegtsFrame* af, SrsBuffer* ab, SrsMpegtsFrame* vf, SrsBuffer* vb); +public: + virtual int update_acodec(SrsCodecAudio ac); + virtual int flush_audio(SrsTsCache* cache); + virtual int flush_video(SrsTsCache* cache); /** * close segment(ts). * @param log_desc the description for log. @@ -211,8 +309,7 @@ class SrsHlsMuxer virtual int segment_close(std::string log_desc); private: virtual int refresh_m3u8(); - virtual int _refresh_m3u8(int& fd, std::string m3u8_file); - virtual int create_dir(); + virtual int _refresh_m3u8(std::string m3u8_file); }; /** @@ -229,22 +326,13 @@ class SrsHlsMuxer * about the flv tbn problem: * flv tbn is 1/1000, ts tbn is 1/90000, * when timestamp convert to flv tbn, it will loose precise, -* so we must gather audio frame together, and recalc the timestamp @see SrsHlsAacJitter, +* so we must gather audio frame together, and recalc the timestamp @see SrsTsAacJitter, * we use a aac jitter to correct the audio pts. */ class SrsHlsCache { private: - // current frame and buffer - SrsMpegtsFrame* af; - SrsBuffer* ab; - SrsMpegtsFrame* vf; - SrsBuffer* vb; -private: - // the audio cache buffer start pts, to flush audio if full. - int64_t audio_buffer_start_pts; - // time jitter for aac - SrsHlsAacJitter* aac_jitter; + SrsTsCache* cache; public: SrsHlsCache(); virtual ~SrsHlsCache(); @@ -277,8 +365,6 @@ class SrsHlsCache * so, user must reap_segment then flush_video to hls muxer. */ virtual int reap_segment(std::string log_desc, SrsHlsMuxer* muxer, int64_t segment_start_dts); - virtual int cache_audio(SrsAvcAacCodec* codec, SrsCodecSample* sample); - virtual int cache_video(SrsAvcAacCodec* codec, SrsCodecSample* sample); }; /** @@ -291,13 +377,14 @@ class SrsHls private: SrsHlsMuxer* muxer; SrsHlsCache* hls_cache; + ISrsHlsHandler* handler; private: bool hls_enabled; SrsSource* source; SrsAvcAacCodec* codec; SrsCodecSample* sample; SrsRtmpJitter* jitter; - SrsPithyPrint* pithy_print; + SrsPithyPrint* pprint; /** * we store the stream dts, * for when we notice the hls cache to publish, @@ -313,9 +400,13 @@ class SrsHls */ int64_t stream_dts; public: - SrsHls(SrsSource* _source); + SrsHls(); virtual ~SrsHls(); public: + /** + * initialize the hls by handler and source. + */ + virtual int initialize(SrsSource* s, ISrsHlsHandler* h); /** * publish stream event, continue to write the m3u8, * for the muxer object not destroyed. @@ -332,14 +423,16 @@ class SrsHls virtual int on_meta_data(SrsAmf0Object* metadata); /** * mux the audio packets to ts. + * @param shared_audio, directly ptr, copy it if need to save it. */ - virtual int on_audio(SrsSharedPtrMessage* audio); + virtual int on_audio(SrsSharedPtrMessage* shared_audio); /** * mux the video packets to ts. + * @param shared_video, directly ptr, copy it if need to save it. */ - virtual int on_video(SrsSharedPtrMessage* video); + virtual int on_video(SrsSharedPtrMessage* shared_video); private: - virtual void hls_mux(); + virtual void hls_show_mux_log(); }; #endif diff --git a/trunk/src/app/srs_app_http.cpp b/trunk/src/app/srs_app_http.cpp index a1768130c5..4ed1453f28 100644 --- a/trunk/src/app/srs_app_http.cpp +++ b/trunk/src/app/srs_app_http.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -26,7 +26,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #ifdef SRS_AUTO_HTTP_PARSER #include - +#include +#include using namespace std; #include @@ -36,11 +37,15 @@ using namespace std; #include #include #include +#include +#include +#include +#include +#include +#include #define SRS_DEFAULT_HTTP_PORT 80 -#define SRS_HTTP_HEADER_BUFFER 1024 - // for http parser macros #define SRS_CONSTS_HTTP_OPTIONS HTTP_OPTIONS #define SRS_CONSTS_HTTP_GET HTTP_GET @@ -48,543 +53,1122 @@ using namespace std; #define SRS_CONSTS_HTTP_PUT HTTP_PUT #define SRS_CONSTS_HTTP_DELETE HTTP_DELETE -bool srs_path_equals(const char* expect, const char* path, int nb_path) -{ - int size = strlen(expect); +// for ead all of http body, read each time. +#define SRS_HTTP_READ_CACHE_BYTES 4096 + +#define SRS_HTTP_DEFAULT_PAGE "index.html" + +int srs_go_http_response_json(ISrsHttpResponseWriter* w, string data) +{ + w->header()->set_content_length(data.length()); + w->header()->set_content_type("application/json"); + + return w->write((char*)data.data(), (int)data.length()); +} + +// get the status text of code. +string srs_generate_http_status_text(int status) +{ + static std::map _status_map; + if (_status_map.empty()) { + _status_map[SRS_CONSTS_HTTP_Continue ] = SRS_CONSTS_HTTP_Continue_str ; + _status_map[SRS_CONSTS_HTTP_SwitchingProtocols ] = SRS_CONSTS_HTTP_SwitchingProtocols_str ; + _status_map[SRS_CONSTS_HTTP_OK ] = SRS_CONSTS_HTTP_OK_str ; + _status_map[SRS_CONSTS_HTTP_Created ] = SRS_CONSTS_HTTP_Created_str ; + _status_map[SRS_CONSTS_HTTP_Accepted ] = SRS_CONSTS_HTTP_Accepted_str ; + _status_map[SRS_CONSTS_HTTP_NonAuthoritativeInformation ] = SRS_CONSTS_HTTP_NonAuthoritativeInformation_str ; + _status_map[SRS_CONSTS_HTTP_NoContent ] = SRS_CONSTS_HTTP_NoContent_str ; + _status_map[SRS_CONSTS_HTTP_ResetContent ] = SRS_CONSTS_HTTP_ResetContent_str ; + _status_map[SRS_CONSTS_HTTP_PartialContent ] = SRS_CONSTS_HTTP_PartialContent_str ; + _status_map[SRS_CONSTS_HTTP_MultipleChoices ] = SRS_CONSTS_HTTP_MultipleChoices_str ; + _status_map[SRS_CONSTS_HTTP_MovedPermanently ] = SRS_CONSTS_HTTP_MovedPermanently_str ; + _status_map[SRS_CONSTS_HTTP_Found ] = SRS_CONSTS_HTTP_Found_str ; + _status_map[SRS_CONSTS_HTTP_SeeOther ] = SRS_CONSTS_HTTP_SeeOther_str ; + _status_map[SRS_CONSTS_HTTP_NotModified ] = SRS_CONSTS_HTTP_NotModified_str ; + _status_map[SRS_CONSTS_HTTP_UseProxy ] = SRS_CONSTS_HTTP_UseProxy_str ; + _status_map[SRS_CONSTS_HTTP_TemporaryRedirect ] = SRS_CONSTS_HTTP_TemporaryRedirect_str ; + _status_map[SRS_CONSTS_HTTP_BadRequest ] = SRS_CONSTS_HTTP_BadRequest_str ; + _status_map[SRS_CONSTS_HTTP_Unauthorized ] = SRS_CONSTS_HTTP_Unauthorized_str ; + _status_map[SRS_CONSTS_HTTP_PaymentRequired ] = SRS_CONSTS_HTTP_PaymentRequired_str ; + _status_map[SRS_CONSTS_HTTP_Forbidden ] = SRS_CONSTS_HTTP_Forbidden_str ; + _status_map[SRS_CONSTS_HTTP_NotFound ] = SRS_CONSTS_HTTP_NotFound_str ; + _status_map[SRS_CONSTS_HTTP_MethodNotAllowed ] = SRS_CONSTS_HTTP_MethodNotAllowed_str ; + _status_map[SRS_CONSTS_HTTP_NotAcceptable ] = SRS_CONSTS_HTTP_NotAcceptable_str ; + _status_map[SRS_CONSTS_HTTP_ProxyAuthenticationRequired ] = SRS_CONSTS_HTTP_ProxyAuthenticationRequired_str ; + _status_map[SRS_CONSTS_HTTP_RequestTimeout ] = SRS_CONSTS_HTTP_RequestTimeout_str ; + _status_map[SRS_CONSTS_HTTP_Conflict ] = SRS_CONSTS_HTTP_Conflict_str ; + _status_map[SRS_CONSTS_HTTP_Gone ] = SRS_CONSTS_HTTP_Gone_str ; + _status_map[SRS_CONSTS_HTTP_LengthRequired ] = SRS_CONSTS_HTTP_LengthRequired_str ; + _status_map[SRS_CONSTS_HTTP_PreconditionFailed ] = SRS_CONSTS_HTTP_PreconditionFailed_str ; + _status_map[SRS_CONSTS_HTTP_RequestEntityTooLarge ] = SRS_CONSTS_HTTP_RequestEntityTooLarge_str ; + _status_map[SRS_CONSTS_HTTP_RequestURITooLarge ] = SRS_CONSTS_HTTP_RequestURITooLarge_str ; + _status_map[SRS_CONSTS_HTTP_UnsupportedMediaType ] = SRS_CONSTS_HTTP_UnsupportedMediaType_str ; + _status_map[SRS_CONSTS_HTTP_RequestedRangeNotSatisfiable ] = SRS_CONSTS_HTTP_RequestedRangeNotSatisfiable_str ; + _status_map[SRS_CONSTS_HTTP_ExpectationFailed ] = SRS_CONSTS_HTTP_ExpectationFailed_str ; + _status_map[SRS_CONSTS_HTTP_InternalServerError ] = SRS_CONSTS_HTTP_InternalServerError_str ; + _status_map[SRS_CONSTS_HTTP_NotImplemented ] = SRS_CONSTS_HTTP_NotImplemented_str ; + _status_map[SRS_CONSTS_HTTP_BadGateway ] = SRS_CONSTS_HTTP_BadGateway_str ; + _status_map[SRS_CONSTS_HTTP_ServiceUnavailable ] = SRS_CONSTS_HTTP_ServiceUnavailable_str ; + _status_map[SRS_CONSTS_HTTP_GatewayTimeout ] = SRS_CONSTS_HTTP_GatewayTimeout_str ; + _status_map[SRS_CONSTS_HTTP_HTTPVersionNotSupported ] = SRS_CONSTS_HTTP_HTTPVersionNotSupported_str ; + } - if (size != nb_path) { - return false; + std::string status_text; + if (_status_map.find(status) == _status_map.end()) { + status_text = "Status Unknown"; + } else { + status_text = _status_map[status]; } - bool equals = !memcmp(expect, path, size); - return equals; + return status_text; } -bool srs_path_like(const char* expect, const char* path, int nb_path) +// bodyAllowedForStatus reports whether a given response status code +// permits a body. See RFC2616, section 4.4. +bool srs_go_http_body_allowd(int status) { - int size = strlen(expect); - bool equals = !strncmp(expect, path, srs_min(size, nb_path)); - return equals; + if (status >= 100 && status <= 199) { + return false; + } else if (status == 204 || status == 304) { + return false; + } + + return true; } -SrsHttpHandlerMatch::SrsHttpHandlerMatch() +// DetectContentType implements the algorithm described +// at http://mimesniff.spec.whatwg.org/ to determine the +// Content-Type of the given data. It considers at most the +// first 512 bytes of data. DetectContentType always returns +// a valid MIME type: if it cannot determine a more specific one, it +// returns "application/octet-stream". +string srs_go_http_detect(char* data, int size) { - handler = NULL; + // detect only when data specified. + if (data) { + } + return "application/octet-stream"; // fallback } -SrsHttpHandler::SrsHttpHandler() +// Error replies to the request with the specified error message and HTTP code. +// The error message should be plain text. +int srs_go_http_error(ISrsHttpResponseWriter* w, int code, string error) { + int ret = ERROR_SUCCESS; + + w->header()->set_content_type("text/plain; charset=utf-8"); + w->header()->set_content_length(error.length()); + w->write_header(code); + w->write((char*)error.data(), (int)error.length()); + + return ret; } -SrsHttpHandler::~SrsHttpHandler() +SrsHttpHeader::SrsHttpHeader() { - std::vector::iterator it; - for (it = handlers.begin(); it != handlers.end(); ++it) { - SrsHttpHandler* handler = *it; - srs_freep(handler); - } - handlers.clear(); } -int SrsHttpHandler::initialize() +SrsHttpHeader::~SrsHttpHeader() { - int ret = ERROR_SUCCESS; - return ret; } -bool SrsHttpHandler::can_handle(const char* /*path*/, int /*length*/, const char** /*pchild*/) +void SrsHttpHeader::set(string key, string value) { - return false; + headers[key] = value; } -int SrsHttpHandler::process_request(SrsStSocket* skt, SrsHttpMessage* req) +string SrsHttpHeader::get(string key) { - if (req->method() == SRS_CONSTS_HTTP_OPTIONS) { - req->set_requires_crossdomain(true); - return res_options(skt); + std::string v; + + if (headers.find(key) != headers.end()) { + v = headers[key]; } + + return v; +} - int status_code; - std::string reason_phrase; - if (!is_handler_valid(req, status_code, reason_phrase)) { - std::stringstream ss; - - ss << __SRS_JOBJECT_START - << __SRS_JFIELD_ERROR(ERROR_HTTP_HANDLER_INVALID) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("data", __SRS_JOBJECT_START) - << __SRS_JFIELD_ORG("status_code", status_code) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("reason_phrase", reason_phrase) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("url", req->url()) - << __SRS_JOBJECT_END - << __SRS_JOBJECT_END; - - return res_error(skt, req, status_code, reason_phrase, ss.str()); +int64_t SrsHttpHeader::content_length() +{ + std::string cl = get("Content-Length"); + + if (cl.empty()) { + return -1; } - return do_process_request(skt, req); + return (int64_t)::atof(cl.c_str()); } -bool SrsHttpHandler::is_handler_valid(SrsHttpMessage* req, int& status_code, string& reason_phrase) +void SrsHttpHeader::set_content_length(int64_t size) { - if (!req->match()->unmatched_url.empty()) { - status_code = SRS_CONSTS_HTTP_NotFound; - reason_phrase = SRS_CONSTS_HTTP_NotFound_str; - - return false; + char buf[64]; + snprintf(buf, sizeof(buf), "%"PRId64, size); + set("Content-Length", buf); +} + +string SrsHttpHeader::content_type() +{ + return get("Content-Type"); +} + +void SrsHttpHeader::set_content_type(string ct) +{ + set("Content-Type", ct); +} + +void SrsHttpHeader::write(stringstream& ss) +{ + std::map::iterator it; + for (it = headers.begin(); it != headers.end(); ++it) { + ss << it->first << ": " << it->second << SRS_HTTP_CRLF; } - - return true; } -int SrsHttpHandler::do_process_request(SrsStSocket* /*skt*/, SrsHttpMessage* /*req*/) +ISrsHttpResponseWriter::ISrsHttpResponseWriter() +{ +} + +ISrsHttpResponseWriter::~ISrsHttpResponseWriter() +{ +} + +ISrsHttpResponseReader::ISrsHttpResponseReader() +{ +} + +ISrsHttpResponseReader::~ISrsHttpResponseReader() +{ +} + +ISrsHttpHandler::ISrsHttpHandler() +{ + entry = NULL; +} + +ISrsHttpHandler::~ISrsHttpHandler() +{ +} + +SrsHttpRedirectHandler::SrsHttpRedirectHandler(string u, int c) +{ + url = u; + code = c; +} + +SrsHttpRedirectHandler::~SrsHttpRedirectHandler() +{ +} + +int SrsHttpRedirectHandler::serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r) { int ret = ERROR_SUCCESS; + // TODO: FIXME: implements it. return ret; } -int SrsHttpHandler::response_error(SrsStSocket* skt, SrsHttpMessage* req, int code, string desc) +SrsHttpNotFoundHandler::SrsHttpNotFoundHandler() { - std::stringstream ss; - ss << __SRS_JOBJECT_START - << __SRS_JFIELD_ERROR(code) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("desc", desc) - << __SRS_JOBJECT_END; - - return res_json(skt, req, ss.str()); } -int SrsHttpHandler::best_match(const char* path, int length, SrsHttpHandlerMatch** ppmatch) +SrsHttpNotFoundHandler::~SrsHttpNotFoundHandler() { - int ret = ERROR_SUCCESS; +} + +int SrsHttpNotFoundHandler::serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r) +{ + return srs_go_http_error(w, + SRS_CONSTS_HTTP_NotFound, SRS_CONSTS_HTTP_NotFound_str); +} + +SrsHttpFileServer::SrsHttpFileServer(string root_dir) +{ + dir = root_dir; +} + +SrsHttpFileServer::~SrsHttpFileServer() +{ +} + +int SrsHttpFileServer::serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r) +{ + string upath = r->path(); - SrsHttpHandler* handler = NULL; - const char* match_start = NULL; - int match_length = 0; + // add default pages. + if (srs_string_ends_with(upath, "/")) { + upath += SRS_HTTP_DEFAULT_PAGE; + } + + string fullpath = dir + "/"; - for (;;) { - // ensure cur is not NULL. - // ensure p not NULL and has bytes to parse. - if (!path || length <= 0) { - break; - } - - const char* p = NULL; - for (p = path + 1; p - path < length && *p != SRS_CONSTS_HTTP_PATH_SEP; p++) { - } - - // whether the handler can handler the node. - const char* pchild = p; - if (!can_handle(path, p - path, &pchild)) { - break; - } - - // save current handler, it's ok for current handler atleast. - handler = this; - match_start = path; - match_length = p - path; - - // find the best matched child handler. - std::vector::iterator it; - for (it = handlers.begin(); it != handlers.end(); ++it) { - SrsHttpHandler* h = *it; - - // matched, donot search more. - if (h->best_match(pchild, length - (pchild - path), ppmatch) == ERROR_SUCCESS) { - break; - } - } - - // whatever, donot loop. - break; + // remove the virtual directory. + srs_assert(entry); + size_t pos = entry->pattern.find("/"); + if (upath.length() > entry->pattern.length() && pos != string::npos) { + fullpath += upath.substr(entry->pattern.length() - pos); + } else { + fullpath += upath; } - // if already matched by child, return. - if (*ppmatch) { - return ret; + // stat current dir, if exists, return error. + if (!srs_path_exists(fullpath)) { + srs_warn("http miss file=%s, pattern=%s, upath=%s", + fullpath.c_str(), entry->pattern.c_str(), upath.c_str()); + return SrsHttpNotFoundHandler().serve_http(w, r); } + srs_trace("http match file=%s, pattern=%s, upath=%s", + fullpath.c_str(), entry->pattern.c_str(), upath.c_str()); + + // handle file according to its extension. + // use vod stream for .flv/.fhv + if (srs_string_ends_with(fullpath, ".flv") || srs_string_ends_with(fullpath, ".fhv")) { + return serve_flv_file(w, r, fullpath); + } else if (srs_string_ends_with(fullpath, ".mp4")) { + return serve_mp4_file(w, r, fullpath); + } + + // serve common static file. + return serve_file(w, r, fullpath); +} + +int SrsHttpFileServer::serve_file(ISrsHttpResponseWriter* w, SrsHttpMessage* r, string fullpath) +{ + int ret = ERROR_SUCCESS; + + // open the target file. + SrsFileReader fs; - // not matched, error. - if (handler == NULL) { - ret = ERROR_HTTP_HANDLER_MATCH_URL; + if ((ret = fs.open(fullpath)) != ERROR_SUCCESS) { + srs_warn("open file %s failed, ret=%d", fullpath.c_str(), ret); return ret; } + + int64_t length = fs.filesize(); + + // unset the content length to encode in chunked encoding. + w->header()->set_content_length(length); + + static std::map _mime; + if (_mime.empty()) { + _mime[".ts"] = "video/MP2T"; + _mime[".flv"] = "video/x-flv"; + _mime[".m4v"] = "video/x-m4v"; + _mime[".3gpp"] = "video/3gpp"; + _mime[".3gp"] = "video/3gpp"; + _mime[".mp4"] = "video/mp4"; + _mime[".aac"] = "audio/x-aac"; + _mime[".mp3"] = "audio/mpeg"; + _mime[".m4a"] = "audio/x-m4a"; + _mime[".ogg"] = "audio/ogg"; + // @see hls-m3u8-draft-pantos-http-live-streaming-12.pdf, page 5. + _mime[".m3u8"] = "application/vnd.apple.mpegurl"; // application/x-mpegURL + _mime[".rss"] = "application/rss+xml"; + _mime[".json"] = "application/json"; + _mime[".swf"] = "application/x-shockwave-flash"; + _mime[".doc"] = "application/msword"; + _mime[".zip"] = "application/zip"; + _mime[".rar"] = "application/x-rar-compressed"; + _mime[".xml"] = "text/xml"; + _mime[".html"] = "text/html"; + _mime[".js"] = "text/javascript"; + _mime[".css"] = "text/css"; + _mime[".ico"] = "image/x-icon"; + _mime[".png"] = "image/png"; + _mime[".jpeg"] = "image/jpeg"; + _mime[".jpg"] = "image/jpeg"; + _mime[".gif"] = "image/gif"; + } - // matched by this handler. - *ppmatch = new SrsHttpHandlerMatch(); - (*ppmatch)->handler = handler; - (*ppmatch)->matched_url.append(match_start, match_length); + if (true) { + size_t pos; + std::string ext = fullpath; + if ((pos = ext.rfind(".")) != string::npos) { + ext = ext.substr(pos); + } + + if (_mime.find(ext) == _mime.end()) { + w->header()->set_content_type("application/octet-stream"); + } else { + w->header()->set_content_type(_mime[ext]); + } + } - int unmatch_length = length - match_length; - if (unmatch_length > 0) { - (*ppmatch)->unmatched_url.append(match_start + match_length, unmatch_length); + // write body. + int64_t left = length; + if ((ret = copy(w, &fs, r, (int)left)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("read file=%s size=%d failed, ret=%d", fullpath.c_str(), left, ret); + } + return ret; } - return ret; + return w->final_request(); } -SrsHttpHandler* SrsHttpHandler::res_status_line(stringstream& ss) +int SrsHttpFileServer::serve_flv_file(ISrsHttpResponseWriter* w, SrsHttpMessage* r, string fullpath) { - ss << "HTTP/1.1 200 OK " << __SRS_CRLF - << "Server: "RTMP_SIG_SRS_KEY"/"RTMP_SIG_SRS_VERSION"" << __SRS_CRLF; - return this; -} + std::string start = r->query_get("start"); + if (start.empty()) { + return serve_file(w, r, fullpath); + } -SrsHttpHandler* SrsHttpHandler::res_status_line_error(stringstream& ss, int code, string reason_phrase) -{ - ss << "HTTP/1.1 " << code << " " << reason_phrase << __SRS_CRLF - << "Server: SRS/"RTMP_SIG_SRS_VERSION"" << __SRS_CRLF; - return this; + int offset = ::atoi(start.c_str()); + if (offset <= 0) { + return serve_file(w, r, fullpath); + } + + return serve_flv_stream(w, r, fullpath, offset); } -SrsHttpHandler* SrsHttpHandler::res_content_type(stringstream& ss) +int SrsHttpFileServer::serve_mp4_file(ISrsHttpResponseWriter* w, SrsHttpMessage* r, string fullpath) { - ss << "Content-Type: text/html;charset=utf-8" << __SRS_CRLF - << "Allow: DELETE, GET, HEAD, OPTIONS, POST, PUT" << __SRS_CRLF; - return this; -} + // for flash to request mp4 range in query string. + // for example, http://digitalprimates.net/dash/DashTest.html?url=http://dashdemo.edgesuite.net/digitalprimates/nexus/oops-20120802-manifest.mpd + std::string range = r->query_get("range"); + // or, use bytes to request range, + // for example, http://dashas.castlabs.com/demo/try.html + if (range.empty()) { + range = r->query_get("bytes"); + } -SrsHttpHandler* SrsHttpHandler::res_content_type_xml(stringstream& ss) -{ - ss << "Content-Type: text/xml;charset=utf-8" << __SRS_CRLF - << "Allow: DELETE, GET, HEAD, OPTIONS, POST, PUT" << __SRS_CRLF; - return this; -} + // rollback to serve whole file. + size_t pos = string::npos; + if (range.empty() || (pos = range.find("-")) == string::npos) { + return serve_file(w, r, fullpath); + } -SrsHttpHandler* SrsHttpHandler::res_content_type_javascript(stringstream& ss) -{ - ss << "Content-Type: text/javascript" << __SRS_CRLF - << "Allow: DELETE, GET, HEAD, OPTIONS, POST, PUT" << __SRS_CRLF; - return this; + // parse the start in query string + int start = 0; + if (pos > 0) { + start = ::atoi(range.substr(0, pos).c_str()); + } + + // parse end in query string. + int end = -1; + if (pos < range.length() - 1) { + end = ::atoi(range.substr(pos + 1).c_str()); + } + + // invalid param, serve as whole mp4 file. + if (start < 0 || (end != -1 && start > end)) { + return serve_file(w, r, fullpath); + } + + return serve_mp4_stream(w, r, fullpath, start, end); } -SrsHttpHandler* SrsHttpHandler::res_content_type_swf(stringstream& ss) +int SrsHttpFileServer::serve_flv_stream(ISrsHttpResponseWriter* w, SrsHttpMessage* r, string fullpath, int offset) { - ss << "Content-Type: application/x-shockwave-flash" << __SRS_CRLF - << "Allow: DELETE, GET, HEAD, OPTIONS, POST, PUT" << __SRS_CRLF; - return this; + return serve_file(w, r, fullpath); } -SrsHttpHandler* SrsHttpHandler::res_content_type_css(stringstream& ss) +int SrsHttpFileServer::serve_mp4_stream(ISrsHttpResponseWriter* w, SrsHttpMessage* r, string fullpath, int start, int end) { - ss << "Content-Type: text/css;charset=utf-8" << __SRS_CRLF - << "Allow: DELETE, GET, HEAD, OPTIONS, POST, PUT" << __SRS_CRLF; - return this; + return serve_file(w, r, fullpath); } -SrsHttpHandler* SrsHttpHandler::res_content_type_ico(stringstream& ss) +int SrsHttpFileServer::copy(ISrsHttpResponseWriter* w, SrsFileReader* fs, SrsHttpMessage* r, int size) { - ss << "Content-Type: image/x-icon" << __SRS_CRLF - << "Allow: DELETE, GET, HEAD, OPTIONS, POST, PUT" << __SRS_CRLF; - return this; + int ret = ERROR_SUCCESS; + + int left = size; + char* buf = r->http_ts_send_buffer(); + + while (left > 0) { + ssize_t nread = -1; + int max_read = srs_min(left, SRS_HTTP_TS_SEND_BUFFER_SIZE); + if ((ret = fs->read(buf, max_read, &nread)) != ERROR_SUCCESS) { + break; + } + + left -= nread; + if ((ret = w->write(buf, (int)nread)) != ERROR_SUCCESS) { + break; + } + } + + return ret; } -SrsHttpHandler* SrsHttpHandler::res_content_type_json(stringstream& ss) +SrsHttpMuxEntry::SrsHttpMuxEntry() { - ss << "Content-Type: application/json;charset=utf-8" << __SRS_CRLF - << "Allow: DELETE, GET, HEAD, OPTIONS, POST, PUT" << __SRS_CRLF; - return this; + enabled = true; + explicit_match = false; + handler = NULL; } -SrsHttpHandler* SrsHttpHandler::res_content_type_m3u8(stringstream& ss) +SrsHttpMuxEntry::~SrsHttpMuxEntry() { - ss << "Content-Type: application/x-mpegURL;charset=utf-8" << __SRS_CRLF - << "Allow: DELETE, GET, HEAD, OPTIONS, POST, PUT" << __SRS_CRLF; - return this; + srs_freep(handler); } -SrsHttpHandler* SrsHttpHandler::res_content_type_mpegts(stringstream& ss) +ISrsHttpMatchHijacker::ISrsHttpMatchHijacker() { - ss << "Content-Type: video/MP2T" << __SRS_CRLF - << "Allow: DELETE, GET, HEAD, OPTIONS, POST, PUT" << __SRS_CRLF; - return this; } -SrsHttpHandler* SrsHttpHandler::res_content_type_flv(stringstream& ss) +ISrsHttpMatchHijacker::~ISrsHttpMatchHijacker() { - ss << "Content-Type: video/x-flv" << __SRS_CRLF - << "Allow: DELETE, GET, HEAD, OPTIONS, POST, PUT" << __SRS_CRLF; - return this; } -SrsHttpHandler* SrsHttpHandler::res_content_length(stringstream& ss, int64_t length) +SrsHttpServeMux::SrsHttpServeMux() { - ss << "Content-Length: "<< length << __SRS_CRLF; - return this; } -SrsHttpHandler* SrsHttpHandler::res_enable_crossdomain(stringstream& ss) +SrsHttpServeMux::~SrsHttpServeMux() { - ss << "Access-Control-Allow-Origin: *" << __SRS_CRLF - << "Access-Control-Allow-Methods: " - << "GET, POST, HEAD, PUT, DELETE" << __SRS_CRLF - << "Access-Control-Allow-Headers: " - << "Cache-Control,X-Proxy-Authorization,X-Requested-With,Content-Type" << __SRS_CRLF; - return this; + std::map::iterator it; + for (it = entries.begin(); it != entries.end(); ++it) { + SrsHttpMuxEntry* entry = it->second; + srs_freep(entry); + } + entries.clear(); + + vhosts.clear(); + hijackers.clear(); } -SrsHttpHandler* SrsHttpHandler::res_header_eof(stringstream& ss) +int SrsHttpServeMux::initialize() { - ss << __SRS_CRLF; - return this; + int ret = ERROR_SUCCESS; + // TODO: FIXME: implements it. + return ret; } -SrsHttpHandler* SrsHttpHandler::res_body(stringstream& ss, string body) +void SrsHttpServeMux::hijack(ISrsHttpMatchHijacker* h) { - ss << body; - return this; + std::vector::iterator it = ::find(hijackers.begin(), hijackers.end(), h); + if (it != hijackers.end()) { + return; + } + hijackers.push_back(h); } -int SrsHttpHandler::res_flush(SrsStSocket* skt, stringstream& ss) +void SrsHttpServeMux::unhijack(ISrsHttpMatchHijacker* h) { - return skt->write((void*)ss.str().c_str(), ss.str().length(), NULL); + std::vector::iterator it = ::find(hijackers.begin(), hijackers.end(), h); + if (it == hijackers.end()) { + return; + } + hijackers.erase(it); } -int SrsHttpHandler::res_options(SrsStSocket* skt) +int SrsHttpServeMux::handle(std::string pattern, ISrsHttpHandler* handler) { - std::stringstream ss; + int ret = ERROR_SUCCESS; - res_status_line(ss)->res_content_type(ss) - ->res_content_length(ss, 0)->res_enable_crossdomain(ss) - ->res_header_eof(ss); + srs_assert(handler); + + if (pattern.empty()) { + ret = ERROR_HTTP_PATTERN_EMPTY; + srs_error("http: empty pattern. ret=%d", ret); + return ret; + } + + if (entries.find(pattern) != entries.end()) { + SrsHttpMuxEntry* exists = entries[pattern]; + if (exists->explicit_match) { + ret = ERROR_HTTP_PATTERN_DUPLICATED; + srs_error("http: multiple registrations for %s. ret=%d", pattern.c_str(), ret); + return ret; + } + } + + std::string vhost = pattern; + if (pattern.at(0) != '/') { + if (pattern.find("/") != string::npos) { + vhost = pattern.substr(0, pattern.find("/")); + } + vhosts[vhost] = handler; + } + + if (true) { + SrsHttpMuxEntry* entry = new SrsHttpMuxEntry(); + entry->explicit_match = true; + entry->handler = handler; + entry->pattern = pattern; + entry->handler->entry = entry; + + if (entries.find(pattern) != entries.end()) { + SrsHttpMuxEntry* exists = entries[pattern]; + srs_freep(exists); + } + entries[pattern] = entry; + } + + // Helpful behavior: + // If pattern is /tree/, insert an implicit permanent redirect for /tree. + // It can be overridden by an explicit registration. + if (pattern != "/" && !pattern.empty() && pattern.at(pattern.length() - 1) == '/') { + std::string rpattern = pattern.substr(0, pattern.length() - 1); + SrsHttpMuxEntry* entry = NULL; + + // free the exists not explicit entry + if (entries.find(rpattern) != entries.end()) { + SrsHttpMuxEntry* exists = entries[rpattern]; + if (!exists->explicit_match) { + entry = exists; + } + } + + // create implicit redirect. + if (!entry || entry->explicit_match) { + srs_freep(entry); + + entry = new SrsHttpMuxEntry(); + entry->explicit_match = false; + entry->handler = new SrsHttpRedirectHandler(pattern, SRS_CONSTS_HTTP_MovedPermanently); + entry->pattern = pattern; + entry->handler->entry = entry; + + entries[rpattern] = entry; + } + } - return res_flush(skt, ss); + return ret; } -int SrsHttpHandler::res_text(SrsStSocket* skt, SrsHttpMessage* req, string body) +int SrsHttpServeMux::serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r) { - std::stringstream ss; + int ret = ERROR_SUCCESS; - res_status_line(ss)->res_content_type(ss) - ->res_content_length(ss, (int)body.length()); - - if (req->requires_crossdomain()) { - res_enable_crossdomain(ss); + ISrsHttpHandler* h = NULL; + if ((ret = find_handler(r, &h)) != ERROR_SUCCESS) { + srs_error("find handler failed. ret=%d", ret); + return ret; } - res_header_eof(ss) - ->res_body(ss, body); + srs_assert(h); + if ((ret = h->serve_http(w, r)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("handler serve http failed. ret=%d", ret); + } + return ret; + } - return res_flush(skt, ss); + return ret; } -int SrsHttpHandler::res_xml(SrsStSocket* skt, SrsHttpMessage* req, string body) +int SrsHttpServeMux::find_handler(SrsHttpMessage* r, ISrsHttpHandler** ph) { - std::stringstream ss; + int ret = ERROR_SUCCESS; - res_status_line(ss)->res_content_type_xml(ss) - ->res_content_length(ss, (int)body.length()); - - if (req->requires_crossdomain()) { - res_enable_crossdomain(ss); + // TODO: FIXME: support the path . and .. + if (r->url().find("..") != std::string::npos) { + ret = ERROR_HTTP_URL_NOT_CLEAN; + srs_error("htt url not canonical, url=%s. ret=%d", r->url().c_str(), ret); + return ret; } - res_header_eof(ss) - ->res_body(ss, body); + if ((ret = match(r, ph)) != ERROR_SUCCESS) { + srs_error("http match handler failed. ret=%d", ret); + return ret; + } + + // always hijack. + if (!hijackers.empty()) { + // notice all hijacker the match failed. + std::vector::iterator it; + for (it = hijackers.begin(); it != hijackers.end(); ++it) { + ISrsHttpMatchHijacker* hijacker = *it; + if ((ret = hijacker->hijack(r, ph)) != ERROR_SUCCESS) { + srs_error("hijacker match failed. ret=%d", ret); + return ret; + } + } + } + + if (*ph == NULL) { + // TODO: FIXME: memory leak. + *ph = new SrsHttpNotFoundHandler(); + } - return res_flush(skt, ss); + return ret; } -int SrsHttpHandler::res_javascript(SrsStSocket* skt, SrsHttpMessage* req, string body) +int SrsHttpServeMux::match(SrsHttpMessage* r, ISrsHttpHandler** ph) { - std::stringstream ss; + int ret = ERROR_SUCCESS; + + std::string path = r->path(); + + // Host-specific pattern takes precedence over generic ones + if (!vhosts.empty() && vhosts.find(r->host()) != vhosts.end()) { + path = r->host() + path; + } + + int nb_matched = 0; + ISrsHttpHandler* h = NULL; - res_status_line(ss)->res_content_type_javascript(ss) - ->res_content_length(ss, (int)body.length()); + std::map::iterator it; + for (it = entries.begin(); it != entries.end(); ++it) { + std::string pattern = it->first; + SrsHttpMuxEntry* entry = it->second; - if (req->requires_crossdomain()) { - res_enable_crossdomain(ss); + if (!entry->enabled) { + continue; + } + + if (!path_match(pattern, path)) { + continue; + } + + if (!h || (int)pattern.length() > nb_matched) { + nb_matched = (int)pattern.length(); + h = entry->handler; + } } - res_header_eof(ss) - ->res_body(ss, body); + *ph = h; - return res_flush(skt, ss); + return ret; } -int SrsHttpHandler::res_swf(SrsStSocket* skt, SrsHttpMessage* req, string body) +bool SrsHttpServeMux::path_match(string pattern, string path) { - std::stringstream ss; + if (pattern.empty()) { + return false; + } - res_status_line(ss)->res_content_type_swf(ss) - ->res_content_length(ss, (int)body.length()); - - if (req->requires_crossdomain()) { - res_enable_crossdomain(ss); + int n = (int)pattern.length(); + + // not endswith '/', exactly match. + if (pattern.at(n - 1) != '/') { + return pattern == path; } - res_header_eof(ss) - ->res_body(ss, body); + // endswith '/', match any, + // for example, '/api/' match '/api/[N]' + if ((int)path.length() >= n) { + if (memcmp(pattern.data(), path.data(), n) == 0) { + return true; + } + } - return res_flush(skt, ss); + return false; } -int SrsHttpHandler::res_css(SrsStSocket* skt, SrsHttpMessage* req, string body) +SrsHttpResponseWriter::SrsHttpResponseWriter(SrsStSocket* io) { - std::stringstream ss; - - res_status_line(ss)->res_content_type_css(ss) - ->res_content_length(ss, (int)body.length()); - - if (req->requires_crossdomain()) { - res_enable_crossdomain(ss); + skt = io; + hdr = new SrsHttpHeader(); + header_wrote = false; + status = SRS_CONSTS_HTTP_OK; + content_length = -1; + written = 0; + header_sent = false; +} + +SrsHttpResponseWriter::~SrsHttpResponseWriter() +{ + srs_freep(hdr); +} + +int SrsHttpResponseWriter::final_request() +{ + // complete the chunked encoding. + if (content_length == -1) { + std::stringstream ss; + ss << 0 << SRS_HTTP_CRLF << SRS_HTTP_CRLF; + std::string ch = ss.str(); + return skt->write((void*)ch.data(), (int)ch.length(), NULL); } - res_header_eof(ss) - ->res_body(ss, body); - - return res_flush(skt, ss); + // flush when send with content length + return write(NULL, 0); } -int SrsHttpHandler::res_ico(SrsStSocket* skt, SrsHttpMessage* req, string body) +SrsHttpHeader* SrsHttpResponseWriter::header() { - std::stringstream ss; + return hdr; +} + +int SrsHttpResponseWriter::write(char* data, int size) +{ + int ret = ERROR_SUCCESS; - res_status_line(ss)->res_content_type_ico(ss) - ->res_content_length(ss, (int)body.length()); - - if (req->requires_crossdomain()) { - res_enable_crossdomain(ss); + if (!header_wrote) { + write_header(SRS_CONSTS_HTTP_OK); } - res_header_eof(ss) - ->res_body(ss, body); + written += size; + if (content_length != -1 && written > content_length) { + ret = ERROR_HTTP_CONTENT_LENGTH; + srs_error("http: exceed content length. ret=%d", ret); + return ret; + } - return res_flush(skt, ss); -} + if ((ret = send_header(data, size)) != ERROR_SUCCESS) { + srs_error("http: send header failed. ret=%d", ret); + return ret; + } -int SrsHttpHandler::res_m3u8(SrsStSocket* skt, SrsHttpMessage* req, string body) -{ - std::stringstream ss; + // ignore NULL content. + if (!data) { + return ret; + } - res_status_line(ss)->res_content_type_m3u8(ss) - ->res_content_length(ss, (int)body.length()); - - if (req->requires_crossdomain()) { - res_enable_crossdomain(ss); + // directly send with content length + if (content_length != -1) { + return skt->write((void*)data, size, NULL); } - res_header_eof(ss) - ->res_body(ss, body); + // send in chunked encoding. + std::stringstream ss; + ss << hex << size << SRS_HTTP_CRLF; + std::string ch = ss.str(); + if ((ret = skt->write((void*)ch.data(), (int)ch.length(), NULL)) != ERROR_SUCCESS) { + return ret; + } + if ((ret = skt->write((void*)data, size, NULL)) != ERROR_SUCCESS) { + return ret; + } + if ((ret = skt->write((void*)SRS_HTTP_CRLF, 2, NULL)) != ERROR_SUCCESS) { + return ret; + } - return res_flush(skt, ss); + return ret; } -int SrsHttpHandler::res_mpegts(SrsStSocket* skt, SrsHttpMessage* req, string body) +void SrsHttpResponseWriter::write_header(int code) { - std::stringstream ss; - - res_status_line(ss)->res_content_type_mpegts(ss) - ->res_content_length(ss, (int)body.length()); - - if (req->requires_crossdomain()) { - res_enable_crossdomain(ss); + if (header_wrote) { + srs_warn("http: multiple write_header calls, code=%d", code); + return; } - res_header_eof(ss) - ->res_body(ss, body); + header_wrote = true; + status = code; - return res_flush(skt, ss); + // parse the content length from header. + content_length = hdr->content_length(); } -int SrsHttpHandler::res_json(SrsStSocket* skt, SrsHttpMessage* req, string json) +int SrsHttpResponseWriter::send_header(char* data, int size) { + int ret = ERROR_SUCCESS; + + if (header_sent) { + return ret; + } + header_sent = true; + std::stringstream ss; - res_status_line(ss)->res_content_type_json(ss) - ->res_content_length(ss, (int)json.length()); + // status_line + ss << "HTTP/1.1 " << status << " " + << srs_generate_http_status_text(status) << SRS_HTTP_CRLF; - if (req->requires_crossdomain()) { - res_enable_crossdomain(ss); + // detect content type + if (srs_go_http_body_allowd(status)) { + if (hdr->content_type().empty()) { + hdr->set_content_type(srs_go_http_detect(data, size)); + } } - res_header_eof(ss) - ->res_body(ss, json); + // set server if not set. + if (hdr->get("Server").empty()) { + hdr->set("Server", RTMP_SIG_SRS_KEY"/"RTMP_SIG_SRS_VERSION); + } + + // chunked encoding + if (content_length == -1) { + hdr->set("Transfer-Encoding", "chunked"); + } + + // keep alive to make vlc happy. + hdr->set("Connection", "Keep-Alive"); - return res_flush(skt, ss); + // write headers + hdr->write(ss); + + // header_eof + ss << SRS_HTTP_CRLF; + + std::string buf = ss.str(); + return skt->write((void*)buf.c_str(), buf.length(), NULL); } -int SrsHttpHandler::res_error(SrsStSocket* skt, SrsHttpMessage* req, int code, string reason_phrase, string body) +SrsHttpResponseReader::SrsHttpResponseReader(SrsHttpMessage* msg, SrsStSocket* io) { - std::stringstream ss; + skt = io; + owner = msg; + is_eof = false; + nb_total_read = 0; + nb_left_chunk = 0; + buffer = NULL; +} - res_status_line_error(ss, code, reason_phrase)->res_content_type_json(ss) - ->res_content_length(ss, (int)body.length()); - - if (req->requires_crossdomain()) { - res_enable_crossdomain(ss); +SrsHttpResponseReader::~SrsHttpResponseReader() +{ +} + +int SrsHttpResponseReader::initialize(SrsFastBuffer* body) +{ + int ret = ERROR_SUCCESS; + + nb_chunk = 0; + nb_left_chunk = 0; + nb_total_read = 0; + buffer = body; + + return ret; +} + +bool SrsHttpResponseReader::eof() +{ + return is_eof; +} + +int SrsHttpResponseReader::read(char* data, int nb_data, int* nb_read) +{ + int ret = ERROR_SUCCESS; + + if (is_eof) { + ret = ERROR_HTTP_RESPONSE_EOF; + srs_error("http: response EOF. ret=%d", ret); + return ret; } - res_header_eof(ss) - ->res_body(ss, body); + // chunked encoding. + if (owner->is_chunked()) { + return read_chunked(data, nb_data, nb_read); + } + + // read by specified content-length + int max = (int)owner->content_length() - (int)nb_total_read; + if (max <= 0) { + is_eof = true; + return ret; + } - return res_flush(skt, ss); + // change the max to read. + nb_data = srs_min(nb_data, max); + return read_specified(data, nb_data, nb_read); } -#ifdef SRS_AUTO_HTTP_API -SrsHttpHandler* SrsHttpHandler::create_http_api() +int SrsHttpResponseReader::read_chunked(char* data, int nb_data, int* nb_read) { - return new SrsApiRoot(); + int ret = ERROR_SUCCESS; + + // when no bytes left in chunk, + // parse the chunk length first. + if (nb_left_chunk <= 0) { + char* at = NULL; + int length = 0; + while (!at) { + // find the CRLF of chunk header end. + char* start = buffer->bytes(); + char* end = start + buffer->size(); + for (char* p = start; p < end - 1; p++) { + if (p[0] == SRS_HTTP_CR && p[1] == SRS_HTTP_LF) { + // invalid chunk, ignore. + if (p == start) { + ret = ERROR_HTTP_INVALID_CHUNK_HEADER; + srs_error("chunk header start with CRLF. ret=%d", ret); + return ret; + } + length = (int)(p - start + 2); + at = buffer->read_slice(length); + break; + } + } + + // got at, ok. + if (at) { + break; + } + + // when empty, only grow 1bytes, but the buffer will cache more. + if ((ret = buffer->grow(skt, buffer->size() + 1)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("read body from server failed. ret=%d", ret); + } + return ret; + } + } + srs_assert(length >= 3); + + // it's ok to set the pos and pos+1 to NULL. + at[length - 1] = 0; + at[length - 2] = 0; + + // size is the bytes size, excludes the chunk header and end CRLF. + int ilength = (int)::strtol(at, NULL, 16); + if (ilength < 0) { + ret = ERROR_HTTP_INVALID_CHUNK_HEADER; + srs_error("chunk header negative, length=%d. ret=%d", ilength, ret); + return ret; + } + + // all bytes in chunk is left now. + nb_chunk = nb_left_chunk = ilength; + } + + if (nb_chunk <= 0) { + // for the last chunk, eof. + is_eof = true; + } else { + // for not the last chunk, there must always exists bytes. + // left bytes in chunk, read some. + srs_assert(nb_left_chunk); + + int nb_bytes = srs_min(nb_left_chunk, nb_data); + ret = read_specified(data, nb_bytes, &nb_bytes); + + // the nb_bytes used for output already read size of bytes. + if (nb_read) { + *nb_read = nb_bytes; + } + nb_left_chunk -= nb_bytes; + srs_info("http: read %d bytes of chunk", nb_bytes); + + // error or still left bytes in chunk, ignore and read in future. + if (nb_left_chunk > 0 || (ret != ERROR_SUCCESS)) { + return ret; + } + srs_info("http: read total chunk %dB", nb_chunk); + } + + // for both the last or not, the CRLF of chunk payload end. + if ((ret = buffer->grow(skt, 2)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("read EOF of chunk from server failed. ret=%d", ret); + } + return ret; + } + buffer->read_slice(2); + + return ret; } -#endif -#ifdef SRS_AUTO_HTTP_SERVER -SrsHttpHandler* SrsHttpHandler::create_http_stream() +int SrsHttpResponseReader::read_specified(char* data, int nb_data, int* nb_read) { - return new SrsHttpRoot(); + int ret = ERROR_SUCCESS; + + if (buffer->size() <= 0) { + // when empty, only grow 1bytes, but the buffer will cache more. + if ((ret = buffer->grow(skt, 1)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("read body from server failed. ret=%d", ret); + } + return ret; + } + } + + int nb_bytes = srs_min(nb_data, buffer->size()); + + // read data to buffer. + srs_assert(nb_bytes); + char* p = buffer->read_slice(nb_bytes); + memcpy(data, p, nb_bytes); + if (nb_read) { + *nb_read = nb_bytes; + } + + // increase the total read to determine whether EOF. + nb_total_read += nb_bytes; + + // for not chunked + if (!owner->is_chunked()) { + // when read completed, eof. + if (nb_total_read >= (int)owner->content_length()) { + is_eof = true; + } + } + + return ret; } -#endif -SrsHttpMessage::SrsHttpMessage() +SrsHttpMessage::SrsHttpMessage(SrsStSocket* io, SrsConnection* c) { - _body = new SrsBuffer(); - _state = SrsHttpParseStateInit; + conn = c; + chunked = false; _uri = new SrsHttpUri(); - _match = NULL; - _requires_crossdomain = false; - _http_ts_send_buffer = new char[__SRS_HTTP_TS_SEND_BUFFER_SIZE]; + _body = new SrsHttpResponseReader(this, io); + _http_ts_send_buffer = new char[SRS_HTTP_TS_SEND_BUFFER_SIZE]; } SrsHttpMessage::~SrsHttpMessage() { srs_freep(_body); srs_freep(_uri); - srs_freep(_match); srs_freep(_http_ts_send_buffer); } -char* SrsHttpMessage::http_ts_send_buffer() +int SrsHttpMessage::update(string url, http_parser* header, SrsFastBuffer* body, vector& headers) { - return _http_ts_send_buffer; -} + int ret = ERROR_SUCCESS; -void SrsHttpMessage::reset() -{ - _state = SrsHttpParseStateInit; - _body->erase(_body->length()); - _url = ""; -} + _url = url; + _header = *header; + _headers = headers; -int SrsHttpMessage::parse_uri() -{ - // filter url according to HTTP specification. + // whether chunked. + std::string transfer_encoding = get_request_header("Transfer-Encoding"); + chunked = (transfer_encoding == "chunked"); - // remove the duplicated slash. - std::string filtered_url = srs_string_replace(_url, "//", "/"); + // set the buffer. + if ((ret = _body->initialize(body)) != ERROR_SUCCESS) { + return ret; + } + + // parse uri from url. + std::string host = get_request_header("Host"); + + // donot parse the empty host for uri, + // for example, the response contains no host, + // ignore it is ok. + if (host.empty()) { + return ret; + } - // remove the last / to match resource. - filtered_url = srs_string_trim_end(filtered_url, "/"); + // parse uri to schema/server:port/path?query + std::string uri = "http://" + host + _url; + if ((ret = _uri->initialize(uri)) != ERROR_SUCCESS) { + return ret; + } + + // must format as key=value&...&keyN=valueN + std::string q = _uri->get_query(); + size_t pos = string::npos; + while (!q.empty()) { + std::string k = q; + if ((pos = q.find("=")) != string::npos) { + k = q.substr(0, pos); + q = q.substr(pos + 1); + } else { + q = ""; + } + + std::string v = q; + if ((pos = q.find("&")) != string::npos) { + v = q.substr(0, pos); + q = q.substr(pos + 1); + } else { + q = ""; + } + + _query[k] = v; + } - // if empty, use root. - if (filtered_url.empty()) { - filtered_url = "/"; + // parse ext. + _ext = _uri->get_path(); + if ((pos = _ext.rfind(".")) != string::npos) { + _ext = _ext.substr(pos); + } else { + _ext = ""; } - return _uri->initialize(filtered_url); + return ret; } -bool SrsHttpMessage::is_complete() +char* SrsHttpMessage::http_ts_send_buffer() { - return _state == SrsHttpParseStateComplete; + return _http_ts_send_buffer; +} + +SrsConnection* SrsHttpMessage::connection() +{ + return conn; } u_int8_t SrsHttpMessage::method() @@ -592,6 +1176,11 @@ u_int8_t SrsHttpMessage::method() return (u_int8_t)_header.method; } +u_int16_t SrsHttpMessage::status_code() +{ + return (u_int16_t)_header.status_code; +} + string SrsHttpMessage::method_str() { if (is_http_get()) { @@ -638,12 +1227,18 @@ bool SrsHttpMessage::is_http_options() return _header.method == SRS_CONSTS_HTTP_OPTIONS; } +bool SrsHttpMessage::is_chunked() +{ + return chunked; +} + string SrsHttpMessage::uri() { std::string uri = _uri->get_schema(); if (uri.empty()) { - uri += "http://"; + uri += "http"; } + uri += "://"; uri += host(); uri += path(); @@ -657,7 +1252,7 @@ string SrsHttpMessage::url() string SrsHttpMessage::host() { - return get_request_header("Host"); + return _uri->get_host(); } string SrsHttpMessage::path() @@ -665,30 +1260,37 @@ string SrsHttpMessage::path() return _uri->get_path(); } -string SrsHttpMessage::query() +string SrsHttpMessage::ext() { - return _uri->get_query(); + return _ext; } -string SrsHttpMessage::body() +int SrsHttpMessage::body_read_all(string& body) { - std::string b; + int ret = ERROR_SUCCESS; + + // cache to read. + char* buf = new char[SRS_HTTP_READ_CACHE_BYTES]; + SrsAutoFree(char, buf); - if (_body && _body->length() > 0) { - b.append(_body->bytes(), _body->length()); + // whatever, read util EOF. + while (!_body->eof()) { + int nb_read = 0; + if ((ret = _body->read(buf, SRS_HTTP_READ_CACHE_BYTES, &nb_read)) != ERROR_SUCCESS) { + return ret; + } + + if (nb_read > 0) { + body.append(buf, nb_read); + } } - return b; -} - -char* SrsHttpMessage::body_raw() -{ - return _body? _body->bytes() : NULL; + return ret; } -int64_t SrsHttpMessage::body_size() +ISrsHttpResponseReader* SrsHttpMessage::body_reader() { - return (int64_t)_body->length(); + return _body; } int64_t SrsHttpMessage::content_length() @@ -696,64 +1298,12 @@ int64_t SrsHttpMessage::content_length() return _header.content_length; } -SrsHttpHandlerMatch* SrsHttpMessage::match() -{ - return _match; -} - -bool SrsHttpMessage::requires_crossdomain() -{ - return _requires_crossdomain; -} - -void SrsHttpMessage::set_url(string url) -{ - _url = url; -} - -void SrsHttpMessage::set_state(SrsHttpParseState state) -{ - _state = state; -} - -void SrsHttpMessage::set_header(http_parser* header) -{ - memcpy(&_header, header, sizeof(http_parser)); -} - -void SrsHttpMessage::set_match(SrsHttpHandlerMatch* match) -{ - srs_freep(_match); - _match = match; -} - -void SrsHttpMessage::set_requires_crossdomain(bool requires_crossdomain) -{ - _requires_crossdomain = requires_crossdomain; -} - -void SrsHttpMessage::append_body(const char* body, int length) -{ - _body->append(body, length); -} - string SrsHttpMessage::query_get(string key) { - std::string q = query(); - size_t pos = std::string::npos; + std::string v; - // must format as key=value&...&keyN=valueN - if ((pos = key.find("=")) != key.length() - 1) { - key = key + "="; - } - - if ((pos = q.find(key)) == std::string::npos) { - return ""; - } - - std::string v = q.substr(pos + key.length()); - if ((pos = v.find("&")) != std::string::npos) { - v = v.substr(0, pos); + if (_query.find(key) != _query.end()) { + v = _query[key]; } return v; @@ -761,33 +1311,28 @@ string SrsHttpMessage::query_get(string key) int SrsHttpMessage::request_header_count() { - return (int)headers.size(); + return (int)_headers.size(); } string SrsHttpMessage::request_header_key_at(int index) { srs_assert(index < request_header_count()); - SrsHttpHeaderField item = headers[index]; + SrsHttpHeaderField item = _headers[index]; return item.first; } string SrsHttpMessage::request_header_value_at(int index) { srs_assert(index < request_header_count()); - SrsHttpHeaderField item = headers[index]; + SrsHttpHeaderField item = _headers[index]; return item.second; } -void SrsHttpMessage::set_request_header(string key, string value) -{ - headers.push_back(std::make_pair(key, value)); -} - string SrsHttpMessage::get_request_header(string name) { std::vector::iterator it; - for (it = headers.begin(); it != headers.end(); ++it) { + for (it = _headers.begin(); it != _headers.end(); ++it) { SrsHttpHeaderField& elem = *it; std::string key = elem.first; std::string value = elem.second; @@ -799,14 +1344,40 @@ string SrsHttpMessage::get_request_header(string name) return ""; } +SrsRequest* SrsHttpMessage::to_request(string vhost) +{ + SrsRequest* req = new SrsRequest(); + + req->app = _uri->get_path(); + size_t pos = string::npos; + if ((pos = req->app.rfind("/")) != string::npos) { + req->stream = req->app.substr(pos + 1); + req->app = req->app.substr(0, pos); + } + if ((pos = req->stream.rfind(".")) != string::npos) { + req->stream = req->stream.substr(0, pos); + } + + req->tcUrl = "rtmp://" + vhost + req->app; + req->pageUrl = get_request_header("Referer"); + req->objectEncoding = 0; + + srs_discovery_tc_url(req->tcUrl, + req->schema, req->host, req->vhost, req->app, req->port, + req->param); + req->strip(); + + return req; +} + SrsHttpParser::SrsHttpParser() { - msg = NULL; + buffer = new SrsFastBuffer(); } SrsHttpParser::~SrsHttpParser() { - srs_freep(msg); + srs_freep(buffer); } int SrsHttpParser::initialize(enum http_parser_type type) @@ -829,34 +1400,42 @@ int SrsHttpParser::initialize(enum http_parser_type type) return ret; } -int SrsHttpParser::parse_message(SrsStSocket* skt, SrsHttpMessage** ppmsg) +int SrsHttpParser::parse_message(SrsStSocket* skt, SrsConnection* conn, SrsHttpMessage** ppmsg) { *ppmsg = NULL; int ret = ERROR_SUCCESS; - - // the msg must be always NULL - srs_assert(msg == NULL); - msg = new SrsHttpMessage(); // reset request data. - filed_name = ""; - - // reset response header. - msg->reset(); + field_name = ""; + field_value = ""; + expect_field_name = true; + state = SrsHttpParseStateInit; + header = http_parser(); + url = ""; + headers.clear(); + header_parsed = 0; // do parse if ((ret = parse_message_imp(skt)) != ERROR_SUCCESS) { if (!srs_is_client_gracefully_close(ret)) { srs_error("parse http msg failed. ret=%d", ret); } + return ret; + } + + // create msg + SrsHttpMessage* msg = new SrsHttpMessage(skt, conn); + + // initalize http msg, parse url. + if ((ret = msg->update(url, &header, buffer, headers)) != ERROR_SUCCESS) { + srs_error("initialize http msg failed. ret=%d", ret); srs_freep(msg); return ret; } // parse ok, return the msg. *ppmsg = msg; - msg = NULL; return ret; } @@ -865,42 +1444,52 @@ int SrsHttpParser::parse_message_imp(SrsStSocket* skt) { int ret = ERROR_SUCCESS; - // the msg should never be NULL - srs_assert(msg != NULL); - - // parser header. - char buf[SRS_HTTP_HEADER_BUFFER]; - for (;;) { - ssize_t nread; - if ((ret = skt->read(buf, (size_t)sizeof(buf), &nread)) != ERROR_SUCCESS) { - if (!srs_is_client_gracefully_close(ret)) { - srs_error("read body from server failed. ret=%d", ret); - } - return ret; + while (true) { + ssize_t nparsed = 0; + + // when buffer not empty, parse it. + if (buffer->size() > 0) { + nparsed = http_parser_execute(&parser, &settings, buffer->bytes(), buffer->size()); + srs_info("buffer=%d, nparsed=%d, header=%d", buffer->size(), (int)nparsed, header_parsed); } - ssize_t nparsed = http_parser_execute(&parser, &settings, buf, nread); - srs_info("read_size=%d, nparsed=%d", (int)nread, (int)nparsed); + // consume the parsed bytes. + if (nparsed && header_parsed) { + buffer->read_slice(header_parsed); + } - // check header size. - if (msg->is_complete()) { - return ret; + // ok atleast header completed, + // never wait for body completed, for maybe chunked. + if (state == SrsHttpParseStateHeaderComplete || state == SrsHttpParseStateMessageComplete) { + break; } - if (nparsed != nread) { - ret = ERROR_HTTP_PARSE_HEADER; - srs_error("parse response error, parsed(%d)!=read(%d), ret=%d", (int)nparsed, (int)nread, ret); - return ret; + // when nothing parsed, read more to parse. + if (nparsed == 0) { + // when requires more, only grow 1bytes, but the buffer will cache more. + if ((ret = buffer->grow(skt, buffer->size() + 1)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("read body from server failed. ret=%d", ret); + } + return ret; + } } } + // parse last header. + if (!field_name.empty() && !field_value.empty()) { + headers.push_back(std::make_pair(field_name, field_value)); + } + return ret; } int SrsHttpParser::on_message_begin(http_parser* parser) { SrsHttpParser* obj = (SrsHttpParser*)parser->data; - obj->msg->set_state(SrsHttpParseStateStart); + srs_assert(obj); + + obj->state = SrsHttpParseStateStart; srs_info("***MESSAGE BEGIN***"); @@ -910,7 +1499,12 @@ int SrsHttpParser::on_message_begin(http_parser* parser) int SrsHttpParser::on_headers_complete(http_parser* parser) { SrsHttpParser* obj = (SrsHttpParser*)parser->data; - obj->msg->set_header(parser); + srs_assert(obj); + + obj->header = *parser; + // save the parser when header parse completed. + obj->state = SrsHttpParseStateHeaderComplete; + obj->header_parsed = (int)parser->nread; srs_info("***HEADERS COMPLETE***"); @@ -921,8 +1515,10 @@ int SrsHttpParser::on_headers_complete(http_parser* parser) int SrsHttpParser::on_message_complete(http_parser* parser) { SrsHttpParser* obj = (SrsHttpParser*)parser->data; - // save the parser when header parse completed. - obj->msg->set_state(SrsHttpParseStateComplete); + srs_assert(obj); + + // save the parser when body parse completed. + obj->state = SrsHttpParseStateMessageComplete; srs_info("***MESSAGE COMPLETE***\n"); @@ -932,13 +1528,10 @@ int SrsHttpParser::on_message_complete(http_parser* parser) int SrsHttpParser::on_url(http_parser* parser, const char* at, size_t length) { SrsHttpParser* obj = (SrsHttpParser*)parser->data; + srs_assert(obj); if (length > 0) { - std::string url; - - url.append(at, (int)length); - - obj->msg->set_url(url); + obj->url.append(at, (int)length); } srs_info("Method: %d, Url: %.*s", parser->method, (int)length, at); @@ -949,45 +1542,44 @@ int SrsHttpParser::on_url(http_parser* parser, const char* at, size_t length) int SrsHttpParser::on_header_field(http_parser* parser, const char* at, size_t length) { SrsHttpParser* obj = (SrsHttpParser*)parser->data; + srs_assert(obj); + + // field value=>name, reap the field. + if (!obj->expect_field_name) { + obj->headers.push_back(std::make_pair(obj->field_name, obj->field_value)); + + // reset the field name when parsed. + obj->field_name = ""; + obj->field_value = ""; + } + obj->expect_field_name = true; if (length > 0) { - srs_assert(obj); - obj->filed_name.append(at, (int)length); + obj->field_name.append(at, (int)length); } - srs_info("Header field: %.*s", (int)length, at); + srs_info("Header field(%d bytes): %.*s", (int)length, (int)length, at); return 0; } int SrsHttpParser::on_header_value(http_parser* parser, const char* at, size_t length) { SrsHttpParser* obj = (SrsHttpParser*)parser->data; + srs_assert(obj); if (length > 0) { - srs_assert(obj); - srs_assert(obj->msg); - - std::string field_value; - field_value.append(at, (int)length); - - obj->msg->set_request_header(obj->filed_name, field_value); - obj->filed_name = ""; + obj->field_value.append(at, (int)length); } + obj->expect_field_name = false; - srs_info("Header value: %.*s", (int)length, at); + srs_info("Header value(%d bytes): %.*s", (int)length, (int)length, at); return 0; } int SrsHttpParser::on_body(http_parser* parser, const char* at, size_t length) { SrsHttpParser* obj = (SrsHttpParser*)parser->data; - - if (length > 0) { - srs_assert(obj); - srs_assert(obj->msg); - - obj->msg->append_body(at, (int)length); - } + srs_assert(obj); srs_info("Body: %.*s", (int)length, at); diff --git a/trunk/src/app/srs_app_http.hpp b/trunk/src/app/srs_app_http.hpp index a9dd80a597..190999b506 100644 --- a/trunk/src/app/srs_app_http.hpp +++ b/trunk/src/app/srs_app_http.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -31,6 +31,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #ifdef SRS_AUTO_HTTP_PARSER +#include #include #include #include @@ -38,164 +39,434 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include +#include -class SrsBuffer; class SrsRequest; class SrsStSocket; class SrsHttpUri; class SrsHttpMessage; -class SrsHttpHandler; +class SrsFileReader; +class SrsSimpleBuffer; +class SrsHttpMuxEntry; +class ISrsHttpResponseWriter; +class SrsFastBuffer; +class SrsConnection; // http specification // CR = -#define __SRS_CR "\r" // 0x0D +#define SRS_HTTP_CR SRS_CONSTS_CR // 0x0D // LF = -#define __SRS_LF "\n" // 0x0A +#define SRS_HTTP_LF SRS_CONSTS_LF // 0x0A // SP = -#define __SRS_SP " " // 0x20 +#define SRS_HTTP_SP ' ' // 0x20 // HT = -#define __SRS_HT "\x09" // 0x09 +#define SRS_HTTP_HT '\x09' // 0x09 // HTTP/1.1 defines the sequence CR LF as the end-of-line marker for all // protocol elements except the entity-body (see appendix 19.3 for // tolerant applications). -#define __SRS_CRLF "\r\n" // 0x0D0A -#define __SRS_CRLFCRLF "\r\n\r\n" // 0x0D0A0D0A +#define SRS_HTTP_CRLF "\r\n" // 0x0D0A +#define SRS_HTTP_CRLFCRLF "\r\n\r\n" // 0x0D0A0D0A // @see SrsHttpMessage._http_ts_send_buffer -#define __SRS_HTTP_TS_SEND_BUFFER_SIZE 4096 +#define SRS_HTTP_TS_SEND_BUFFER_SIZE 4096 -// compare the path. -// full compare, extractly match. -// used for api match. -extern bool srs_path_equals(const char* expect, const char* path, int nb_path); -// compare the path use like, -// used for http stream to match, -// if the path like the requires -extern bool srs_path_like(const char* expect, const char* path, int nb_path); +// helper function: response in json format. +extern int srs_go_http_response_json(ISrsHttpResponseWriter* w, std::string data); // state of message enum SrsHttpParseState { SrsHttpParseStateInit = 0, SrsHttpParseStateStart, - SrsHttpParseStateComplete + SrsHttpParseStateHeaderComplete, + SrsHttpParseStateMessageComplete }; -/** -* the matched handler info. -*/ -class SrsHttpHandlerMatch +// A Header represents the key-value pairs in an HTTP header. +class SrsHttpHeader { +private: + std::map headers; +public: + SrsHttpHeader(); + virtual ~SrsHttpHeader(); +public: + // Add adds the key, value pair to the header. + // It appends to any existing values associated with key. + virtual void set(std::string key, std::string value); + // Get gets the first value associated with the given key. + // If there are no values associated with the key, Get returns "". + // To access multiple values of a key, access the map directly + // with CanonicalHeaderKey. + virtual std::string get(std::string key); +public: + /** + * get the content length. -1 if not set. + */ + virtual int64_t content_length(); + /** + * set the content length by header "Content-Length" + */ + virtual void set_content_length(int64_t size); public: - SrsHttpHandler* handler; - std::string matched_url; - std::string unmatched_url; + /** + * get the content type. empty string if not set. + */ + virtual std::string content_type(); + /** + * set the content type by header "Content-Type" + */ + virtual void set_content_type(std::string ct); public: - SrsHttpHandlerMatch(); + /** + * write all headers to string stream. + */ + virtual void write(std::stringstream& ss); +}; + +// A ResponseWriter interface is used by an HTTP handler to +// construct an HTTP response. +// Usage 1, response with specified length content: +// ISrsHttpResponseWriter* w; // create or get response. +// std::string msg = "Hello, HTTP!"; +// w->header()->set_content_type("text/plain; charset=utf-8"); +// w->header()->set_content_length(msg.length()); +// w->write_header(SRS_CONSTS_HTTP_OK); +// w->write((char*)msg.data(), (int)msg.length()); +// w->final_request(); // optional flush. +// Usage 2, response with HTTP code only, zero content length. +// ISrsHttpResponseWriter* w; // create or get response. +// w->header()->set_content_length(0); +// w->write_header(SRS_CONSTS_HTTP_OK); +// w->final_request(); +// Usage 3, response in chunked encoding. +// ISrsHttpResponseWriter* w; // create or get response. +// std::string msg = "Hello, HTTP!"; +// w->header()->set_content_type("application/octet-stream"); +// w->write_header(SRS_CONSTS_HTTP_OK); +// w->write((char*)msg.data(), (int)msg.length()); +// w->write((char*)msg.data(), (int)msg.length()); +// w->write((char*)msg.data(), (int)msg.length()); +// w->write((char*)msg.data(), (int)msg.length()); +// w->final_request(); // required to end the chunked and flush. +class ISrsHttpResponseWriter +{ +public: + ISrsHttpResponseWriter(); + virtual ~ISrsHttpResponseWriter(); +public: + // when chunked mode, + // final the request to complete the chunked encoding. + // for no-chunked mode, + // final to send request, for example, content-length is 0. + virtual int final_request() = 0; + + // Header returns the header map that will be sent by WriteHeader. + // Changing the header after a call to WriteHeader (or Write) has + // no effect. + virtual SrsHttpHeader* header() = 0; + + // Write writes the data to the connection as part of an HTTP reply. + // If WriteHeader has not yet been called, Write calls WriteHeader(http.StatusOK) + // before writing the data. If the Header does not contain a + // Content-Type line, Write adds a Content-Type set to the result of passing + // the initial 512 bytes of written data to DetectContentType. + // @param data, the data to send. NULL to flush header only. + virtual int write(char* data, int size) = 0; + + // WriteHeader sends an HTTP response header with status code. + // If WriteHeader is not called explicitly, the first call to Write + // will trigger an implicit WriteHeader(http.StatusOK). + // Thus explicit calls to WriteHeader are mainly used to + // send error codes. + // @remark, user must set header then write or write_header. + virtual void write_header(int code) = 0; }; /** -* resource handler for HTTP RESTful api. +* the reader interface for http response. */ -class SrsHttpHandler +class ISrsHttpResponseReader { -protected: +public: + ISrsHttpResponseReader(); + virtual ~ISrsHttpResponseReader(); +public: /** - * we use handler chain to process request. + * whether response read EOF. */ - std::vector handlers; + virtual bool eof() = 0; + /** + * read from the response body. + * @param data, the buffer to read data buffer to. + * @param nb_data, the max size of data buffer. + * @param nb_read, the actual read size of bytes. NULL to ignore. + * @remark when eof(), return error. + */ + virtual int read(char* data, int nb_data, int* nb_read) = 0; +}; + +// Objects implementing the Handler interface can be +// registered to serve a particular path or subtree +// in the HTTP server. +// +// ServeHTTP should write reply headers and data to the ResponseWriter +// and then return. Returning signals that the request is finished +// and that the HTTP server can move on to the next request on +// the connection. +class ISrsHttpHandler +{ public: - SrsHttpHandler(); - virtual ~SrsHttpHandler(); + SrsHttpMuxEntry* entry; public: + ISrsHttpHandler(); + virtual ~ISrsHttpHandler(); +public: + virtual int serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r) = 0; +}; + +// Redirect to a fixed URL +class SrsHttpRedirectHandler : public ISrsHttpHandler +{ +private: + std::string url; + int code; +public: + SrsHttpRedirectHandler(std::string u, int c); + virtual ~SrsHttpRedirectHandler(); +public: + virtual int serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r); +}; + +// NotFound replies to the request with an HTTP 404 not found error. +class SrsHttpNotFoundHandler : public ISrsHttpHandler +{ +public: + SrsHttpNotFoundHandler(); + virtual ~SrsHttpNotFoundHandler(); +public: + virtual int serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r); +}; + +// FileServer returns a handler that serves HTTP requests +// with the contents of the file system rooted at root. +// +// To use the operating system's file system implementation, +// use http.Dir: +// +// http.Handle("/", SrsHttpFileServer("/tmp")) +// http.Handle("/", SrsHttpFileServer("static-dir")) +class SrsHttpFileServer : public ISrsHttpHandler +{ +protected: + std::string dir; +public: + SrsHttpFileServer(std::string root_dir); + virtual ~SrsHttpFileServer(); +public: + virtual int serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r); +private: /** - * initialize the handler. + * serve the file by specified path */ - virtual int initialize(); + virtual int serve_file(ISrsHttpResponseWriter* w, SrsHttpMessage* r, std::string fullpath); + virtual int serve_flv_file(ISrsHttpResponseWriter* w, SrsHttpMessage* r, std::string fullpath); + virtual int serve_mp4_file(ISrsHttpResponseWriter* w, SrsHttpMessage* r, std::string fullpath); +protected: + /** + * when access flv file with x.flv?start=xxx + */ + virtual int serve_flv_stream(ISrsHttpResponseWriter* w, SrsHttpMessage* r, std::string fullpath, int offset); /** - * whether current handler can handle the specified path. - * @pchild set the next child path, if needed. - * for example, the root handler will reset pchild to path, - * to reparse the path use child handlers. + * when access mp4 file with x.mp4?range=start-end + * @param start the start offset in bytes. + * @param end the end offset in bytes. -1 to end of file. + * @remark response data in [start, end]. */ - virtual bool can_handle(const char* path, int length, const char** pchild); + virtual int serve_mp4_stream(ISrsHttpResponseWriter* w, SrsHttpMessage* r, std::string fullpath, int start, int end); +protected: /** - * use the handler to process the request. - * @remark sub classes should override the do_process_request. + * copy the fs to response writer in size bytes. */ - virtual int process_request(SrsStSocket* skt, SrsHttpMessage* req); + virtual int copy(ISrsHttpResponseWriter* w, SrsFileReader* fs, SrsHttpMessage* r, int size); +}; + +// the mux entry for server mux. +// the matcher info, for example, the pattern and handler. +class SrsHttpMuxEntry +{ +public: + bool explicit_match; + ISrsHttpHandler* handler; + std::string pattern; + bool enabled; +public: + SrsHttpMuxEntry(); + virtual ~SrsHttpMuxEntry(); +}; + +/** +* the hijacker for http pattern match. +*/ +class ISrsHttpMatchHijacker +{ +public: + ISrsHttpMatchHijacker(); + virtual ~ISrsHttpMatchHijacker(); public: /** - * find the best matched handler + * when match the request failed, no handler to process request. + * @param request the http request message to match the handler. + * @param ph the already matched handler, hijack can rewrite it. */ - virtual int best_match(const char* path, int length, SrsHttpHandlerMatch** ppmatch); -// factory methods -protected: + virtual int hijack(SrsHttpMessage* request, ISrsHttpHandler** ph) = 0; +}; + +// ServeMux is an HTTP request multiplexer. +// It matches the URL of each incoming request against a list of registered +// patterns and calls the handler for the pattern that +// most closely matches the URL. +// +// Patterns name fixed, rooted paths, like "/favicon.ico", +// or rooted subtrees, like "/images/" (note the trailing slash). +// Longer patterns take precedence over shorter ones, so that +// if there are handlers registered for both "/images/" +// and "/images/thumbnails/", the latter handler will be +// called for paths beginning "/images/thumbnails/" and the +// former will receive requests for any other paths in the +// "/images/" subtree. +// +// Note that since a pattern ending in a slash names a rooted subtree, +// the pattern "/" matches all paths not matched by other registered +// patterns, not just the URL with Path == "/". +// +// Patterns may optionally begin with a host name, restricting matches to +// URLs on that host only. Host-specific patterns take precedence over +// general patterns, so that a handler might register for the two patterns +// "/codesearch" and "codesearch.google.com/" without also taking over +// requests for "http://www.google.com/". +// +// ServeMux also takes care of sanitizing the URL request path, +// redirecting any request containing . or .. elements to an +// equivalent .- and ..-free URL. +class SrsHttpServeMux +{ +private: + // the pattern handler, to handle the http request. + std::map entries; + // the vhost handler. + // when find the handler to process the request, + // append the matched vhost when pattern not starts with /, + // for example, for pattern /live/livestream.flv of vhost ossrs.net, + // the path will rewrite to ossrs.net/live/livestream.flv + std::map vhosts; + // all hijackers for http match. + // for example, the hstrs(http stream trigger rtmp source) + // can hijack and install handler when request incoming and no handler. + std::vector hijackers; +public: + SrsHttpServeMux(); + virtual ~SrsHttpServeMux(); +public: /** - * check whether the handler is valid. - * for example, user access /apis, actually it's not found, - * we will find the root handler to process it. - * @remark user can override this method, and should invoke it first. - * @see SrsApiRoot::is_handler_valid - */ - virtual bool is_handler_valid(SrsHttpMessage* req, int& status_code, std::string& reason_phrase); - /** - * do the actual process of request., format as, for example: - * {"code":0, "data":{}} - */ - virtual int do_process_request(SrsStSocket* skt, SrsHttpMessage* req); - /** - * response error, format as, for example: - * {"code":100, "desc":"description"} - */ - virtual int response_error(SrsStSocket* skt, SrsHttpMessage* req, int code, std::string desc); -// response writer -public: - virtual SrsHttpHandler* res_status_line(std::stringstream& ss); - virtual SrsHttpHandler* res_status_line_error(std::stringstream& ss, int code, std::string reason_phrase); - virtual SrsHttpHandler* res_content_type(std::stringstream& ss); - virtual SrsHttpHandler* res_content_type_xml(std::stringstream& ss); - virtual SrsHttpHandler* res_content_type_javascript(std::stringstream& ss); - virtual SrsHttpHandler* res_content_type_swf(std::stringstream& ss); - virtual SrsHttpHandler* res_content_type_css(std::stringstream& ss); - virtual SrsHttpHandler* res_content_type_ico(std::stringstream& ss); - virtual SrsHttpHandler* res_content_type_json(std::stringstream& ss); - virtual SrsHttpHandler* res_content_type_m3u8(std::stringstream& ss); - virtual SrsHttpHandler* res_content_type_mpegts(std::stringstream& ss); - virtual SrsHttpHandler* res_content_type_flv(std::stringstream& ss); - virtual SrsHttpHandler* res_content_length(std::stringstream& ss, int64_t length); - virtual SrsHttpHandler* res_enable_crossdomain(std::stringstream& ss); - virtual SrsHttpHandler* res_header_eof(std::stringstream& ss); - virtual SrsHttpHandler* res_body(std::stringstream& ss, std::string body); - virtual int res_flush(SrsStSocket* skt, std::stringstream& ss); -public: - virtual int res_options(SrsStSocket* skt); - virtual int res_text(SrsStSocket* skt, SrsHttpMessage* req, std::string body); - virtual int res_xml(SrsStSocket* skt, SrsHttpMessage* req, std::string body); - virtual int res_javascript(SrsStSocket* skt, SrsHttpMessage* req, std::string body); - virtual int res_swf(SrsStSocket* skt, SrsHttpMessage* req, std::string body); - virtual int res_css(SrsStSocket* skt, SrsHttpMessage* req, std::string body); - virtual int res_ico(SrsStSocket* skt, SrsHttpMessage* req, std::string body); - virtual int res_m3u8(SrsStSocket* skt, SrsHttpMessage* req, std::string body); - virtual int res_mpegts(SrsStSocket* skt, SrsHttpMessage* req, std::string body); - virtual int res_json(SrsStSocket* skt, SrsHttpMessage* req, std::string json); - virtual int res_error(SrsStSocket* skt, SrsHttpMessage* req, int code, std::string reason_phrase, std::string body); -// object creator -public: - /** - * create http api resource handler. - */ -#ifdef SRS_AUTO_HTTP_API - static SrsHttpHandler* create_http_api(); -#endif + * initialize the http serve mux. + */ + virtual int initialize(); /** - * create http stream resource handler. + * hijack the http match. */ -#ifdef SRS_AUTO_HTTP_SERVER - static SrsHttpHandler* create_http_stream(); -#endif + virtual void hijack(ISrsHttpMatchHijacker* h); + virtual void unhijack(ISrsHttpMatchHijacker* h); +public: + // Handle registers the handler for the given pattern. + // If a handler already exists for pattern, Handle panics. + virtual int handle(std::string pattern, ISrsHttpHandler* handler); +// interface ISrsHttpHandler +public: + virtual int serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r); +private: + virtual int find_handler(SrsHttpMessage* r, ISrsHttpHandler** ph); + virtual int match(SrsHttpMessage* r, ISrsHttpHandler** ph); + virtual bool path_match(std::string pattern, std::string path); }; +/** +* response writer use st socket +*/ +class SrsHttpResponseWriter : public ISrsHttpResponseWriter +{ +private: + SrsStSocket* skt; + SrsHttpHeader* hdr; +private: + // reply header has been (logically) written + bool header_wrote; + // status code passed to WriteHeader + int status; +private: + // explicitly-declared Content-Length; or -1 + int64_t content_length; + // number of bytes written in body + int64_t written; +private: + // wroteHeader tells whether the header's been written to "the + // wire" (or rather: w.conn.buf). this is unlike + // (*response).wroteHeader, which tells only whether it was + // logically written. + bool header_sent; +public: + SrsHttpResponseWriter(SrsStSocket* io); + virtual ~SrsHttpResponseWriter(); +public: + virtual int final_request(); + virtual SrsHttpHeader* header(); + virtual int write(char* data, int size); + virtual void write_header(int code); + virtual int send_header(char* data, int size); +}; + +/** +* response reader use st socket. +*/ +class SrsHttpResponseReader : virtual public ISrsHttpResponseReader +{ +private: + SrsStSocket* skt; + SrsHttpMessage* owner; + SrsFastBuffer* buffer; + bool is_eof; + // the left bytes in chunk. + int nb_left_chunk; + // the number of bytes of current chunk. + int nb_chunk; + // already read total bytes. + int64_t nb_total_read; +public: + SrsHttpResponseReader(SrsHttpMessage* msg, SrsStSocket* io); + virtual ~SrsHttpResponseReader(); +public: + /** + * initialize the response reader with buffer. + */ + virtual int initialize(SrsFastBuffer* buffer); +// interface ISrsHttpResponseReader +public: + virtual bool eof(); + virtual int read(char* data, int nb_data, int* nb_read); +private: + virtual int read_chunked(char* data, int nb_data, int* nb_read); + virtual int read_specified(char* data, int nb_data, int* nb_read); +}; + +// for http header. +typedef std::pair SrsHttpHeaderField; + +// A Request represents an HTTP request received by a server +// or to be sent by a client. +// +// The field semantics differ slightly between client and server +// usage. In addition to the notes on the fields below, see the +// documentation for Request.Write and RoundTripper. /** * the http message, request or response. */ @@ -207,84 +478,111 @@ class SrsHttpMessage */ std::string _url; /** + * the extension of file, for example, .flv + */ + std::string _ext; + /** * parsed http header. */ http_parser _header; /** - * body object, in bytes. + * body object, reader object. * @remark, user can get body in string by get_body(). */ - SrsBuffer* _body; + SrsHttpResponseReader* _body; /** - * parser state - * @remark, user can use is_complete() to determine the state. + * whether the body is chunked. */ - SrsHttpParseState _state; + bool chunked; /** * uri parser */ SrsHttpUri* _uri; /** - * best matched handler. - */ - SrsHttpHandlerMatch* _match; - /** - * whether the message requires crossdomain. - */ - bool _requires_crossdomain; - /** * use a buffer to read and send ts file. */ + // TODO: FIXME: remove it. char* _http_ts_send_buffer; // http headers - typedef std::pair SrsHttpHeaderField; - std::vector headers; + std::vector _headers; + // the query map + std::map _query; + // the transport connection, can be NULL. + SrsConnection* conn; public: - SrsHttpMessage(); + SrsHttpMessage(SrsStSocket* io, SrsConnection* c); virtual ~SrsHttpMessage(); +public: + /** + * set the original messages, then update the message. + */ + virtual int update(std::string url, http_parser* header, + SrsFastBuffer* body, std::vector& headers + ); public: virtual char* http_ts_send_buffer(); - virtual void reset(); - virtual int parse_uri(); + virtual SrsConnection* connection(); public: - virtual bool is_complete(); virtual u_int8_t method(); + virtual u_int16_t status_code(); + /** + * method helpers. + */ virtual std::string method_str(); virtual bool is_http_get(); virtual bool is_http_put(); virtual bool is_http_post(); virtual bool is_http_delete(); virtual bool is_http_options(); + /** + * whether body is chunked encoding, for reader only. + */ + virtual bool is_chunked(); + /** + * the uri contains the host and path. + */ virtual std::string uri(); + /** + * the url maybe the path. + */ virtual std::string url(); virtual std::string host(); virtual std::string path(); - virtual std::string query(); - virtual std::string body(); - virtual char* body_raw(); - virtual int64_t body_size(); - virtual int64_t content_length(); - virtual SrsHttpHandlerMatch* match(); - virtual bool requires_crossdomain(); - virtual void set_url(std::string url); - virtual void set_state(SrsHttpParseState state); - virtual void set_header(http_parser* header); - virtual void set_match(SrsHttpHandlerMatch* match); - virtual void set_requires_crossdomain(bool requires_crossdomain); - virtual void append_body(const char* body, int length); + virtual std::string ext(); public: + /** + * read body to string. + * @remark for small http body. + */ + virtual int body_read_all(std::string& body); + /** + * get the body reader, to read one by one. + * @remark when body is very large, or chunked, use this. + */ + virtual ISrsHttpResponseReader* body_reader(); + /** + * the content length, -1 for chunked or not set. + */ + virtual int64_t content_length(); /** * get the param in query string, * for instance, query is "start=100&end=200", * then query_get("start") is "100", and query_get("end") is "200" */ virtual std::string query_get(std::string key); -public: + /** + * get the headers. + */ virtual int request_header_count(); virtual std::string request_header_key_at(int index); virtual std::string request_header_value_at(int index); - virtual void set_request_header(std::string key, std::string value); virtual std::string get_request_header(std::string name); +public: + /** + * convert the http message to a request. + * @remark user must free the return request. + */ + virtual SrsRequest* to_request(std::string vhost); }; /** @@ -296,8 +594,18 @@ class SrsHttpParser private: http_parser_settings settings; http_parser parser; - SrsHttpMessage* msg; - std::string filed_name; + // the global parse buffer. + SrsFastBuffer* buffer; +private: + // http parse data, reset before parse message. + bool expect_field_name; + std::string field_name; + std::string field_value; + SrsHttpParseState state; + http_parser header; + std::string url; + std::vector headers; + int header_parsed; public: SrsHttpParser(); virtual ~SrsHttpParser(); @@ -313,7 +621,7 @@ class SrsHttpParser * or error and *ppmsg must be NULL. * @remark, if success, *ppmsg always NOT-NULL, *ppmsg always is_complete(). */ - virtual int parse_message(SrsStSocket* skt, SrsHttpMessage** ppmsg); + virtual int parse_message(SrsStSocket* skt, SrsConnection* conn, SrsHttpMessage** ppmsg); private: /** * parse the HTTP message to member field: msg. diff --git a/trunk/src/app/srs_app_http_api.cpp b/trunk/src/app/srs_app_http_api.cpp index cd0ea5d17b..d4e3ea5fcb 100644 --- a/trunk/src/app/srs_app_http_api.cpp +++ b/trunk/src/app/srs_app_http_api.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -35,477 +35,450 @@ using namespace std; #include #include #include +#include +#include +#include +#include -SrsApiRoot::SrsApiRoot() +SrsGoApiRoot::SrsGoApiRoot() { - handlers.push_back(new SrsApiApi()); } -SrsApiRoot::~SrsApiRoot() +SrsGoApiRoot::~SrsGoApiRoot() { } -bool SrsApiRoot::is_handler_valid(SrsHttpMessage* req, int& status_code, std::string& reason_phrase) +int SrsGoApiRoot::serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r) { - if (!SrsHttpHandler::is_handler_valid(req, status_code, reason_phrase)) { - return false; - } - - if (req->match()->matched_url.length() != 1) { - status_code = SRS_CONSTS_HTTP_NotFound; - reason_phrase = SRS_CONSTS_HTTP_NotFound_str; - return false; - } + std::stringstream ss; - return true; + ss << SRS_JOBJECT_START + << SRS_JFIELD_ERROR(ERROR_SUCCESS) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("urls", SRS_JOBJECT_START) + << SRS_JFIELD_STR("api", "the api root") + << SRS_JOBJECT_END + << SRS_JOBJECT_END; + + return srs_go_http_response_json(w, ss.str()); } -bool SrsApiRoot::can_handle(const char* path, int /*length*/, const char** pchild) +SrsGoApiApi::SrsGoApiApi() { - // reset the child path to path, - // for child to reparse the path. - *pchild = path; - - // only compare the first char. - return srs_path_equals("/", path, 1); } -int SrsApiRoot::do_process_request(SrsStSocket* skt, SrsHttpMessage* req) +SrsGoApiApi::~SrsGoApiApi() { - std::stringstream ss; - - ss << __SRS_JOBJECT_START - << __SRS_JFIELD_ERROR(ERROR_SUCCESS) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("urls", __SRS_JOBJECT_START) - << __SRS_JFIELD_STR("api", "the api root") - << __SRS_JOBJECT_END - << __SRS_JOBJECT_END; - - return res_json(skt, req, ss.str()); } -SrsApiApi::SrsApiApi() +int SrsGoApiApi::serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r) { - handlers.push_back(new SrsApiV1()); + std::stringstream ss; + + ss << SRS_JOBJECT_START + << SRS_JFIELD_ERROR(ERROR_SUCCESS) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("urls", SRS_JOBJECT_START) + << SRS_JFIELD_STR("v1", "the api version 1.0") + << SRS_JOBJECT_END + << SRS_JOBJECT_END; + + return srs_go_http_response_json(w, ss.str()); } -SrsApiApi::~SrsApiApi() +SrsGoApiV1::SrsGoApiV1() { } -bool SrsApiApi::can_handle(const char* path, int length, const char** /*pchild*/) +SrsGoApiV1::~SrsGoApiV1() { - return srs_path_equals("/api", path, length); } -int SrsApiApi::do_process_request(SrsStSocket* skt, SrsHttpMessage* req) +int SrsGoApiV1::serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r) { std::stringstream ss; - ss << __SRS_JOBJECT_START - << __SRS_JFIELD_ERROR(ERROR_SUCCESS) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("urls", __SRS_JOBJECT_START) - << __SRS_JFIELD_STR("v1", "the api version 1.0") - << __SRS_JOBJECT_END - << __SRS_JOBJECT_END; + ss << SRS_JOBJECT_START + << SRS_JFIELD_ERROR(ERROR_SUCCESS) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("urls", SRS_JOBJECT_START) + << SRS_JFIELD_STR("versions", "the version of SRS") << SRS_JFIELD_CONT + << SRS_JFIELD_STR("summaries", "the summary(pid, argv, pwd, cpu, mem) of SRS") << SRS_JFIELD_CONT + << SRS_JFIELD_STR("rusages", "the rusage of SRS") << SRS_JFIELD_CONT + << SRS_JFIELD_STR("self_proc_stats", "the self process stats") << SRS_JFIELD_CONT + << SRS_JFIELD_STR("system_proc_stats", "the system process stats") << SRS_JFIELD_CONT + << SRS_JFIELD_STR("meminfos", "the meminfo of system") << SRS_JFIELD_CONT + << SRS_JFIELD_STR("authors", "the primary authors and contributors") << SRS_JFIELD_CONT + << SRS_JFIELD_STR("requests", "the request itself, for http debug") << SRS_JFIELD_CONT + << SRS_JFIELD_STR("vhosts", "dumps vhost to json") << SRS_JFIELD_CONT + << SRS_JFIELD_STR("streams", "dumps streams to json") + << SRS_JOBJECT_END + << SRS_JOBJECT_END; - return res_json(skt, req, ss.str()); + return srs_go_http_response_json(w, ss.str()); } -SrsApiV1::SrsApiV1() +SrsGoApiVersion::SrsGoApiVersion() { - handlers.push_back(new SrsApiVersion()); - handlers.push_back(new SrsApiSummaries()); - handlers.push_back(new SrsApiRusages()); - handlers.push_back(new SrsApiSelfProcStats()); - handlers.push_back(new SrsApiSystemProcStats()); - handlers.push_back(new SrsApiMemInfos()); - handlers.push_back(new SrsApiAuthors()); - handlers.push_back(new SrsApiRequests()); } -SrsApiV1::~SrsApiV1() +SrsGoApiVersion::~SrsGoApiVersion() { } -bool SrsApiV1::can_handle(const char* path, int length, const char** /*pchild*/) -{ - return srs_path_equals("/v1", path, length); -} - -int SrsApiV1::do_process_request(SrsStSocket* skt, SrsHttpMessage* req) +int SrsGoApiVersion::serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r) { std::stringstream ss; - ss << __SRS_JOBJECT_START - << __SRS_JFIELD_ERROR(ERROR_SUCCESS) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("urls", __SRS_JOBJECT_START) - << __SRS_JFIELD_STR("versions", "the version of SRS") << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("summaries", "the summary(pid, argv, pwd, cpu, mem) of SRS") << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("rusages", "the rusage of SRS") << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("self_proc_stats", "the self process stats") << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("system_proc_stats", "the system process stats") << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("meminfos", "the meminfo of system") << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("authors", "the primary authors and contributors") << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("requests", "the request itself, for http debug") - << __SRS_JOBJECT_END - << __SRS_JOBJECT_END; + ss << SRS_JOBJECT_START + << SRS_JFIELD_ERROR(ERROR_SUCCESS) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("data", SRS_JOBJECT_START) + << SRS_JFIELD_ORG("major", VERSION_MAJOR) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("minor", VERSION_MINOR) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("revision", VERSION_REVISION) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("version", RTMP_SIG_SRS_VERSION) + << SRS_JOBJECT_END + << SRS_JOBJECT_END; - return res_json(skt, req, ss.str()); + return srs_go_http_response_json(w, ss.str()); } -SrsApiRequests::SrsApiRequests() +SrsGoApiSummaries::SrsGoApiSummaries() { } -SrsApiRequests::~SrsApiRequests() +SrsGoApiSummaries::~SrsGoApiSummaries() { } -bool SrsApiRequests::can_handle(const char* path, int length, const char** /*pchild*/) +int SrsGoApiSummaries::serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r) +{ + std::stringstream ss; + srs_api_dump_summaries(ss); + return srs_go_http_response_json(w, ss.str()); +} + +SrsGoApiRusages::SrsGoApiRusages() { - return srs_path_equals("/requests", path, length); } -int SrsApiRequests::do_process_request(SrsStSocket* skt, SrsHttpMessage* req) +SrsGoApiRusages::~SrsGoApiRusages() +{ +} + +int SrsGoApiRusages::serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* req) { std::stringstream ss; - ss << __SRS_JOBJECT_START - << __SRS_JFIELD_ERROR(ERROR_SUCCESS) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("data", __SRS_JOBJECT_START) - << __SRS_JFIELD_STR("uri", req->uri()) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("path", req->path()) << __SRS_JFIELD_CONT; - - // method - if (req->is_http_get()) { - ss << __SRS_JFIELD_STR("METHOD", "GET"); - } else if (req->is_http_post()) { - ss << __SRS_JFIELD_STR("METHOD", "POST"); - } else if (req->is_http_put()) { - ss << __SRS_JFIELD_STR("METHOD", "PUT"); - } else if (req->is_http_delete()) { - ss << __SRS_JFIELD_STR("METHOD", "DELETE"); - } else { - ss << __SRS_JFIELD_ORG("METHOD", req->method()); - } - ss << __SRS_JFIELD_CONT; - - // request headers - ss << __SRS_JFIELD_NAME("headers") << __SRS_JOBJECT_START; - for (int i = 0; i < req->request_header_count(); i++) { - std::string key = req->request_header_key_at(i); - std::string value = req->request_header_value_at(i); - if ( i < req->request_header_count() - 1) { - ss << __SRS_JFIELD_STR(key, value) << __SRS_JFIELD_CONT; - } else { - ss << __SRS_JFIELD_STR(key, value); - } - } - ss << __SRS_JOBJECT_END << __SRS_JFIELD_CONT; + SrsRusage* r = srs_get_system_rusage(); - // server informations - ss << __SRS_JFIELD_NAME("server") << __SRS_JOBJECT_START - << __SRS_JFIELD_STR("sigature", RTMP_SIG_SRS_KEY) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("name", RTMP_SIG_SRS_NAME) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("version", RTMP_SIG_SRS_VERSION) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("link", RTMP_SIG_SRS_URL) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("time", srs_get_system_time_ms()) - << __SRS_JOBJECT_END - << __SRS_JOBJECT_END - << __SRS_JOBJECT_END; + ss << SRS_JOBJECT_START + << SRS_JFIELD_ERROR(ERROR_SUCCESS) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("data", SRS_JOBJECT_START) + << SRS_JFIELD_ORG("ok", (r->ok? "true":"false")) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("sample_time", r->sample_time) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("ru_utime", r->r.ru_utime.tv_sec) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("ru_stime", r->r.ru_stime.tv_sec) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("ru_maxrss", r->r.ru_maxrss) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("ru_ixrss", r->r.ru_ixrss) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("ru_idrss", r->r.ru_idrss) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("ru_isrss", r->r.ru_isrss) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("ru_minflt", r->r.ru_minflt) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("ru_majflt", r->r.ru_majflt) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("ru_nswap", r->r.ru_nswap) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("ru_inblock", r->r.ru_inblock) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("ru_oublock", r->r.ru_oublock) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("ru_msgsnd", r->r.ru_msgsnd) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("ru_msgrcv", r->r.ru_msgrcv) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("ru_nsignals", r->r.ru_nsignals) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("ru_nvcsw", r->r.ru_nvcsw) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("ru_nivcsw", r->r.ru_nivcsw) + << SRS_JOBJECT_END + << SRS_JOBJECT_END; - return res_json(skt, req, ss.str()); + return srs_go_http_response_json(w, ss.str()); } -SrsApiVersion::SrsApiVersion() +SrsGoApiSelfProcStats::SrsGoApiSelfProcStats() { } -SrsApiVersion::~SrsApiVersion() +SrsGoApiSelfProcStats::~SrsGoApiSelfProcStats() { } -bool SrsApiVersion::can_handle(const char* path, int length, const char** /*pchild*/) -{ - return srs_path_equals("/versions", path, length); -} - -int SrsApiVersion::do_process_request(SrsStSocket* skt, SrsHttpMessage* req) +int SrsGoApiSelfProcStats::serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r) { std::stringstream ss; - ss << __SRS_JOBJECT_START - << __SRS_JFIELD_ERROR(ERROR_SUCCESS) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("data", __SRS_JOBJECT_START) - << __SRS_JFIELD_ORG("major", VERSION_MAJOR) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("minor", VERSION_MINOR) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("revision", VERSION_REVISION) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("version", RTMP_SIG_SRS_VERSION) - << __SRS_JOBJECT_END - << __SRS_JOBJECT_END; + SrsProcSelfStat* u = srs_get_self_proc_stat(); - return res_json(skt, req, ss.str()); -} - -SrsApiSummaries::SrsApiSummaries() + ss << SRS_JOBJECT_START + << SRS_JFIELD_ERROR(ERROR_SUCCESS) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("data", SRS_JOBJECT_START) + << SRS_JFIELD_ORG("ok", (u->ok? "true":"false")) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("sample_time", u->sample_time) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("percent", u->percent) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("pid", u->pid) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("comm", u->comm) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("state", u->state) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("ppid", u->ppid) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("pgrp", u->pgrp) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("session", u->session) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("tty_nr", u->tty_nr) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("tpgid", u->tpgid) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("flags", u->flags) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("minflt", u->minflt) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("cminflt", u->cminflt) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("majflt", u->majflt) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("cmajflt", u->cmajflt) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("utime", u->utime) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("stime", u->stime) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("cutime", u->cutime) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("cstime", u->cstime) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("priority", u->priority) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("nice", u->nice) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("num_threads", u->num_threads) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("itrealvalue", u->itrealvalue) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("starttime", u->starttime) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("vsize", u->vsize) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("rss", u->rss) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("rsslim", u->rsslim) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("startcode", u->startcode) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("endcode", u->endcode) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("startstack", u->startstack) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("kstkesp", u->kstkesp) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("kstkeip", u->kstkeip) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("signal", u->signal) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("blocked", u->blocked) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("sigignore", u->sigignore) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("sigcatch", u->sigcatch) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("wchan", u->wchan) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("nswap", u->nswap) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("cnswap", u->cnswap) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("exit_signal", u->exit_signal) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("processor", u->processor) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("rt_priority", u->rt_priority) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("policy", u->policy) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("delayacct_blkio_ticks", u->delayacct_blkio_ticks) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("guest_time", u->guest_time) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("cguest_time", u->cguest_time) + << SRS_JOBJECT_END + << SRS_JOBJECT_END; + + return srs_go_http_response_json(w, ss.str()); +} + +SrsGoApiSystemProcStats::SrsGoApiSystemProcStats() +{ +} + +SrsGoApiSystemProcStats::~SrsGoApiSystemProcStats() +{ +} + +int SrsGoApiSystemProcStats::serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r) { + std::stringstream ss; + + SrsProcSystemStat* s = srs_get_system_proc_stat(); + + ss << SRS_JOBJECT_START + << SRS_JFIELD_ERROR(ERROR_SUCCESS) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("data", SRS_JOBJECT_START) + << SRS_JFIELD_ORG("ok", (s->ok? "true":"false")) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("sample_time", s->sample_time) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("percent", s->percent) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("user", s->user) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("nice", s->nice) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("sys", s->sys) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("idle", s->idle) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("iowait", s->iowait) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("irq", s->irq) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("softirq", s->softirq) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("steal", s->steal) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("guest", s->guest) + << SRS_JOBJECT_END + << SRS_JOBJECT_END; + + return srs_go_http_response_json(w, ss.str()); } -SrsApiSummaries::~SrsApiSummaries() +SrsGoApiMemInfos::SrsGoApiMemInfos() { } -bool SrsApiSummaries::can_handle(const char* path, int length, const char** /*pchild*/) +SrsGoApiMemInfos::~SrsGoApiMemInfos() { - return srs_path_equals("/summaries", path, length); } -int SrsApiSummaries::do_process_request(SrsStSocket* skt, SrsHttpMessage* req) +int SrsGoApiMemInfos::serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r) { std::stringstream ss; - srs_api_dump_summaries(ss); - return res_json(skt, req, ss.str()); -} - -SrsApiRusages::SrsApiRusages() -{ + + SrsMemInfo* m = srs_get_meminfo(); + + ss << SRS_JOBJECT_START + << SRS_JFIELD_ERROR(ERROR_SUCCESS) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("data", SRS_JOBJECT_START) + << SRS_JFIELD_ORG("ok", (m->ok? "true":"false")) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("sample_time", m->sample_time) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("percent_ram", m->percent_ram) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("percent_swap", m->percent_swap) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("MemActive", m->MemActive) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("RealInUse", m->RealInUse) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("NotInUse", m->NotInUse) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("MemTotal", m->MemTotal) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("MemFree", m->MemFree) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("Buffers", m->Buffers) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("Cached", m->Cached) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("SwapTotal", m->SwapTotal) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("SwapFree", m->SwapFree) + << SRS_JOBJECT_END + << SRS_JOBJECT_END; + + return srs_go_http_response_json(w, ss.str()); } -SrsApiRusages::~SrsApiRusages() +SrsGoApiAuthors::SrsGoApiAuthors() { } -bool SrsApiRusages::can_handle(const char* path, int length, const char** /*pchild*/) +SrsGoApiAuthors::~SrsGoApiAuthors() { - return srs_path_equals("/rusages", path, length); } -int SrsApiRusages::do_process_request(SrsStSocket* skt, SrsHttpMessage* req) +int SrsGoApiAuthors::serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r) { std::stringstream ss; - SrsRusage* r = srs_get_system_rusage(); - - ss << __SRS_JOBJECT_START - << __SRS_JFIELD_ERROR(ERROR_SUCCESS) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("data", __SRS_JOBJECT_START) - << __SRS_JFIELD_ORG("ok", (r->ok? "true":"false")) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("sample_time", r->sample_time) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("ru_utime", r->r.ru_utime.tv_sec) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("ru_stime", r->r.ru_stime.tv_sec) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("ru_maxrss", r->r.ru_maxrss) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("ru_ixrss", r->r.ru_ixrss) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("ru_idrss", r->r.ru_idrss) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("ru_isrss", r->r.ru_isrss) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("ru_minflt", r->r.ru_minflt) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("ru_majflt", r->r.ru_majflt) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("ru_nswap", r->r.ru_nswap) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("ru_inblock", r->r.ru_inblock) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("ru_oublock", r->r.ru_oublock) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("ru_msgsnd", r->r.ru_msgsnd) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("ru_msgrcv", r->r.ru_msgrcv) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("ru_nsignals", r->r.ru_nsignals) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("ru_nvcsw", r->r.ru_nvcsw) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("ru_nivcsw", r->r.ru_nivcsw) - << __SRS_JOBJECT_END - << __SRS_JOBJECT_END; + ss << SRS_JOBJECT_START + << SRS_JFIELD_ERROR(ERROR_SUCCESS) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("data", SRS_JOBJECT_START) + << SRS_JFIELD_STR("primary", RTMP_SIG_SRS_PRIMARY) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("authors", RTMP_SIG_SRS_AUTHROS) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("contributors_link", RTMP_SIG_SRS_CONTRIBUTORS_URL) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("contributors", SRS_AUTO_CONSTRIBUTORS) + << SRS_JOBJECT_END + << SRS_JOBJECT_END; - return res_json(skt, req, ss.str()); -} - -SrsApiSelfProcStats::SrsApiSelfProcStats() -{ + return srs_go_http_response_json(w, ss.str()); } -SrsApiSelfProcStats::~SrsApiSelfProcStats() +SrsGoApiRequests::SrsGoApiRequests() { } -bool SrsApiSelfProcStats::can_handle(const char* path, int length, const char** /*pchild*/) +SrsGoApiRequests::~SrsGoApiRequests() { - return srs_path_equals("/self_proc_stats", path, length); } -int SrsApiSelfProcStats::do_process_request(SrsStSocket* skt, SrsHttpMessage* req) +int SrsGoApiRequests::serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r) { + SrsHttpMessage* req = r; + std::stringstream ss; - SrsProcSelfStat* u = srs_get_self_proc_stat(); + ss << SRS_JOBJECT_START + << SRS_JFIELD_ERROR(ERROR_SUCCESS) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("data", SRS_JOBJECT_START) + << SRS_JFIELD_STR("uri", req->uri()) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("path", req->path()) << SRS_JFIELD_CONT; - ss << __SRS_JOBJECT_START - << __SRS_JFIELD_ERROR(ERROR_SUCCESS) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("data", __SRS_JOBJECT_START) - << __SRS_JFIELD_ORG("ok", (u->ok? "true":"false")) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("sample_time", u->sample_time) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("percent", u->percent) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("pid", u->pid) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("comm", u->comm) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("state", u->state) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("ppid", u->ppid) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("pgrp", u->pgrp) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("session", u->session) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("tty_nr", u->tty_nr) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("tpgid", u->tpgid) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("flags", u->flags) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("minflt", u->minflt) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("cminflt", u->cminflt) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("majflt", u->majflt) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("cmajflt", u->cmajflt) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("utime", u->utime) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("stime", u->stime) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("cutime", u->cutime) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("cstime", u->cstime) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("priority", u->priority) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("nice", u->nice) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("num_threads", u->num_threads) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("itrealvalue", u->itrealvalue) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("starttime", u->starttime) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("vsize", u->vsize) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("rss", u->rss) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("rsslim", u->rsslim) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("startcode", u->startcode) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("endcode", u->endcode) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("startstack", u->startstack) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("kstkesp", u->kstkesp) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("kstkeip", u->kstkeip) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("signal", u->signal) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("blocked", u->blocked) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("sigignore", u->sigignore) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("sigcatch", u->sigcatch) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("wchan", u->wchan) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("nswap", u->nswap) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("cnswap", u->cnswap) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("exit_signal", u->exit_signal) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("processor", u->processor) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("rt_priority", u->rt_priority) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("policy", u->policy) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("delayacct_blkio_ticks", u->delayacct_blkio_ticks) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("guest_time", u->guest_time) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("cguest_time", u->cguest_time) - << __SRS_JOBJECT_END - << __SRS_JOBJECT_END; - - return res_json(skt, req, ss.str()); -} - -SrsApiSystemProcStats::SrsApiSystemProcStats() -{ -} - -SrsApiSystemProcStats::~SrsApiSystemProcStats() -{ -} - -bool SrsApiSystemProcStats::can_handle(const char* path, int length, const char** /*pchild*/) -{ - return srs_path_equals("/system_proc_stats", path, length); -} - -int SrsApiSystemProcStats::do_process_request(SrsStSocket* skt, SrsHttpMessage* req) -{ - std::stringstream ss; + // method + if (req->is_http_get()) { + ss << SRS_JFIELD_STR("METHOD", "GET"); + } else if (req->is_http_post()) { + ss << SRS_JFIELD_STR("METHOD", "POST"); + } else if (req->is_http_put()) { + ss << SRS_JFIELD_STR("METHOD", "PUT"); + } else if (req->is_http_delete()) { + ss << SRS_JFIELD_STR("METHOD", "DELETE"); + } else { + ss << SRS_JFIELD_ORG("METHOD", req->method()); + } + ss << SRS_JFIELD_CONT; - SrsProcSystemStat* s = srs_get_system_proc_stat(); + // request headers + ss << SRS_JFIELD_NAME("headers") << SRS_JOBJECT_START; + for (int i = 0; i < req->request_header_count(); i++) { + std::string key = req->request_header_key_at(i); + std::string value = req->request_header_value_at(i); + if ( i < req->request_header_count() - 1) { + ss << SRS_JFIELD_STR(key, value) << SRS_JFIELD_CONT; + } else { + ss << SRS_JFIELD_STR(key, value); + } + } + ss << SRS_JOBJECT_END << SRS_JFIELD_CONT; - ss << __SRS_JOBJECT_START - << __SRS_JFIELD_ERROR(ERROR_SUCCESS) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("data", __SRS_JOBJECT_START) - << __SRS_JFIELD_ORG("ok", (s->ok? "true":"false")) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("sample_time", s->sample_time) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("percent", s->percent) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("user", s->user) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("nice", s->nice) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("sys", s->sys) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("idle", s->idle) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("iowait", s->iowait) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("irq", s->irq) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("softirq", s->softirq) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("steal", s->steal) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("guest", s->guest) - << __SRS_JOBJECT_END - << __SRS_JOBJECT_END; + // server informations + ss << SRS_JFIELD_NAME("server") << SRS_JOBJECT_START + << SRS_JFIELD_STR("sigature", RTMP_SIG_SRS_KEY) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("name", RTMP_SIG_SRS_NAME) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("version", RTMP_SIG_SRS_VERSION) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("link", RTMP_SIG_SRS_URL) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("time", srs_get_system_time_ms()) + << SRS_JOBJECT_END + << SRS_JOBJECT_END + << SRS_JOBJECT_END; - return res_json(skt, req, ss.str()); + return srs_go_http_response_json(w, ss.str()); } -SrsApiMemInfos::SrsApiMemInfos() +SrsGoApiVhosts::SrsGoApiVhosts() { } -SrsApiMemInfos::~SrsApiMemInfos() +SrsGoApiVhosts::~SrsGoApiVhosts() { } -bool SrsApiMemInfos::can_handle(const char* path, int length, const char** /*pchild*/) +int SrsGoApiVhosts::serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r) { - return srs_path_equals("/meminfos", path, length); -} - -int SrsApiMemInfos::do_process_request(SrsStSocket* skt, SrsHttpMessage* req) -{ - std::stringstream ss; + std::stringstream data; + SrsStatistic* stat = SrsStatistic::instance(); + int ret = stat->dumps_vhosts(data); - SrsMemInfo* m = srs_get_meminfo(); + std::stringstream ss; - ss << __SRS_JOBJECT_START - << __SRS_JFIELD_ERROR(ERROR_SUCCESS) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("data", __SRS_JOBJECT_START) - << __SRS_JFIELD_ORG("ok", (m->ok? "true":"false")) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("sample_time", m->sample_time) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("percent_ram", m->percent_ram) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("percent_swap", m->percent_swap) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("MemActive", m->MemActive) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("RealInUse", m->RealInUse) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("NotInUse", m->NotInUse) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("MemTotal", m->MemTotal) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("MemFree", m->MemFree) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("Buffers", m->Buffers) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("Cached", m->Cached) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("SwapTotal", m->SwapTotal) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("SwapFree", m->SwapFree) - << __SRS_JOBJECT_END - << __SRS_JOBJECT_END; + ss << SRS_JOBJECT_START + << SRS_JFIELD_ERROR(ret) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("server", stat->server_id()) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("vhosts", data.str()) + << SRS_JOBJECT_END; - return res_json(skt, req, ss.str()); -} - -SrsApiAuthors::SrsApiAuthors() -{ + return srs_go_http_response_json(w, ss.str()); } -SrsApiAuthors::~SrsApiAuthors() +SrsGoApiStreams::SrsGoApiStreams() { } -bool SrsApiAuthors::can_handle(const char* path, int length, const char** /*pchild*/) +SrsGoApiStreams::~SrsGoApiStreams() { - return srs_path_equals("/authors", path, length); } -int SrsApiAuthors::do_process_request(SrsStSocket* skt, SrsHttpMessage* req) +int SrsGoApiStreams::serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r) { + std::stringstream data; + SrsStatistic* stat = SrsStatistic::instance(); + int ret = stat->dumps_streams(data); + std::stringstream ss; - ss << __SRS_JOBJECT_START - << __SRS_JFIELD_ERROR(ERROR_SUCCESS) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("data", __SRS_JOBJECT_START) - << __SRS_JFIELD_STR("primary", RTMP_SIG_SRS_PRIMARY) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("authors", RTMP_SIG_SRS_AUTHROS) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("contributors_link", RTMP_SIG_SRS_CONTRIBUTORS_URL) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("contributors", SRS_AUTO_CONSTRIBUTORS) - << __SRS_JOBJECT_END - << __SRS_JOBJECT_END; + ss << SRS_JOBJECT_START + << SRS_JFIELD_ERROR(ret) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("server", stat->server_id()) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("streams", data.str()) + << SRS_JOBJECT_END; - return res_json(skt, req, ss.str()); + return srs_go_http_response_json(w, ss.str()); } -SrsHttpApi::SrsHttpApi(SrsServer* srs_server, st_netfd_t client_stfd, SrsHttpHandler* _handler) - : SrsConnection(srs_server, client_stfd) +SrsHttpApi::SrsHttpApi(IConnectionManager* cm, st_netfd_t fd, SrsHttpServeMux* m) + : SrsConnection(cm, fd) { + mux = m; parser = new SrsHttpParser(); - handler = _handler; - requires_crossdomain = false; + crossdomain_required = false; } SrsHttpApi::~SrsHttpApi() @@ -513,7 +486,7 @@ SrsHttpApi::~SrsHttpApi() srs_freep(parser); } -void SrsHttpApi::kbps_resample() +void SrsHttpApi::resample() { // TODO: FIXME: implements it } @@ -530,6 +503,11 @@ int64_t SrsHttpApi::get_recv_bytes_delta() return 0; } +void SrsHttpApi::cleanup() +{ + // TODO: FIXME: implements it +} + int SrsHttpApi::do_cycle() { int ret = ERROR_SUCCESS; @@ -550,19 +528,27 @@ int SrsHttpApi::do_cycle() SrsHttpMessage* req = NULL; // get a http message - if ((ret = parser->parse_message(&skt, &req)) != ERROR_SUCCESS) { + if ((ret = parser->parse_message(&skt, this, &req)) != ERROR_SUCCESS) { return ret; } - // if SUCCESS, always NOT-NULL and completed message. + // if SUCCESS, always NOT-NULL. srs_assert(req); - srs_assert(req->is_complete()); // always free it in this scope. SrsAutoFree(SrsHttpMessage, req); + // TODO: FIXME: use the post body. + std::string res; + + // get response body. + if ((ret = req->body_read_all(res)) != ERROR_SUCCESS) { + return ret; + } + // ok, handle http request. - if ((ret = process_request(&skt, req)) != ERROR_SUCCESS) { + SrsHttpResponseWriter writer(&skt); + if ((ret = process_request(&writer, req)) != ERROR_SUCCESS) { return ret; } } @@ -570,44 +556,42 @@ int SrsHttpApi::do_cycle() return ret; } -int SrsHttpApi::process_request(SrsStSocket* skt, SrsHttpMessage* req) +int SrsHttpApi::process_request(ISrsHttpResponseWriter* w, SrsHttpMessage* r) { int ret = ERROR_SUCCESS; - - // parse uri to schema/server:port/path?query - if ((ret = req->parse_uri()) != ERROR_SUCCESS) { - return ret; - } srs_trace("HTTP %s %s, content-length=%"PRId64"", - req->method_str().c_str(), req->url().c_str(), req->content_length()); + r->method_str().c_str(), r->url().c_str(), r->content_length()); - // TODO: maybe need to parse the url. - std::string url = req->path(); - - SrsHttpHandlerMatch* p = NULL; - if ((ret = handler->best_match(url.data(), url.length(), &p)) != ERROR_SUCCESS) { - srs_warn("failed to find the best match handler for url. ret=%d", ret); - return ret; + // method is OPTIONS and enable crossdomain, required crossdomain header. + if (r->is_http_options() && _srs_config->get_http_api_crossdomain()) { + crossdomain_required = true; } - - // if success, p and pstart should be valid. - srs_assert(p); - srs_assert(p->handler); - srs_assert(p->matched_url.length() <= url.length()); - srs_info("best match handler, matched_url=%s", p->matched_url.c_str()); - - req->set_match(p); - req->set_requires_crossdomain(requires_crossdomain); - - // use handler to process request. - if ((ret = p->handler->process_request(skt, req)) != ERROR_SUCCESS) { - srs_warn("handler failed to process http request. ret=%d", ret); - return ret; + + // whenever crossdomain required, set crossdomain header. + if (crossdomain_required) { + w->header()->set("Access-Control-Allow-Origin", "*"); + w->header()->set("Access-Control-Allow-Methods", "GET, POST, HEAD, PUT, DELETE"); + w->header()->set("Access-Control-Allow-Headers", "Cache-Control,X-Proxy-Authorization,X-Requested-With,Content-Type"); + } + + // handle the http options. + if (r->is_http_options()) { + w->header()->set_content_length(0); + if (_srs_config->get_http_api_crossdomain()) { + w->write_header(SRS_CONSTS_HTTP_OK); + } else { + w->write_header(SRS_CONSTS_HTTP_MethodNotAllowed); + } + return w->final_request(); } - if (req->requires_crossdomain()) { - requires_crossdomain = true; + // use default server mux to serve http request. + if ((ret = mux->serve_http(w, r)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("serve http msg failed. ret=%d", ret); + } + return ret; } return ret; diff --git a/trunk/src/app/srs_app_http_api.hpp b/trunk/src/app/srs_app_http_api.hpp index b79ce06a8f..66e971088c 100644 --- a/trunk/src/app/srs_app_http_api.hpp +++ b/trunk/src/app/srs_app_http_api.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -42,147 +42,142 @@ class SrsHttpHandler; #include // for http root. -class SrsApiRoot : public SrsHttpHandler +class SrsGoApiRoot : public ISrsHttpHandler { public: - SrsApiRoot(); - virtual ~SrsApiRoot(); + SrsGoApiRoot(); + virtual ~SrsGoApiRoot(); public: - virtual bool is_handler_valid(SrsHttpMessage* req, int& status_code, std::string& reason_phrase); -protected: - virtual bool can_handle(const char* path, int length, const char** pchild); - virtual int do_process_request(SrsStSocket* skt, SrsHttpMessage* req); + virtual int serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r); }; -class SrsApiApi : public SrsHttpHandler +class SrsGoApiApi : public ISrsHttpHandler { public: - SrsApiApi(); - virtual ~SrsApiApi(); + SrsGoApiApi(); + virtual ~SrsGoApiApi(); public: - virtual bool can_handle(const char* path, int length, const char** pchild); -protected: - virtual int do_process_request(SrsStSocket* skt, SrsHttpMessage* req); + virtual int serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r); }; -class SrsApiV1 : public SrsHttpHandler +class SrsGoApiV1 : public ISrsHttpHandler { public: - SrsApiV1(); - virtual ~SrsApiV1(); + SrsGoApiV1(); + virtual ~SrsGoApiV1(); public: - virtual bool can_handle(const char* path, int length, const char** pchild); -protected: - virtual int do_process_request(SrsStSocket* skt, SrsHttpMessage* req); + virtual int serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r); }; -class SrsApiRequests : public SrsHttpHandler +class SrsGoApiVersion : public ISrsHttpHandler { public: - SrsApiRequests(); - virtual ~SrsApiRequests(); + SrsGoApiVersion(); + virtual ~SrsGoApiVersion(); public: - virtual bool can_handle(const char* path, int length, const char** pchild); -protected: - virtual int do_process_request(SrsStSocket* skt, SrsHttpMessage* req); + virtual int serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r); }; -class SrsApiVersion : public SrsHttpHandler +class SrsGoApiSummaries : public ISrsHttpHandler { public: - SrsApiVersion(); - virtual ~SrsApiVersion(); + SrsGoApiSummaries(); + virtual ~SrsGoApiSummaries(); public: - virtual bool can_handle(const char* path, int length, const char** pchild); -protected: - virtual int do_process_request(SrsStSocket* skt, SrsHttpMessage* req); + virtual int serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r); }; -class SrsApiSummaries : public SrsHttpHandler +class SrsGoApiRusages : public ISrsHttpHandler { public: - SrsApiSummaries(); - virtual ~SrsApiSummaries(); + SrsGoApiRusages(); + virtual ~SrsGoApiRusages(); public: - virtual bool can_handle(const char* path, int length, const char** pchild); -protected: - virtual int do_process_request(SrsStSocket* skt, SrsHttpMessage* req); + virtual int serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r); }; -class SrsApiRusages : public SrsHttpHandler +class SrsGoApiSelfProcStats : public ISrsHttpHandler { public: - SrsApiRusages(); - virtual ~SrsApiRusages(); + SrsGoApiSelfProcStats(); + virtual ~SrsGoApiSelfProcStats(); public: - virtual bool can_handle(const char* path, int length, const char** pchild); -protected: - virtual int do_process_request(SrsStSocket* skt, SrsHttpMessage* req); + virtual int serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r); }; -class SrsApiSelfProcStats : public SrsHttpHandler +class SrsGoApiSystemProcStats : public ISrsHttpHandler { public: - SrsApiSelfProcStats(); - virtual ~SrsApiSelfProcStats(); + SrsGoApiSystemProcStats(); + virtual ~SrsGoApiSystemProcStats(); public: - virtual bool can_handle(const char* path, int length, const char** pchild); -protected: - virtual int do_process_request(SrsStSocket* skt, SrsHttpMessage* req); + virtual int serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r); }; -class SrsApiSystemProcStats : public SrsHttpHandler +class SrsGoApiMemInfos : public ISrsHttpHandler { public: - SrsApiSystemProcStats(); - virtual ~SrsApiSystemProcStats(); + SrsGoApiMemInfos(); + virtual ~SrsGoApiMemInfos(); public: - virtual bool can_handle(const char* path, int length, const char** pchild); -protected: - virtual int do_process_request(SrsStSocket* skt, SrsHttpMessage* req); + virtual int serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r); }; -class SrsApiMemInfos : public SrsHttpHandler +class SrsGoApiAuthors : public ISrsHttpHandler { public: - SrsApiMemInfos(); - virtual ~SrsApiMemInfos(); + SrsGoApiAuthors(); + virtual ~SrsGoApiAuthors(); public: - virtual bool can_handle(const char* path, int length, const char** pchild); -protected: - virtual int do_process_request(SrsStSocket* skt, SrsHttpMessage* req); + virtual int serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r); }; -class SrsApiAuthors : public SrsHttpHandler +class SrsGoApiRequests : public ISrsHttpHandler { public: - SrsApiAuthors(); - virtual ~SrsApiAuthors(); + SrsGoApiRequests(); + virtual ~SrsGoApiRequests(); public: - virtual bool can_handle(const char* path, int length, const char** pchild); -protected: - virtual int do_process_request(SrsStSocket* skt, SrsHttpMessage* req); + virtual int serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r); +}; + +class SrsGoApiVhosts : public ISrsHttpHandler +{ +public: + SrsGoApiVhosts(); + virtual ~SrsGoApiVhosts(); +public: + virtual int serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r); +}; + +class SrsGoApiStreams : public ISrsHttpHandler +{ +public: + SrsGoApiStreams(); + virtual ~SrsGoApiStreams(); +public: + virtual int serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r); }; class SrsHttpApi : public SrsConnection { private: SrsHttpParser* parser; - SrsHttpHandler* handler; - bool requires_crossdomain; + SrsHttpServeMux* mux; + bool crossdomain_required; public: - SrsHttpApi(SrsServer* srs_server, st_netfd_t client_stfd, SrsHttpHandler* _handler); + SrsHttpApi(IConnectionManager* cm, st_netfd_t fd, SrsHttpServeMux* m); virtual ~SrsHttpApi(); -public: - virtual void kbps_resample(); // interface IKbpsDelta public: + virtual void resample(); virtual int64_t get_send_bytes_delta(); virtual int64_t get_recv_bytes_delta(); + virtual void cleanup(); protected: virtual int do_cycle(); private: - virtual int process_request(SrsStSocket* skt, SrsHttpMessage* req); + virtual int process_request(ISrsHttpResponseWriter* w, SrsHttpMessage* r); }; #endif diff --git a/trunk/src/app/srs_app_http_client.cpp b/trunk/src/app/srs_app_http_client.cpp index 978777599c..377f9a79b3 100644 --- a/trunk/src/app/srs_app_http_client.cpp +++ b/trunk/src/app/srs_app_http_client.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -35,15 +35,15 @@ using namespace std; #include #include #include - -// when error, http client sleep for a while and retry. -#define SRS_HTTP_CLIENT_SLEEP_US (int64_t)(3*1000*1000LL) +#include SrsHttpClient::SrsHttpClient() { connected = false; stfd = NULL; + skt = NULL; parser = NULL; + timeout_us = 0; } SrsHttpClient::~SrsHttpClient() @@ -52,22 +52,32 @@ SrsHttpClient::~SrsHttpClient() srs_freep(parser); } -int SrsHttpClient::post(SrsHttpUri* uri, string req, string& res) +int SrsHttpClient::initialize(string h, int p, int64_t t_us) { - res = ""; - int ret = ERROR_SUCCESS; - if (!parser) { - parser = new SrsHttpParser(); - - if ((ret = parser->initialize(HTTP_RESPONSE)) != ERROR_SUCCESS) { - srs_error("initialize parser failed. ret=%d", ret); - return ret; - } + srs_freep(parser); + parser = new SrsHttpParser(); + + if ((ret = parser->initialize(HTTP_RESPONSE)) != ERROR_SUCCESS) { + srs_error("initialize parser failed. ret=%d", ret); + return ret; } - if ((ret = connect(uri)) != ERROR_SUCCESS) { + host = h; + port = p; + timeout_us = t_us; + + return ret; +} + +int SrsHttpClient::post(string path, string req, SrsHttpMessage** ppmsg) +{ + *ppmsg = NULL; + + int ret = ERROR_SUCCESS; + + if ((ret = connect()) != ERROR_SUCCESS) { srs_warn("http connect server failed. ret=%d", ret); return ret; } @@ -75,20 +85,18 @@ int SrsHttpClient::post(SrsHttpUri* uri, string req, string& res) // send POST request to uri // POST %s HTTP/1.1\r\nHost: %s\r\nContent-Length: %d\r\n\r\n%s std::stringstream ss; - ss << "POST " << uri->get_path() << " " - << "HTTP/1.1" << __SRS_CRLF - << "Host: " << uri->get_host() << __SRS_CRLF - << "Connection: Keep-Alive" << __SRS_CRLF - << "Content-Length: " << std::dec << req.length() << __SRS_CRLF - << "User-Agent: " << RTMP_SIG_SRS_NAME << RTMP_SIG_SRS_VERSION << __SRS_CRLF - << "Content-Type: application/json" << __SRS_CRLF - << __SRS_CRLF + ss << "POST " << path << " " + << "HTTP/1.1" << SRS_HTTP_CRLF + << "Host: " << host << SRS_HTTP_CRLF + << "Connection: Keep-Alive" << SRS_HTTP_CRLF + << "Content-Length: " << std::dec << req.length() << SRS_HTTP_CRLF + << "User-Agent: " << RTMP_SIG_SRS_NAME << RTMP_SIG_SRS_VERSION << SRS_HTTP_CRLF + << "Content-Type: application/json" << SRS_HTTP_CRLF + << SRS_HTTP_CRLF << req; - SrsStSocket skt(stfd); - std::string data = ss.str(); - if ((ret = skt.write((void*)data.c_str(), data.length(), NULL)) != ERROR_SUCCESS) { + if ((ret = skt->write((void*)data.c_str(), data.length(), NULL)) != ERROR_SUCCESS) { // disconnect when error. disconnect(); @@ -97,22 +105,61 @@ int SrsHttpClient::post(SrsHttpUri* uri, string req, string& res) } SrsHttpMessage* msg = NULL; - if ((ret = parser->parse_message(&skt, &msg)) != ERROR_SUCCESS) { + if ((ret = parser->parse_message(skt, NULL, &msg)) != ERROR_SUCCESS) { srs_error("parse http post response failed. ret=%d", ret); return ret; } srs_assert(msg); - srs_assert(msg->is_complete()); - - // get response body. - if (msg->body_size() > 0) { - res = msg->body(); - } + *ppmsg = msg; srs_info("parse http post response success."); - srs_freep(msg); - + return ret; +} + +int SrsHttpClient::get(string path, std::string req, SrsHttpMessage** ppmsg) +{ + *ppmsg = NULL; + + int ret = ERROR_SUCCESS; + + if ((ret = connect()) != ERROR_SUCCESS) { + srs_warn("http connect server failed. ret=%d", ret); + return ret; + } + + // send POST request to uri + // GET %s HTTP/1.1\r\nHost: %s\r\nContent-Length: %d\r\n\r\n%s + std::stringstream ss; + ss << "GET " << path << " " + << "HTTP/1.1" << SRS_HTTP_CRLF + << "Host: " << host << SRS_HTTP_CRLF + << "Connection: Keep-Alive" << SRS_HTTP_CRLF + << "Content-Length: " << std::dec << req.length() << SRS_HTTP_CRLF + << "User-Agent: " << RTMP_SIG_SRS_NAME << RTMP_SIG_SRS_VERSION << SRS_HTTP_CRLF + << "Content-Type: application/json" << SRS_HTTP_CRLF + << SRS_HTTP_CRLF + << req; + + std::string data = ss.str(); + if ((ret = skt->write((void*)data.c_str(), data.length(), NULL)) != ERROR_SUCCESS) { + // disconnect when error. + disconnect(); + + srs_error("write http get failed. ret=%d", ret); + return ret; + } + + SrsHttpMessage* msg = NULL; + if ((ret = parser->parse_message(skt, NULL, &msg)) != ERROR_SUCCESS) { + srs_error("parse http post response failed. ret=%d", ret); + return ret; + } + srs_assert(msg); + + *ppmsg = msg; + srs_info("parse http get response success."); + return ret; } @@ -121,9 +168,10 @@ void SrsHttpClient::disconnect() connected = false; srs_close_stfd(stfd); + srs_freep(skt); } -int SrsHttpClient::connect(SrsHttpUri* uri) +int SrsHttpClient::connect() { int ret = ERROR_SUCCESS; @@ -133,21 +181,22 @@ int SrsHttpClient::connect(SrsHttpUri* uri) disconnect(); - std::string server = uri->get_host(); - int port = uri->get_port(); - // open socket. - int64_t timeout = SRS_HTTP_CLIENT_SLEEP_US; - if ((ret = srs_socket_connect(server, port, timeout, &stfd)) != ERROR_SUCCESS) { + if ((ret = srs_socket_connect(host, port, timeout_us, &stfd)) != ERROR_SUCCESS) { srs_warn("http client failed, server=%s, port=%d, timeout=%"PRId64", ret=%d", - server.c_str(), port, timeout, ret); + host.c_str(), port, timeout_us, ret); return ret; } - srs_info("connect to server success. http url=%s, server=%s, port=%d", - uri->get_url(), uri->get_host(), uri->get_port()); + srs_info("connect to server success. server=%s, port=%d", host, port); + srs_assert(!skt); + skt = new SrsStSocket(stfd); connected = true; + // set the recv/send timeout in us. + skt->set_recv_timeout(timeout_us); + skt->set_send_timeout(timeout_us); + return ret; } diff --git a/trunk/src/app/srs_app_http_client.hpp b/trunk/src/app/srs_app_http_client.hpp index e62a5e54e6..87f2f36bdd 100644 --- a/trunk/src/app/srs_app_http_client.hpp +++ b/trunk/src/app/srs_app_http_client.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -37,6 +37,11 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. class SrsHttpUri; class SrsHttpParser; +class SrsHttpMessage; +class SrsStSocket; + +// the default timeout for http client. +#define SRS_HTTP_CLIENT_TIMEOUT_US (int64_t)(30*1000*1000LL) /** * http client to GET/POST/PUT/DELETE uri @@ -46,20 +51,39 @@ class SrsHttpClient private: bool connected; st_netfd_t stfd; + SrsStSocket* skt; SrsHttpParser* parser; +private: + int64_t timeout_us; + // host name or ip. + std::string host; + int port; public: SrsHttpClient(); virtual ~SrsHttpClient(); +public: + /** + * initialize the client, connect to host and port. + */ + virtual int initialize(std::string h, int p, int64_t t_us = SRS_HTTP_CLIENT_TIMEOUT_US); public: /** * to post data to the uri. - * @param req the data post to uri. - * @param res the response data from server. + * @param the path to request on. + * @param req the data post to uri. empty string to ignore. + * @param ppmsg output the http message to read the response. + */ + virtual int post(std::string path, std::string req, SrsHttpMessage** ppmsg); + /** + * to get data from the uri. + * @param the path to request on. + * @param req the data post to uri. empty string to ignore. + * @param ppmsg output the http message to read the response. */ - virtual int post(SrsHttpUri* uri, std::string req, std::string& res); + virtual int get(std::string path, std::string req, SrsHttpMessage** ppmsg); private: virtual void disconnect(); - virtual int connect(SrsHttpUri* uri); + virtual int connect(); }; #endif diff --git a/trunk/src/app/srs_app_http_conn.cpp b/trunk/src/app/srs_app_http_conn.cpp index 183b88bbaf..bdf8e2dc0f 100644 --- a/trunk/src/app/srs_app_http_conn.cpp +++ b/trunk/src/app/srs_app_http_conn.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -38,346 +38,1172 @@ using namespace std; #include #include #include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#define SRS_HTTP_DEFAULT_PAGE "index.html" +SrsVodStream::SrsVodStream(string root_dir) + : SrsHttpFileServer(root_dir) +{ +} -SrsHttpRoot::SrsHttpRoot() +SrsVodStream::~SrsVodStream() { - // TODO: FIXME: support reload vhosts. } -SrsHttpRoot::~SrsHttpRoot() +int SrsVodStream::serve_flv_stream(ISrsHttpResponseWriter* w, SrsHttpMessage* r, string fullpath, int offset) { + int ret = ERROR_SUCCESS; + + SrsFileReader fs; + + // open flv file + if ((ret = fs.open(fullpath)) != ERROR_SUCCESS) { + return ret; + } + + if (offset > fs.filesize()) { + ret = ERROR_HTTP_REMUX_OFFSET_OVERFLOW; + srs_warn("http flv streaming %s overflow. size=%"PRId64", offset=%d, ret=%d", + fullpath.c_str(), fs.filesize(), offset, ret); + return ret; + } + + SrsFlvVodStreamDecoder ffd; + + // open fast decoder + if ((ret = ffd.initialize(&fs)) != ERROR_SUCCESS) { + return ret; + } + + // save header, send later. + char flv_header[13]; + + // send flv header + if ((ret = ffd.read_header_ext(flv_header)) != ERROR_SUCCESS) { + return ret; + } + + // save sequence header, send later + char* sh_data = NULL; + int sh_size = 0; + + if (true) { + // send sequence header + int64_t start = 0; + if ((ret = ffd.read_sequence_header_summary(&start, &sh_size)) != ERROR_SUCCESS) { + return ret; + } + if (sh_size <= 0) { + ret = ERROR_HTTP_REMUX_SEQUENCE_HEADER; + srs_warn("http flv streaming no sequence header. size=%d, ret=%d", sh_size, ret); + return ret; + } + } + sh_data = new char[sh_size]; + SrsAutoFree(char, sh_data); + if ((ret = fs.read(sh_data, sh_size, NULL)) != ERROR_SUCCESS) { + return ret; + } + + // seek to data offset + int64_t left = fs.filesize() - offset; + + // write http header for ts. + w->header()->set_content_length((int)(sizeof(flv_header) + sh_size + left)); + w->header()->set_content_type("video/x-flv"); + + // write flv header and sequence header. + if ((ret = w->write(flv_header, sizeof(flv_header))) != ERROR_SUCCESS) { + return ret; + } + if (sh_size > 0 && (ret = w->write(sh_data, sh_size)) != ERROR_SUCCESS) { + return ret; + } + + // write body. + if ((ret = ffd.lseek(offset)) != ERROR_SUCCESS) { + return ret; + } + + // send data + if ((ret = copy(w, &fs, r, (int)left)) != ERROR_SUCCESS) { + srs_warn("read flv=%s size=%d failed, ret=%d", fullpath.c_str(), left, ret); + return ret; + } + + return ret; } -int SrsHttpRoot::initialize() +int SrsVodStream::serve_mp4_stream(ISrsHttpResponseWriter* w, SrsHttpMessage* r, string fullpath, int start, int end) { int ret = ERROR_SUCCESS; + + srs_assert(start >= 0); + srs_assert(end == -1 || end >= 0); - bool default_root_exists = false; + SrsFileReader fs; - // add other virtual path - SrsConfDirective* root = _srs_config->get_root(); - for (int i = 0; i < (int)root->directives.size(); i++) { - SrsConfDirective* conf = root->at(i); + // open flv file + if ((ret = fs.open(fullpath)) != ERROR_SUCCESS) { + return ret; + } + + // parse -1 to whole file. + if (end == -1) { + end = (int)fs.filesize(); + } + + if (end > fs.filesize() || start > end) { + ret = ERROR_HTTP_REMUX_OFFSET_OVERFLOW; + srs_warn("http mp4 streaming %s overflow. size=%"PRId64", offset=%d, ret=%d", + fullpath.c_str(), fs.filesize(), start, ret); + return ret; + } + + // seek to data offset, [start, end] for range. + int64_t left = end - start + 1; + + // write http header for ts. + w->header()->set_content_length(left); + w->header()->set_content_type("video/mp4"); + + // status code 206 to make dash.as happy. + w->write_header(SRS_CONSTS_HTTP_PartialContent); + + // response the content range header. + std::stringstream content_range; + content_range << "bytes " << start << "-" << end << "/" << fs.filesize(); + w->header()->set("Content-Range", content_range.str()); + + // write body. + fs.lseek(start); + + // send data + if ((ret = copy(w, &fs, r, (int)left)) != ERROR_SUCCESS) { + srs_warn("read mp4=%s size=%d failed, ret=%d", fullpath.c_str(), left, ret); + return ret; + } + + return ret; +} + +SrsStreamCache::SrsStreamCache(SrsSource* s, SrsRequest* r) +{ + req = r->copy(); + source = s; + queue = new SrsMessageQueue(true); + pthread = new SrsThread("http-stream", this, 0, false); +} + +SrsStreamCache::~SrsStreamCache() +{ + pthread->stop(); + srs_freep(pthread); + + srs_freep(queue); + srs_freep(req); +} + +int SrsStreamCache::start() +{ + return pthread->start(); +} + +int SrsStreamCache::dump_cache(SrsConsumer* consumer) +{ + int ret = ERROR_SUCCESS; + + double fast_cache = _srs_config->get_vhost_http_remux_fast_cache(req->vhost); + + if (fast_cache <= 0) { + srs_info("http: ignore dump fast cache."); + return ret; + } + + // TODO: FIXME: config it. + if ((ret = queue->dump_packets(consumer, false, 0, 0, SrsRtmpJitterAlgorithmOFF)) != ERROR_SUCCESS) { + return ret; + } + + srs_trace("http: dump cache %d msgs, duration=%dms, cache=%.2fs", + queue->size(), queue->duration(), fast_cache); + + return ret; +} + +int SrsStreamCache::cycle() +{ + int ret = ERROR_SUCCESS; + + SrsConsumer* consumer = NULL; + if ((ret = source->create_consumer(consumer, false, false, true)) != ERROR_SUCCESS) { + srs_error("http: create consumer failed. ret=%d", ret); + return ret; + } + SrsAutoFree(SrsConsumer, consumer); + + SrsPithyPrint* pprint = SrsPithyPrint::create_http_stream_cache(); + SrsAutoFree(SrsPithyPrint, pprint); + + SrsMessageArray msgs(SRS_PERF_MW_MSGS); + + // TODO: FIXME: support reload. + double fast_cache = _srs_config->get_vhost_http_remux_fast_cache(req->vhost); + if (fast_cache > 0) { + queue->set_queue_size(fast_cache); + } + + while (true) { + pprint->elapse(); + + // get messages from consumer. + // each msg in msgs.msgs must be free, for the SrsMessageArray never free them. + int count = 0; + if ((ret = consumer->dump_packets(&msgs, count)) != ERROR_SUCCESS) { + srs_error("http: get messages from consumer failed. ret=%d", ret); + return ret; + } - if (!conf->is_vhost()) { + if (count <= 0) { + srs_info("http: mw sleep %dms for no msg", mw_sleep); + // directly use sleep, donot use consumer wait. + st_usleep(SRS_CONSTS_RTMP_PULSE_TIMEOUT_US); + + // ignore when nothing got. continue; } + + if (pprint->can_print()) { + srs_trace("-> "SRS_CONSTS_LOG_HTTP_STREAM_CACHE" http: got %d msgs, age=%d, min=%d, mw=%d", + count, pprint->age(), SRS_PERF_MW_MIN_MSGS, SRS_CONSTS_RTMP_PULSE_TIMEOUT_US / 1000); + } + + // free the messages. + for (int i = 0; i < count; i++) { + SrsSharedPtrMessage* msg = msgs.msgs[i]; + if (fast_cache > 0) { + queue->enqueue(msg); + } else { + srs_freep(msg); + } + } + } + + return ret; +} + +ISrsStreamEncoder::ISrsStreamEncoder() +{ +} + +ISrsStreamEncoder::~ISrsStreamEncoder() +{ +} + +SrsTsStreamEncoder::SrsTsStreamEncoder() +{ + enc = new SrsTsEncoder(); +} + +SrsTsStreamEncoder::~SrsTsStreamEncoder() +{ + srs_freep(enc); +} + +int SrsTsStreamEncoder::initialize(SrsFileWriter* w, SrsStreamCache* /*c*/) +{ + int ret = ERROR_SUCCESS; + + if ((ret = enc->initialize(w)) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +int SrsTsStreamEncoder::write_audio(int64_t timestamp, char* data, int size) +{ + return enc->write_audio(timestamp, data, size); +} + +int SrsTsStreamEncoder::write_video(int64_t timestamp, char* data, int size) +{ + return enc->write_video(timestamp, data, size); +} + +int SrsTsStreamEncoder::write_metadata(int64_t /*timestamp*/, char* /*data*/, int /*size*/) +{ + return ERROR_SUCCESS; +} + +bool SrsTsStreamEncoder::has_cache() +{ + // for ts stream, use gop cache of SrsSource is ok. + return false; +} + +int SrsTsStreamEncoder::dump_cache(SrsConsumer* /*consumer*/) +{ + // for ts stream, ignore cache. + return ERROR_SUCCESS; +} + +SrsFlvStreamEncoder::SrsFlvStreamEncoder() +{ + enc = new SrsFlvEncoder(); +} + +SrsFlvStreamEncoder::~SrsFlvStreamEncoder() +{ + srs_freep(enc); +} + +int SrsFlvStreamEncoder::initialize(SrsFileWriter* w, SrsStreamCache* /*c*/) +{ + int ret = ERROR_SUCCESS; + + if ((ret = enc->initialize(w)) != ERROR_SUCCESS) { + return ret; + } + + // write flv header. + if ((ret = enc->write_header()) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +int SrsFlvStreamEncoder::write_audio(int64_t timestamp, char* data, int size) +{ + return enc->write_audio(timestamp, data, size); +} + +int SrsFlvStreamEncoder::write_video(int64_t timestamp, char* data, int size) +{ + return enc->write_video(timestamp, data, size); +} + +int SrsFlvStreamEncoder::write_metadata(int64_t timestamp, char* data, int size) +{ + return enc->write_metadata(SrsCodecFlvTagScript, data, size); +} + +bool SrsFlvStreamEncoder::has_cache() +{ + // for flv stream, use gop cache of SrsSource is ok. + return false; +} + +int SrsFlvStreamEncoder::dump_cache(SrsConsumer* /*consumer*/) +{ + // for flv stream, ignore cache. + return ERROR_SUCCESS; +} + +SrsAacStreamEncoder::SrsAacStreamEncoder() +{ + enc = new SrsAacEncoder(); + cache = NULL; +} + +SrsAacStreamEncoder::~SrsAacStreamEncoder() +{ + srs_freep(enc); +} + +int SrsAacStreamEncoder::initialize(SrsFileWriter* w, SrsStreamCache* c) +{ + int ret = ERROR_SUCCESS; + + cache = c; + + if ((ret = enc->initialize(w)) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +int SrsAacStreamEncoder::write_audio(int64_t timestamp, char* data, int size) +{ + return enc->write_audio(timestamp, data, size); +} + +int SrsAacStreamEncoder::write_video(int64_t /*timestamp*/, char* /*data*/, int /*size*/) +{ + // aac ignore any flv video. + return ERROR_SUCCESS; +} + +int SrsAacStreamEncoder::write_metadata(int64_t /*timestamp*/, char* /*data*/, int /*size*/) +{ + // aac ignore any flv metadata. + return ERROR_SUCCESS; +} + +bool SrsAacStreamEncoder::has_cache() +{ + return true; +} + +int SrsAacStreamEncoder::dump_cache(SrsConsumer* consumer) +{ + srs_assert(cache); + return cache->dump_cache(consumer); +} + +SrsMp3StreamEncoder::SrsMp3StreamEncoder() +{ + enc = new SrsMp3Encoder(); + cache = NULL; +} + +SrsMp3StreamEncoder::~SrsMp3StreamEncoder() +{ + srs_freep(enc); +} + +int SrsMp3StreamEncoder::initialize(SrsFileWriter* w, SrsStreamCache* c) +{ + int ret = ERROR_SUCCESS; + + cache = c; + + if ((ret = enc->initialize(w)) != ERROR_SUCCESS) { + return ret; + } + + if ((ret = enc->write_header()) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +int SrsMp3StreamEncoder::write_audio(int64_t timestamp, char* data, int size) +{ + return enc->write_audio(timestamp, data, size); +} + +int SrsMp3StreamEncoder::write_video(int64_t /*timestamp*/, char* /*data*/, int /*size*/) +{ + // mp3 ignore any flv video. + return ERROR_SUCCESS; +} + +int SrsMp3StreamEncoder::write_metadata(int64_t /*timestamp*/, char* /*data*/, int /*size*/) +{ + // mp3 ignore any flv metadata. + return ERROR_SUCCESS; +} + +bool SrsMp3StreamEncoder::has_cache() +{ + return true; +} + +int SrsMp3StreamEncoder::dump_cache(SrsConsumer* consumer) +{ + srs_assert(cache); + return cache->dump_cache(consumer); +} + +SrsStreamWriter::SrsStreamWriter(ISrsHttpResponseWriter* w) +{ + writer = w; +} + +SrsStreamWriter::~SrsStreamWriter() +{ +} + +int SrsStreamWriter::open(std::string /*file*/) +{ + return ERROR_SUCCESS; +} + +void SrsStreamWriter::close() +{ +} + +bool SrsStreamWriter::is_open() +{ + return true; +} + +int64_t SrsStreamWriter::tellg() +{ + return 0; +} + +int SrsStreamWriter::write(void* buf, size_t count, ssize_t* pnwrite) +{ + if (pnwrite) { + *pnwrite = count; + } + return writer->write((char*)buf, (int)count); +} + +SrsLiveStream::SrsLiveStream(SrsSource* s, SrsRequest* r, SrsStreamCache* c) +{ + source = s; + cache = c; + req = r->copy(); +} + +SrsLiveStream::~SrsLiveStream() +{ + srs_freep(req); +} + +int SrsLiveStream::serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r) +{ + int ret = ERROR_SUCCESS; + + ISrsStreamEncoder* enc = NULL; + + srs_assert(entry); + if (srs_string_ends_with(entry->pattern, ".flv")) { + w->header()->set_content_type("video/x-flv"); + enc = new SrsFlvStreamEncoder(); + } else if (srs_string_ends_with(entry->pattern, ".aac")) { + w->header()->set_content_type("audio/x-aac"); + enc = new SrsAacStreamEncoder(); + } else if (srs_string_ends_with(entry->pattern, ".mp3")) { + w->header()->set_content_type("audio/mpeg"); + enc = new SrsMp3StreamEncoder(); + } else if (srs_string_ends_with(entry->pattern, ".ts")) { + w->header()->set_content_type("video/MP2T"); + enc = new SrsTsStreamEncoder(); + } else { + ret = ERROR_HTTP_LIVE_STREAM_EXT; + srs_error("http: unsupported pattern %s", entry->pattern.c_str()); + return ret; + } + SrsAutoFree(ISrsStreamEncoder, enc); + + // create consumer of souce, ignore gop cache, use the audio gop cache. + SrsConsumer* consumer = NULL; + if ((ret = source->create_consumer(consumer, true, true, !enc->has_cache())) != ERROR_SUCCESS) { + srs_error("http: create consumer failed. ret=%d", ret); + return ret; + } + SrsAutoFree(SrsConsumer, consumer); + srs_verbose("http: consumer created success."); + + SrsPithyPrint* pprint = SrsPithyPrint::create_http_stream(); + SrsAutoFree(SrsPithyPrint, pprint); + + SrsMessageArray msgs(SRS_PERF_MW_MSGS); + + // the memory writer. + SrsStreamWriter writer(w); + if ((ret = enc->initialize(&writer, cache)) != ERROR_SUCCESS) { + srs_error("http: initialize stream encoder failed. ret=%d", ret); + return ret; + } + + // if gop cache enabled for encoder, dump to consumer. + if (enc->has_cache()) { + if ((ret = enc->dump_cache(consumer)) != ERROR_SUCCESS) { + srs_error("http: dump cache to consumer failed. ret=%d", ret); + return ret; + } + } + + while (true) { + pprint->elapse(); + + // get messages from consumer. + // each msg in msgs.msgs must be free, for the SrsMessageArray never free them. + int count = 0; + if ((ret = consumer->dump_packets(&msgs, count)) != ERROR_SUCCESS) { + srs_error("http: get messages from consumer failed. ret=%d", ret); + return ret; + } - std::string vhost = conf->arg0(); - if (!_srs_config->get_vhost_http_enabled(vhost)) { + if (count <= 0) { + srs_info("http: mw sleep %dms for no msg", mw_sleep); + // directly use sleep, donot use consumer wait. + st_usleep(SRS_CONSTS_RTMP_PULSE_TIMEOUT_US); + + // ignore when nothing got. continue; } + + if (pprint->can_print()) { + srs_info("-> "SRS_CONSTS_LOG_HTTP_STREAM" http: got %d msgs, age=%d, min=%d, mw=%d", + count, pprint->age(), SRS_PERF_MW_MIN_MSGS, SRS_CONSTS_RTMP_PULSE_TIMEOUT_US / 1000); + } - std::string mount = _srs_config->get_vhost_http_mount(vhost); - std::string dir = _srs_config->get_vhost_http_dir(vhost); + // sendout all messages. + ret = streaming_send_messages(enc, msgs.msgs, count); + + // free the messages. + for (int i = 0; i < count; i++) { + SrsSharedPtrMessage* msg = msgs.msgs[i]; + srs_freep(msg); + } + + // check send error code. + if (ret != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("http: send messages to client failed. ret=%d", ret); + } + return ret; + } + } + + return ret; +} + +int SrsLiveStream::streaming_send_messages(ISrsStreamEncoder* enc, SrsSharedPtrMessage** msgs, int nb_msgs) +{ + int ret = ERROR_SUCCESS; + + for (int i = 0; i < nb_msgs; i++) { + SrsSharedPtrMessage* msg = msgs[i]; - handlers.push_back(new SrsHttpVhost(vhost, mount, dir)); + if (msg->is_audio()) { + ret = enc->write_audio(msg->timestamp, msg->payload, msg->size); + } else if (msg->is_video()) { + ret = enc->write_video(msg->timestamp, msg->payload, msg->size); + } else { + ret = enc->write_metadata(msg->timestamp, msg->payload, msg->size); + } - if (mount == "/") { - default_root_exists = true; + if (ret != ERROR_SUCCESS) { + return ret; } } - if (!default_root_exists) { - // add root - handlers.push_back(new SrsHttpVhost( - "__http__", "/", _srs_config->get_http_stream_dir())); + return ret; +} + +SrsLiveEntry::SrsLiveEntry(std::string m, bool h) +{ + mount = m; + hstrs = h; + + stream = NULL; + cache = NULL; + + std::string ext; + size_t pos = string::npos; + if ((pos = m.rfind(".")) != string::npos) { + ext = m.substr(pos); + } + _is_flv = (ext == ".flv"); + _is_ts = (ext == ".ts"); + _is_mp3 = (ext == ".mp3"); + _is_aac = (ext == ".aac"); +} + +bool SrsLiveEntry::is_flv() +{ + return _is_flv; +} + +bool SrsLiveEntry::is_ts() +{ + return _is_ts; +} + +bool SrsLiveEntry::is_aac() +{ + return _is_aac; +} + +bool SrsLiveEntry::is_mp3() +{ + return _is_mp3; +} + +SrsHlsM3u8Stream::SrsHlsM3u8Stream() +{ +} + +SrsHlsM3u8Stream::~SrsHlsM3u8Stream() +{ +} + +void SrsHlsM3u8Stream::set_m3u8(std::string v) +{ + m3u8 = v; +} + +int SrsHlsM3u8Stream::serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r) +{ + int ret = ERROR_SUCCESS; + + std::string data = m3u8; + + w->header()->set_content_length((int)data.length()); + w->header()->set_content_type("application/x-mpegURL;charset=utf-8"); + + if ((ret = w->write((char*)data.data(), (int)data.length())) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("send m3u8 failed. ret=%d", ret); + } + return ret; + } + + return ret; +} + +SrsHlsTsStream::SrsHlsTsStream() +{ +} + +SrsHlsTsStream::~SrsHlsTsStream() +{ +} + +void SrsHlsTsStream::set_ts(std::string v) +{ + ts = v; +} + +int SrsHlsTsStream::serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r) +{ + int ret = ERROR_SUCCESS; + + std::string data = ts; + + w->header()->set_content_length((int)data.length()); + w->header()->set_content_type("video/MP2T"); + + if ((ret = w->write((char*)data.data(), (int)data.length())) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("send ts failed. ret=%d", ret); + } + return ret; + } + + return ret; +} + +SrsHlsEntry::SrsHlsEntry() +{ +} + +SrsHttpServer::SrsHttpServer(SrsServer* svr) +{ + server = svr; + + mux.hijack(this); +} + +SrsHttpServer::~SrsHttpServer() +{ + mux.unhijack(this); + + if (true) { + std::map::iterator it; + for (it = tflvs.begin(); it != tflvs.end(); ++it) { + SrsLiveEntry* entry = it->second; + srs_freep(entry); + } + tflvs.clear(); + } + if (true) { + std::map::iterator it; + for (it = sflvs.begin(); it != sflvs.end(); ++it) { + SrsLiveEntry* entry = it->second; + srs_freep(entry); + } + sflvs.clear(); + } + if (true) { + std::map::iterator it; + for (it = thls.begin(); it != thls.end(); ++it) { + SrsHlsEntry* entry = it->second; + srs_freep(entry); + } + thls.clear(); + } + if (true) { + std::map::iterator it; + for (it = shls.begin(); it != shls.end(); ++it) { + SrsHlsEntry* entry = it->second; + srs_freep(entry); + } + shls.clear(); + } +} + +int SrsHttpServer::initialize() +{ + int ret = ERROR_SUCCESS; + + // static file + // flv vod streaming. + if ((ret = initialize_static_file()) != ERROR_SUCCESS) { + return ret; + } + + // remux rtmp to flv live streaming + if ((ret = initialize_flv_streaming()) != ERROR_SUCCESS) { + return ret; + } + + // remux rtmp to hls live streaming + if ((ret = initialize_hls_streaming()) != ERROR_SUCCESS) { + return ret; } return ret; } -int SrsHttpRoot::best_match(const char* path, int length, SrsHttpHandlerMatch** ppmatch) +int SrsHttpServer::http_mount(SrsSource* s, SrsRequest* r) { int ret = ERROR_SUCCESS; + + // the id to identify stream. + std::string sid = r->get_stream_url(); + SrsLiveEntry* entry = NULL; + + // create stream from template when not found. + if (sflvs.find(sid) == sflvs.end()) { + if (tflvs.find(r->vhost) == tflvs.end()) { + srs_info("ignore mount flv stream for disabled"); + return ret; + } + + SrsLiveEntry* tmpl = tflvs[r->vhost]; + + std::string mount = tmpl->mount; + + // replace the vhost variable + mount = srs_string_replace(mount, "[vhost]", r->vhost); + mount = srs_string_replace(mount, "[app]", r->app); + mount = srs_string_replace(mount, "[stream]", r->stream); + + // remove the default vhost mount + mount = srs_string_replace(mount, SRS_CONSTS_RTMP_DEFAULT_VHOST"/", "/"); - // find the best matched child handler. - std::vector::iterator it; - for (it = handlers.begin(); it != handlers.end(); ++it) { - SrsHttpHandler* h = *it; + entry = new SrsLiveEntry(mount, tmpl->hstrs); + + entry->cache = new SrsStreamCache(s, r); + entry->stream = new SrsLiveStream(s, r, entry->cache); - // search all child handlers. - h->best_match(path, length, ppmatch); + sflvs[sid] = entry; + + // start http stream cache thread + if ((ret = entry->cache->start()) != ERROR_SUCCESS) { + srs_error("http: start stream cache failed. ret=%d", ret); + return ret; + } + + // mount the http flv stream. + if ((ret = mux.handle(mount, entry->stream)) != ERROR_SUCCESS) { + srs_error("http: mount flv stream for vhost=%s failed. ret=%d", sid.c_str(), ret); + return ret; + } + srs_trace("http: mount flv stream for vhost=%s, mount=%s", sid.c_str(), mount.c_str()); + } else { + entry = sflvs[sid]; } - // if already matched by child, return. - if (*ppmatch) { + // TODO: FIXME: supports reload. + if (entry->stream) { + entry->stream->entry->enabled = true; return ret; } - // not matched, error. - return ERROR_HTTP_HANDLER_MATCH_URL; -} - -bool SrsHttpRoot::is_handler_valid(SrsHttpMessage* /*req*/, int& status_code, std::string& reason_phrase) -{ - status_code = SRS_CONSTS_HTTP_InternalServerError; - reason_phrase = SRS_CONSTS_HTTP_InternalServerError_str; - - return false; -} - -int SrsHttpRoot::do_process_request(SrsStSocket* /*skt*/, SrsHttpMessage* /*req*/) -{ - int ret = ERROR_SUCCESS; return ret; } -SrsHttpVhost::SrsHttpVhost(std::string vhost, std::string mount, std::string dir) -{ - _vhost = vhost; - _mount = mount; - _dir = dir; -} - -SrsHttpVhost::~SrsHttpVhost() +void SrsHttpServer::http_unmount(SrsSource* s, SrsRequest* r) { -} + std::string sid = r->get_stream_url(); + + if (sflvs.find(sid) == sflvs.end()) { + srs_info("ignore unmount flv stream for disabled"); + return; + } -bool SrsHttpVhost::can_handle(const char* path, int length, const char** /*pchild*/) -{ - return srs_path_like(_mount.c_str(), path, length); + SrsLiveEntry* entry = sflvs[sid]; + entry->stream->entry->enabled = false; } -bool SrsHttpVhost::is_handler_valid(SrsHttpMessage* req, int& status_code, std::string& reason_phrase) +int SrsHttpServer::mount_hls(SrsRequest* r) { - std::string fullpath = get_request_file(req); + int ret = ERROR_SUCCESS; - if (::access(fullpath.c_str(), F_OK | R_OK) < 0) { - srs_warn("check file %s does not exists", fullpath.c_str()); - - status_code = SRS_CONSTS_HTTP_NotFound; - reason_phrase = SRS_CONSTS_HTTP_NotFound_str; - return false; + std::string sid = r->get_stream_url(); + + if (shls.find(sid) == shls.end()) { + srs_info("ignore mount hls stream for disabled"); + return ret; } - return true; -} - -int SrsHttpVhost::do_process_request(SrsStSocket* skt, SrsHttpMessage* req) -{ - std::string fullpath = get_request_file(req); + SrsHlsEntry* entry = shls[sid]; - // TODO: FIXME: support mp4, @see https://github.com/winlinvip/simple-rtmp-server/issues/174 - if (srs_string_ends_with(fullpath, ".ts")) { - return response_ts_file(skt, req, fullpath); - } else if (srs_string_ends_with(fullpath, ".flv") || srs_string_ends_with(fullpath, ".fhv")) { - std::string start = req->query_get("start"); - if (start.empty()) { - return response_flv_file(skt, req, fullpath); - } - - int offset = ::atoi(start.c_str()); - if (offset <= 0) { - return response_flv_file(skt, req, fullpath); - } - - return response_flv_file2(skt, req, fullpath, offset); + // TODO: FIXME: supports reload. + std::map::iterator it; + for (it = entry->streams.begin(); it != entry->streams.end(); ++it) { + ISrsHttpHandler* stream = it->second; + stream->entry->enabled = true; } - return response_regular_file(skt, req, fullpath); + return ret; } -int SrsHttpVhost::response_regular_file(SrsStSocket* skt, SrsHttpMessage* req, string fullpath) +int SrsHttpServer::hls_update_m3u8(SrsRequest* r, string m3u8) { int ret = ERROR_SUCCESS; - SrsFileReader fs; + std::string mount; - if ((ret = fs.open(fullpath)) != ERROR_SUCCESS) { - srs_warn("open file %s failed, ret=%d", fullpath.c_str(), ret); - return ret; - } - - int64_t length = fs.filesize(); + std::string sid = r->get_stream_url(); + SrsHlsEntry* entry = NULL; - char* buf = new char[length]; - SrsAutoFree(char, buf); + // create stream from template when not found. + if (shls.find(sid) == shls.end()) { + if (thls.find(r->vhost) == thls.end()) { + srs_info("ignore mount hls stream for disabled"); + return ret; + } - if ((ret = fs.read(buf, length, NULL)) != ERROR_SUCCESS) { - srs_warn("read file %s failed, ret=%d", fullpath.c_str(), ret); - return ret; - } + SrsHlsEntry* tmpl = thls[r->vhost]; + + entry = new SrsHlsEntry(); + mount = tmpl->mount; + + // replace the vhost variable + mount = srs_string_replace(mount, "[vhost]", r->vhost); + mount = srs_string_replace(mount, "[app]", r->app); + mount = srs_string_replace(mount, "[stream]", r->stream); - std::string str; - str.append(buf, length); + // remove the default vhost mount + mount = srs_string_replace(mount, SRS_CONSTS_RTMP_DEFAULT_VHOST"/", "/"); + + entry->mount = mount; + shls[sid] = entry; + + if (entry->streams.find(mount) == entry->streams.end()) { + ISrsHttpHandler* he = new SrsHlsM3u8Stream(); + entry->streams[mount] = he; - if (srs_string_ends_with(fullpath, ".ts")) { - return res_mpegts(skt, req, str); - } else if (srs_string_ends_with(fullpath, ".m3u8")) { - return res_m3u8(skt, req, str); - } else if (srs_string_ends_with(fullpath, ".xml")) { - return res_xml(skt, req, str); - } else if (srs_string_ends_with(fullpath, ".js")) { - return res_javascript(skt, req, str); - } else if (srs_string_ends_with(fullpath, ".json")) { - return res_json(skt, req, str); - } else if (srs_string_ends_with(fullpath, ".swf")) { - return res_swf(skt, req, str); - } else if (srs_string_ends_with(fullpath, ".css")) { - return res_css(skt, req, str); - } else if (srs_string_ends_with(fullpath, ".ico")) { - return res_ico(skt, req, str); + if ((ret = mux.handle(mount, he)) != ERROR_SUCCESS) { + srs_error("handle mount=%s failed. ret=%d", mount.c_str(), ret); + return ret; + } + } } else { - return res_text(skt, req, str); + entry = shls[sid]; } - + + mount = entry->mount; + + // update the m3u8 stream. + SrsHlsM3u8Stream* hms = dynamic_cast(entry->streams[mount]); + if (hms) { + hms->set_m3u8(m3u8); + } + srs_trace("hls update m3u8 ok, mount=%s", mount.c_str()); + return ret; } -int SrsHttpVhost::response_flv_file(SrsStSocket* skt, SrsHttpMessage* req, string fullpath) +int SrsHttpServer::hls_update_ts(SrsRequest* r, string uri, string ts) { int ret = ERROR_SUCCESS; - - SrsFileReader fs; - // TODO: FIXME: use more advance cache. - if ((ret = fs.open(fullpath)) != ERROR_SUCCESS) { - srs_warn("open file %s failed, ret=%d", fullpath.c_str(), ret); + std::string sid = r->get_stream_url(); + + // when no hls mounted, ignore. + if (shls.find(sid) == shls.end()) { return ret; } - int64_t length = fs.filesize(); + SrsHlsEntry* entry = shls[sid]; + srs_assert(entry); - // write http header for ts. - std::stringstream ss; - - res_status_line(ss)->res_content_type_flv(ss) - ->res_content_length(ss, (int)length); - - if (req->requires_crossdomain()) { - res_enable_crossdomain(ss); - } + std::string mount = entry->mount; - res_header_eof(ss); - - // flush http header to peer - if ((ret = res_flush(skt, ss)) != ERROR_SUCCESS) { - return ret; + // the ts is relative from the m3u8, the same start dir. + size_t pos = string::npos; + if ((pos = mount.rfind("/")) != string::npos) { + mount = mount.substr(0, pos); } - - // write body. - int64_t left = length; - char* buf = req->http_ts_send_buffer(); - - while (left > 0) { - ssize_t nread = -1; - if ((ret = fs.read(buf, __SRS_HTTP_TS_SEND_BUFFER_SIZE, &nread)) != ERROR_SUCCESS) { - srs_warn("read file %s failed, ret=%d", fullpath.c_str(), ret); - break; - } - - left -= nread; - if ((ret = skt->write(buf, nread, NULL)) != ERROR_SUCCESS) { - break; + + // replace the vhost variable + mount = srs_string_replace(mount, "[vhost]", r->vhost); + mount = srs_string_replace(mount, "[app]", r->app); + + // remove the default vhost mount + mount = srs_string_replace(mount, SRS_CONSTS_RTMP_DEFAULT_VHOST"/", "/"); + + // mount with ts. + mount += "/"; + mount += uri; + + if (entry->streams.find(mount) == entry->streams.end()) { + ISrsHttpHandler* he = new SrsHlsTsStream(); + entry->streams[mount] = he; + + if ((ret = mux.handle(mount, he)) != ERROR_SUCCESS) { + srs_error("handle mount=%s failed. ret=%d", mount.c_str(), ret); + return ret; } } - + + // update the ts stream. + SrsHlsTsStream* hts = dynamic_cast(entry->streams[mount]); + if (hts) { + hts->set_ts(ts); + } + srs_trace("hls update ts ok, mount=%s", mount.c_str()); + return ret; } -int SrsHttpVhost::response_flv_file2(SrsStSocket* skt, SrsHttpMessage* req, string fullpath, int offset) +void SrsHttpServer::unmount_hls(SrsRequest* r) { - int ret = ERROR_SUCCESS; - - SrsFileReader fs; + std::string sid = r->get_stream_url(); - // open flv file - if ((ret = fs.open(fullpath)) != ERROR_SUCCESS) { - return ret; + if (shls.find(sid) == shls.end()) { + srs_info("ignore unmount hls stream for disabled"); + return; + } + + SrsHlsEntry* entry = shls[sid]; + + std::map::iterator it; + for (it = entry->streams.begin(); it != entry->streams.end(); ++it) { + ISrsHttpHandler* stream = it->second; + stream->entry->enabled = false; } +} + +int SrsHttpServer::on_reload_vhost_http_updated() +{ + int ret = ERROR_SUCCESS; + // TODO: FIXME: implements it. + return ret; +} + +int SrsHttpServer::on_reload_vhost_http_remux_updated() +{ + int ret = ERROR_SUCCESS; + // TODO: FIXME: implements it. + return ret; +} + +int SrsHttpServer::on_reload_vhost_hls(string vhost) +{ + int ret = ERROR_SUCCESS; + // TODO: FIXME: implements it. + return ret; +} + +int SrsHttpServer::hijack(SrsHttpMessage* request, ISrsHttpHandler** ph) +{ + int ret = ERROR_SUCCESS; - if (offset > fs.filesize()) { - ret = ERROR_HTTP_FLV_OFFSET_OVERFLOW; - srs_warn("http flv streaming %s overflow. size=%"PRId64", offset=%d, ret=%d", - fullpath.c_str(), fs.filesize(), offset, ret); + // when handler not the root, we think the handler is ok. + ISrsHttpHandler* h = *ph? *ph : NULL; + if (h && h->entry && h->entry->pattern != "/") { return ret; } - SrsFlvVodStreamDecoder ffd; - - // open fast decoder - if ((ret = ffd.initialize(&fs)) != ERROR_SUCCESS) { + // only hijack for http streaming, http-flv/ts/mp3/aac. + std::string ext = request->ext(); + if (ext.empty()) { return ret; } - // save header, send later. - char flv_header[13]; - - // send flv header - if ((ret = ffd.read_header_ext(flv_header)) != ERROR_SUCCESS) { + // find the actually request vhost. + SrsConfDirective* vhost = _srs_config->get_vhost(request->host()); + if (!vhost || !_srs_config->get_vhost_enabled(vhost)) { return ret; } - // save sequence header, send later - char* sh_data = NULL; - int sh_size = 0; - + // find the entry template for the stream. + SrsLiveEntry* entry = NULL; if (true) { - // send sequence header - int64_t start = 0; - if ((ret = ffd.read_sequence_header_summary(&start, &sh_size)) != ERROR_SUCCESS) { + // no http streaming on vhost, ignore. + std::map::iterator it = tflvs.find(vhost->arg0()); + if (it == tflvs.end()) { return ret; } - if (sh_size <= 0) { - ret = ERROR_HTTP_FLV_SEQUENCE_HEADER; - srs_warn("http flv streaming no sequence header. size=%d, ret=%d", sh_size, ret); + + // hstrs not enabled, ignore. + entry = it->second; + if (!entry->hstrs) { return ret; } - } - sh_data = new char[sh_size]; - SrsAutoFree(char, sh_data); - if ((ret = fs.read(sh_data, sh_size, NULL)) != ERROR_SUCCESS) { - return ret; - } - - // seek to data offset - int64_t left = fs.filesize() - offset; - // write http header for ts. - std::stringstream ss; - - res_status_line(ss)->res_content_type_flv(ss) - ->res_content_length(ss, (int)(sizeof(flv_header) + sh_size + left)); - - if (req->requires_crossdomain()) { - res_enable_crossdomain(ss); + // check entry and request extension. + if (entry->is_flv()) { + if (ext != ".flv") { + return ret; + } + } else if (entry->is_ts()) { + if (ext != ".ts") { + return ret; + } + } else if (entry->is_mp3()) { + if (ext != ".mp3") { + return ret; + } + } else if (entry->is_aac()) { + if (ext != ".aac") { + return ret; + } + } else { + return ret; + } } - res_header_eof(ss); - - // flush http header to peer - if ((ret = res_flush(skt, ss)) != ERROR_SUCCESS) { - return ret; + // hijack for entry. + SrsRequest* r = request->to_request(vhost->arg0()); + SrsAutoFree(SrsRequest, r); + SrsSource* s = SrsSource::fetch(r); + if (!s) { + if ((ret = SrsSource::create(r, server, server, &s)) != ERROR_SUCCESS) { + return ret; + } } + srs_assert(s != NULL); - if ((ret = skt->write(flv_header, sizeof(flv_header), NULL)) != ERROR_SUCCESS) { - return ret; - } - if (sh_size > 0 && (ret = skt->write(sh_data, sh_size, NULL)) != ERROR_SUCCESS) { + // create http streaming handler. + if ((ret = http_mount(s, r)) != ERROR_SUCCESS) { return ret; } - // write body. - char* buf = req->http_ts_send_buffer(); - if ((ret = ffd.lseek(offset)) != ERROR_SUCCESS) { - return ret; + // use the handler if exists. + if (ph) { + std::string sid = r->get_stream_url(); + if (sflvs.find(sid) != sflvs.end()) { + entry = sflvs[sid]; + *ph = entry->stream; + } } - // send data - while (left > 0) { - ssize_t nread = -1; - if ((ret = fs.read(buf, __SRS_HTTP_TS_SEND_BUFFER_SIZE, &nread)) != ERROR_SUCCESS) { - return ret; - } - - left -= nread; - if ((ret = skt->write(buf, nread, NULL)) != ERROR_SUCCESS) { + // trigger edge to fetch from origin. + bool vhost_is_edge = _srs_config->get_vhost_is_edge(r->vhost); + srs_trace("hstrs: source url=%s, is_edge=%d, source_id=%d[%d]", + r->get_stream_url().c_str(), vhost_is_edge, s->source_id(), s->source_id()); + + // TODO: FIXME: disconnect when all connection closed. + if (vhost_is_edge) { + // notice edge to start for the first client. + if ((ret = s->on_edge_start_play()) != ERROR_SUCCESS) { + srs_error("notice edge start play stream failed. ret=%d", ret); return ret; } } @@ -385,103 +1211,134 @@ int SrsHttpVhost::response_flv_file2(SrsStSocket* skt, SrsHttpMessage* req, stri return ret; } -int SrsHttpVhost::response_ts_file(SrsStSocket* skt, SrsHttpMessage* req, string fullpath) +int SrsHttpServer::initialize_static_file() { int ret = ERROR_SUCCESS; - SrsFileReader fs; + bool default_root_exists = false; - // TODO: FIXME: use more advance cache. - if ((ret = fs.open(fullpath)) != ERROR_SUCCESS) { - srs_warn("open file %s failed, ret=%d", fullpath.c_str(), ret); - return ret; - } - - int64_t length = fs.filesize(); + // http static file and flv vod stream mount for each vhost. + SrsConfDirective* root = _srs_config->get_root(); + for (int i = 0; i < (int)root->directives.size(); i++) { + SrsConfDirective* conf = root->at(i); + + if (!conf->is_vhost()) { + continue; + } + + std::string vhost = conf->arg0(); + if (!_srs_config->get_vhost_http_enabled(vhost)) { + continue; + } + + std::string mount = _srs_config->get_vhost_http_mount(vhost); + std::string dir = _srs_config->get_vhost_http_dir(vhost); - // write http header for ts. - std::stringstream ss; + // replace the vhost variable + mount = srs_string_replace(mount, "[vhost]", vhost); - res_status_line(ss)->res_content_type_mpegts(ss) - ->res_content_length(ss, (int)length); + // remove the default vhost mount + mount = srs_string_replace(mount, SRS_CONSTS_RTMP_DEFAULT_VHOST"/", "/"); + + // the dir mount must always ends with "/" + if (mount != "/" && mount.rfind("/") != mount.length() - 1) { + mount += "/"; + } - if (req->requires_crossdomain()) { - res_enable_crossdomain(ss); + // mount the http of vhost. + if ((ret = mux.handle(mount, new SrsVodStream(dir))) != ERROR_SUCCESS) { + srs_error("http: mount dir=%s for vhost=%s failed. ret=%d", dir.c_str(), vhost.c_str(), ret); + return ret; + } + + if (mount == "/") { + default_root_exists = true; + srs_warn("http: root mount to %s", dir.c_str()); + } + srs_trace("http: vhost=%s mount to %s", vhost.c_str(), mount.c_str()); } - res_header_eof(ss); - - // flush http header to peer - if ((ret = res_flush(skt, ss)) != ERROR_SUCCESS) { - return ret; + if (!default_root_exists) { + // add root + std::string dir = _srs_config->get_http_stream_dir(); + if ((ret = mux.handle("/", new SrsVodStream(dir))) != ERROR_SUCCESS) { + srs_error("http: mount root dir=%s failed. ret=%d", dir.c_str(), ret); + return ret; + } + srs_trace("http: root mount to %s", dir.c_str()); } - // write body. - int64_t left = length; - char* buf = req->http_ts_send_buffer(); + return ret; +} + +int SrsHttpServer::initialize_flv_streaming() +{ + int ret = ERROR_SUCCESS; - while (left > 0) { - ssize_t nread = -1; - if ((ret = fs.read(buf, __SRS_HTTP_TS_SEND_BUFFER_SIZE, &nread)) != ERROR_SUCCESS) { - srs_warn("read file %s failed, ret=%d", fullpath.c_str(), ret); - break; + // http flv live stream mount for each vhost. + SrsConfDirective* root = _srs_config->get_root(); + for (int i = 0; i < (int)root->directives.size(); i++) { + SrsConfDirective* conf = root->at(i); + + if (!conf->is_vhost()) { + continue; } - left -= nread; - if ((ret = skt->write(buf, nread, NULL)) != ERROR_SUCCESS) { - break; + std::string vhost = conf->arg0(); + if (!_srs_config->get_vhost_http_remux_enabled(vhost)) { + continue; } + + SrsLiveEntry* entry = new SrsLiveEntry( + _srs_config->get_vhost_http_remux_mount(vhost), + _srs_config->get_vhost_http_remux_hstrs(vhost) + ); + tflvs[vhost] = entry; + srs_trace("http flv live stream, vhost=%s, mount=%s", + vhost.c_str(), entry->mount.c_str()); } return ret; } -string SrsHttpVhost::get_request_file(SrsHttpMessage* req) +int SrsHttpServer::initialize_hls_streaming() { - std::string fullpath = _dir + "/"; + int ret = ERROR_SUCCESS; - // if root, directly use the matched url. - if (_mount == "/") { - // add the dir - fullpath += req->match()->matched_url; - // if file speicified, add the file. - if (!req->match()->unmatched_url.empty()) { - fullpath += "/" + req->match()->unmatched_url; + // http hls live stream mount for each vhost. + SrsConfDirective* root = _srs_config->get_root(); + for (int i = 0; i < (int)root->directives.size(); i++) { + SrsConfDirective* conf = root->at(i); + + if (!conf->is_vhost()) { + continue; } - } else { - // virtual path, ignore the virutal path. - fullpath += req->match()->unmatched_url; - } - - // add default pages. - if (srs_string_ends_with(fullpath, "/")) { - fullpath += SRS_HTTP_DEFAULT_PAGE; + + std::string vhost = conf->arg0(); + if (!_srs_config->get_hls_enabled(vhost)) { + continue; + } + + std::string storage = _srs_config->get_hls_storage(vhost); + if (storage != "ram" && storage != "both") { + continue; + } + + SrsHlsEntry* entry = new SrsHlsEntry(); + entry->mount = _srs_config->get_hls_mount(vhost); + thls[vhost] = entry; + srs_trace("http hls live stream, vhost=%s, mount=%s", + vhost.c_str(), entry->mount.c_str()); } - return fullpath; -} - -string SrsHttpVhost::vhost() -{ - return _vhost; -} - -string SrsHttpVhost::mount() -{ - return _mount; -} - -string SrsHttpVhost::dir() -{ - return _dir; + return ret; } -SrsHttpConn::SrsHttpConn(SrsServer* srs_server, st_netfd_t client_stfd, SrsHttpHandler* _handler) - : SrsConnection(srs_server, client_stfd) +SrsHttpConn::SrsHttpConn(IConnectionManager* cm, st_netfd_t fd, SrsHttpServeMux* m) + : SrsConnection(cm, fd) { parser = new SrsHttpParser(); - handler = _handler; - requires_crossdomain = false; + http_mux = m; } SrsHttpConn::~SrsHttpConn() @@ -489,7 +1346,7 @@ SrsHttpConn::~SrsHttpConn() srs_freep(parser); } -void SrsHttpConn::kbps_resample() +void SrsHttpConn::resample() { // TODO: FIXME: implements it } @@ -506,6 +1363,11 @@ int64_t SrsHttpConn::get_recv_bytes_delta() return 0; } +void SrsHttpConn::cleanup() +{ + // TODO: FIXME: implements it +} + int SrsHttpConn::do_cycle() { int ret = ERROR_SUCCESS; @@ -526,19 +1388,24 @@ int SrsHttpConn::do_cycle() SrsHttpMessage* req = NULL; // get a http message - if ((ret = parser->parse_message(&skt, &req)) != ERROR_SUCCESS) { + if ((ret = parser->parse_message(&skt, this, &req)) != ERROR_SUCCESS) { return ret; } - // if SUCCESS, always NOT-NULL and completed message. + // if SUCCESS, always NOT-NULL. srs_assert(req); - srs_assert(req->is_complete()); // always free it in this scope. SrsAutoFree(SrsHttpMessage, req); + // may should discard the body. + if ((ret = on_got_http_message(req)) != ERROR_SUCCESS) { + return ret; + } + // ok, handle http request. - if ((ret = process_request(&skt, req)) != ERROR_SUCCESS) { + SrsHttpResponseWriter writer(&skt); + if ((ret = process_request(&writer, req)) != ERROR_SUCCESS) { return ret; } } @@ -546,46 +1413,45 @@ int SrsHttpConn::do_cycle() return ret; } -int SrsHttpConn::process_request(SrsStSocket* skt, SrsHttpMessage* req) +int SrsHttpConn::process_request(ISrsHttpResponseWriter* w, SrsHttpMessage* r) { int ret = ERROR_SUCCESS; - - // parse uri to schema/server:port/path?query - if ((ret = req->parse_uri()) != ERROR_SUCCESS) { - return ret; - } srs_trace("HTTP %s %s, content-length=%"PRId64"", - req->method_str().c_str(), req->url().c_str(), req->content_length()); - - // TODO: maybe need to parse the url. - std::string url = req->path(); + r->method_str().c_str(), r->url().c_str(), r->content_length()); - SrsHttpHandlerMatch* p = NULL; - if ((ret = handler->best_match(url.data(), url.length(), &p)) != ERROR_SUCCESS) { - srs_warn("failed to find the best match handler for url. ret=%d", ret); + // use default server mux to serve http request. + if ((ret = http_mux->serve_http(w, r)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("serve http msg failed. ret=%d", ret); + } return ret; } - // if success, p and pstart should be valid. - srs_assert(p); - srs_assert(p->handler); - srs_assert(p->matched_url.length() <= url.length()); - srs_info("best match handler, matched_url=%s", p->matched_url.c_str()); + return ret; +} + +SrsStaticHttpConn::SrsStaticHttpConn(IConnectionManager* cm, st_netfd_t fd, SrsHttpServeMux* m) + : SrsHttpConn(cm, fd, m) +{ +} + +SrsStaticHttpConn::~SrsStaticHttpConn() +{ +} + +int SrsStaticHttpConn::on_got_http_message(SrsHttpMessage* msg) +{ + int ret = ERROR_SUCCESS; - req->set_match(p); - req->set_requires_crossdomain(requires_crossdomain); + // TODO: FIXME: use the post body. + std::string res; - // use handler to process request. - if ((ret = p->handler->process_request(skt, req)) != ERROR_SUCCESS) { - srs_warn("handler failed to process http request. ret=%d", ret); + // get response body. + if ((ret = msg->body_read_all(res)) != ERROR_SUCCESS) { return ret; } - if (req->requires_crossdomain()) { - requires_crossdomain = true; - } - return ret; } diff --git a/trunk/src/app/srs_app_http_conn.hpp b/trunk/src/app/srs_app_http_conn.hpp index 22c9914617..5b4bf62d58 100644 --- a/trunk/src/app/srs_app_http_conn.hpp +++ b/trunk/src/app/srs_app_http_conn.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -35,71 +35,375 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include #include +#include +#include +#include +class SrsServer; +class SrsSource; +class SrsRequest; +class SrsConsumer; class SrsStSocket; +class SrsTsEncoder; +class SrsAacEncoder; +class SrsMp3Encoder; +class SrsFlvEncoder; class SrsHttpParser; class SrsHttpMessage; class SrsHttpHandler; +class SrsMessageQueue; +class SrsSharedPtrMessage; -// for http root. -class SrsHttpRoot : public SrsHttpHandler +/** +* the flv vod stream supports flv?start=offset-bytes. +* for example, http://server/file.flv?start=10240 +* server will write flv header and sequence header, +* then seek(10240) and response flv tag data. +*/ +class SrsVodStream : public SrsHttpFileServer { public: - SrsHttpRoot(); - virtual ~SrsHttpRoot(); -public: - virtual int initialize(); - virtual int best_match(const char* path, int length, SrsHttpHandlerMatch** ppmatch); + SrsVodStream(std::string root_dir); + virtual ~SrsVodStream(); protected: - virtual bool is_handler_valid(SrsHttpMessage* req, int& status_code, std::string& reason_phrase); - virtual int do_process_request(SrsStSocket* skt, SrsHttpMessage* req); + virtual int serve_flv_stream(ISrsHttpResponseWriter* w, SrsHttpMessage* r, std::string fullpath, int offset); + virtual int serve_mp4_stream(ISrsHttpResponseWriter* w, SrsHttpMessage* r, std::string fullpath, int start, int end); }; -class SrsHttpVhost : public SrsHttpHandler +/** +* for the srs http stream cache, +* for example, the audio stream cache to make android(weixin) happy. +* we start a thread to shrink the queue. +*/ +class SrsStreamCache : public ISrsThreadHandler { private: - std::string _vhost; - std::string _mount; - std::string _dir; + SrsMessageQueue* queue; + SrsSource* source; + SrsRequest* req; + SrsThread* pthread; public: - SrsHttpVhost(std::string vhost, std::string mount, std::string dir); - virtual ~SrsHttpVhost(); + SrsStreamCache(SrsSource* s, SrsRequest* r); + virtual ~SrsStreamCache(); public: - virtual bool can_handle(const char* path, int length, const char** pchild); -protected: - virtual bool is_handler_valid(SrsHttpMessage* req, int& status_code, std::string& reason_phrase); - virtual int do_process_request(SrsStSocket* skt, SrsHttpMessage* req); + virtual int start(); + virtual int dump_cache(SrsConsumer* consumer); +// interface ISrsThreadHandler. +public: + virtual int cycle(); +}; + +/** +* the stream encoder in some codec, for example, flv or aac. +*/ +class ISrsStreamEncoder +{ +public: + ISrsStreamEncoder(); + virtual ~ISrsStreamEncoder(); +public: + /** + * initialize the encoder with file writer(to http response) and stream cache. + * @param w the writer to write to http response. + * @param c the stream cache for audio stream fast startup. + */ + virtual int initialize(SrsFileWriter* w, SrsStreamCache* c) = 0; + /** + * write rtmp video/audio/metadata. + */ + virtual int write_audio(int64_t timestamp, char* data, int size) = 0; + virtual int write_video(int64_t timestamp, char* data, int size) = 0; + virtual int write_metadata(int64_t timestamp, char* data, int size) = 0; +public: + /** + * for some stream, for example, mp3 and aac, the audio stream, + * we use large gop cache in encoder, for the gop cache of SrsSource is ignore audio. + * @return true to use gop cache of encoder; otherwise, use SrsSource. + */ + virtual bool has_cache() = 0; + /** + * dumps the cache of encoder to consumer. + */ + virtual int dump_cache(SrsConsumer* consumer) = 0; +}; + +/** +* the flv stream encoder, remux rtmp stream to flv stream. +*/ +class SrsFlvStreamEncoder : public ISrsStreamEncoder +{ +private: + SrsFlvEncoder* enc; +public: + SrsFlvStreamEncoder(); + virtual ~SrsFlvStreamEncoder(); +public: + virtual int initialize(SrsFileWriter* w, SrsStreamCache* c); + virtual int write_audio(int64_t timestamp, char* data, int size); + virtual int write_video(int64_t timestamp, char* data, int size); + virtual int write_metadata(int64_t timestamp, char* data, int size); +public: + virtual bool has_cache(); + virtual int dump_cache(SrsConsumer* consumer); +}; + +/** +* the ts stream encoder, remux rtmp stream to ts stream. +*/ +class SrsTsStreamEncoder : public ISrsStreamEncoder +{ +private: + SrsTsEncoder* enc; +public: + SrsTsStreamEncoder(); + virtual ~SrsTsStreamEncoder(); +public: + virtual int initialize(SrsFileWriter* w, SrsStreamCache* c); + virtual int write_audio(int64_t timestamp, char* data, int size); + virtual int write_video(int64_t timestamp, char* data, int size); + virtual int write_metadata(int64_t timestamp, char* data, int size); +public: + virtual bool has_cache(); + virtual int dump_cache(SrsConsumer* consumer); +}; + +/** +* the aac stream encoder, remux rtmp stream to aac stream. +*/ +class SrsAacStreamEncoder : public ISrsStreamEncoder +{ +private: + SrsAacEncoder* enc; + SrsStreamCache* cache; +public: + SrsAacStreamEncoder(); + virtual ~SrsAacStreamEncoder(); +public: + virtual int initialize(SrsFileWriter* w, SrsStreamCache* c); + virtual int write_audio(int64_t timestamp, char* data, int size); + virtual int write_video(int64_t timestamp, char* data, int size); + virtual int write_metadata(int64_t timestamp, char* data, int size); +public: + virtual bool has_cache(); + virtual int dump_cache(SrsConsumer* consumer); +}; + +/** +* the mp3 stream encoder, remux rtmp stream to mp3 stream. +*/ +class SrsMp3StreamEncoder : public ISrsStreamEncoder +{ +private: + SrsMp3Encoder* enc; + SrsStreamCache* cache; +public: + SrsMp3StreamEncoder(); + virtual ~SrsMp3StreamEncoder(); +public: + virtual int initialize(SrsFileWriter* w, SrsStreamCache* c); + virtual int write_audio(int64_t timestamp, char* data, int size); + virtual int write_video(int64_t timestamp, char* data, int size); + virtual int write_metadata(int64_t timestamp, char* data, int size); +public: + virtual bool has_cache(); + virtual int dump_cache(SrsConsumer* consumer); +}; + +/** +* write stream to http response direclty. +*/ +class SrsStreamWriter : public SrsFileWriter +{ +private: + ISrsHttpResponseWriter* writer; +public: + SrsStreamWriter(ISrsHttpResponseWriter* w); + virtual ~SrsStreamWriter(); +public: + virtual int open(std::string file); + virtual void close(); +public: + virtual bool is_open(); + virtual int64_t tellg(); +public: + virtual int write(void* buf, size_t count, ssize_t* pnwrite); +}; + +/** +* the flv live stream supports access rtmp in flv over http. +* srs will remux rtmp to flv streaming. +*/ +class SrsLiveStream : public ISrsHttpHandler +{ +private: + SrsRequest* req; + SrsSource* source; + SrsStreamCache* cache; +public: + SrsLiveStream(SrsSource* s, SrsRequest* r, SrsStreamCache* c); + virtual ~SrsLiveStream(); +public: + virtual int serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r); +private: + virtual int streaming_send_messages(ISrsStreamEncoder* enc, SrsSharedPtrMessage** msgs, int nb_msgs); +}; + +/** +* the srs live entry +*/ +struct SrsLiveEntry +{ +private: + bool _is_flv; + bool _is_ts; + bool _is_aac; + bool _is_mp3; +public: + // for template, the mount contains variables. + // for concrete stream, the mount is url to access. + std::string mount; + // whether hstrs(http stream trigger rtmp source) + bool hstrs; + + SrsLiveStream* stream; + SrsStreamCache* cache; + + SrsLiveEntry(std::string m, bool h); + + bool is_flv(); + bool is_ts(); + bool is_mp3(); + bool is_aac(); +}; + +/** +* the m3u8 stream handler. +*/ +class SrsHlsM3u8Stream : public ISrsHttpHandler +{ +private: + std::string m3u8; +public: + SrsHlsM3u8Stream(); + virtual ~SrsHlsM3u8Stream(); +public: + virtual void set_m3u8(std::string v); +public: + virtual int serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r); +}; + +/** +* the ts stream handler. +*/ +class SrsHlsTsStream : public ISrsHttpHandler +{ +private: + std::string ts; +public: + SrsHlsTsStream(); + virtual ~SrsHlsTsStream(); +public: + virtual void set_ts(std::string v); +public: + virtual int serve_http(ISrsHttpResponseWriter* w, SrsHttpMessage* r); +}; + +/** +* the srs hls entry. +*/ +struct SrsHlsEntry +{ + // for template, the mount contains variables. + // for concrete stream, the mount is url to access. + std::string mount; + + // key: the m3u8/ts file path. + // value: the http handler. + std::map streams; + + SrsHlsEntry(); +}; + +/** +* the http server instance, +* serve http static file, flv vod stream and flv live stream. +*/ +class SrsHttpServer : virtual public ISrsReloadHandler + , virtual public ISrsHttpMatchHijacker +{ private: - virtual int response_regular_file(SrsStSocket* skt, SrsHttpMessage* req, std::string fullpath); - virtual int response_flv_file(SrsStSocket* skt, SrsHttpMessage* req, std::string fullpath); - virtual int response_flv_file2(SrsStSocket* skt, SrsHttpMessage* req, std::string fullpath, int offset); - virtual int response_ts_file(SrsStSocket* skt, SrsHttpMessage* req, std::string fullpath); - virtual std::string get_request_file(SrsHttpMessage* req); + SrsServer* server; +public: + SrsHttpServeMux mux; + // the http live streaming template, to create streams. + std::map tflvs; + // the http live streaming streams, crote by template. + std::map sflvs; + // the hls live streaming template, to create streams. + std::map thls; + // the hls live streaming streams, crote by template. + std::map shls; +public: + SrsHttpServer(SrsServer* svr); + virtual ~SrsHttpServer(); public: - virtual std::string vhost(); - virtual std::string mount(); - virtual std::string dir(); + virtual int initialize(); +// http flv/ts/mp3/aac stream +public: + virtual int http_mount(SrsSource* s, SrsRequest* r); + virtual void http_unmount(SrsSource* s, SrsRequest* r); +// hls stream +public: + virtual int mount_hls(SrsRequest* r); + virtual int hls_update_m3u8(SrsRequest* r, std::string m3u8); + virtual int hls_update_ts(SrsRequest* r, std::string uri, std::string ts); + virtual void unmount_hls(SrsRequest* r); +// interface ISrsThreadHandler. +public: + virtual int on_reload_vhost_http_updated(); + virtual int on_reload_vhost_http_remux_updated(); + virtual int on_reload_vhost_hls(std::string vhost); +// interface ISrsHttpMatchHijacker +public: + virtual int hijack(SrsHttpMessage* request, ISrsHttpHandler** ph); +private: + virtual int initialize_static_file(); + virtual int initialize_flv_streaming(); + virtual int initialize_hls_streaming(); }; class SrsHttpConn : public SrsConnection { private: SrsHttpParser* parser; - SrsHttpHandler* handler; - bool requires_crossdomain; + SrsHttpServeMux* http_mux; public: - SrsHttpConn(SrsServer* srs_server, st_netfd_t client_stfd, SrsHttpHandler* _handler); + SrsHttpConn(IConnectionManager* cm, st_netfd_t fd, SrsHttpServeMux* m); virtual ~SrsHttpConn(); -public: - virtual void kbps_resample(); // interface IKbpsDelta public: + virtual void resample(); virtual int64_t get_send_bytes_delta(); virtual int64_t get_recv_bytes_delta(); + virtual void cleanup(); protected: virtual int do_cycle(); +protected: + // when got http message, + // for the static service or api, discard any body. + // for the stream caster, for instance, http flv streaming, may discard the flv header or not. + virtual int on_got_http_message(SrsHttpMessage* msg) = 0; private: - virtual int process_request(SrsStSocket* skt, SrsHttpMessage* req); + virtual int process_request(ISrsHttpResponseWriter* w, SrsHttpMessage* r); +}; + +class SrsStaticHttpConn : public SrsHttpConn +{ +public: + SrsStaticHttpConn(IConnectionManager* cm, st_netfd_t fd, SrsHttpServeMux* m); + virtual ~SrsStaticHttpConn(); +public: + virtual int on_got_http_message(SrsHttpMessage* msg); }; #endif diff --git a/trunk/src/app/srs_app_http_hooks.cpp b/trunk/src/app/srs_app_http_hooks.cpp index 3d96c501ab..71b1e63486 100644 --- a/trunk/src/app/srs_app_http_hooks.cpp +++ b/trunk/src/app/srs_app_http_hooks.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -29,18 +29,25 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using namespace std; #include -#include +#include #include #include #include #include #include +#include +#include +#include -#define SRS_HTTP_RESPONSE_OK "0" +#define SRS_HTTP_RESPONSE_OK SRS_XSTR(ERROR_SUCCESS) #define SRS_HTTP_HEADER_BUFFER 1024 +#define SRS_HTTP_READ_BUFFER 4096 #define SRS_HTTP_BODY_BUFFER 32 * 1024 +// the timeout for hls notify, in us. +#define SRS_HLS_NOTIFY_TIMEOUT_US (int64_t)(10*1000*1000LL) + SrsHttpHooks::SrsHttpHooks() { } @@ -49,42 +56,30 @@ SrsHttpHooks::~SrsHttpHooks() { } -int SrsHttpHooks::on_connect(string url, int client_id, string ip, SrsRequest* req) +int SrsHttpHooks::on_connect(string url, SrsRequest* req) { int ret = ERROR_SUCCESS; - SrsHttpUri uri; - if ((ret = uri.initialize(url)) != ERROR_SUCCESS) { - srs_error("http uri parse on_connect url failed. " - "client_id=%d, url=%s, ret=%d", client_id, url.c_str(), ret); - return ret; - } + int client_id = _srs_context->get_id(); std::stringstream ss; - ss << __SRS_JOBJECT_START - << __SRS_JFIELD_STR("action", "on_connect") << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("client_id", client_id) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("ip", ip) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("vhost", req->vhost) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("app", req->app) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("tcUrl", req->tcUrl) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("pageUrl", req->pageUrl) - << __SRS_JOBJECT_END; + ss << SRS_JOBJECT_START + << SRS_JFIELD_STR("action", "on_connect") << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("client_id", client_id) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("ip", req->ip) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("vhost", req->vhost) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("app", req->app) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("tcUrl", req->tcUrl) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("pageUrl", req->pageUrl) + << SRS_JOBJECT_END; + std::string data = ss.str(); std::string res; - - SrsHttpClient http; - if ((ret = http.post(&uri, data, res)) != ERROR_SUCCESS) { + int status_code; + if ((ret = do_post(url, data, status_code, res)) != ERROR_SUCCESS) { srs_error("http post on_connect uri failed. " - "client_id=%d, url=%s, request=%s, response=%s, ret=%d", - client_id, url.c_str(), data.c_str(), res.c_str(), ret); - return ret; - } - - if (res.empty() || res != SRS_HTTP_RESPONSE_OK) { - ret = ERROR_HTTP_DATA_INVLIAD; - srs_error("http hook on_connect validate failed. " - "client_id=%d, res=%s, ret=%d", client_id, res.c_str(), ret); + "client_id=%d, url=%s, request=%s, response=%s, code=%d, ret=%d", + client_id, url.c_str(), data.c_str(), res.c_str(), status_code, ret); return ret; } @@ -95,40 +90,30 @@ int SrsHttpHooks::on_connect(string url, int client_id, string ip, SrsRequest* r return ret; } -void SrsHttpHooks::on_close(string url, int client_id, string ip, SrsRequest* req) +void SrsHttpHooks::on_close(string url, SrsRequest* req, int64_t send_bytes, int64_t recv_bytes) { int ret = ERROR_SUCCESS; - SrsHttpUri uri; - if ((ret = uri.initialize(url)) != ERROR_SUCCESS) { - srs_warn("http uri parse on_close url failed, ignored. " - "client_id=%d, url=%s, ret=%d", client_id, url.c_str(), ret); - return; - } + int client_id = _srs_context->get_id(); std::stringstream ss; - ss << __SRS_JOBJECT_START - << __SRS_JFIELD_STR("action", "on_close") << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("client_id", client_id) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("ip", ip) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("vhost", req->vhost) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("app", req->app) - << __SRS_JOBJECT_END; + ss << SRS_JOBJECT_START + << SRS_JFIELD_STR("action", "on_close") << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("client_id", client_id) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("ip", req->ip) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("vhost", req->vhost) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("send_bytes", send_bytes) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("recv_bytes", recv_bytes) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("app", req->app) + << SRS_JOBJECT_END; + std::string data = ss.str(); std::string res; - - SrsHttpClient http; - if ((ret = http.post(&uri, data, res)) != ERROR_SUCCESS) { + int status_code; + if ((ret = do_post(url, data, status_code, res)) != ERROR_SUCCESS) { srs_warn("http post on_close uri failed, ignored. " - "client_id=%d, url=%s, request=%s, response=%s, ret=%d", - client_id, url.c_str(), data.c_str(), res.c_str(), ret); - return; - } - - if (res.empty() || res != SRS_HTTP_RESPONSE_OK) { - ret = ERROR_HTTP_DATA_INVLIAD; - srs_warn("http hook on_close validate failed, ignored. " - "client_id=%d, res=%s, ret=%d", client_id, res.c_str(), ret); + "client_id=%d, url=%s, request=%s, response=%s, code=%d, ret=%d", + client_id, url.c_str(), data.c_str(), res.c_str(), status_code, ret); return; } @@ -139,41 +124,29 @@ void SrsHttpHooks::on_close(string url, int client_id, string ip, SrsRequest* re return; } -int SrsHttpHooks::on_publish(string url, int client_id, string ip, SrsRequest* req) +int SrsHttpHooks::on_publish(string url, SrsRequest* req) { int ret = ERROR_SUCCESS; - SrsHttpUri uri; - if ((ret = uri.initialize(url)) != ERROR_SUCCESS) { - srs_error("http uri parse on_publish url failed. " - "client_id=%d, url=%s, ret=%d", client_id, url.c_str(), ret); - return ret; - } + int client_id = _srs_context->get_id(); std::stringstream ss; - ss << __SRS_JOBJECT_START - << __SRS_JFIELD_STR("action", "on_publish") << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("client_id", client_id) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("ip", ip) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("vhost", req->vhost) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("app", req->app) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("stream", req->stream) - << __SRS_JOBJECT_END; + ss << SRS_JOBJECT_START + << SRS_JFIELD_STR("action", "on_publish") << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("client_id", client_id) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("ip", req->ip) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("vhost", req->vhost) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("app", req->app) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("stream", req->stream) + << SRS_JOBJECT_END; + std::string data = ss.str(); std::string res; - - SrsHttpClient http; - if ((ret = http.post(&uri, data, res)) != ERROR_SUCCESS) { + int status_code; + if ((ret = do_post(url, data, status_code, res)) != ERROR_SUCCESS) { srs_error("http post on_publish uri failed. " - "client_id=%d, url=%s, request=%s, response=%s, ret=%d", - client_id, url.c_str(), data.c_str(), res.c_str(), ret); - return ret; - } - - if (res.empty() || res != SRS_HTTP_RESPONSE_OK) { - ret = ERROR_HTTP_DATA_INVLIAD; - srs_error("http hook on_publish validate failed. " - "client_id=%d, res=%s, ret=%d", client_id, res.c_str(), ret); + "client_id=%d, url=%s, request=%s, response=%s, code=%d, ret=%d", + client_id, url.c_str(), data.c_str(), res.c_str(), status_code, ret); return ret; } @@ -184,41 +157,29 @@ int SrsHttpHooks::on_publish(string url, int client_id, string ip, SrsRequest* r return ret; } -void SrsHttpHooks::on_unpublish(string url, int client_id, string ip, SrsRequest* req) +void SrsHttpHooks::on_unpublish(string url, SrsRequest* req) { int ret = ERROR_SUCCESS; - SrsHttpUri uri; - if ((ret = uri.initialize(url)) != ERROR_SUCCESS) { - srs_warn("http uri parse on_unpublish url failed, ignored. " - "client_id=%d, url=%s, ret=%d", client_id, url.c_str(), ret); - return; - } + int client_id = _srs_context->get_id(); std::stringstream ss; - ss << __SRS_JOBJECT_START - << __SRS_JFIELD_STR("action", "on_unpublish") << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("client_id", client_id) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("ip", ip) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("vhost", req->vhost) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("app", req->app) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("stream", req->stream) - << __SRS_JOBJECT_END; + ss << SRS_JOBJECT_START + << SRS_JFIELD_STR("action", "on_unpublish") << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("client_id", client_id) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("ip", req->ip) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("vhost", req->vhost) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("app", req->app) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("stream", req->stream) + << SRS_JOBJECT_END; + std::string data = ss.str(); std::string res; - - SrsHttpClient http; - if ((ret = http.post(&uri, data, res)) != ERROR_SUCCESS) { + int status_code; + if ((ret = do_post(url, data, status_code, res)) != ERROR_SUCCESS) { srs_warn("http post on_unpublish uri failed, ignored. " - "client_id=%d, url=%s, request=%s, response=%s, ret=%d", - client_id, url.c_str(), data.c_str(), res.c_str(), ret); - return; - } - - if (res.empty() || res != SRS_HTTP_RESPONSE_OK) { - ret = ERROR_HTTP_DATA_INVLIAD; - srs_warn("http hook on_unpublish validate failed, ignored. " - "client_id=%d, res=%s, ret=%d", client_id, res.c_str(), ret); + "client_id=%d, url=%s, request=%s, response=%s, code=%d, ret=%d", + client_id, url.c_str(), data.c_str(), res.c_str(), status_code, ret); return; } @@ -229,95 +190,255 @@ void SrsHttpHooks::on_unpublish(string url, int client_id, string ip, SrsRequest return; } -int SrsHttpHooks::on_play(string url, int client_id, string ip, SrsRequest* req) +int SrsHttpHooks::on_play(string url, SrsRequest* req) { int ret = ERROR_SUCCESS; - SrsHttpUri uri; - if ((ret = uri.initialize(url)) != ERROR_SUCCESS) { - srs_error("http uri parse on_play url failed. " - "client_id=%d, url=%s, ret=%d", client_id, url.c_str(), ret); + int client_id = _srs_context->get_id(); + + std::stringstream ss; + ss << SRS_JOBJECT_START + << SRS_JFIELD_STR("action", "on_play") << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("client_id", client_id) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("ip", req->ip) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("vhost", req->vhost) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("app", req->app) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("stream", req->stream) + << SRS_JOBJECT_END; + + std::string data = ss.str(); + std::string res; + int status_code; + if ((ret = do_post(url, data, status_code, res)) != ERROR_SUCCESS) { + srs_error("http post on_play uri failed. " + "client_id=%d, url=%s, request=%s, response=%s, code=%d, ret=%d", + client_id, url.c_str(), data.c_str(), res.c_str(), status_code, ret); return ret; } + srs_trace("http hook on_play success. " + "client_id=%d, url=%s, request=%s, response=%s, ret=%d", + client_id, url.c_str(), data.c_str(), res.c_str(), ret); + + return ret; +} + +void SrsHttpHooks::on_stop(string url, SrsRequest* req) +{ + int ret = ERROR_SUCCESS; + + int client_id = _srs_context->get_id(); + std::stringstream ss; - ss << __SRS_JOBJECT_START - << __SRS_JFIELD_STR("action", "on_play") << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("client_id", client_id) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("ip", ip) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("vhost", req->vhost) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("app", req->app) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("stream", req->stream) - << __SRS_JOBJECT_END; + ss << SRS_JOBJECT_START + << SRS_JFIELD_STR("action", "on_stop") << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("client_id", client_id) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("ip", req->ip) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("vhost", req->vhost) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("app", req->app) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("stream", req->stream) + << SRS_JOBJECT_END; + std::string data = ss.str(); std::string res; + int status_code; + if ((ret = do_post(url, data, status_code, res)) != ERROR_SUCCESS) { + srs_warn("http post on_stop uri failed, ignored. " + "client_id=%d, url=%s, request=%s, response=%s, code=%d, ret=%d", + client_id, url.c_str(), data.c_str(), res.c_str(), status_code, ret); + return; + } - SrsHttpClient http; - if ((ret = http.post(&uri, data, res)) != ERROR_SUCCESS) { - srs_error("http post on_play uri failed. " - "client_id=%d, url=%s, request=%s, response=%s, ret=%d", - client_id, url.c_str(), data.c_str(), res.c_str(), ret); + srs_trace("http hook on_stop success. " + "client_id=%d, url=%s, request=%s, response=%s, ret=%d", + client_id, url.c_str(), data.c_str(), res.c_str(), ret); + + return; +} + +int SrsHttpHooks::on_dvr(string url, SrsRequest* req, string file) +{ + int ret = ERROR_SUCCESS; + + int client_id = _srs_context->get_id(); + std::string cwd = _srs_config->cwd(); + + std::stringstream ss; + ss << SRS_JOBJECT_START + << SRS_JFIELD_STR("action", "on_dvr") << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("client_id", client_id) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("ip", req->ip) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("vhost", req->vhost) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("app", req->app) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("stream", req->stream) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("cwd", cwd) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("file", file) + << SRS_JOBJECT_END; + + std::string data = ss.str(); + std::string res; + int status_code; + if ((ret = do_post(url, data, status_code, res)) != ERROR_SUCCESS) { + srs_error("http post on_dvr uri failed, ignored. " + "client_id=%d, url=%s, request=%s, response=%s, code=%d, ret=%d", + client_id, url.c_str(), data.c_str(), res.c_str(), status_code, ret); return ret; } - if (res.empty() || res != SRS_HTTP_RESPONSE_OK) { - ret = ERROR_HTTP_DATA_INVLIAD; - srs_error("http hook on_play validate failed. " - "client_id=%d, res=%s, ret=%d", client_id, res.c_str(), ret); + srs_trace("http hook on_dvr success. " + "client_id=%d, url=%s, request=%s, response=%s, ret=%d", + client_id, url.c_str(), data.c_str(), res.c_str(), ret); + + return ret; +} + +int SrsHttpHooks::on_hls(string url, SrsRequest* req, string file, string ts_url, string m3u8, string m3u8_url, int sn, double duration) +{ + int ret = ERROR_SUCCESS; + + int client_id = _srs_context->get_id(); + std::string cwd = _srs_config->cwd(); + + std::stringstream ss; + ss << SRS_JOBJECT_START + << SRS_JFIELD_STR("action", "on_hls") << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("client_id", client_id) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("ip", req->ip) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("vhost", req->vhost) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("app", req->app) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("stream", req->stream) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("duration", duration) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("cwd", cwd) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("file", file) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("url", ts_url) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("m3u8", m3u8) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("m3u8_url", m3u8_url) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("seq_no", sn) + << SRS_JOBJECT_END; + + std::string data = ss.str(); + std::string res; + int status_code; + if ((ret = do_post(url, data, status_code, res)) != ERROR_SUCCESS) { + srs_error("http post on_hls uri failed, ignored. " + "client_id=%d, url=%s, request=%s, response=%s, code=%d, ret=%d", + client_id, url.c_str(), data.c_str(), res.c_str(), status_code, ret); return ret; } - srs_trace("http hook on_play success. " + srs_trace("http hook on_hls success. " "client_id=%d, url=%s, request=%s, response=%s, ret=%d", client_id, url.c_str(), data.c_str(), res.c_str(), ret); return ret; } -void SrsHttpHooks::on_stop(string url, int client_id, string ip, SrsRequest* req) +int SrsHttpHooks::on_hls_notify(std::string url, SrsRequest* req, std::string ts_url, int nb_notify) { int ret = ERROR_SUCCESS; + int client_id = _srs_context->get_id(); + std::string cwd = _srs_config->cwd(); + + if (srs_string_starts_with(ts_url, "http://") || srs_string_starts_with(ts_url, "https://")) { + url = ts_url; + } + + url = srs_string_replace(url, "[app]", req->app); + url = srs_string_replace(url, "[stream]", req->stream); + url = srs_string_replace(url, "[ts_url]", ts_url); + + int64_t starttime = srs_update_system_time_ms(); + SrsHttpUri uri; if ((ret = uri.initialize(url)) != ERROR_SUCCESS) { - srs_warn("http uri parse on_stop url failed, ignored. " - "client_id=%d, url=%s, ret=%d", client_id, url.c_str(), ret); - return; + srs_error("http: post failed. url=%s, ret=%d", url.c_str(), ret); + return ret; } - std::stringstream ss; - ss << __SRS_JOBJECT_START - << __SRS_JFIELD_STR("action", "on_stop") << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("client_id", client_id) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("ip", ip) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("vhost", req->vhost) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("app", req->app) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("stream", req->stream) - << __SRS_JOBJECT_END; - std::string data = ss.str(); - std::string res; + SrsHttpClient http; + if ((ret = http.initialize(uri.get_host(), uri.get_port(), SRS_HLS_NOTIFY_TIMEOUT_US)) != ERROR_SUCCESS) { + return ret; + } + + std::string path = uri.get_query(); + if (path.empty()) { + path = uri.get_path(); + } else { + path = uri.get_path(); + path += "?"; + path += uri.get_query(); + } + srs_warn("GET %s", path.c_str()); + + SrsHttpMessage* msg = NULL; + if ((ret = http.get(path.c_str(), "", &msg)) != ERROR_SUCCESS) { + return ret; + } + SrsAutoFree(SrsHttpMessage, msg); + + int nb_buf = srs_min(nb_notify, SRS_HTTP_READ_BUFFER); + char* buf = new char[nb_buf]; + SrsAutoFree(char, buf); + + int nb_read = 0; + ISrsHttpResponseReader* br = msg->body_reader(); + while (nb_read < nb_notify && !br->eof()) { + int nb_bytes = 0; + if ((ret = br->read(buf, nb_buf, &nb_bytes)) != ERROR_SUCCESS) { + break; + } + nb_read += nb_bytes; + } + + int spenttime = (int)(srs_update_system_time_ms() - starttime); + srs_trace("http hook on_hls_notify success. client_id=%d, url=%s, code=%d, spent=%dms, read=%dB, ret=%d", + client_id, url.c_str(), msg->status_code(), spenttime, nb_read, ret); + + // ignore any error for on_hls_notify. + ret = ERROR_SUCCESS; + + return ret; +} + +int SrsHttpHooks::do_post(std::string url, std::string req, int& code, string& res) +{ + int ret = ERROR_SUCCESS; + + SrsHttpUri uri; + if ((ret = uri.initialize(url)) != ERROR_SUCCESS) { + srs_error("http: post failed. url=%s, ret=%d", url.c_str(), ret); + return ret; + } SrsHttpClient http; - if ((ret = http.post(&uri, data, res)) != ERROR_SUCCESS) { - srs_warn("http post on_stop uri failed, ignored. " - "client_id=%d, url=%s, request=%s, response=%s, ret=%d", - client_id, url.c_str(), data.c_str(), res.c_str(), ret); - return; + if ((ret = http.initialize(uri.get_host(), uri.get_port())) != ERROR_SUCCESS) { + return ret; } - if (res.empty() || res != SRS_HTTP_RESPONSE_OK) { - ret = ERROR_HTTP_DATA_INVLIAD; - srs_warn("http hook on_stop validate failed, ignored. " - "client_id=%d, res=%s, ret=%d", client_id, res.c_str(), ret); - return; + SrsHttpMessage* msg = NULL; + if ((ret = http.post(uri.get_path(), req, &msg)) != ERROR_SUCCESS) { + return ret; } + SrsAutoFree(SrsHttpMessage, msg); - srs_trace("http hook on_stop success. " - "client_id=%d, url=%s, request=%s, response=%s, ret=%d", - client_id, url.c_str(), data.c_str(), res.c_str(), ret); + code = msg->status_code(); + if ((ret = msg->body_read_all(res)) != ERROR_SUCCESS) { + return ret; + } - return; + // ensure the http status is ok. + // https://github.com/simple-rtmp-server/srs/issues/158 + if (code != SRS_CONSTS_HTTP_OK) { + return ERROR_HTTP_STATUS_INVLIAD; + } + + // TODO: FIXME: parse json. + if (res.empty() || res != SRS_HTTP_RESPONSE_OK) { + return ERROR_HTTP_DATA_INVLIAD; + } + + return ret; } #endif - diff --git a/trunk/src/app/srs_app_http_hooks.hpp b/trunk/src/app/srs_app_http_hooks.hpp index 7b3ce32c86..47b27431ba 100644 --- a/trunk/src/app/srs_app_http_hooks.hpp +++ b/trunk/src/app/srs_app_http_hooks.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -55,49 +55,69 @@ class SrsHttpHooks public: /** * on_connect hook, when client connect to srs. - * @param client_id the id of client on server. * @param url the api server url, to valid the client. * ignore if empty. - * @return valid failed or connect to the url failed. */ - static int on_connect(std::string url, int client_id, std::string ip, SrsRequest* req); + static int on_connect(std::string url, SrsRequest* req); /** * on_close hook, when client disconnect to srs, where client is valid by on_connect. - * @param client_id the id of client on server. * @param url the api server url, to process the event. * ignore if empty. */ - static void on_close(std::string url, int client_id, std::string ip, SrsRequest* req); + static void on_close(std::string url, SrsRequest* req, int64_t send_bytes, int64_t recv_bytes); /** * on_publish hook, when client(encoder) start to publish stream - * @param client_id the id of client on server. * @param url the api server url, to valid the client. * ignore if empty. - * @return valid failed or connect to the url failed. */ - static int on_publish(std::string url, int client_id, std::string ip, SrsRequest* req); + static int on_publish(std::string url, SrsRequest* req); /** * on_unpublish hook, when client(encoder) stop publish stream. - * @param client_id the id of client on server. * @param url the api server url, to process the event. * ignore if empty. */ - static void on_unpublish(std::string url, int client_id, std::string ip, SrsRequest* req); + static void on_unpublish(std::string url, SrsRequest* req); /** * on_play hook, when client start to play stream. - * @param client_id the id of client on server. * @param url the api server url, to valid the client. * ignore if empty. - * @return valid failed or connect to the url failed. */ - static int on_play(std::string url, int client_id, std::string ip, SrsRequest* req); + static int on_play(std::string url, SrsRequest* req); /** * on_stop hook, when client stop to play the stream. - * @param client_id the id of client on server. * @param url the api server url, to process the event. * ignore if empty. */ - static void on_stop(std::string url, int client_id, std::string ip, SrsRequest* req); + static void on_stop(std::string url, SrsRequest* req); + /** + * on_dvr hook, when reap a dvr file. + * @param url the api server url, to process the event. + * ignore if empty. + * @param file the file path, can be relative or absolute path. + */ + static int on_dvr(std::string url, SrsRequest* req, std::string file); + /** + * when hls reap segment, callback. + * @param url the api server url, to process the event. + * ignore if empty. + * @param file the ts file path, can be relative or absolute path. + * @param ts_url the ts url, which used for m3u8. + * @param m3u8 the m3u8 file path, can be relative or absolute path. + * @param m3u8_url the m3u8 url, which is used for the http mount path. + * @param sn the seq_no, the sequence number of ts in hls/m3u8. + * @param duration the segment duration in seconds. + */ + static int on_hls(std::string url, SrsRequest* req, std::string file, std::string ts_url, std::string m3u8, std::string m3u8_url, int sn, double duration); + /** + * when hls reap segment, callback. + * @param url the api server url, to process the event. + * ignore if empty. + * @param ts_url the ts uri, used to replace the variable [ts_url] in url. + * @param nb_notify the max bytes to read from notify server. + */ + static int on_hls_notify(std::string url, SrsRequest* req, std::string ts_url, int nb_notify); +private: + static int do_post(std::string url, std::string req, int& code, std::string& res); }; #endif diff --git a/trunk/src/app/srs_app_ingest.cpp b/trunk/src/app/srs_app_ingest.cpp index 7c5f6a1dee..c378a61952 100644 --- a/trunk/src/app/srs_app_ingest.cpp +++ b/trunk/src/app/srs_app_ingest.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -33,10 +33,11 @@ using namespace std; #include #include #include +#include // when error, ingester sleep for a while and retry. // ingest never sleep a long time, for we must start the stream ASAP. -#define SRS_AUTO_INGESTER_SLEEP_US (int64_t)(6*100*1000LL) +#define SRS_AUTO_INGESTER_SLEEP_US (int64_t)(3*1000*1000LL) SrsIngesterFFMPEG::SrsIngesterFFMPEG(SrsFFMPEG* _ffmpeg, string _vhost, string _id) { @@ -54,8 +55,8 @@ SrsIngester::SrsIngester() { _srs_config->subscribe(this); - pthread = new SrsThread(this, SRS_AUTO_INGESTER_SLEEP_US, true); - pithy_print = new SrsPithyPrint(SRS_CONSTS_STAGE_INGESTER); + pthread = new SrsThread("ingest", this, SRS_AUTO_INGESTER_SLEEP_US, true); + pprint = SrsPithyPrint::create_ingester(); } SrsIngester::~SrsIngester() @@ -186,8 +187,7 @@ int SrsIngester::cycle() } // pithy print - ingester(); - pithy_print->elapse(); + show_ingest_log_message(); return ret; } @@ -229,9 +229,15 @@ int SrsIngester::initialize_ffmpeg(SrsFFMPEG* ffmpeg, SrsConfDirective* vhost, S { int ret = ERROR_SUCCESS; - std::vector ports = _srs_config->get_listen(); - srs_assert(ports.size() > 0); - std::string port = ports[0]; + std::string port; + if (true) { + std::vector ip_ports = _srs_config->get_listens(); + srs_assert(ip_ports.size() > 0); + + std::string ep = ip_ports[0]; + std::string ip; + srs_parse_endpoint(ep, ip, port); + } std::string output = _srs_config->get_engine_output(engine); // output stream, to other/self server @@ -340,17 +346,19 @@ int SrsIngester::initialize_ffmpeg(SrsFFMPEG* ffmpeg, SrsConfDirective* vhost, S return ret; } -void SrsIngester::ingester() +void SrsIngester::show_ingest_log_message() { + pprint->elapse(); + if ((int)ingesters.size() <= 0) { return; } // reportable - if (pithy_print->can_print()) { + if (pprint->can_print()) { // TODO: FIXME: show more info. srs_trace("-> "SRS_CONSTS_LOG_INGESTER - " time=%"PRId64", ingesters=%d", pithy_print->age(), (int)ingesters.size()); + " time=%"PRId64", ingesters=%d", pprint->age(), (int)ingesters.size()); } } diff --git a/trunk/src/app/srs_app_ingest.hpp b/trunk/src/app/srs_app_ingest.hpp index 5ae88b21df..acdeb7d8cd 100644 --- a/trunk/src/app/srs_app_ingest.hpp +++ b/trunk/src/app/srs_app_ingest.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -65,7 +65,7 @@ class SrsIngester : public ISrsThreadHandler, public ISrsReloadHandler std::vector ingesters; private: SrsThread* pthread; - SrsPithyPrint* pithy_print; + SrsPithyPrint* pprint; public: SrsIngester(); virtual ~SrsIngester(); @@ -82,7 +82,7 @@ class SrsIngester : public ISrsThreadHandler, public ISrsReloadHandler virtual int parse_ingesters(SrsConfDirective* vhost); virtual int parse_engines(SrsConfDirective* vhost, SrsConfDirective* ingest); virtual int initialize_ffmpeg(SrsFFMPEG* ffmpeg, SrsConfDirective* vhost, SrsConfDirective* ingest, SrsConfDirective* engine); - virtual void ingester(); + virtual void show_ingest_log_message(); // interface ISrsReloadHandler. public: virtual int on_reload_vhost_removed(std::string vhost); diff --git a/trunk/src/app/srs_app_json.cpp b/trunk/src/app/srs_app_json.cpp index 2c92603c20..5afda5abf4 100644 --- a/trunk/src/app/srs_app_json.cpp +++ b/trunk/src/app/srs_app_json.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -27,7 +27,7 @@ using namespace std; #include -#ifdef __SRS_JSON_USE_NXJSON +#ifdef SRS_JSON_USE_NXJSON //////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////// @@ -109,75 +109,75 @@ const nx_json* nx_json_item(const nx_json* json, int idx); // get array element #define SRS_JSON_Null 0x06 #define SRS_JSON_Array 0x07 -class __SrsJsonString : public SrsJsonAny +class SrsJsonString : public SrsJsonAny { public: std::string value; - __SrsJsonString(const char* _value) + SrsJsonString(const char* _value) { marker = SRS_JSON_String; if (_value) { value = _value; } } - virtual ~__SrsJsonString() + virtual ~SrsJsonString() { } }; -class __SrsJsonBoolean : public SrsJsonAny +class SrsJsonBoolean : public SrsJsonAny { public: bool value; - __SrsJsonBoolean(bool _value) + SrsJsonBoolean(bool _value) { marker = SRS_JSON_Boolean; value = _value; } - virtual ~__SrsJsonBoolean() + virtual ~SrsJsonBoolean() { } }; -class __SrsJsonInteger : public SrsJsonAny +class SrsJsonInteger : public SrsJsonAny { public: int64_t value; - __SrsJsonInteger(int64_t _value) + SrsJsonInteger(int64_t _value) { marker = SRS_JSON_Integer; value = _value; } - virtual ~__SrsJsonInteger() + virtual ~SrsJsonInteger() { } }; -class __SrsJsonNumber : public SrsJsonAny +class SrsJsonNumber : public SrsJsonAny { public: double value; - __SrsJsonNumber(double _value) + SrsJsonNumber(double _value) { marker = SRS_JSON_Number; value = _value; } - virtual ~__SrsJsonNumber() + virtual ~SrsJsonNumber() { } }; -class __SrsJsonNull : public SrsJsonAny +class SrsJsonNull : public SrsJsonAny { public: - __SrsJsonNull() { + SrsJsonNull() { marker = SRS_JSON_Null; } - virtual ~__SrsJsonNull() { + virtual ~SrsJsonNull() { } }; @@ -227,28 +227,28 @@ bool SrsJsonAny::is_null() string SrsJsonAny::to_str() { - __SrsJsonString* p = dynamic_cast<__SrsJsonString*>(this); + SrsJsonString* p = dynamic_cast(this); srs_assert(p != NULL); return p->value; } bool SrsJsonAny::to_boolean() { - __SrsJsonBoolean* p = dynamic_cast<__SrsJsonBoolean*>(this); + SrsJsonBoolean* p = dynamic_cast(this); srs_assert(p != NULL); return p->value; } int64_t SrsJsonAny::to_integer() { - __SrsJsonInteger* p = dynamic_cast<__SrsJsonInteger*>(this); + SrsJsonInteger* p = dynamic_cast(this); srs_assert(p != NULL); return p->value; } double SrsJsonAny::to_number() { - __SrsJsonNumber* p = dynamic_cast<__SrsJsonNumber*>(this); + SrsJsonNumber* p = dynamic_cast(this); srs_assert(p != NULL); return p->value; } @@ -269,27 +269,27 @@ SrsJsonArray* SrsJsonAny::to_array() SrsJsonAny* SrsJsonAny::str(const char* value) { - return new __SrsJsonString(value); + return new SrsJsonString(value); } SrsJsonAny* SrsJsonAny::boolean(bool value) { - return new __SrsJsonBoolean(value); + return new SrsJsonBoolean(value); } SrsJsonAny* SrsJsonAny::ingeter(int64_t value) { - return new __SrsJsonInteger(value); + return new SrsJsonInteger(value); } SrsJsonAny* SrsJsonAny::number(double value) { - return new __SrsJsonNumber(value); + return new SrsJsonNumber(value); } SrsJsonAny* SrsJsonAny::null() { - return new __SrsJsonNull(); + return new SrsJsonNull(); } SrsJsonObject* SrsJsonAny::object() @@ -302,7 +302,7 @@ SrsJsonArray* SrsJsonAny::array() return new SrsJsonArray(); } -#ifdef __SRS_JSON_USE_NXJSON +#ifdef SRS_JSON_USE_NXJSON SrsJsonAny* srs_json_parse_tree_nx_json(const nx_json* node) { if (!node) { @@ -457,6 +457,36 @@ SrsJsonAny* SrsJsonObject::ensure_property_string(string name) return prop; } +SrsJsonAny* SrsJsonObject::ensure_property_integer(string name) +{ + SrsJsonAny* prop = get_property(name); + + if (!prop) { + return NULL; + } + + if (!prop->is_integer()) { + return NULL; + } + + return prop; +} + +SrsJsonAny* SrsJsonObject::ensure_property_boolean(string name) +{ + SrsJsonAny* prop = get_property(name); + + if (!prop) { + return NULL; + } + + if (!prop->is_boolean()) { + return NULL; + } + + return prop; +} + SrsJsonArray::SrsJsonArray() { marker = SRS_JSON_Array; @@ -489,7 +519,7 @@ void SrsJsonArray::add(SrsJsonAny* value) properties.push_back(value); } -#ifdef __SRS_JSON_USE_NXJSON +#ifdef SRS_JSON_USE_NXJSON //////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/trunk/src/app/srs_app_json.hpp b/trunk/src/app/srs_app_json.hpp index ec7fdce6d7..2e555bcd47 100644 --- a/trunk/src/app/srs_app_json.hpp +++ b/trunk/src/app/srs_app_json.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -34,8 +34,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // whether use nxjson // @see: https://bitbucket.org/yarosla/nxjson -#undef __SRS_JSON_USE_NXJSON -#define __SRS_JSON_USE_NXJSON +#undef SRS_JSON_USE_NXJSON +#define SRS_JSON_USE_NXJSON //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// @@ -123,6 +123,7 @@ class SrsJsonAny public: /** * read json tree from str:char* + * @return json object. NULL if error. */ static SrsJsonAny* loads(char* str); }; @@ -148,6 +149,8 @@ class SrsJsonObject : public SrsJsonAny virtual void set(std::string key, SrsJsonAny* value); virtual SrsJsonAny* get_property(std::string name); virtual SrsJsonAny* ensure_property_string(std::string name); + virtual SrsJsonAny* ensure_property_integer(std::string name); + virtual SrsJsonAny* ensure_property_boolean(std::string name); }; class SrsJsonArray : public SrsJsonAny @@ -172,16 +175,16 @@ class SrsJsonArray : public SrsJsonAny //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// /* json encode - cout<< __SRS_JOBJECT_START - << __SRS_JFIELD_STR("name", "srs") << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("version", 100) << __SRS_JFIELD_CONT - << __SRS_JFIELD_NAME("features") << __SRS_JOBJECT_START - << __SRS_JFIELD_STR("rtmp", "released") << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("hls", "released") << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("dash", "plan") - << __SRS_JOBJECT_END << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("author", "srs team") - << __SRS_JOBJECT_END + cout<< SRS_JOBJECT_START + << SRS_JFIELD_STR("name", "srs") << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("version", 100) << SRS_JFIELD_CONT + << SRS_JFIELD_NAME("features") << SRS_JOBJECT_START + << SRS_JFIELD_STR("rtmp", "released") << SRS_JFIELD_CONT + << SRS_JFIELD_STR("hls", "released") << SRS_JFIELD_CONT + << SRS_JFIELD_STR("dash", "plan") + << SRS_JOBJECT_END << SRS_JFIELD_CONT + << SRS_JFIELD_STR("author", "srs team") + << SRS_JOBJECT_END it's: cont<< "{" << "name:" << "srs" << "," @@ -210,14 +213,16 @@ that is: //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// -#define __SRS_JOBJECT_START "{" -#define __SRS_JFIELD_NAME(k) "\"" << k << "\":" -#define __SRS_JFIELD_STR(k, v) "\"" << k << "\":\"" << v << "\"" -#define __SRS_JFIELD_ORG(k, v) "\"" << k << "\":" << std::dec << v -#define __SRS_JFIELD_ERROR(ret) "\"" << "code" << "\":" << ret -#define __SRS_JFIELD_CONT "," -#define __SRS_JOBJECT_END "}" -#define __SRS_JARRAY_START "[" -#define __SRS_JARRAY_END "]" +#define SRS_JOBJECT_START "{" +#define SRS_JFIELD_NAME(k) "\"" << k << "\":" +#define SRS_JFIELD_STR(k, v) "\"" << k << "\":\"" << v << "\"" +#define SRS_JFIELD_ORG(k, v) "\"" << k << "\":" << std::dec << v +#define SRS_JFIELD_BOOL(k, v) SRS_JFIELD_ORG(k, (v? "true":"false")) +#define SRS_JFIELD_NULL(k) "\"" << k << "\":null" +#define SRS_JFIELD_ERROR(ret) "\"" << "code" << "\":" << ret +#define SRS_JFIELD_CONT "," +#define SRS_JOBJECT_END "}" +#define SRS_JARRAY_START "[" +#define SRS_JARRAY_END "]" #endif diff --git a/trunk/src/app/srs_app_kbps.cpp b/trunk/src/app/srs_app_kbps.cpp index 1e6505a27c..c7fd53e0bf 100644 --- a/trunk/src/app/srs_app_kbps.cpp +++ b/trunk/src/app/srs_app_kbps.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -203,20 +203,29 @@ int64_t SrsKbps::get_recv_bytes() return is.get_total_bytes(); } +void SrsKbps::resample() +{ + sample(); +} + int64_t SrsKbps::get_send_bytes_delta() { int64_t delta = os.get_total_bytes() - os.delta_bytes; - os.delta_bytes = os.get_total_bytes(); return delta; } int64_t SrsKbps::get_recv_bytes_delta() { int64_t delta = is.get_total_bytes() - is.delta_bytes; - is.delta_bytes = is.get_total_bytes(); return delta; } +void SrsKbps::cleanup() +{ + os.delta_bytes = os.get_total_bytes(); + is.delta_bytes = is.get_total_bytes(); +} + void SrsKbps::add_delta(IKbpsDelta* delta) { srs_assert(delta); diff --git a/trunk/src/app/srs_app_kbps.hpp b/trunk/src/app/srs_app_kbps.hpp index 63113888e3..39e5c11ebf 100644 --- a/trunk/src/app/srs_app_kbps.hpp +++ b/trunk/src/app/srs_app_kbps.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -30,7 +30,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include -#include +#include /** * a kbps sample, for example, 1minute kbps, @@ -107,6 +107,11 @@ class SrsKbpsSlice /** * the interface which provices delta of bytes. +* for a delta, for example, a live stream connection, we can got the delta by: +* IKbpsDelta* delta = ...; +* delta->resample(); +* kbps->add_delta(delta); +* delta->cleanup(); */ class IKbpsDelta { @@ -114,25 +119,41 @@ class IKbpsDelta IKbpsDelta(); virtual ~IKbpsDelta(); public: + /** + * resample to generate the value of delta bytes. + */ + virtual void resample() = 0; + /** + * get the send or recv bytes delta. + */ virtual int64_t get_send_bytes_delta() = 0; virtual int64_t get_recv_bytes_delta() = 0; + /** + * cleanup the value of delta bytes. + */ + virtual void cleanup() = 0; }; /** * to statistic the kbps of io. * itself can be a statistic source, for example, used for SRS bytes stat. * there are two usage scenarios: -* 1. connections to calc kbps: -* set_io(in, out) -* sample() -* get_xxx_kbps(). +* 1. connections to calc kbps by sample(): +* SrsKbps* kbps = ...; +* kbps->set_io(in, out) +* kbps->sample() +* kbps->get_xxx_kbps(). * the connections know how many bytes already send/recv. -* 2. server to calc kbps: -* set_io(NULL, NULL) +* 2. server to calc kbps by add_delta(): +* SrsKbps* kbps = ...; +* kbps->set_io(NULL, NULL) * for each connection in connections: -* add_delta(connections) // where connection is a IKbpsDelta* -* sample() -* get_xxx_kbps(). +* IKbpsDelta* delta = connection; // where connection implements IKbpsDelta +* delta->resample() +* kbps->add_delta(delta) +* delta->cleanup() +* kbps->sample() +* kbps->get_xxx_kbps(). * the server never know how many bytes already send/recv, for the connection maybe closed. */ class SrsKbps : public virtual ISrsProtocolStatistic, public virtual IKbpsDelta @@ -174,18 +195,26 @@ class SrsKbps : public virtual ISrsProtocolStatistic, public virtual IKbpsDelta */ virtual int64_t get_send_bytes(); virtual int64_t get_recv_bytes(); +public: + /** + * resample to get the delta. + */ + virtual void resample(); /** * get the delta of send/recv bytes. - * @remark, used for add_delta to calc the total system bytes/kbps. */ virtual int64_t get_send_bytes_delta(); virtual int64_t get_recv_bytes_delta(); + /** + * cleanup the delta. + */ + virtual void cleanup(); public: /** * add delta to kbps clac mechenism. * we donot know the total bytes, but know the delta, for instance, * for rtmp server to calc total bytes and kbps. - * @remark user must invoke sample() when invoke this method. + * @remark user must invoke sample() to calc result after invoke this method. * @param delta, assert should never be NULL. */ virtual void add_delta(IKbpsDelta* delta); diff --git a/trunk/src/app/srs_app_listener.cpp b/trunk/src/app/srs_app_listener.cpp new file mode 100644 index 0000000000..fb370d708a --- /dev/null +++ b/trunk/src/app/srs_app_listener.cpp @@ -0,0 +1,277 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +#include +#include +#include +#include + +// set the max packet size. +#define SRS_UDP_MAX_PACKET_SIZE 65535 + +// sleep in ms for udp recv packet. +#define SRS_UDP_PACKET_RECV_CYCLE_INTERVAL_MS 0 + +// nginx also set to 512 +#define SERVER_LISTEN_BACKLOG 512 + +ISrsUdpHandler::ISrsUdpHandler() +{ +} + +ISrsUdpHandler::~ISrsUdpHandler() +{ +} + +ISrsTcpHandler::ISrsTcpHandler() +{ +} + +ISrsTcpHandler::~ISrsTcpHandler() +{ +} + +SrsUdpListener::SrsUdpListener(ISrsUdpHandler* h, string i, int p) +{ + handler = h; + ip = i; + port = p; + + _fd = -1; + stfd = NULL; + + nb_buf = SRS_UDP_MAX_PACKET_SIZE; + buf = new char[nb_buf]; + + pthread = new SrsThread("udp", this, 0, true); +} + +SrsUdpListener::~SrsUdpListener() +{ + // close the stfd to trigger thread to interrupted. + srs_close_stfd(stfd); + + pthread->stop(); + srs_freep(pthread); + + // st does not close it sometimes, + // close it manually. + close(_fd); + + srs_freep(buf); +} + +int SrsUdpListener::fd() +{ + return _fd; +} + +int SrsUdpListener::listen() +{ + int ret = ERROR_SUCCESS; + + if ((_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { + ret = ERROR_SOCKET_CREATE; + srs_error("create linux socket error. port=%d, ret=%d", ip.c_str(), port, ret); + return ret; + } + srs_verbose("create linux socket success. port=%d, fd=%d", ip.c_str(), port, _fd); + + int reuse_socket = 1; + if (setsockopt(_fd, SOL_SOCKET, SO_REUSEADDR, &reuse_socket, sizeof(int)) == -1) { + ret = ERROR_SOCKET_SETREUSE; + srs_error("setsockopt reuse-addr error. port=%d, ret=%d", ip.c_str(), port, ret); + return ret; + } + srs_verbose("setsockopt reuse-addr success. port=%d, fd=%d", ip.c_str(), port, _fd); + + sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = inet_addr(ip.c_str()); + if (bind(_fd, (const sockaddr*)&addr, sizeof(sockaddr_in)) == -1) { + ret = ERROR_SOCKET_BIND; + srs_error("bind socket error. ep=%s:%d, ret=%d", ip.c_str(), port, ret); + return ret; + } + srs_verbose("bind socket success. ep=%s:%d, fd=%d", ip.c_str(), port, _fd); + + if ((stfd = st_netfd_open_socket(_fd)) == NULL){ + ret = ERROR_ST_OPEN_SOCKET; + srs_error("st_netfd_open_socket open socket failed. ep=%s:%d, ret=%d", ip.c_str(), port, ret); + return ret; + } + srs_verbose("st open socket success. ep=%s:%d, fd=%d", ip.c_str(), port, _fd); + + if ((ret = pthread->start()) != ERROR_SUCCESS) { + srs_error("st_thread_create listen thread error. ep=%s:%d, ret=%d", ip.c_str(), port, ret); + return ret; + } + srs_verbose("create st listen thread success, ep=%s:%d", ip.c_str(), port); + + return ret; +} + +int SrsUdpListener::cycle() +{ + int ret = ERROR_SUCCESS; + + while (pthread->can_loop()) { + // TODO: FIXME: support ipv6, @see man 7 ipv6 + sockaddr_in from; + int nb_from = sizeof(sockaddr_in); + int nread = 0; + + if ((nread = st_recvfrom(stfd, buf, nb_buf, (sockaddr*)&from, &nb_from, ST_UTIME_NO_TIMEOUT)) <= 0) { + srs_warn("ignore recv udp packet failed, nread=%d", nread); + continue; + } + + if ((ret = handler->on_udp_packet(&from, buf, nread)) != ERROR_SUCCESS) { + srs_warn("handle udp packet failed. ret=%d", ret); + continue; + } + + if (SRS_UDP_PACKET_RECV_CYCLE_INTERVAL_MS > 0) { + st_usleep(SRS_UDP_PACKET_RECV_CYCLE_INTERVAL_MS * 1000); + } + } + + return ret; +} + +SrsTcpListener::SrsTcpListener(ISrsTcpHandler* h, string i, int p) +{ + handler = h; + ip = i; + port = p; + + _fd = -1; + stfd = NULL; + + pthread = new SrsThread("tcp", this, 0, true); +} + +SrsTcpListener::~SrsTcpListener() +{ + // close the stfd to trigger thread to interrupted. + srs_close_stfd(stfd); + + pthread->stop(); + srs_freep(pthread); + + // st does not close it sometimes, + // close it manually. + close(_fd); +} + +int SrsTcpListener::fd() +{ + return _fd; +} + +int SrsTcpListener::listen() +{ + int ret = ERROR_SUCCESS; + + if ((_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + ret = ERROR_SOCKET_CREATE; + srs_error("create linux socket error. port=%d, ret=%d", port, ret); + return ret; + } + srs_verbose("create linux socket success. port=%d, fd=%d", port, _fd); + + int reuse_socket = 1; + if (setsockopt(_fd, SOL_SOCKET, SO_REUSEADDR, &reuse_socket, sizeof(int)) == -1) { + ret = ERROR_SOCKET_SETREUSE; + srs_error("setsockopt reuse-addr error. port=%d, ret=%d", port, ret); + return ret; + } + srs_verbose("setsockopt reuse-addr success. port=%d, fd=%d", port, _fd); + + sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = inet_addr(ip.c_str()); + if (bind(_fd, (const sockaddr*)&addr, sizeof(sockaddr_in)) == -1) { + ret = ERROR_SOCKET_BIND; + srs_error("bind socket error. ep=%s:%d, ret=%d", ip.c_str(), port, ret); + return ret; + } + srs_verbose("bind socket success. ep=%s:%d, fd=%d", ip.c_str(), port, _fd); + + if (::listen(_fd, SERVER_LISTEN_BACKLOG) == -1) { + ret = ERROR_SOCKET_LISTEN; + srs_error("listen socket error. ep=%s:%d, ret=%d", ip.c_str(), port, ret); + return ret; + } + srs_verbose("listen socket success. ep=%s:%d, fd=%d", ip.c_str(), port, _fd); + + if ((stfd = st_netfd_open_socket(_fd)) == NULL){ + ret = ERROR_ST_OPEN_SOCKET; + srs_error("st_netfd_open_socket open socket failed. ep=%s:%d, ret=%d", ip.c_str(), port, ret); + return ret; + } + srs_verbose("st open socket success. ep=%s:%d, fd=%d", ip.c_str(), port, _fd); + + if ((ret = pthread->start()) != ERROR_SUCCESS) { + srs_error("st_thread_create listen thread error. ep=%s:%d, ret=%d", ip.c_str(), port, ret); + return ret; + } + srs_verbose("create st listen thread success, ep=%s:%d", ip.c_str(), port); + + return ret; +} + +int SrsTcpListener::cycle() +{ + int ret = ERROR_SUCCESS; + + st_netfd_t client_stfd = st_accept(stfd, NULL, NULL, ST_UTIME_NO_TIMEOUT); + + if(client_stfd == NULL){ + // ignore error. + srs_error("ignore accept thread stoppped for accept client error"); + return ret; + } + srs_verbose("get a client. fd=%d", st_netfd_fileno(client_stfd)); + + if ((ret = handler->on_tcp_client(client_stfd)) != ERROR_SUCCESS) { + srs_warn("accept client error. ret=%d", ret); + return ret; + } + + return ret; +} + diff --git a/trunk/src/app/srs_app_listener.hpp b/trunk/src/app/srs_app_listener.hpp new file mode 100644 index 0000000000..679bf1bda6 --- /dev/null +++ b/trunk/src/app/srs_app_listener.hpp @@ -0,0 +1,129 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef SRS_APP_LISTENER_HPP +#define SRS_APP_LISTENER_HPP + +/* +#include +*/ + +#include + +#include + +#include +#include + +struct sockaddr_in; + +/** +* the udp packet handler. +*/ +class ISrsUdpHandler +{ +public: + ISrsUdpHandler(); + virtual ~ISrsUdpHandler(); +public: + /** + * when udp listener got a udp packet, notice server to process it. + * @param type, the client type, used to create concrete connection, + * for instance RTMP connection to serve client. + * @param from, the udp packet from address. + * @param buf, the udp packet bytes, user should copy if need to use. + * @param nb_buf, the size of udp packet bytes. + * @remark user should never use the buf, for it's a shared memory bytes. + */ + virtual int on_udp_packet(sockaddr_in* from, char* buf, int nb_buf) = 0; +}; + +/** +* the tcp connection handler. +*/ +class ISrsTcpHandler +{ +public: + ISrsTcpHandler(); + virtual ~ISrsTcpHandler(); +public: + /** + * when got tcp client. + */ + virtual int on_tcp_client(st_netfd_t stfd) = 0; +}; + +/** +* bind udp port, start thread to recv packet and handler it. +*/ +class SrsUdpListener : public ISrsThreadHandler +{ +private: + int _fd; + st_netfd_t stfd; + SrsThread* pthread; +private: + char* buf; + int nb_buf; +private: + ISrsUdpHandler* handler; + std::string ip; + int port; +public: + SrsUdpListener(ISrsUdpHandler* h, std::string i, int p); + virtual ~SrsUdpListener(); +public: + virtual int fd(); +public: + virtual int listen(); +// interface ISrsThreadHandler. +public: + virtual int cycle(); +}; + +/** +* bind and listen tcp port, use handler to process the client. +*/ +class SrsTcpListener : public ISrsThreadHandler +{ +private: + int _fd; + st_netfd_t stfd; + SrsThread* pthread; +private: + ISrsTcpHandler* handler; + std::string ip; + int port; +public: + SrsTcpListener(ISrsTcpHandler* h, std::string i, int p); + virtual ~SrsTcpListener(); +public: + virtual int fd(); +public: + virtual int listen(); +// interface ISrsThreadHandler. +public: + virtual int cycle(); +}; + +#endif diff --git a/trunk/src/app/srs_app_log.cpp b/trunk/src/app/srs_app_log.cpp index 6dbff78291..b6a43bac35 100644 --- a/trunk/src/app/srs_app_log.cpp +++ b/trunk/src/app/srs_app_log.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -274,8 +274,14 @@ bool SrsFastLog::generate_header(bool error, const char* tag, int context_id, co // to calendar time struct tm* tm; - if ((tm = localtime(&tv.tv_sec)) == NULL) { - return false; + if (_srs_config && _srs_config->get_utc_time()) { + if ((tm = gmtime(&tv.tv_sec)) == NULL) { + return false; + } + } else { + if ((tm = localtime(&tv.tv_sec)) == NULL) { + return false; + } } // write log header diff --git a/trunk/src/app/srs_app_log.hpp b/trunk/src/app/srs_app_log.hpp index d53b9768d4..cdb86c8db4 100644 --- a/trunk/src/app/srs_app_log.hpp +++ b/trunk/src/app/srs_app_log.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/trunk/src/app/srs_app_mpegts_udp.cpp b/trunk/src/app/srs_app_mpegts_udp.cpp new file mode 100644 index 0000000000..4b33f1089a --- /dev/null +++ b/trunk/src/app/srs_app_mpegts_udp.cpp @@ -0,0 +1,723 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include + +#ifdef SRS_AUTO_STREAM_CASTER + +#include +#include +#include +#include +using namespace std; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +SrsMpegtsQueue::SrsMpegtsQueue() +{ + nb_audios = nb_videos = 0; +} + +SrsMpegtsQueue::~SrsMpegtsQueue() +{ + std::map::iterator it; + for (it = msgs.begin(); it != msgs.end(); ++it) { + SrsSharedPtrMessage* msg = it->second; + srs_freep(msg); + } + msgs.clear(); +} + +int SrsMpegtsQueue::push(SrsSharedPtrMessage* msg) +{ + int ret = ERROR_SUCCESS; + + // TODO: FIXME: use right way. + for (int i = 0; i < 10; i++) { + if (msgs.find(msg->timestamp) == msgs.end()) { + break; + } + + // adjust the ts, add 1ms. + msg->timestamp += 1; + + if (i >= 5) { + srs_warn("mpegts: free the msg for dts exists, dts=%"PRId64, msg->timestamp); + srs_freep(msg); + return ret; + } + } + + if (msg->is_audio()) { + nb_audios++; + } + + if (msg->is_video()) { + nb_videos++; + } + + msgs[msg->timestamp] = msg; + + return ret; +} + +SrsSharedPtrMessage* SrsMpegtsQueue::dequeue() +{ + // got 2+ videos and audios, ok to dequeue. + bool av_ok = nb_videos >= 2 && nb_audios >= 2; + // 100 videos about 30s, while 300 audios about 30s + bool av_overflow = nb_videos > 100 || nb_audios > 300; + + if (av_ok || av_overflow) { + std::map::iterator it = msgs.begin(); + SrsSharedPtrMessage* msg = it->second; + msgs.erase(it); + + if (msg->is_audio()) { + nb_audios--; + } + + if (msg->is_video()) { + nb_videos--; + } + + return msg; + } + + return NULL; +} + +SrsMpegtsOverUdp::SrsMpegtsOverUdp(SrsConfDirective* c) +{ + stream = new SrsStream(); + context = new SrsTsContext(); + buffer = new SrsSimpleBuffer(); + output = _srs_config->get_stream_caster_output(c); + + req = NULL; + io = NULL; + client = NULL; + stfd = NULL; + stream_id = 0; + + avc = new SrsRawH264Stream(); + aac = new SrsRawAacStream(); + h264_sps_changed = false; + h264_pps_changed = false; + h264_sps_pps_sent = false; + queue = new SrsMpegtsQueue(); + pprint = SrsPithyPrint::create_caster(); +} + +SrsMpegtsOverUdp::~SrsMpegtsOverUdp() +{ + close(); + + srs_freep(buffer); + srs_freep(stream); + srs_freep(context); + srs_freep(avc); + srs_freep(aac); + srs_freep(queue); + srs_freep(pprint); +} + +int SrsMpegtsOverUdp::on_udp_packet(sockaddr_in* from, char* buf, int nb_buf) +{ + std::string peer_ip = inet_ntoa(from->sin_addr); + int peer_port = ntohs(from->sin_port); + + // append to buffer. + buffer->append(buf, nb_buf); + + srs_info("udp: got %s:%d packet %d/%d bytes", + peer_ip.c_str(), peer_port, nb_buf, buffer->length()); + + return on_udp_bytes(peer_ip, peer_port, buf, nb_buf); +} + +int SrsMpegtsOverUdp::on_udp_bytes(string host, int port, char* buf, int nb_buf) +{ + int ret = ERROR_SUCCESS; + + // collect nMB data to parse in a time. + // TODO: FIXME: comment the following for release. + //if (buffer->length() < 3 * 1024 * 1024) return ret; + // TODO: FIXME: remove the debug to file. +#if 0 + SrsFileWriter fw; + if ((ret = fw.open("latest.ts")) != ERROR_SUCCESS) { + return ret; + } + if ((ret = fw.write(buffer->bytes(), buffer->length(), NULL)) != ERROR_SUCCESS) { + return ret; + } + fw.close(); +#endif +#if 0 + SrsFileReader fr; + if ((ret = fr.open("latest.ts")) != ERROR_SUCCESS) { + return ret; + } + buffer->erase(buffer->length()); + int nb_fbuf = fr.filesize(); + char* fbuf = new char[nb_fbuf]; + SrsAutoFree(char, fbuf); + if ((ret = fr.read(fbuf, nb_fbuf, NULL)) != ERROR_SUCCESS) { + return ret; + } + fr.close(); + buffer->append(fbuf, nb_fbuf); +#endif + + // find the sync byte of mpegts. + char* p = buffer->bytes(); + for (int i = 0; i < buffer->length(); i++) { + if (p[i] != 0x47) { + continue; + } + + if (i > 0) { + buffer->erase(i); + } + break; + } + + // drop ts packet when size not modulus by 188 + if (buffer->length() < SRS_TS_PACKET_SIZE) { + srs_warn("udp: wait %s:%d packet %d/%d bytes", host.c_str(), port, nb_buf, buffer->length()); + return ret; + } + + // use stream to parse ts packet. + int nb_packet = buffer->length() / SRS_TS_PACKET_SIZE; + for (int i = 0; i < nb_packet; i++) { + char* p = buffer->bytes() + (i * SRS_TS_PACKET_SIZE); + if ((ret = stream->initialize(p, SRS_TS_PACKET_SIZE)) != ERROR_SUCCESS) { + return ret; + } + + // process each ts packet + if ((ret = context->decode(stream, this)) != ERROR_SUCCESS) { + srs_warn("mpegts: ignore parse ts packet failed. ret=%d", ret); + continue; + } + srs_info("mpegts: parse ts packet completed"); + } + srs_info("mpegts: parse udp packet completed"); + + // erase consumed bytes + if (nb_packet > 0) { + buffer->erase(nb_packet * SRS_TS_PACKET_SIZE); + } + + return ret; +} + +int SrsMpegtsOverUdp::on_ts_message(SrsTsMessage* msg) +{ + int ret = ERROR_SUCCESS; + + pprint->elapse(); + + // about the bytes of msg, specified by elementary stream which indicates by PES_packet_data_byte and stream_id + // for example, when SrsTsStream of SrsTsChannel indicates stream_type is SrsTsStreamVideoMpeg4 and SrsTsStreamAudioMpeg4, + // the elementary stream can be mux in "2.11 Carriage of ISO/IEC 14496 data" in hls-mpeg-ts-iso13818-1.pdf, page 103 + // @remark, the most popular stream_id is 0xe0 for h.264 over mpegts, which indicates the stream_id is video and + // stream_number is 0, where I guess the elementary is specified in annexb format(H.264-AVC-ISO_IEC_14496-10.pdf, page 211). + // because when audio stream_number is 0, the elementary is ADTS(aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 75, 1.A.2.2 ADTS). + + // about the bytes of PES_packet_data_byte, defined in hls-mpeg-ts-iso13818-1.pdf, page 58 + // PES_packet_data_byte C PES_packet_data_bytes shall be contiguous bytes of data from the elementary stream + // indicated by the packets stream_id or PID. When the elementary stream data conforms to ITU-T + // Rec. H.262 | ISO/IEC 13818-2 or ISO/IEC 13818-3, the PES_packet_data_bytes shall be byte aligned to the bytes of this + // Recommendation | International Standard. The byte-order of the elementary stream shall be preserved. The number of + // PES_packet_data_bytes, N, is specified by the PES_packet_length field. N shall be equal to the value indicated in the + // PES_packet_length minus the number of bytes between the last byte of the PES_packet_length field and the first + // PES_packet_data_byte. + // + // In the case of a private_stream_1, private_stream_2, ECM_stream, or EMM_stream, the contents of the + // PES_packet_data_byte field are user definable and will not be specified by ITU-T | ISO/IEC in the future. + + // about the bytes of stream_id, define in hls-mpeg-ts-iso13818-1.pdf, page 49 + // stream_id C In Program Streams, the stream_id specifies the type and number of the elementary stream as defined by the + // stream_id Table 2-18. In Transport Streams, the stream_id may be set to any valid value which correctly describes the + // elementary stream type as defined in Table 2-18. In Transport Streams, the elementary stream type is specified in the + // Program Specific Information as specified in 2.4.4. + + // about the stream_id table, define in Table 2-18 C Stream_id assignments, hls-mpeg-ts-iso13818-1.pdf, page 52. + // + // 110x xxxx + // ISO/IEC 13818-3 or ISO/IEC 11172-3 or ISO/IEC 13818-7 or ISO/IEC + // 14496-3 audio stream number x xxxx + // ((sid >> 5) & 0x07) == SrsTsPESStreamIdAudio + // + // 1110 xxxx + // ITU-T Rec. H.262 | ISO/IEC 13818-2 or ISO/IEC 11172-2 or ISO/IEC + // 14496-2 video stream number xxxx + // ((stream_id >> 4) & 0x0f) == SrsTsPESStreamIdVideo + + if (pprint->can_print()) { + srs_trace("<- "SRS_CONSTS_LOG_STREAM_CASTER" mpegts: got %s age=%d stream=%s, dts=%"PRId64", pts=%"PRId64", size=%d, us=%d, cc=%d, sid=%#x(%s-%d)", + (msg->channel->apply == SrsTsPidApplyVideo)? "Video":"Audio", pprint->age(), srs_ts_stream2string(msg->channel->stream).c_str(), + msg->dts, msg->pts, msg->payload->length(), msg->packet->payload_unit_start_indicator, msg->continuity_counter, msg->sid, + msg->is_audio()? "A":msg->is_video()? "V":"N", msg->stream_number()); + } + + // when not audio/video, or not adts/annexb format, donot support. + if (msg->stream_number() != 0) { + ret = ERROR_STREAM_CASTER_TS_ES; + srs_error("mpegts: unsupported stream format, sid=%#x(%s-%d). ret=%d", + msg->sid, msg->is_audio()? "A":msg->is_video()? "V":"N", msg->stream_number(), ret); + return ret; + } + + // check supported codec + if (msg->channel->stream != SrsTsStreamVideoH264 && msg->channel->stream != SrsTsStreamAudioAAC) { + ret = ERROR_STREAM_CASTER_TS_CODEC; + srs_error("mpegts: unsupported stream codec=%d. ret=%d", msg->channel->stream, ret); + return ret; + } + + // parse the stream. + SrsStream avs; + if ((ret = avs.initialize(msg->payload->bytes(), msg->payload->length())) != ERROR_SUCCESS) { + srs_error("mpegts: initialize av stream failed. ret=%d", ret); + return ret; + } + + // publish audio or video. + if (msg->channel->stream == SrsTsStreamVideoH264) { + return on_ts_video(msg, &avs); + } + if (msg->channel->stream == SrsTsStreamAudioAAC) { + return on_ts_audio(msg, &avs); + } + + // TODO: FIXME: implements it. + return ret; +} + +int SrsMpegtsOverUdp::on_ts_video(SrsTsMessage* msg, SrsStream* avs) +{ + int ret = ERROR_SUCCESS; + + // ensure rtmp connected. + if ((ret = connect()) != ERROR_SUCCESS) { + return ret; + } + + // ts tbn to flv tbn. + u_int32_t dts = msg->dts / 90; + u_int32_t pts = msg->dts / 90; + + // send each frame. + while (!avs->empty()) { + char* frame = NULL; + int frame_size = 0; + if ((ret = avc->annexb_demux(avs, &frame, &frame_size)) != ERROR_SUCCESS) { + return ret; + } + + // 5bits, 7.3.1 NAL unit syntax, + // H.264-AVC-ISO_IEC_14496-10.pdf, page 44. + // 7: SPS, 8: PPS, 5: I Frame, 1: P Frame + SrsAvcNaluType nal_unit_type = (SrsAvcNaluType)(frame[0] & 0x1f); + + // ignore the nalu type sps(7), pps(8), aud(9) + if (nal_unit_type == SrsAvcNaluTypeAccessUnitDelimiter) { + continue; + } + + // for sps + if (avc->is_sps(frame, frame_size)) { + std::string sps; + if ((ret = avc->sps_demux(frame, frame_size, sps)) != ERROR_SUCCESS) { + return ret; + } + + if (h264_sps == sps) { + continue; + } + h264_sps_changed = true; + h264_sps = sps; + + if ((ret = write_h264_sps_pps(dts, pts)) != ERROR_SUCCESS) { + return ret; + } + continue; + } + + // for pps + if (avc->is_pps(frame, frame_size)) { + std::string pps; + if ((ret = avc->pps_demux(frame, frame_size, pps)) != ERROR_SUCCESS) { + return ret; + } + + if (h264_pps == pps) { + continue; + } + h264_pps_changed = true; + h264_pps = pps; + + if ((ret = write_h264_sps_pps(dts, pts)) != ERROR_SUCCESS) { + return ret; + } + continue; + } + + // ibp frame. + // TODO: FIXME: we should group all frames to a rtmp/flv message from one ts message. + srs_info("mpegts: demux avc ibp frame size=%d, dts=%d", ibpframe_size, dts); + if ((ret = write_h264_ipb_frame(frame, frame_size, dts, pts)) != ERROR_SUCCESS) { + return ret; + } + } + + return ret; +} + +int SrsMpegtsOverUdp::write_h264_sps_pps(u_int32_t dts, u_int32_t pts) +{ + int ret = ERROR_SUCCESS; + + // TODO: FIMXE: there exists bug, see following comments. + // when sps or pps changed, update the sequence header, + // for the pps maybe not changed while sps changed. + // so, we must check when each video ts message frame parsed. + if (!h264_sps_changed || !h264_pps_changed) { + return ret; + } + + // h264 raw to h264 packet. + std::string sh; + if ((ret = avc->mux_sequence_header(h264_sps, h264_pps, dts, pts, sh)) != ERROR_SUCCESS) { + return ret; + } + + // h264 packet to flv packet. + int8_t frame_type = SrsCodecVideoAVCFrameKeyFrame; + int8_t avc_packet_type = SrsCodecVideoAVCTypeSequenceHeader; + char* flv = NULL; + int nb_flv = 0; + if ((ret = avc->mux_avc2flv(sh, frame_type, avc_packet_type, dts, pts, &flv, &nb_flv)) != ERROR_SUCCESS) { + return ret; + } + + // the timestamp in rtmp message header is dts. + u_int32_t timestamp = dts; + if ((ret = rtmp_write_packet(SrsCodecFlvTagVideo, timestamp, flv, nb_flv)) != ERROR_SUCCESS) { + return ret; + } + + // reset sps and pps. + h264_sps_changed = false; + h264_pps_changed = false; + h264_sps_pps_sent = true; + + return ret; +} + +int SrsMpegtsOverUdp::write_h264_ipb_frame(char* frame, int frame_size, u_int32_t dts, u_int32_t pts) +{ + int ret = ERROR_SUCCESS; + + // when sps or pps not sent, ignore the packet. + // @see https://github.com/simple-rtmp-server/srs/issues/203 + if (!h264_sps_pps_sent) { + return ERROR_H264_DROP_BEFORE_SPS_PPS; + } + + // 5bits, 7.3.1 NAL unit syntax, + // H.264-AVC-ISO_IEC_14496-10.pdf, page 44. + // 7: SPS, 8: PPS, 5: I Frame, 1: P Frame + SrsAvcNaluType nal_unit_type = (SrsAvcNaluType)(frame[0] & 0x1f); + + // for IDR frame, the frame is keyframe. + SrsCodecVideoAVCFrame frame_type = SrsCodecVideoAVCFrameInterFrame; + if (nal_unit_type == SrsAvcNaluTypeIDR) { + frame_type = SrsCodecVideoAVCFrameKeyFrame; + } + + std::string ibp; + if ((ret = avc->mux_ipb_frame(frame, frame_size, ibp)) != ERROR_SUCCESS) { + return ret; + } + + int8_t avc_packet_type = SrsCodecVideoAVCTypeNALU; + char* flv = NULL; + int nb_flv = 0; + if ((ret = avc->mux_avc2flv(ibp, frame_type, avc_packet_type, dts, pts, &flv, &nb_flv)) != ERROR_SUCCESS) { + return ret; + } + + // the timestamp in rtmp message header is dts. + u_int32_t timestamp = dts; + return rtmp_write_packet(SrsCodecFlvTagVideo, timestamp, flv, nb_flv); +} + +int SrsMpegtsOverUdp::on_ts_audio(SrsTsMessage* msg, SrsStream* avs) +{ + int ret = ERROR_SUCCESS; + + // ensure rtmp connected. + if ((ret = connect()) != ERROR_SUCCESS) { + return ret; + } + + // ts tbn to flv tbn. + u_int32_t dts = msg->dts / 90; + + // send each frame. + while (!avs->empty()) { + char* frame = NULL; + int frame_size = 0; + SrsRawAacStreamCodec codec; + if ((ret = aac->adts_demux(avs, &frame, &frame_size, codec)) != ERROR_SUCCESS) { + return ret; + } + + // ignore invalid frame, + // * atleast 1bytes for aac to decode the data. + if (frame_size <= 0) { + continue; + } + srs_info("mpegts: demux aac frame size=%d, dts=%d", frame_size, dts); + + // generate sh. + if (aac_specific_config.empty()) { + std::string sh; + if ((ret = aac->mux_sequence_header(&codec, sh)) != ERROR_SUCCESS) { + return ret; + } + aac_specific_config = sh; + + codec.aac_packet_type = 0; + + if ((ret = write_audio_raw_frame((char*)sh.data(), (int)sh.length(), &codec, dts)) != ERROR_SUCCESS) { + return ret; + } + } + + // audio raw data. + codec.aac_packet_type = 1; + if ((ret = write_audio_raw_frame(frame, frame_size, &codec, dts)) != ERROR_SUCCESS) { + return ret; + } + } + + return ret; +} + +int SrsMpegtsOverUdp::write_audio_raw_frame(char* frame, int frame_size, SrsRawAacStreamCodec* codec, u_int32_t dts) +{ + int ret = ERROR_SUCCESS; + + char* data = NULL; + int size = 0; + if ((ret = aac->mux_aac2flv(frame, frame_size, codec, dts, &data, &size)) != ERROR_SUCCESS) { + return ret; + } + + return rtmp_write_packet(SrsCodecFlvTagAudio, dts, data, size); +} + +int SrsMpegtsOverUdp::rtmp_write_packet(char type, u_int32_t timestamp, char* data, int size) +{ + int ret = ERROR_SUCCESS; + + SrsSharedPtrMessage* msg = NULL; + + if ((ret = srs_rtmp_create_msg(type, timestamp, data, size, stream_id, &msg)) != ERROR_SUCCESS) { + srs_error("mpegts: create shared ptr msg failed. ret=%d", ret); + return ret; + } + srs_assert(msg); + + // push msg to queue. + if ((ret = queue->push(msg)) != ERROR_SUCCESS) { + srs_error("mpegts: push msg to queue failed. ret=%d", ret); + return ret; + } + + // for all ready msg, dequeue and send out. + for (;;) { + if ((msg = queue->dequeue()) == NULL) { + break; + } + + if (pprint->can_print()) { + srs_trace("mpegts: send msg %s age=%d, dts=%"PRId64", size=%d", + msg->is_audio()? "A":msg->is_video()? "V":"N", pprint->age(), msg->timestamp, msg->size); + } + + // send out encoded msg. + if ((ret = client->send_and_free_message(msg, stream_id)) != ERROR_SUCCESS) { + return ret; + } + } + + return ret; +} + +int SrsMpegtsOverUdp::connect() +{ + int ret = ERROR_SUCCESS; + + // when ok, ignore. + // TODO: FIXME: should reconnect when disconnected. + if (io || client) { + return ret; + } + + // parse uri + if (!req) { + req = new SrsRequest(); + + size_t pos = string::npos; + string uri = req->tcUrl = output; + + // tcUrl, stream + if ((pos = uri.rfind("/")) != string::npos) { + req->stream = uri.substr(pos + 1); + req->tcUrl = uri = uri.substr(0, pos); + } + + srs_discovery_tc_url(req->tcUrl, + req->schema, req->host, req->vhost, req->app, req->port, + req->param); + } + + // connect host. + if ((ret = srs_socket_connect(req->host, ::atoi(req->port.c_str()), ST_UTIME_NO_TIMEOUT, &stfd)) != ERROR_SUCCESS) { + srs_error("mpegts: connect server %s:%s failed. ret=%d", req->host.c_str(), req->port.c_str(), ret); + return ret; + } + io = new SrsStSocket(stfd); + client = new SrsRtmpClient(io); + + client->set_recv_timeout(SRS_CONSTS_RTMP_RECV_TIMEOUT_US); + client->set_send_timeout(SRS_CONSTS_RTMP_SEND_TIMEOUT_US); + + // connect to vhost/app + if ((ret = client->handshake()) != ERROR_SUCCESS) { + srs_error("mpegts: handshake with server failed. ret=%d", ret); + return ret; + } + if ((ret = connect_app(req->host, req->port)) != ERROR_SUCCESS) { + srs_error("mpegts: connect with server failed. ret=%d", ret); + return ret; + } + if ((ret = client->create_stream(stream_id)) != ERROR_SUCCESS) { + srs_error("mpegts: connect with server failed, stream_id=%d. ret=%d", stream_id, ret); + return ret; + } + + // publish. + if ((ret = client->publish(req->stream, stream_id)) != ERROR_SUCCESS) { + srs_error("mpegts: publish failed, stream=%s, stream_id=%d. ret=%d", + req->stream.c_str(), stream_id, ret); + return ret; + } + + return ret; +} + +// TODO: FIXME: refine the connect_app. +int SrsMpegtsOverUdp::connect_app(string ep_server, string ep_port) +{ + int ret = ERROR_SUCCESS; + + // args of request takes the srs info. + if (req->args == NULL) { + req->args = SrsAmf0Any::object(); + } + + // notify server the edge identity, + // @see https://github.com/simple-rtmp-server/srs/issues/147 + SrsAmf0Object* data = req->args; + data->set("srs_sig", SrsAmf0Any::str(RTMP_SIG_SRS_KEY)); + data->set("srs_server", SrsAmf0Any::str(RTMP_SIG_SRS_KEY" "RTMP_SIG_SRS_VERSION" ("RTMP_SIG_SRS_URL_SHORT")")); + data->set("srs_license", SrsAmf0Any::str(RTMP_SIG_SRS_LICENSE)); + data->set("srs_role", SrsAmf0Any::str(RTMP_SIG_SRS_ROLE)); + data->set("srs_url", SrsAmf0Any::str(RTMP_SIG_SRS_URL)); + data->set("srs_version", SrsAmf0Any::str(RTMP_SIG_SRS_VERSION)); + data->set("srs_site", SrsAmf0Any::str(RTMP_SIG_SRS_WEB)); + data->set("srs_email", SrsAmf0Any::str(RTMP_SIG_SRS_EMAIL)); + data->set("srs_copyright", SrsAmf0Any::str(RTMP_SIG_SRS_COPYRIGHT)); + data->set("srs_primary", SrsAmf0Any::str(RTMP_SIG_SRS_PRIMARY)); + data->set("srs_authors", SrsAmf0Any::str(RTMP_SIG_SRS_AUTHROS)); + // for edge to directly get the id of client. + data->set("srs_pid", SrsAmf0Any::number(getpid())); + data->set("srs_id", SrsAmf0Any::number(_srs_context->get_id())); + + // local ip of edge + std::vector ips = srs_get_local_ipv4_ips(); + assert(_srs_config->get_stats_network() < (int)ips.size()); + std::string local_ip = ips[_srs_config->get_stats_network()]; + data->set("srs_server_ip", SrsAmf0Any::str(local_ip.c_str())); + + // generate the tcUrl + std::string param = ""; + std::string tc_url = srs_generate_tc_url(ep_server, req->vhost, req->app, ep_port, param); + + // upnode server identity will show in the connect_app of client. + // @see https://github.com/simple-rtmp-server/srs/issues/160 + // the debug_srs_upnode is config in vhost and default to true. + bool debug_srs_upnode = _srs_config->get_debug_srs_upnode(req->vhost); + if ((ret = client->connect_app(req->app, tc_url, req, debug_srs_upnode)) != ERROR_SUCCESS) { + srs_error("mpegts: connect with server failed, tcUrl=%s, dsu=%d. ret=%d", + tc_url.c_str(), debug_srs_upnode, ret); + return ret; + } + + return ret; +} + +void SrsMpegtsOverUdp::close() +{ + srs_freep(client); + srs_freep(io); + srs_freep(req); + srs_close_stfd(stfd); +} + +#endif diff --git a/trunk/src/app/srs_app_mpegts_udp.hpp b/trunk/src/app/srs_app_mpegts_udp.hpp new file mode 100644 index 0000000000..64e7e2fa13 --- /dev/null +++ b/trunk/src/app/srs_app_mpegts_udp.hpp @@ -0,0 +1,136 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef SRS_APP_MPEGTS_UDP_HPP +#define SRS_APP_MPEGTS_UDP_HPP + +/* +#include +*/ + +#include + +#ifdef SRS_AUTO_STREAM_CASTER + +struct sockaddr_in; +#include +#include + +class SrsStream; +class SrsTsContext; +class SrsConfDirective; +class SrsSimpleBuffer; +class SrsRtmpClient; +class SrsStSocket; +class SrsRequest; +class SrsRawH264Stream; +class SrsSharedPtrMessage; +class SrsRawAacStream; +struct SrsRawAacStreamCodec; +class SrsPithyPrint; + +#include +#include +#include + +/** +* the queue for mpegts over udp to send packets. +* for the aac in mpegts contains many flv packets in a pes packet, +* we must recalc the timestamp. +*/ +class SrsMpegtsQueue +{ +private: + // key: dts, value: msg. + std::map msgs; + int nb_audios; + int nb_videos; +public: + SrsMpegtsQueue(); + virtual ~SrsMpegtsQueue(); +public: + virtual int push(SrsSharedPtrMessage* msg); + virtual SrsSharedPtrMessage* dequeue(); +}; + +/** +* the mpegts over udp stream caster. +*/ +class SrsMpegtsOverUdp : virtual public ISrsTsHandler + , virtual public ISrsUdpHandler +{ +private: + SrsStream* stream; + SrsTsContext* context; + SrsSimpleBuffer* buffer; + std::string output; +private: + SrsRequest* req; + st_netfd_t stfd; + SrsStSocket* io; + SrsRtmpClient* client; + int stream_id; +private: + SrsRawH264Stream* avc; + std::string h264_sps; + bool h264_sps_changed; + std::string h264_pps; + bool h264_pps_changed; + bool h264_sps_pps_sent; +private: + SrsRawAacStream* aac; + std::string aac_specific_config; +private: + SrsMpegtsQueue* queue; + SrsPithyPrint* pprint; +public: + SrsMpegtsOverUdp(SrsConfDirective* c); + virtual ~SrsMpegtsOverUdp(); +// interface ISrsUdpHandler +public: + virtual int on_udp_packet(sockaddr_in* from, char* buf, int nb_buf); +private: + virtual int on_udp_bytes(std::string host, int port, char* buf, int nb_buf); +// interface ISrsTsHandler +public: + virtual int on_ts_message(SrsTsMessage* msg); +private: + virtual int on_ts_video(SrsTsMessage* msg, SrsStream* avs); + virtual int write_h264_sps_pps(u_int32_t dts, u_int32_t pts); + virtual int write_h264_ipb_frame(char* frame, int frame_size, u_int32_t dts, u_int32_t pts); + virtual int on_ts_audio(SrsTsMessage* msg, SrsStream* avs); + virtual int write_audio_raw_frame(char* frame, int frame_size, SrsRawAacStreamCodec* codec, u_int32_t dts); +private: + virtual int rtmp_write_packet(char type, u_int32_t timestamp, char* data, int size); +private: + // connect to rtmp output url. + // @remark ignore when not connected, reconnect when disconnected. + virtual int connect(); + virtual int connect_app(std::string ep_server, std::string ep_port); + // close the connected io and rtmp to ready to be re-connect. + virtual void close(); +}; + +#endif + +#endif diff --git a/trunk/src/app/srs_app_pithy_print.cpp b/trunk/src/app/srs_app_pithy_print.cpp index 9b6c935f40..5edd5de448 100644 --- a/trunk/src/app/srs_app_pithy_print.cpp +++ b/trunk/src/app/srs_app_pithy_print.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -31,8 +31,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include -#define SRS_CONSTS_STAGE_DEFAULT_INTERVAL_MS 1200 - SrsStageInfo::SrsStageInfo(int _stage_id) { stage_id = _stage_id; @@ -51,40 +49,7 @@ SrsStageInfo::~SrsStageInfo() void SrsStageInfo::update_print_time() { - switch (stage_id) { - case SRS_CONSTS_STAGE_PLAY_USER: { - pithy_print_time_ms = _srs_config->get_pithy_print_play(); - break; - } - case SRS_CONSTS_STAGE_PUBLISH_USER: { - pithy_print_time_ms = _srs_config->get_pithy_print_publish(); - break; - } - case SRS_CONSTS_STAGE_FORWARDER: { - pithy_print_time_ms = _srs_config->get_pithy_print_forwarder(); - break; - } - case SRS_CONSTS_STAGE_ENCODER: { - pithy_print_time_ms = _srs_config->get_pithy_print_encoder(); - break; - } - case SRS_CONSTS_STAGE_INGESTER: { - pithy_print_time_ms = _srs_config->get_pithy_print_ingester(); - break; - } - case SRS_CONSTS_STAGE_EDGE: { - pithy_print_time_ms = _srs_config->get_pithy_print_edge(); - break; - } - case SRS_CONSTS_STAGE_HLS: { - pithy_print_time_ms = _srs_config->get_pithy_print_hls(); - break; - } - default: { - pithy_print_time_ms = SRS_CONSTS_STAGE_DEFAULT_INTERVAL_MS; - break; - } - } + pithy_print_time_ms = _srs_config->get_pithy_print_ms(); } void SrsStageInfo::elapse(int64_t diff) @@ -120,6 +85,80 @@ SrsPithyPrint::SrsPithyPrint(int _stage_id) _age = 0; } +/////////////////////////////////////////////////////////// +// pithy-print consts values +/////////////////////////////////////////////////////////// +// the pithy stage for all play clients. +#define SRS_CONSTS_STAGE_PLAY_USER 1 +// the pithy stage for all publish clients. +#define SRS_CONSTS_STAGE_PUBLISH_USER 2 +// the pithy stage for all forward clients. +#define SRS_CONSTS_STAGE_FORWARDER 3 +// the pithy stage for all encoders. +#define SRS_CONSTS_STAGE_ENCODER 4 +// the pithy stage for all hls. +#define SRS_CONSTS_STAGE_HLS 5 +// the pithy stage for all ingesters. +#define SRS_CONSTS_STAGE_INGESTER 6 +// the pithy stage for all edge. +#define SRS_CONSTS_STAGE_EDGE 7 +// the pithy stage for all stream caster. +#define SRS_CONSTS_STAGE_CASTER 8 +// the pithy stage for all http stream. +#define SRS_CONSTS_STAGE_HTTP_STREAM 9 +// the pithy stage for all http stream cache. +#define SRS_CONSTS_STAGE_HTTP_STREAM_CACHE 10 + +SrsPithyPrint* SrsPithyPrint::create_rtmp_play() +{ + return new SrsPithyPrint(SRS_CONSTS_STAGE_PLAY_USER); +} + +SrsPithyPrint* SrsPithyPrint::create_rtmp_publish() +{ + return new SrsPithyPrint(SRS_CONSTS_STAGE_PUBLISH_USER); +} + +SrsPithyPrint* SrsPithyPrint::create_hls() +{ + return new SrsPithyPrint(SRS_CONSTS_STAGE_HLS); +} + +SrsPithyPrint* SrsPithyPrint::create_forwarder() +{ + return new SrsPithyPrint(SRS_CONSTS_STAGE_FORWARDER); +} + +SrsPithyPrint* SrsPithyPrint::create_encoder() +{ + return new SrsPithyPrint(SRS_CONSTS_STAGE_ENCODER); +} + +SrsPithyPrint* SrsPithyPrint::create_ingester() +{ + return new SrsPithyPrint(SRS_CONSTS_STAGE_INGESTER); +} + +SrsPithyPrint* SrsPithyPrint::create_edge() +{ + return new SrsPithyPrint(SRS_CONSTS_STAGE_EDGE); +} + +SrsPithyPrint* SrsPithyPrint::create_caster() +{ + return new SrsPithyPrint(SRS_CONSTS_STAGE_CASTER); +} + +SrsPithyPrint* SrsPithyPrint::create_http_stream() +{ + return new SrsPithyPrint(SRS_CONSTS_STAGE_HTTP_STREAM); +} + +SrsPithyPrint* SrsPithyPrint::create_http_stream_cache() +{ + return new SrsPithyPrint(SRS_CONSTS_STAGE_HTTP_STREAM_CACHE); +} + SrsPithyPrint::~SrsPithyPrint() { leave_stage(); diff --git a/trunk/src/app/srs_app_pithy_print.hpp b/trunk/src/app/srs_app_pithy_print.hpp index e29471fe51..9e333b83d2 100644 --- a/trunk/src/app/srs_app_pithy_print.hpp +++ b/trunk/src/app/srs_app_pithy_print.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -32,6 +32,9 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include +/** +* the stage info to calc the age. +*/ class SrsStageInfo : public ISrsReloadHandler { public: @@ -56,6 +59,17 @@ class SrsStageInfo : public ISrsReloadHandler * the print time in a stage is constant and not changed. * for example, stage #1 for all play clients, print time is 3s, * if there is 10clients, then all clients should print in 10*3s. +* Usage: + SrsPithyPrint* pprint = SrsPithyPrint::create_rtmp_play(); + SrsAutoFree(SrsPithyPrint, pprint); + while (true) { + pprint->elapse(); + if (pprint->can_print()) { + // print pithy message. + // user can get the elapse time by: pprint->age() + } + // read and write RTMP messages. + } */ class SrsPithyPrint { @@ -65,11 +79,19 @@ class SrsPithyPrint // in ms. int64_t _age; int64_t previous_tick; -public: - /** - * @param _stage_id defined in SRS_CONSTS_STAGE_xxx, eg. SRS_CONSTS_STAGE_PLAY_USER. - */ +private: SrsPithyPrint(int _stage_id); +public: + static SrsPithyPrint* create_rtmp_play(); + static SrsPithyPrint* create_rtmp_publish(); + static SrsPithyPrint* create_hls(); + static SrsPithyPrint* create_forwarder(); + static SrsPithyPrint* create_encoder(); + static SrsPithyPrint* create_ingester(); + static SrsPithyPrint* create_edge(); + static SrsPithyPrint* create_caster(); + static SrsPithyPrint* create_http_stream(); + static SrsPithyPrint* create_http_stream_cache(); virtual ~SrsPithyPrint(); private: /** diff --git a/trunk/src/app/srs_app_recv_thread.cpp b/trunk/src/app/srs_app_recv_thread.cpp new file mode 100644 index 0000000000..537e3dbf1e --- /dev/null +++ b/trunk/src/app/srs_app_recv_thread.cpp @@ -0,0 +1,491 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +// the max small bytes to group +#define SRS_MR_SMALL_BYTES 4096 + +ISrsMessageHandler::ISrsMessageHandler() +{ +} + +ISrsMessageHandler::~ISrsMessageHandler() +{ +} + +SrsRecvThread::SrsRecvThread(ISrsMessageHandler* msg_handler, SrsRtmpServer* rtmp_sdk, int timeout_ms) +{ + timeout = timeout_ms; + handler = msg_handler; + rtmp = rtmp_sdk; + trd = new SrsThread("recv", this, 0, true); +} + +SrsRecvThread::~SrsRecvThread() +{ + // stop recv thread. + stop(); + + // destroy the thread. + srs_freep(trd); +} + +int SrsRecvThread::start() +{ + return trd->start(); +} + +void SrsRecvThread::stop() +{ + trd->stop(); +} + +int SrsRecvThread::cycle() +{ + int ret = ERROR_SUCCESS; + + while (trd->can_loop()) { + if (!handler->can_handle()) { + st_usleep(timeout * 1000); + continue; + } + + SrsCommonMessage* msg = NULL; + + // recv and handle message + ret = rtmp->recv_message(&msg); + if (ret == ERROR_SUCCESS) { + ret = handler->handle(msg); + } + + if (ret != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("thread process message failed. ret=%d", ret); + } + + // we use no timeout to recv, should never got any error. + trd->stop_loop(); + + // notice the handler got a recv error. + handler->on_recv_error(ret); + + return ret; + } + srs_verbose("thread loop recv message. ret=%d", ret); + } + + return ret; +} + +void SrsRecvThread::stop_loop() +{ + trd->stop_loop(); +} + +void SrsRecvThread::on_thread_start() +{ + // the multiple messages writev improve performance large, + // but the timeout recv will cause 33% sys call performance, + // to use isolate thread to recv, can improve about 33% performance. + // @see https://github.com/simple-rtmp-server/srs/issues/194 + // @see: https://github.com/simple-rtmp-server/srs/issues/217 + rtmp->set_recv_timeout(ST_UTIME_NO_TIMEOUT); + + handler->on_thread_start(); +} + +void SrsRecvThread::on_thread_stop() +{ + // reset the timeout to pulse mode. + rtmp->set_recv_timeout(timeout * 1000); + + handler->on_thread_stop(); +} + +SrsQueueRecvThread::SrsQueueRecvThread(SrsConsumer* consumer, SrsRtmpServer* rtmp_sdk, int timeout_ms) + : trd(this, rtmp_sdk, timeout_ms) +{ + _consumer = consumer; + rtmp = rtmp_sdk; + recv_error_code = ERROR_SUCCESS; +} + +SrsQueueRecvThread::~SrsQueueRecvThread() +{ + stop(); + + // clear all messages. + std::vector::iterator it; + for (it = queue.begin(); it != queue.end(); ++it) { + SrsCommonMessage* msg = *it; + srs_freep(msg); + } + queue.clear(); +} + +int SrsQueueRecvThread::start() +{ + return trd.start(); +} + +void SrsQueueRecvThread::stop() +{ + trd.stop(); +} + +bool SrsQueueRecvThread::empty() +{ + return queue.empty(); +} + +int SrsQueueRecvThread::size() +{ + return (int)queue.size(); +} + +SrsCommonMessage* SrsQueueRecvThread::pump() +{ + srs_assert(!queue.empty()); + + SrsCommonMessage* msg = *queue.begin(); + + queue.erase(queue.begin()); + + return msg; +} + +int SrsQueueRecvThread::error_code() +{ + return recv_error_code; +} + +bool SrsQueueRecvThread::can_handle() +{ + // we only recv one message and then process it, + // for the message may cause the thread to stop, + // when stop, the thread is freed, so the messages + // are dropped. + return empty(); +} + +int SrsQueueRecvThread::handle(SrsCommonMessage* msg) +{ + // put into queue, the send thread will get and process it, + // @see SrsRtmpConn::process_play_control_msg + queue.push_back(msg); +#ifdef SRS_PERF_QUEUE_COND_WAIT + if (_consumer) { + _consumer->wakeup(); + } +#endif + return ERROR_SUCCESS; +} + +void SrsQueueRecvThread::on_recv_error(int ret) +{ + recv_error_code = ret; +#ifdef SRS_PERF_QUEUE_COND_WAIT + if (_consumer) { + _consumer->wakeup(); + } +#endif +} + +void SrsQueueRecvThread::on_thread_start() +{ + // disable the protocol auto response, + // for the isolate recv thread should never send any messages. + rtmp->set_auto_response(false); +} + +void SrsQueueRecvThread::on_thread_stop() +{ + // enable the protocol auto response, + // for the isolate recv thread terminated. + rtmp->set_auto_response(true); +} + +SrsPublishRecvThread::SrsPublishRecvThread( + SrsRtmpServer* rtmp_sdk, + SrsRequest* _req, int mr_sock_fd, int timeout_ms, + SrsRtmpConn* conn, SrsSource* source, bool is_fmle, bool is_edge +): trd(this, rtmp_sdk, timeout_ms) +{ + rtmp = rtmp_sdk; + + _conn = conn; + _source = source; + _is_fmle = is_fmle; + _is_edge = is_edge; + + recv_error_code = ERROR_SUCCESS; + _nb_msgs = 0; + error = st_cond_new(); + + req = _req; + mr_fd = mr_sock_fd; + + // the mr settings, + // @see https://github.com/simple-rtmp-server/srs/issues/241 + mr = _srs_config->get_mr_enabled(req->vhost); + mr_sleep = _srs_config->get_mr_sleep_ms(req->vhost); + + realtime = _srs_config->get_realtime_enabled(req->vhost); + + _srs_config->subscribe(this); +} + +SrsPublishRecvThread::~SrsPublishRecvThread() +{ + _srs_config->unsubscribe(this); + + trd.stop(); + st_cond_destroy(error); +} + +int SrsPublishRecvThread::wait(int timeout_ms) +{ + if (recv_error_code != ERROR_SUCCESS) { + return recv_error_code; + } + + // ignore any return of cond wait. + st_cond_timedwait(error, timeout_ms * 1000); + + return ERROR_SUCCESS; +} + +int64_t SrsPublishRecvThread::nb_msgs() +{ + return _nb_msgs; +} + +int SrsPublishRecvThread::error_code() +{ + return recv_error_code; +} + +int SrsPublishRecvThread::start() +{ + return trd.start(); +} + +void SrsPublishRecvThread::stop() +{ + trd.stop(); +} + +void SrsPublishRecvThread::on_thread_start() +{ + // we donot set the auto response to false, + // for the main thread never send message. + +#ifdef SRS_PERF_MERGED_READ + if (mr) { + // set underlayer buffer size + set_socket_buffer(mr_sleep); + + // disable the merge read + // @see https://github.com/simple-rtmp-server/srs/issues/241 + rtmp->set_merge_read(true, this); + } +#endif +} + +void SrsPublishRecvThread::on_thread_stop() +{ + // we donot set the auto response to true, + // for we donot set to false yet. + + // when thread stop, signal the conn thread which wait. + // @see https://github.com/simple-rtmp-server/srs/issues/244 + st_cond_signal(error); + +#ifdef SRS_PERF_MERGED_READ + if (mr) { + // disable the merge read + // @see https://github.com/simple-rtmp-server/srs/issues/241 + rtmp->set_merge_read(false, NULL); + } +#endif +} + +bool SrsPublishRecvThread::can_handle() +{ + // publish thread always can handle message. + return true; +} + +int SrsPublishRecvThread::handle(SrsCommonMessage* msg) +{ + int ret = ERROR_SUCCESS; + + _nb_msgs++; + + // log to show the time of recv thread. + srs_verbose("recv thread now=%"PRId64"us, got msg time=%"PRId64"ms, size=%d", + srs_update_system_time_ms(), msg->header.timestamp, msg->size); + + // the rtmp connection will handle this message + ret = _conn->handle_publish_message(_source, msg, _is_fmle, _is_edge); + + // must always free it, + // the source will copy it if need to use. + srs_freep(msg); + + return ret; +} + +void SrsPublishRecvThread::on_recv_error(int ret) +{ + recv_error_code = ret; + + // when recv thread error, signal the conn thread to process it. + // @see https://github.com/simple-rtmp-server/srs/issues/244 + st_cond_signal(error); +} + +#ifdef SRS_PERF_MERGED_READ +void SrsPublishRecvThread::on_read(ssize_t nread) +{ + if (!mr || realtime) { + return; + } + + if (nread < 0 || mr_sleep <= 0) { + return; + } + + /** + * to improve read performance, merge some packets then read, + * when it on and read small bytes, we sleep to wait more data., + * that is, we merge some data to read together. + * @see https://github.com/simple-rtmp-server/srs/issues/241 + */ + if (nread < SRS_MR_SMALL_BYTES) { + st_usleep(mr_sleep * 1000); + } +} +#endif + +int SrsPublishRecvThread::on_reload_vhost_mr(string vhost) +{ + int ret = ERROR_SUCCESS; + + if (req->vhost != vhost) { + return ret; + } + + // the mr settings, + // @see https://github.com/simple-rtmp-server/srs/issues/241 + bool mr_enabled = _srs_config->get_mr_enabled(req->vhost); + int sleep_ms = _srs_config->get_mr_sleep_ms(req->vhost); + + // update buffer when sleep ms changed. + if (mr_sleep != sleep_ms) { + set_socket_buffer(sleep_ms); + } + +#ifdef SRS_PERF_MERGED_READ + // mr enabled=>disabled + if (mr && !mr_enabled) { + // disable the merge read + // @see https://github.com/simple-rtmp-server/srs/issues/241 + rtmp->set_merge_read(false, NULL); + } + // mr disabled=>enabled + if (!mr && mr_enabled) { + // enable the merge read + // @see https://github.com/simple-rtmp-server/srs/issues/241 + rtmp->set_merge_read(true, this); + } +#endif + + // update to new state + mr = mr_enabled; + mr_sleep = sleep_ms; + + return ret; +} + +int SrsPublishRecvThread::on_reload_vhost_realtime(string vhost) +{ + int ret = ERROR_SUCCESS; + + if (req->vhost != vhost) { + return ret; + } + + bool realtime_enabled = _srs_config->get_realtime_enabled(req->vhost); + srs_trace("realtime changed %d=>%d", realtime, realtime_enabled); + realtime = realtime_enabled; + + return ret; +} + +void SrsPublishRecvThread::set_socket_buffer(int sleep_ms) +{ + // the bytes: + // 4KB=4096, 8KB=8192, 16KB=16384, 32KB=32768, 64KB=65536, + // 128KB=131072, 256KB=262144, 512KB=524288 + // the buffer should set to sleep*kbps/8, + // for example, your system delivery stream in 1000kbps, + // sleep 800ms for small bytes, the buffer should set to: + // 800*1000/8=100000B(about 128KB). + // other examples: + // 2000*3000/8=750000B(about 732KB). + // 2000*5000/8=1250000B(about 1220KB). + int kbps = 5000; + int socket_buffer_size = sleep_ms * kbps / 8; + + int fd = mr_fd; + int onb_rbuf = 0; + socklen_t sock_buf_size = sizeof(int); + getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &onb_rbuf, &sock_buf_size); + + // socket recv buffer, system will double it. + int nb_rbuf = socket_buffer_size / 2; + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &nb_rbuf, sock_buf_size) < 0) { + srs_warn("set sock SO_RCVBUF=%d failed.", nb_rbuf); + } + getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &nb_rbuf, &sock_buf_size); + + srs_trace("mr change sleep %d=>%d, erbuf=%d, rbuf %d=>%d, sbytes=%d, realtime=%d", + mr_sleep, sleep_ms, socket_buffer_size, onb_rbuf, nb_rbuf, + SRS_MR_SMALL_BYTES, realtime); + + rtmp->set_recv_buffer(nb_rbuf); +} + diff --git a/trunk/src/app/srs_app_recv_thread.hpp b/trunk/src/app/srs_app_recv_thread.hpp new file mode 100644 index 0000000000..f6ba086df0 --- /dev/null +++ b/trunk/src/app/srs_app_recv_thread.hpp @@ -0,0 +1,207 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef SRS_APP_RECV_THREAD_HPP +#define SRS_APP_RECV_THREAD_HPP + +/* +#include +*/ + +#include + +#include + +#include +#include +#include +#include + +class SrsRtmpServer; +class SrsCommonMessage; +class SrsRtmpConn; +class SrsSource; +class SrsRequest; +class SrsConsumer; + +/** + * for the recv thread to handle the message. + */ +class ISrsMessageHandler +{ +public: + ISrsMessageHandler(); + virtual ~ISrsMessageHandler(); +public: + /** + * whether the handler can handle, + * for example, when queue recv handler got an message, + * it wait the user to process it, then the recv thread + * never recv message util the handler is ok. + */ + virtual bool can_handle() = 0; + /** + * process the received message. + */ + virtual int handle(SrsCommonMessage* msg) = 0; + /** + * when recv message error. + */ + virtual void on_recv_error(int ret) = 0; + /** + * when thread start or stop, + * for example, the message handler can set whether auto response. + */ + virtual void on_thread_start() = 0; + virtual void on_thread_stop() = 0; +}; + +/** + * the recv thread, use message handler to handle each received message. + */ +class SrsRecvThread : public ISrsThreadHandler +{ +protected: + SrsThread* trd; + ISrsMessageHandler* handler; + SrsRtmpServer* rtmp; + int timeout; +public: + SrsRecvThread(ISrsMessageHandler* msg_handler, SrsRtmpServer* rtmp_sdk, int timeout_ms); + virtual ~SrsRecvThread(); +public: + virtual int start(); + virtual void stop(); + virtual int cycle(); + virtual void stop_loop(); +public: + virtual void on_thread_start(); + virtual void on_thread_stop(); +}; + +/** +* the recv thread used to replace the timeout recv, +* which hurt performance for the epoll_ctrl is frequently used. +* @see: SrsRtmpConn::playing +* @see: https://github.com/simple-rtmp-server/srs/issues/217 +*/ +class SrsQueueRecvThread : public ISrsMessageHandler +{ +private: + std::vector queue; + SrsRecvThread trd; + SrsRtmpServer* rtmp; + // the recv thread error code. + int recv_error_code; + SrsConsumer* _consumer; +public: + SrsQueueRecvThread(SrsConsumer* consumer, SrsRtmpServer* rtmp_sdk, int timeout_ms); + virtual ~SrsQueueRecvThread(); +public: + virtual int start(); + virtual void stop(); +public: + virtual bool empty(); + virtual int size(); + virtual SrsCommonMessage* pump(); + virtual int error_code(); +public: + virtual bool can_handle(); + virtual int handle(SrsCommonMessage* msg); + virtual void on_recv_error(int ret); +public: + virtual void on_thread_start(); + virtual void on_thread_stop(); +}; + +/** +* the publish recv thread got message and callback the source method to process message. +* @see: https://github.com/simple-rtmp-server/srs/issues/237 +*/ +class SrsPublishRecvThread : virtual public ISrsMessageHandler +#ifdef SRS_PERF_MERGED_READ + , virtual public IMergeReadHandler +#endif + , virtual public ISrsReloadHandler +{ +private: + SrsRecvThread trd; + SrsRtmpServer* rtmp; + SrsRequest* req; + // the msgs already got. + int64_t _nb_msgs; + // for mr(merged read), + // @see https://github.com/simple-rtmp-server/srs/issues/241 + bool mr; + int mr_fd; + int mr_sleep; + // for realtime + // @see https://github.com/simple-rtmp-server/srs/issues/257 + bool realtime; + // the recv thread error code. + int recv_error_code; + SrsRtmpConn* _conn; + // the params for conn callback. + SrsSource* _source; + bool _is_fmle; + bool _is_edge; + // the error timeout cond + // @see https://github.com/simple-rtmp-server/srs/issues/244 + st_cond_t error; +public: + SrsPublishRecvThread(SrsRtmpServer* rtmp_sdk, + SrsRequest* _req, int mr_sock_fd, int timeout_ms, + SrsRtmpConn* conn, SrsSource* source, bool is_fmle, bool is_edge); + virtual ~SrsPublishRecvThread(); +public: + /** + * wait for error for some timeout. + */ + virtual int wait(int timeout_ms); + virtual int64_t nb_msgs(); + virtual int error_code(); +public: + virtual int start(); + virtual void stop(); + virtual void on_thread_start(); + virtual void on_thread_stop(); +// interface ISrsMessageHandler +public: + virtual bool can_handle(); + virtual int handle(SrsCommonMessage* msg); + virtual void on_recv_error(int ret); +// interface IMergeReadHandler +public: +#ifdef SRS_PERF_MERGED_READ + virtual void on_read(ssize_t nread); +#endif +// interface ISrsReloadHandler +public: + virtual int on_reload_vhost_mr(std::string vhost); + virtual int on_reload_vhost_realtime(std::string vhost); +private: + virtual void set_socket_buffer(int sleep_ms); +}; + +#endif + diff --git a/trunk/src/app/srs_app_refer.cpp b/trunk/src/app/srs_app_refer.cpp index 699569fa3a..ef4112d220 100644 --- a/trunk/src/app/srs_app_refer.cpp +++ b/trunk/src/app/srs_app_refer.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/trunk/src/app/srs_app_refer.hpp b/trunk/src/app/srs_app_refer.hpp index 1e488ad197..b1e0298789 100644 --- a/trunk/src/app/srs_app_refer.hpp +++ b/trunk/src/app/srs_app_refer.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/trunk/src/app/srs_app_reload.cpp b/trunk/src/app/srs_app_reload.cpp index 8bed3f19ba..ffd1868b17 100644 --- a/trunk/src/app/srs_app_reload.cpp +++ b/trunk/src/app/srs_app_reload.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -95,6 +95,11 @@ int ISrsReloadHandler::on_reload_vhost_http_updated() return ERROR_SUCCESS; } +int ISrsReloadHandler::on_reload_vhost_http_remux_updated() +{ + return ERROR_SUCCESS; +} + int ISrsReloadHandler::on_reload_vhost_added(string /*vhost*/) { return ERROR_SUCCESS; @@ -125,6 +130,11 @@ int ISrsReloadHandler::on_reload_vhost_time_jitter(string /*vhost*/) return ERROR_SUCCESS; } +int ISrsReloadHandler::on_reload_vhost_mix_correct(string /*vhost*/) +{ + return ERROR_SUCCESS; +} + int ISrsReloadHandler::on_reload_vhost_forward(string /*vhost*/) { return ERROR_SUCCESS; @@ -135,11 +145,36 @@ int ISrsReloadHandler::on_reload_vhost_hls(string /*vhost*/) return ERROR_SUCCESS; } +int ISrsReloadHandler::on_reload_vhost_hds(string /*vhost*/) +{ + return ERROR_SUCCESS; +} + int ISrsReloadHandler::on_reload_vhost_dvr(string /*vhost*/) { return ERROR_SUCCESS; } +int ISrsReloadHandler::on_reload_vhost_mr(string /*vhost*/) +{ + return ERROR_SUCCESS; +} + +int ISrsReloadHandler::on_reload_vhost_mw(string /*vhost*/) +{ + return ERROR_SUCCESS; +} + +int ISrsReloadHandler::on_reload_vhost_realtime(string /*vhost*/) +{ + return ERROR_SUCCESS; +} + +int ISrsReloadHandler::on_reload_vhost_chunk_size(string /*vhost*/) +{ + return ERROR_SUCCESS; +} + int ISrsReloadHandler::on_reload_vhost_transcode(string /*vhost*/) { return ERROR_SUCCESS; @@ -160,4 +195,8 @@ int ISrsReloadHandler::on_reload_ingest_updated(string /*vhost*/, string /*inges return ERROR_SUCCESS; } +int ISrsReloadHandler::on_reload_user_info() +{ + return ERROR_SUCCESS; +} diff --git a/trunk/src/app/srs_app_reload.hpp b/trunk/src/app/srs_app_reload.hpp index 85d5be016e..2e9ad3d81b 100644 --- a/trunk/src/app/srs_app_reload.hpp +++ b/trunk/src/app/srs_app_reload.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -36,7 +36,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * when reload callback, the config is updated yet. * * features not support reload, -* @see: https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_Reload#notsupportedfeatures +* @see: https://github.com/simple-rtmp-server/srs/wiki/v1_CN_Reload#notsupportedfeatures */ class ISrsReloadHandler { @@ -56,19 +56,27 @@ class ISrsReloadHandler virtual int on_reload_http_stream_disabled(); virtual int on_reload_http_stream_updated(); virtual int on_reload_vhost_http_updated(); + virtual int on_reload_vhost_http_remux_updated(); virtual int on_reload_vhost_added(std::string vhost); virtual int on_reload_vhost_removed(std::string vhost); virtual int on_reload_vhost_atc(std::string vhost); virtual int on_reload_vhost_gop_cache(std::string vhost); virtual int on_reload_vhost_queue_length(std::string vhost); virtual int on_reload_vhost_time_jitter(std::string vhost); + virtual int on_reload_vhost_mix_correct(std::string vhost); virtual int on_reload_vhost_forward(std::string vhost); virtual int on_reload_vhost_hls(std::string vhost); + virtual int on_reload_vhost_hds(std::string vhost); virtual int on_reload_vhost_dvr(std::string vhost); + virtual int on_reload_vhost_mr(std::string vhost); + virtual int on_reload_vhost_mw(std::string vhost); + virtual int on_reload_vhost_realtime(std::string vhost); + virtual int on_reload_vhost_chunk_size(std::string vhost); virtual int on_reload_vhost_transcode(std::string vhost); virtual int on_reload_ingest_removed(std::string vhost, std::string ingest_id); virtual int on_reload_ingest_added(std::string vhost, std::string ingest_id); virtual int on_reload_ingest_updated(std::string vhost, std::string ingest_id); + virtual int on_reload_user_info(); }; #endif diff --git a/trunk/src/app/srs_app_rtmp_conn.cpp b/trunk/src/app/srs_app_rtmp_conn.cpp index 9ac1d33403..6b0583f495 100644 --- a/trunk/src/app/srs_app_rtmp_conn.cpp +++ b/trunk/src/app/srs_app_rtmp_conn.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -27,12 +27,13 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include #include +#include using namespace std; #include #include -#include +#include #include #include #include @@ -46,8 +47,13 @@ using namespace std; #include #include #include -#include -#include +#include +#include +#include +#include +#include +#include +#include // when stream is busy, for example, streaming is already // publishing, when a new client to request to publish, @@ -69,22 +75,25 @@ using namespace std; // when edge timeout, retry next. #define SRS_EDGE_TOKEN_TRAVERSE_TIMEOUT_US (int64_t)(3*1000*1000LL) -// to get msgs then totally send out. -#define SYS_MAX_PLAY_SEND_MSGS 128 - -SrsRtmpConn::SrsRtmpConn(SrsServer* srs_server, st_netfd_t client_stfd) - : SrsConnection(srs_server, client_stfd) +SrsRtmpConn::SrsRtmpConn(SrsServer* svr, st_netfd_t c) + : SrsConnection(svr, c) { + server = svr; req = new SrsRequest(); res = new SrsResponse(); - skt = new SrsStSocket(client_stfd); + skt = new SrsStSocket(c); rtmp = new SrsRtmpServer(skt); refer = new SrsRefer(); bandwidth = new SrsBandwidth(); + security = new SrsSecurity(); duration = 0; kbps = new SrsKbps(); kbps->set_io(skt, skt); + mw_sleep = SRS_PERF_MW_SLEEP; + mw_enabled = false; + realtime = SRS_PERF_MIN_LATENCY_ENABLED; + _srs_config->subscribe(this); } @@ -98,14 +107,10 @@ SrsRtmpConn::~SrsRtmpConn() srs_freep(skt); srs_freep(refer); srs_freep(bandwidth); + srs_freep(security); srs_freep(kbps); } -void SrsRtmpConn::kbps_resample() -{ - kbps->sample(); -} - // TODO: return detail message when error for client. int SrsRtmpConn::do_cycle() { @@ -128,6 +133,9 @@ int SrsRtmpConn::do_cycle() } srs_verbose("rtmp connect app success"); + // set client ip to request. + req->ip = ip; + // discovery vhost, resolve the vhost from config SrsConfDirective* parsed_vhost = _srs_config->get_vhost(req->vhost); if (parsed_vhost) { @@ -189,6 +197,8 @@ int SrsRtmpConn::do_cycle() ret = service_cycle(); http_hooks_on_close(); + SrsStatistic* stat = SrsStatistic::instance(); + stat->on_disconnect(_srs_context->get_id()); return ret; } @@ -210,6 +220,42 @@ int SrsRtmpConn::on_reload_vhost_removed(string vhost) return ret; } +int SrsRtmpConn::on_reload_vhost_mw(string vhost) +{ + int ret = ERROR_SUCCESS; + + if (req->vhost != vhost) { + return ret; + } + + int sleep_ms = _srs_config->get_mw_sleep_ms(req->vhost); + + // when mw_sleep changed, resize the socket send buffer. + change_mw_sleep(sleep_ms); + + return ret; +} + +int SrsRtmpConn::on_reload_vhost_realtime(string vhost) +{ + int ret = ERROR_SUCCESS; + + if (req->vhost != vhost) { + return ret; + } + + bool realtime_enabled = _srs_config->get_realtime_enabled(req->vhost); + srs_trace("realtime changed %d=>%d", realtime, realtime_enabled); + realtime = realtime_enabled; + + return ret; +} + +void SrsRtmpConn::resample() +{ + kbps->resample(); +} + int64_t SrsRtmpConn::get_send_bytes_delta() { return kbps->get_send_bytes_delta(); @@ -219,6 +265,11 @@ int64_t SrsRtmpConn::get_recv_bytes_delta() { return kbps->get_recv_bytes_delta(); } + +void SrsRtmpConn::cleanup() +{ + kbps->cleanup(); +} int SrsRtmpConn::service_cycle() { @@ -245,13 +296,15 @@ int SrsRtmpConn::service_cycle() } // do token traverse before serve it. - // @see https://github.com/winlinvip/simple-rtmp-server/pull/239 - bool vhost_is_edge = _srs_config->get_vhost_is_edge(req->vhost); - bool edge_traverse = _srs_config->get_vhost_edge_token_traverse(req->vhost); - if (vhost_is_edge && edge_traverse) { - if ((ret = check_edge_token_traverse_auth()) != ERROR_SUCCESS) { - srs_warn("token auth failed, ret=%d", ret); - return ret; + // @see https://github.com/simple-rtmp-server/srs/pull/239 + if (true) { + bool vhost_is_edge = _srs_config->get_vhost_is_edge(req->vhost); + bool edge_traverse = _srs_config->get_vhost_edge_token_traverse(req->vhost); + if (vhost_is_edge && edge_traverse) { + if ((ret = check_edge_token_traverse_auth()) != ERROR_SUCCESS) { + srs_warn("token auth failed, ret=%d", ret); + return ret; + } } } @@ -296,7 +349,7 @@ int SrsRtmpConn::service_cycle() // logical accept and retry stream service. if (ret == ERROR_CONTROL_RTMP_CLOSE) { // TODO: FIXME: use ping message to anti-death of socket. - // @see: https://github.com/winlinvip/simple-rtmp-server/issues/39 + // @see: https://github.com/simple-rtmp-server/srs/issues/39 // set timeout to a larger value, for user paused. rtmp->set_recv_timeout(SRS_PAUSED_RECV_TIMEOUT_US); rtmp->set_send_timeout(SRS_PAUSED_SEND_TIMEOUT_US); @@ -325,6 +378,13 @@ int SrsRtmpConn::stream_service_cycle() req->strip(); srs_trace("client identified, type=%s, stream_name=%s, duration=%.2f", srs_client_type_string(type).c_str(), req->stream.c_str(), req->duration); + + // security check + if ((ret = security->check(type, ip, req)) != ERROR_SUCCESS) { + srs_error("security check failed. ret=%d", ret); + return ret; + } + srs_info("security check ok"); // client is identified, set the timeout to service timeout. rtmp->set_recv_timeout(SRS_CONSTS_RTMP_RECV_TIMEOUT_US); @@ -341,12 +401,21 @@ int SrsRtmpConn::stream_service_cycle() bool vhost_is_edge = _srs_config->get_vhost_is_edge(req->vhost); // find a source to serve. - SrsSource* source = NULL; - if ((ret = SrsSource::find(req, &source)) != ERROR_SUCCESS) { - return ret; + SrsSource* source = SrsSource::fetch(req); + if (!source) { + if ((ret = SrsSource::create(req, server, server, &source)) != ERROR_SUCCESS) { + return ret; + } } srs_assert(source != NULL); + // update the statistic when source disconveried. + SrsStatistic* stat = SrsStatistic::instance(); + if ((ret = stat->on_client(_srs_context->get_id(), req)) != ERROR_SUCCESS) { + srs_error("stat client failed. ret=%d", ret); + return ret; + } + // check ASAP, to fail it faster if invalid. if (type != SrsRtmpConnPlay && !vhost_is_edge) { // check publish available @@ -362,7 +431,7 @@ int SrsRtmpConn::stream_service_cycle() } bool enabled_cache = _srs_config->get_gop_cache(req->vhost); - srs_trace("source url=%s, ip=%s, cache=%d, is_edge=%d, source_id=%d[%d]", + srs_trace("source url=%s, ip=%s, cache=%d, is_edge=%d, source_id=%d[%d]", req->get_stream_url().c_str(), ip.c_str(), enabled_cache, vhost_is_edge, source->source_id(), source->source_id()); source->set_cache(enabled_cache); @@ -504,107 +573,185 @@ int SrsRtmpConn::playing(SrsSource* source) { int ret = ERROR_SUCCESS; - if ((ret = refer->check(req->pageUrl, _srs_config->get_refer_play(req->vhost))) != ERROR_SUCCESS) { - srs_error("check play_refer failed. ret=%d", ret); - return ret; - } - srs_verbose("check play_refer success."); - + // create consumer of souce. SrsConsumer* consumer = NULL; if ((ret = source->create_consumer(consumer)) != ERROR_SUCCESS) { srs_error("create consumer failed. ret=%d", ret); return ret; } - - srs_assert(consumer != NULL); SrsAutoFree(SrsConsumer, consumer); srs_verbose("consumer created success."); - rtmp->set_recv_timeout(SRS_CONSTS_RTMP_PULSE_TIMEOUT_US); + // use isolate thread to recv, + // @see: https://github.com/simple-rtmp-server/srs/issues/217 + SrsQueueRecvThread trd(consumer, rtmp, SRS_PERF_MW_SLEEP); + + // start isolate recv thread. + if ((ret = trd.start()) != ERROR_SUCCESS) { + srs_error("start isolate recv thread failed. ret=%d", ret); + return ret; + } + + // delivery messages for clients playing stream. + ret = do_playing(source, consumer, &trd); - SrsPithyPrint pithy_print(SRS_CONSTS_STAGE_PLAY_USER); + // stop isolate recv thread + trd.stop(); - SrsSharedPtrMessageArray msgs(SYS_MAX_PLAY_SEND_MSGS); + // warn for the message is dropped. + if (!trd.empty()) { + srs_warn("drop the received %d messages", trd.size()); + } + + return ret; +} + +int SrsRtmpConn::do_playing(SrsSource* source, SrsConsumer* consumer, SrsQueueRecvThread* trd) +{ + int ret = ERROR_SUCCESS; + + srs_assert(consumer != NULL); + + if ((ret = refer->check(req->pageUrl, _srs_config->get_refer_play(req->vhost))) != ERROR_SUCCESS) { + srs_error("check play_refer failed. ret=%d", ret); + return ret; + } + srs_verbose("check play_refer success."); + + // initialize other components + SrsPithyPrint* pprint = SrsPithyPrint::create_rtmp_play(); + SrsAutoFree(SrsPithyPrint, pprint); + SrsMessageArray msgs(SRS_PERF_MW_MSGS); bool user_specified_duration_to_stop = (req->duration > 0); int64_t starttime = -1; + // setup the realtime. + realtime = _srs_config->get_realtime_enabled(req->vhost); + // setup the mw config. + // when mw_sleep changed, resize the socket send buffer. + mw_enabled = true; + change_mw_sleep(_srs_config->get_mw_sleep_ms(req->vhost)); + + // set the sock options. + play_set_sock_options(); + while (true) { // collect elapse for pithy print. - pithy_print.elapse(); + pprint->elapse(); - // read from client. - if (true) { - SrsMessage* msg = NULL; - ret = rtmp->recv_message(&msg); - srs_verbose("play loop recv message. ret=%d", ret); + // to use isolate thread to recv, can improve about 33% performance. + // @see: https://github.com/simple-rtmp-server/srs/issues/196 + // @see: https://github.com/simple-rtmp-server/srs/issues/217 + while (!trd->empty()) { + SrsCommonMessage* msg = trd->pump(); + srs_verbose("pump client message to process."); - if (ret == ERROR_SOCKET_TIMEOUT) { - // it's ok, do nothing. - ret = ERROR_SUCCESS; - } else if (ret != ERROR_SUCCESS) { - if (!srs_is_client_gracefully_close(ret)) { - srs_error("recv client control message failed. ret=%d", ret); + if ((ret = process_play_control_msg(consumer, msg)) != ERROR_SUCCESS) { + if (!srs_is_system_control_error(ret) && !srs_is_client_gracefully_close(ret)) { + srs_error("process play control message failed. ret=%d", ret); } return ret; - } else { - if ((ret = process_play_control_msg(consumer, msg)) != ERROR_SUCCESS) { - if (!srs_is_system_control_error(ret)) { - srs_error("process play control message failed. ret=%d", ret); - } - return ret; - } } } + // quit when recv thread error. + if ((ret = trd->error_code()) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("recv thread failed. ret=%d", ret); + } + return ret; + } + +#ifdef SRS_PERF_QUEUE_COND_WAIT + // for send wait time debug + srs_verbose("send thread now=%"PRId64"us, wait %dms", srs_update_system_time_ms(), mw_sleep); + + // wait for message to incoming. + // @see https://github.com/simple-rtmp-server/srs/issues/251 + // @see https://github.com/simple-rtmp-server/srs/issues/257 + if (realtime) { + // for realtime, min required msgs is 0, send when got one+ msgs. + consumer->wait(0, mw_sleep); + } else { + // for no-realtime, got some msgs then send. + consumer->wait(SRS_PERF_MW_MIN_MSGS, mw_sleep); + } + + // for send wait time debug + srs_verbose("send thread now=%"PRId64"us wakeup", srs_update_system_time_ms()); +#endif + // get messages from consumer. + // each msg in msgs.msgs must be free, for the SrsMessageArray never free them. int count = 0; - if ((ret = consumer->dump_packets(msgs.size, msgs.msgs, count)) != ERROR_SUCCESS) { + if ((ret = consumer->dump_packets(&msgs, count)) != ERROR_SUCCESS) { srs_error("get messages from consumer failed. ret=%d", ret); return ret; } // reportable - if (pithy_print.can_print()) { + if (pprint->can_print()) { kbps->sample(); srs_trace("-> "SRS_CONSTS_LOG_PLAY - " time=%"PRId64", msgs=%d, okbps=%d,%d,%d, ikbps=%d,%d,%d", - pithy_print.age(), count, + " time=%"PRId64", msgs=%d, okbps=%d,%d,%d, ikbps=%d,%d,%d, mw=%d", + pprint->age(), count, kbps->get_send_kbps(), kbps->get_send_kbps_30s(), kbps->get_send_kbps_5m(), - kbps->get_recv_kbps(), kbps->get_recv_kbps_30s(), kbps->get_recv_kbps_5m()); + kbps->get_recv_kbps(), kbps->get_recv_kbps_30s(), kbps->get_recv_kbps_5m(), + mw_sleep + ); } - // sendout messages - // @remark, becareful, all msgs must be free explicitly, - // free by send_and_free_message or srs_freep. - for (int i = 0; i < count; i++) { - SrsSharedPtrMessage* msg = msgs.msgs[i]; - - // the send_message will free the msg, - // so set the msgs[i] to NULL. - msgs.msgs[i] = NULL; - - // only when user specifies the duration, - // we start to collect the durations for each message. - if (user_specified_duration_to_stop) { + // we use wait timeout to get messages, + // for min latency event no message incoming, + // so the count maybe zero. + if (count > 0) { + srs_verbose("mw wait %dms and got %d msgs %d(%"PRId64"-%"PRId64")ms", + mw_sleep, count, + (count > 0? msgs.msgs[count - 1]->timestamp - msgs.msgs[0]->timestamp : 0), + (count > 0? msgs.msgs[0]->timestamp : 0), + (count > 0? msgs.msgs[count - 1]->timestamp : 0)); + } + + if (count <= 0) { +#ifndef SRS_PERF_QUEUE_COND_WAIT + srs_info("mw sleep %dms for no msg", mw_sleep); + st_usleep(mw_sleep * 1000); +#else + srs_verbose("mw wait %dms and got nothing.", mw_sleep); +#endif + // ignore when nothing got. + continue; + } + srs_info("got %d msgs, min=%d, mw=%d", count, SRS_PERF_MW_MIN_MSGS, mw_sleep); + + // only when user specifies the duration, + // we start to collect the durations for each message. + if (user_specified_duration_to_stop) { + for (int i = 0; i < count; i++) { + SrsSharedPtrMessage* msg = msgs.msgs[i]; + // foreach msg, collect the duration. // @remark: never use msg when sent it, for the protocol sdk will free it. - if (starttime < 0 || starttime > msg->header.timestamp) { - starttime = msg->header.timestamp; + if (starttime < 0 || starttime > msg->timestamp) { + starttime = msg->timestamp; } - duration += msg->header.timestamp - starttime; - starttime = msg->header.timestamp; + duration += msg->timestamp - starttime; + starttime = msg->timestamp; } - - // no need to assert msg, for the rtmp will assert it. - if ((ret = rtmp->send_and_free_message(msg, res->stream_id)) != ERROR_SUCCESS) { - srs_error("send message to client failed. ret=%d", ret); - return ret; + } + + // sendout messages, all messages are freed by send_and_free_messages(). + // no need to assert msg, for the rtmp will assert it. + if (count > 0 && (ret = rtmp->send_and_free_messages(msgs.msgs, count, res->stream_id)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("send messages to client failed. ret=%d", ret); } + return ret; } // if duration specified, and exceed it, stop play live. - // @see: https://github.com/winlinvip/simple-rtmp-server/issues/45 + // @see: https://github.com/simple-rtmp-server/srs/issues/45 if (user_specified_duration_to_stop) { if (duration >= (int64_t)req->duration) { ret = ERROR_RTMP_DURATION_EXCEED; @@ -612,9 +759,6 @@ int SrsRtmpConn::playing(SrsSource* source) return ret; } } - - // switch to other threads, to anti dead loop. - st_usleep(0); } return ret; @@ -631,8 +775,16 @@ int SrsRtmpConn::fmle_publishing(SrsSource* source) return ret; } + // use isolate thread to recv, + // @see: https://github.com/simple-rtmp-server/srs/issues/237 + SrsPublishRecvThread trd(rtmp, req, + st_netfd_fileno(stfd), 0, this, source, true, vhost_is_edge); + srs_info("start to publish stream %s success", req->stream.c_str()); - ret = do_fmle_publishing(source); + ret = do_publishing(source, &trd); + + // stop isolate recv thread + trd.stop(); // when edge, notice edge to change state. // when origin, notice all service to unpublish. @@ -647,98 +799,27 @@ int SrsRtmpConn::fmle_publishing(SrsSource* source) return ret; } -int SrsRtmpConn::do_fmle_publishing(SrsSource* source) -{ - int ret = ERROR_SUCCESS; - - if ((ret = refer->check(req->pageUrl, _srs_config->get_refer_publish(req->vhost))) != ERROR_SUCCESS) { - srs_error("fmle check publish_refer failed. ret=%d", ret); - return ret; - } - srs_verbose("fmle check publish_refer success."); - - SrsPithyPrint pithy_print(SRS_CONSTS_STAGE_PUBLISH_USER); - - bool vhost_is_edge = _srs_config->get_vhost_is_edge(req->vhost); - - // when edge, ignore the publish event, directly proxy it. - if (!vhost_is_edge) { - // notify the hls to prepare when publish start. - if ((ret = source->on_publish()) != ERROR_SUCCESS) { - srs_error("fmle hls on_publish failed. ret=%d", ret); - return ret; - } - srs_verbose("fmle hls on_publish success."); - } - - while (true) { - // switch to other st-threads. - st_usleep(0); - - SrsMessage* msg = NULL; - if ((ret = rtmp->recv_message(&msg)) != ERROR_SUCCESS) { - srs_error("fmle recv identify client message failed. ret=%d", ret); - return ret; - } - - SrsAutoFree(SrsMessage, msg); - - pithy_print.elapse(); - - // reportable - if (pithy_print.can_print()) { - kbps->sample(); - srs_trace("<- "SRS_CONSTS_LOG_CLIENT_PUBLISH - " time=%"PRId64", okbps=%d,%d,%d, ikbps=%d,%d,%d", pithy_print.age(), - kbps->get_send_kbps(), kbps->get_send_kbps_30s(), kbps->get_send_kbps_5m(), - kbps->get_recv_kbps(), kbps->get_recv_kbps_30s(), kbps->get_recv_kbps_5m()); - } - - // process UnPublish event. - if (msg->header.is_amf0_command() || msg->header.is_amf3_command()) { - SrsPacket* pkt = NULL; - if ((ret = rtmp->decode_message(msg, &pkt)) != ERROR_SUCCESS) { - srs_error("fmle decode unpublish message failed. ret=%d", ret); - return ret; - } - - SrsAutoFree(SrsPacket, pkt); - - if (dynamic_cast(pkt)) { - SrsFMLEStartPacket* unpublish = dynamic_cast(pkt); - if ((ret = rtmp->fmle_unpublish(res->stream_id, unpublish->transaction_id)) != ERROR_SUCCESS) { - return ret; - } - return ERROR_CONTROL_REPUBLISH; - } - - srs_trace("fmle ignore AMF0/AMF3 command message."); - continue; - } - - // video, audio, data message - if ((ret = process_publish_message(source, msg, vhost_is_edge)) != ERROR_SUCCESS) { - srs_error("fmle process publish message failed. ret=%d", ret); - return ret; - } - } - - return ret; -} - int SrsRtmpConn::flash_publishing(SrsSource* source) { int ret = ERROR_SUCCESS; - + bool vhost_is_edge = _srs_config->get_vhost_is_edge(req->vhost); - + if ((ret = http_hooks_on_publish()) != ERROR_SUCCESS) { srs_error("http hook on_publish failed. ret=%d", ret); return ret; } - - srs_info("flash start to publish stream %s success", req->stream.c_str()); - ret = do_flash_publishing(source); + + // use isolate thread to recv, + // @see: https://github.com/simple-rtmp-server/srs/issues/237 + SrsPublishRecvThread trd(rtmp, req, + st_netfd_fileno(stfd), 0, this, source, true, vhost_is_edge); + + srs_info("start to publish stream %s success", req->stream.c_str()); + ret = do_publishing(source, &trd); + + // stop isolate recv thread + trd.stop(); // when edge, notice edge to change state. // when origin, notice all service to unpublish. @@ -747,89 +828,129 @@ int SrsRtmpConn::flash_publishing(SrsSource* source) } else { source->on_unpublish(); } - + http_hooks_on_unpublish(); - + return ret; } -int SrsRtmpConn::do_flash_publishing(SrsSource* source) +int SrsRtmpConn::do_publishing(SrsSource* source, SrsPublishRecvThread* trd) { int ret = ERROR_SUCCESS; - + if ((ret = refer->check(req->pageUrl, _srs_config->get_refer_publish(req->vhost))) != ERROR_SUCCESS) { - srs_error("flash check publish_refer failed. ret=%d", ret); + srs_error("check publish_refer failed. ret=%d", ret); return ret; } - srs_verbose("flash check publish_refer success."); - - SrsPithyPrint pithy_print(SRS_CONSTS_STAGE_PUBLISH_USER); + srs_verbose("check publish_refer success."); + SrsPithyPrint* pprint = SrsPithyPrint::create_rtmp_publish(); + SrsAutoFree(SrsPithyPrint, pprint); + bool vhost_is_edge = _srs_config->get_vhost_is_edge(req->vhost); - + // when edge, ignore the publish event, directly proxy it. if (!vhost_is_edge) { // notify the hls to prepare when publish start. if ((ret = source->on_publish()) != ERROR_SUCCESS) { - srs_error("flash hls on_publish failed. ret=%d", ret); + srs_error("hls on_publish failed. ret=%d", ret); return ret; } - srs_verbose("flash hls on_publish success."); + srs_verbose("hls on_publish success."); } - + + // start isolate recv thread. + if ((ret = trd->start()) != ERROR_SUCCESS) { + srs_error("start isolate recv thread failed. ret=%d", ret); + return ret; + } + + int64_t nb_msgs = 0; while (true) { - // switch to other st-threads. - st_usleep(0); - - SrsMessage* msg = NULL; - if ((ret = rtmp->recv_message(&msg)) != ERROR_SUCCESS) { + pprint->elapse(); + + // cond wait for error. + trd->wait(SRS_CONSTS_RTMP_RECV_TIMEOUT_US / 1000); + + // check the thread error code. + if ((ret = trd->error_code()) != ERROR_SUCCESS) { if (!srs_is_client_gracefully_close(ret)) { - srs_error("flash recv identify client message failed. ret=%d", ret); + srs_error("recv thread failed. ret=%d", ret); } return ret; } - SrsAutoFree(SrsMessage, msg); - - pithy_print.elapse(); + // when not got any messages, timeout. + if (trd->nb_msgs() <= nb_msgs) { + ret = ERROR_SOCKET_TIMEOUT; + srs_warn("publish timeout %"PRId64"us, nb_msgs=%"PRId64", ret=%d", + SRS_CONSTS_RTMP_RECV_TIMEOUT_US, nb_msgs, ret); + break; + } + nb_msgs = trd->nb_msgs(); // reportable - if (pithy_print.can_print()) { + if (pprint->can_print()) { kbps->sample(); - srs_trace("<- "SRS_CONSTS_LOG_WEB_PUBLISH - " time=%"PRId64", okbps=%d,%d,%d, ikbps=%d,%d,%d", - pithy_print.age(), + bool mr = _srs_config->get_mr_enabled(req->vhost); + int mr_sleep = _srs_config->get_mr_sleep_ms(req->vhost); + srs_trace("<- "SRS_CONSTS_LOG_CLIENT_PUBLISH + " time=%"PRId64", okbps=%d,%d,%d, ikbps=%d,%d,%d, mr=%d/%d", pprint->age(), kbps->get_send_kbps(), kbps->get_send_kbps_30s(), kbps->get_send_kbps_5m(), - kbps->get_recv_kbps(), kbps->get_recv_kbps_30s(), kbps->get_recv_kbps_5m()); + kbps->get_recv_kbps(), kbps->get_recv_kbps_30s(), kbps->get_recv_kbps_5m(), + mr, mr_sleep + ); } + } + + return ret; +} + +int SrsRtmpConn::handle_publish_message(SrsSource* source, SrsCommonMessage* msg, bool is_fmle, bool vhost_is_edge) +{ + int ret = ERROR_SUCCESS; - // process UnPublish event. - if (msg->header.is_amf0_command() || msg->header.is_amf3_command()) { - SrsPacket* pkt = NULL; - if ((ret = rtmp->decode_message(msg, &pkt)) != ERROR_SUCCESS) { - srs_error("flash decode unpublish message failed. ret=%d", ret); - return ret; - } - - SrsAutoFree(SrsPacket, pkt); - + // process publish event. + if (msg->header.is_amf0_command() || msg->header.is_amf3_command()) { + SrsPacket* pkt = NULL; + if ((ret = rtmp->decode_message(msg, &pkt)) != ERROR_SUCCESS) { + srs_error("fmle decode unpublish message failed. ret=%d", ret); + return ret; + } + + SrsAutoFree(SrsPacket, pkt); + + // for flash, any packet is republish. + if (!is_fmle) { // flash unpublish. // TODO: maybe need to support republish. srs_trace("flash flash publish finished."); return ERROR_CONTROL_REPUBLISH; } - // video, audio, data message - if ((ret = process_publish_message(source, msg, vhost_is_edge)) != ERROR_SUCCESS) { - srs_error("flash process publish message failed. ret=%d", ret); - return ret; + // for fmle, drop others except the fmle start packet. + if (dynamic_cast(pkt)) { + SrsFMLEStartPacket* unpublish = dynamic_cast(pkt); + if ((ret = rtmp->fmle_unpublish(res->stream_id, unpublish->transaction_id)) != ERROR_SUCCESS) { + return ret; + } + return ERROR_CONTROL_REPUBLISH; } + + srs_trace("fmle ignore AMF0/AMF3 command message."); + return ret; + } + + // video, audio, data message + if ((ret = process_publish_message(source, msg, vhost_is_edge)) != ERROR_SUCCESS) { + srs_error("fmle process publish message failed. ret=%d", ret); + return ret; } return ret; } -int SrsRtmpConn::process_publish_message(SrsSource* source, SrsMessage* msg, bool vhost_is_edge) +int SrsRtmpConn::process_publish_message(SrsSource* source, SrsCommonMessage* msg, bool vhost_is_edge) { int ret = ERROR_SUCCESS; @@ -894,7 +1015,7 @@ int SrsRtmpConn::process_publish_message(SrsSource* source, SrsMessage* msg, boo return ret; } -int SrsRtmpConn::process_play_control_msg(SrsConsumer* consumer, SrsMessage* msg) +int SrsRtmpConn::process_play_control_msg(SrsConsumer* consumer, SrsCommonMessage* msg) { int ret = ERROR_SUCCESS; @@ -902,7 +1023,7 @@ int SrsRtmpConn::process_play_control_msg(SrsConsumer* consumer, SrsMessage* msg srs_verbose("ignore all empty message."); return ret; } - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); if (!msg->header.is_amf0_command() && !msg->header.is_amf3_command()) { srs_info("ignore all message except amf0/amf3 command."); @@ -919,7 +1040,7 @@ int SrsRtmpConn::process_play_control_msg(SrsConsumer* consumer, SrsMessage* msg SrsAutoFree(SrsPacket, pkt); // for jwplayer/flowplayer, which send close as pause message. - // @see https://github.com/winlinvip/simple-rtmp-server/issues/6 + // @see https://github.com/simple-rtmp-server/srs/issues/6 SrsCloseStreamPacket* close = dynamic_cast(pkt); if (close) { ret = ERROR_CONTROL_RTMP_CLOSE; @@ -929,16 +1050,20 @@ int SrsRtmpConn::process_play_control_msg(SrsConsumer* consumer, SrsMessage* msg // call msg, // support response null first, - // @see https://github.com/winlinvip/simple-rtmp-server/issues/106 + // @see https://github.com/simple-rtmp-server/srs/issues/106 // TODO: FIXME: response in right way, or forward in edge mode. SrsCallPacket* call = dynamic_cast(pkt); if (call) { - SrsCallResPacket* res = new SrsCallResPacket(call->transaction_id); - res->command_object = SrsAmf0Any::null(); - res->response = SrsAmf0Any::null(); - if ((ret = rtmp->send_and_free_packet(res, 0)) != ERROR_SUCCESS) { - srs_warn("response call failed. ret=%d", ret); - return ret; + // only response it when transaction id not zero, + // for the zero means donot need response. + if (call->transaction_id > 0) { + SrsCallResPacket* res = new SrsCallResPacket(call->transaction_id); + res->command_object = SrsAmf0Any::null(); + res->response = SrsAmf0Any::null(); + if ((ret = rtmp->send_and_free_packet(res, 0)) != ERROR_SUCCESS) { + srs_warn("response call failed. ret=%d", ret); + return ret; + } } return ret; } @@ -964,6 +1089,80 @@ int SrsRtmpConn::process_play_control_msg(SrsConsumer* consumer, SrsMessage* msg return ret; } +void SrsRtmpConn::change_mw_sleep(int sleep_ms) +{ + if (!mw_enabled) { + return; + } + + // get the sock buffer size. + int fd = st_netfd_fileno(stfd); + int onb_sbuf = 0; + socklen_t sock_buf_size = sizeof(int); + getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &onb_sbuf, &sock_buf_size); + +#ifdef SRS_PERF_MW_SO_SNDBUF + // the bytes: + // 4KB=4096, 8KB=8192, 16KB=16384, 32KB=32768, 64KB=65536, + // 128KB=131072, 256KB=262144, 512KB=524288 + // the buffer should set to sleep*kbps/8, + // for example, your system delivery stream in 1000kbps, + // sleep 800ms for small bytes, the buffer should set to: + // 800*1000/8=100000B(about 128KB). + // other examples: + // 2000*3000/8=750000B(about 732KB). + // 2000*5000/8=1250000B(about 1220KB). + int kbps = 5000; + int socket_buffer_size = sleep_ms * kbps / 8; + + // socket send buffer, system will double it. + int nb_sbuf = socket_buffer_size / 2; + + // override the send buffer by macro. + #ifdef SRS_PERF_SO_SNDBUF_SIZE + nb_sbuf = SRS_PERF_SO_SNDBUF_SIZE / 2; + #endif + + // set the socket send buffer when required larger buffer + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &nb_sbuf, sock_buf_size) < 0) { + srs_warn("set sock SO_SENDBUF=%d failed.", nb_sbuf); + } + getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &nb_sbuf, &sock_buf_size); + + srs_trace("mw changed sleep %d=>%d, max_msgs=%d, esbuf=%d, sbuf %d=>%d, realtime=%d", + mw_sleep, sleep_ms, SRS_PERF_MW_MSGS, socket_buffer_size, + onb_sbuf, nb_sbuf, realtime); +#else + srs_trace("mw changed sleep %d=>%d, max_msgs=%d, sbuf %d, realtime=%d", + mw_sleep, sleep_ms, SRS_PERF_MW_MSGS, onb_sbuf, realtime); +#endif + + mw_sleep = sleep_ms; +} + +void SrsRtmpConn::play_set_sock_options() +{ +#ifdef SRS_PERF_TCP_NODELAY + if (true) { + int fd = st_netfd_fileno(stfd); + + socklen_t nb_v = sizeof(int); + + int ov = 0; + getsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &ov, &nb_v); + + int v = 1; + // set the socket send buffer when required larger buffer + if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &v, nb_v) < 0) { + srs_warn("set sock TCP_NODELAY=%d failed.", v); + } + getsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &v, &nb_v); + + srs_trace("set TCP_NODELAY %d=>%d", ov, v); + } +#endif +} + int SrsRtmpConn::check_edge_token_traverse_auth() { int ret = ERROR_SUCCESS; @@ -1067,10 +1266,9 @@ int SrsRtmpConn::http_hooks_on_connect() return ret; } - int connection_id = _srs_context->get_id(); for (int i = 0; i < (int)on_connect->args.size(); i++) { std::string url = on_connect->args.at(i); - if ((ret = SrsHttpHooks::on_connect(url, connection_id, ip, req)) != ERROR_SUCCESS) { + if ((ret = SrsHttpHooks::on_connect(url, req)) != ERROR_SUCCESS) { srs_error("hook client on_connect failed. url=%s, ret=%d", url.c_str(), ret); return ret; } @@ -1093,10 +1291,9 @@ void SrsRtmpConn::http_hooks_on_close() return; } - int connection_id = _srs_context->get_id(); for (int i = 0; i < (int)on_close->args.size(); i++) { std::string url = on_close->args.at(i); - SrsHttpHooks::on_close(url, connection_id, ip, req); + SrsHttpHooks::on_close(url, req, kbps->get_send_bytes(), kbps->get_recv_bytes()); } } #endif @@ -1115,10 +1312,9 @@ int SrsRtmpConn::http_hooks_on_publish() return ret; } - int connection_id = _srs_context->get_id(); for (int i = 0; i < (int)on_publish->args.size(); i++) { std::string url = on_publish->args.at(i); - if ((ret = SrsHttpHooks::on_publish(url, connection_id, ip, req)) != ERROR_SUCCESS) { + if ((ret = SrsHttpHooks::on_publish(url, req)) != ERROR_SUCCESS) { srs_error("hook client on_publish failed. url=%s, ret=%d", url.c_str(), ret); return ret; } @@ -1141,10 +1337,9 @@ void SrsRtmpConn::http_hooks_on_unpublish() return; } - int connection_id = _srs_context->get_id(); for (int i = 0; i < (int)on_unpublish->args.size(); i++) { std::string url = on_unpublish->args.at(i); - SrsHttpHooks::on_unpublish(url, connection_id, ip, req); + SrsHttpHooks::on_unpublish(url, req); } } #endif @@ -1163,10 +1358,9 @@ int SrsRtmpConn::http_hooks_on_play() return ret; } - int connection_id = _srs_context->get_id(); for (int i = 0; i < (int)on_play->args.size(); i++) { std::string url = on_play->args.at(i); - if ((ret = SrsHttpHooks::on_play(url, connection_id, ip, req)) != ERROR_SUCCESS) { + if ((ret = SrsHttpHooks::on_play(url, req)) != ERROR_SUCCESS) { srs_error("hook client on_play failed. url=%s, ret=%d", url.c_str(), ret); return ret; } @@ -1189,10 +1383,9 @@ void SrsRtmpConn::http_hooks_on_stop() return; } - int connection_id = _srs_context->get_id(); for (int i = 0; i < (int)on_stop->args.size(); i++) { std::string url = on_stop->args.at(i); - SrsHttpHooks::on_stop(url, connection_id, ip, req); + SrsHttpHooks::on_stop(url, req); } } #endif diff --git a/trunk/src/app/srs_app_rtmp_conn.hpp b/trunk/src/app/srs_app_rtmp_conn.hpp index 716d325882..39aad6e0e4 100644 --- a/trunk/src/app/srs_app_rtmp_conn.hpp +++ b/trunk/src/app/srs_app_rtmp_conn.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -34,13 +34,14 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include +class SrsServer; class SrsRtmpServer; class SrsRequest; class SrsResponse; class SrsSource; class SrsRefer; class SrsConsumer; -class SrsMessage; +class SrsCommonMessage; class SrsStSocket; #ifdef SRS_AUTO_HTTP_CALLBACK class SrsHttpHooks; @@ -49,38 +50,54 @@ class SrsBandwidth; class SrsKbps; class SrsRtmpClient; class SrsSharedPtrMessage; +class SrsQueueRecvThread; +class SrsPublishRecvThread; +class SrsSecurity; /** * the client provides the main logic control for RTMP clients. */ class SrsRtmpConn : public virtual SrsConnection, public virtual ISrsReloadHandler { + // for the thread to directly access any field of connection. + friend class SrsPublishRecvThread; private: + SrsServer* server; SrsRequest* req; SrsResponse* res; SrsStSocket* skt; SrsRtmpServer* rtmp; SrsRefer* refer; SrsBandwidth* bandwidth; + SrsSecurity* security; // elapse duration in ms // for live play duration, for instance, rtmpdump to record. - // @see https://github.com/winlinvip/simple-rtmp-server/issues/47 + // @see https://github.com/simple-rtmp-server/srs/issues/47 int64_t duration; SrsKbps* kbps; + // the MR(merged-write) sleep time in ms. + int mw_sleep; + // the MR(merged-write) only enabled for play. + int mw_enabled; + // for realtime + // @see https://github.com/simple-rtmp-server/srs/issues/257 + bool realtime; public: - SrsRtmpConn(SrsServer* srs_server, st_netfd_t client_stfd); + SrsRtmpConn(SrsServer* svr, st_netfd_t c); virtual ~SrsRtmpConn(); -public: - virtual void kbps_resample(); protected: virtual int do_cycle(); // interface ISrsReloadHandler public: virtual int on_reload_vhost_removed(std::string vhost); + virtual int on_reload_vhost_mw(std::string vhost); + virtual int on_reload_vhost_realtime(std::string vhost); // interface IKbpsDelta public: + virtual void resample(); virtual int64_t get_send_bytes_delta(); virtual int64_t get_recv_bytes_delta(); + virtual void cleanup(); private: // when valid and connected to vhost/app, service the client. virtual int service_cycle(); @@ -88,12 +105,15 @@ class SrsRtmpConn : public virtual SrsConnection, public virtual ISrsReloadHandl virtual int stream_service_cycle(); virtual int check_vhost(); virtual int playing(SrsSource* source); + virtual int do_playing(SrsSource* source, SrsConsumer* consumer, SrsQueueRecvThread* trd); virtual int fmle_publishing(SrsSource* source); - virtual int do_fmle_publishing(SrsSource* source); virtual int flash_publishing(SrsSource* source); - virtual int do_flash_publishing(SrsSource* source); - virtual int process_publish_message(SrsSource* source, SrsMessage* msg, bool vhost_is_edge); - virtual int process_play_control_msg(SrsConsumer* consumer, SrsMessage* msg); + virtual int do_publishing(SrsSource* source, SrsPublishRecvThread* trd); + virtual int handle_publish_message(SrsSource* source, SrsCommonMessage* msg, bool is_fmle, bool vhost_is_edge); + virtual int process_publish_message(SrsSource* source, SrsCommonMessage* msg, bool vhost_is_edge); + virtual int process_play_control_msg(SrsConsumer* consumer, SrsCommonMessage* msg); + virtual void change_mw_sleep(int sleep_ms); + virtual void play_set_sock_options(); private: virtual int check_edge_token_traverse_auth(); virtual int connect_server(int origin_index, st_netfd_t* pstsock); diff --git a/trunk/src/app/srs_app_rtsp.cpp b/trunk/src/app/srs_app_rtsp.cpp new file mode 100644 index 0000000000..5fe5fb5d6c --- /dev/null +++ b/trunk/src/app/srs_app_rtsp.cpp @@ -0,0 +1,849 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include + +#include +using namespace std; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef SRS_AUTO_STREAM_CASTER + +SrsRtpConn::SrsRtpConn(SrsRtspConn* r, int p, int sid) +{ + rtsp = r; + _port = p; + stream_id = sid; + // TODO: support listen at <[ip:]port> + listener = new SrsUdpListener(this, "0.0.0.0", p); + cache = new SrsRtpPacket(); + pprint = SrsPithyPrint::create_caster(); +} + +SrsRtpConn::~SrsRtpConn() +{ + srs_freep(listener); + srs_freep(cache); + srs_freep(pprint); +} + +int SrsRtpConn::port() +{ + return _port; +} + +int SrsRtpConn::listen() +{ + return listener->listen(); +} + +int SrsRtpConn::on_udp_packet(sockaddr_in* from, char* buf, int nb_buf) +{ + int ret = ERROR_SUCCESS; + + pprint->elapse(); + + if (true) { + SrsStream stream; + + if ((ret = stream.initialize(buf, nb_buf)) != ERROR_SUCCESS) { + return ret; + } + + SrsRtpPacket pkt; + if ((ret = pkt.decode(&stream)) != ERROR_SUCCESS) { + srs_error("rtsp: decode rtp packet failed. ret=%d", ret); + return ret; + } + + if (pkt.chunked) { + if (!cache) { + cache = new SrsRtpPacket(); + } + cache->copy(&pkt); + cache->payload->append(pkt.payload->bytes(), pkt.payload->length()); + if (!cache->completed && pprint->can_print()) { + srs_trace("<- "SRS_CONSTS_LOG_STREAM_CASTER" rtsp: rtp chunked %dB, age=%d, vt=%d/%u, sts=%u/%#x/%#x, paylod=%dB", + nb_buf, pprint->age(), cache->version, cache->payload_type, cache->sequence_number, cache->timestamp, cache->ssrc, + cache->payload->length() + ); + return ret; + } + } else { + srs_freep(cache); + cache = new SrsRtpPacket(); + cache->reap(&pkt); + } + } + + if (pprint->can_print()) { + srs_trace("<- "SRS_CONSTS_LOG_STREAM_CASTER" rtsp: rtp #%d %dB, age=%d, vt=%d/%u, sts=%u/%u/%#x, paylod=%dB, chunked=%d", + stream_id, nb_buf, pprint->age(), cache->version, cache->payload_type, cache->sequence_number, cache->timestamp, cache->ssrc, + cache->payload->length(), cache->chunked + ); + } + + // always free it. + SrsAutoFree(SrsRtpPacket, cache); + + if ((ret = rtsp->on_rtp_packet(cache, stream_id)) != ERROR_SUCCESS) { + srs_error("rtsp: process rtp packet failed. ret=%d", ret); + return ret; + } + + return ret; +} + +SrsRtspAudioCache::SrsRtspAudioCache() +{ + dts = 0; + audio_samples = NULL; + payload = NULL; +} + +SrsRtspAudioCache::~SrsRtspAudioCache() +{ + srs_freep(audio_samples); + srs_freep(payload); +} + +SrsRtspJitter::SrsRtspJitter() +{ + delta = 0; + previous_timestamp = 0; + pts = 0; +} + +SrsRtspJitter::~SrsRtspJitter() +{ +} + +int64_t SrsRtspJitter::timestamp() +{ + return pts; +} + +int SrsRtspJitter::correct(int64_t& ts) +{ + int ret = ERROR_SUCCESS; + + if (previous_timestamp == 0) { + previous_timestamp = ts; + } + + delta = srs_max(0, ts - previous_timestamp); + if (delta > 90000) { + delta = 0; + } + + previous_timestamp = ts; + + ts = pts + delta; + pts = ts; + + return ret; +} + +SrsRtspConn::SrsRtspConn(SrsRtspCaster* c, st_netfd_t fd, std::string o) +{ + output_template = o; + + session = ""; + video_rtp = NULL; + audio_rtp = NULL; + + caster = c; + stfd = fd; + skt = new SrsStSocket(fd); + rtsp = new SrsRtspStack(skt); + trd = new SrsThread("rtsp", this, 0, false); + + req = NULL; + io = NULL; + client = NULL; + stream_id = 0; + vjitter = new SrsRtspJitter(); + ajitter = new SrsRtspJitter(); + + avc = new SrsRawH264Stream(); + aac = new SrsRawAacStream(); + acodec = new SrsRawAacStreamCodec(); + acache = new SrsRtspAudioCache(); +} + +SrsRtspConn::~SrsRtspConn() +{ + srs_close_stfd(stfd); + trd->stop(); + + srs_freep(video_rtp); + srs_freep(audio_rtp); + + srs_freep(trd); + srs_freep(skt); + srs_freep(rtsp); + + close(); + + srs_freep(vjitter); + srs_freep(ajitter); + srs_freep(acodec); + srs_freep(acache); +} + +int SrsRtspConn::serve() +{ + return trd->start(); +} + +int SrsRtspConn::do_cycle() +{ + int ret = ERROR_SUCCESS; + + // retrieve ip of client. + std::string ip = srs_get_peer_ip(st_netfd_fileno(stfd)); + srs_trace("rtsp: serve %s", ip.c_str()); + + // consume all rtsp messages. + for (;;) { + SrsRtspRequest* req = NULL; + if ((ret = rtsp->recv_message(&req)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("rtsp: recv request failed. ret=%d", ret); + } + return ret; + } + SrsAutoFree(SrsRtspRequest, req); + srs_info("rtsp: got rtsp request"); + + if (req->is_options()) { + SrsRtspOptionsResponse* res = new SrsRtspOptionsResponse(req->seq); + res->session = session; + if ((ret = rtsp->send_message(res)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("rtsp: send OPTIONS response failed. ret=%d", ret); + } + return ret; + } + } else if (req->is_announce()) { + if (rtsp_tcUrl.empty()) { + rtsp_tcUrl = req->uri; + } + size_t pos = string::npos; + if ((pos = rtsp_tcUrl.rfind(".sdp")) != string::npos) { + rtsp_tcUrl = rtsp_tcUrl.substr(0, pos); + } + if ((pos = rtsp_tcUrl.rfind("/")) != string::npos) { + rtsp_stream = rtsp_tcUrl.substr(pos + 1); + rtsp_tcUrl = rtsp_tcUrl.substr(0, pos); + } + + srs_assert(req->sdp); + video_id = ::atoi(req->sdp->video_stream_id.c_str()); + audio_id = ::atoi(req->sdp->audio_stream_id.c_str()); + video_codec = req->sdp->video_codec; + audio_codec = req->sdp->audio_codec; + audio_sample_rate = ::atoi(req->sdp->audio_sample_rate.c_str()); + audio_channel = ::atoi(req->sdp->audio_channel.c_str()); + h264_sps = req->sdp->video_sps; + h264_pps = req->sdp->video_pps; + aac_specific_config = req->sdp->audio_sh; + srs_trace("rtsp: video(#%d, %s, %s/%s), audio(#%d, %s, %s/%s, %dHZ %dchannels), %s/%s", + video_id, video_codec.c_str(), req->sdp->video_protocol.c_str(), req->sdp->video_transport_format.c_str(), + audio_id, audio_codec.c_str(), req->sdp->audio_protocol.c_str(), req->sdp->audio_transport_format.c_str(), + audio_sample_rate, audio_channel, rtsp_tcUrl.c_str(), rtsp_stream.c_str() + ); + + SrsRtspResponse* res = new SrsRtspResponse(req->seq); + res->session = session; + if ((ret = rtsp->send_message(res)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("rtsp: send ANNOUNCE response failed. ret=%d", ret); + } + return ret; + } + } else if (req->is_setup()) { + srs_assert(req->transport); + int lpm = 0; + if ((ret = caster->alloc_port(&lpm)) != ERROR_SUCCESS) { + srs_error("rtsp: alloc port failed. ret=%d", ret); + return ret; + } + + SrsRtpConn* rtp = NULL; + if (req->stream_id == video_id) { + srs_freep(video_rtp); + rtp = video_rtp = new SrsRtpConn(this, lpm, video_id); + } else { + srs_freep(audio_rtp); + rtp = audio_rtp = new SrsRtpConn(this, lpm, audio_id); + } + if ((ret = rtp->listen()) != ERROR_SUCCESS) { + srs_error("rtsp: rtp listen at port=%d failed. ret=%d", lpm, ret); + return ret; + } + srs_trace("rtsp: #%d %s over %s/%s/%s %s client-port=%d-%d, server-port=%d-%d", + req->stream_id, (req->stream_id == video_id)? "Video":"Audio", + req->transport->transport.c_str(), req->transport->profile.c_str(), req->transport->lower_transport.c_str(), + req->transport->cast_type.c_str(), req->transport->client_port_min, req->transport->client_port_max, + lpm, lpm + 1 + ); + + // create session. + if (session.empty()) { + session = "O9EaZ4bf"; // TODO: FIXME: generate session id. + } + + SrsRtspSetupResponse* res = new SrsRtspSetupResponse(req->seq); + res->client_port_min = req->transport->client_port_min; + res->client_port_max = req->transport->client_port_max; + res->local_port_min = lpm; + res->local_port_max = lpm + 1; + res->session = session; + if ((ret = rtsp->send_message(res)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("rtsp: send SETUP response failed. ret=%d", ret); + } + return ret; + } + } else if (req->is_record()) { + SrsRtspResponse* res = new SrsRtspResponse(req->seq); + res->session = session; + if ((ret = rtsp->send_message(res)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("rtsp: send SETUP response failed. ret=%d", ret); + } + return ret; + } + } + } + + return ret; +} + +int SrsRtspConn::on_rtp_packet(SrsRtpPacket* pkt, int stream_id) +{ + int ret = ERROR_SUCCESS; + + // ensure rtmp connected. + if ((ret = connect()) != ERROR_SUCCESS) { + return ret; + } + + if (stream_id == video_id) { + // rtsp tbn is ts tbn. + int64_t pts = pkt->timestamp; + if ((ret = vjitter->correct(pts)) != ERROR_SUCCESS) { + srs_error("rtsp: correct by jitter failed. ret=%d", ret); + return ret; + } + + // TODO: FIXME: set dts to pts, please finger out the right dts. + int64_t dts = pts; + + return on_rtp_video(pkt, dts, pts); + } else { + // rtsp tbn is ts tbn. + int64_t pts = pkt->timestamp; + if ((ret = ajitter->correct(pts)) != ERROR_SUCCESS) { + srs_error("rtsp: correct by jitter failed. ret=%d", ret); + return ret; + } + + return on_rtp_audio(pkt, pts); + } + + return ret; +} + +int SrsRtspConn::cycle() +{ + // serve the rtsp client. + int ret = do_cycle(); + + // if socket io error, set to closed. + if (srs_is_client_gracefully_close(ret)) { + ret = ERROR_SOCKET_CLOSED; + } + + // success. + if (ret == ERROR_SUCCESS) { + srs_trace("client finished."); + } + + // client close peer. + if (ret == ERROR_SOCKET_CLOSED) { + srs_warn("client disconnect peer. ret=%d", ret); + } + + // terminate thread in the thread cycle itself. + trd->stop_loop(); + + return ERROR_SUCCESS; +} + +void SrsRtspConn::on_thread_stop() +{ + if (video_rtp) { + caster->free_port(video_rtp->port(), video_rtp->port() + 1); + } + + if (audio_rtp) { + caster->free_port(audio_rtp->port(), audio_rtp->port() + 1); + } + + caster->remove(this); +} + +int SrsRtspConn::on_rtp_video(SrsRtpPacket* pkt, int64_t dts, int64_t pts) +{ + int ret = ERROR_SUCCESS; + + if ((ret = kickoff_audio_cache(pkt, dts)) != ERROR_SUCCESS) { + return ret; + } + + if ((ret = write_h264_ipb_frame(pkt->payload->bytes(), pkt->payload->length(), dts / 90, pts / 90)) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +int SrsRtspConn::on_rtp_audio(SrsRtpPacket* pkt, int64_t dts) +{ + int ret = ERROR_SUCCESS; + + if ((ret = kickoff_audio_cache(pkt, dts)) != ERROR_SUCCESS) { + return ret; + } + + // cache current audio to kickoff. + acache->dts = dts; + acache->audio_samples = pkt->audio_samples; + acache->payload = pkt->payload; + + pkt->audio_samples = NULL; + pkt->payload = NULL; + + return ret; +} + +int SrsRtspConn::kickoff_audio_cache(SrsRtpPacket* pkt, int64_t dts) +{ + int ret = ERROR_SUCCESS; + + // nothing to kick off. + if (!acache->payload) { + return ret; + } + + if (dts - acache->dts > 0 && acache->audio_samples->nb_sample_units > 0) { + int64_t delta = (dts - acache->dts) / acache->audio_samples->nb_sample_units; + for (int i = 0; i < acache->audio_samples->nb_sample_units; i++) { + char* frame = acache->audio_samples->sample_units[i].bytes; + int nb_frame = acache->audio_samples->sample_units[i].size; + int64_t timestamp = (acache->dts + delta * i) / 90; + acodec->aac_packet_type = 1; + if ((ret = write_audio_raw_frame(frame, nb_frame, acodec, timestamp)) != ERROR_SUCCESS) { + return ret; + } + } + } + + acache->dts = 0; + srs_freep(acache->audio_samples); + srs_freep(acache->payload); + + return ret; +} + +int SrsRtspConn::write_sequence_header() +{ + int ret = ERROR_SUCCESS; + + // use the current dts. + int64_t dts = vjitter->timestamp() / 90; + + // send video sps/pps + if ((ret = write_h264_sps_pps(dts, dts)) != ERROR_SUCCESS) { + return ret; + } + + // generate audio sh by audio specific config. + if (true) { + std::string sh = aac_specific_config; + + SrsAvcAacCodec dec; + if ((ret = dec.audio_aac_sequence_header_demux((char*)sh.c_str(), (int)sh.length())) != ERROR_SUCCESS) { + return ret; + } + + acodec->sound_format = SrsCodecAudioAAC; + acodec->sound_type = (dec.aac_channels == 2)? SrsCodecAudioSoundTypeStereo : SrsCodecAudioSoundTypeMono; + acodec->sound_size = SrsCodecAudioSampleSize16bit; + acodec->aac_packet_type = 0; + + static int aac_sample_rates[] = { + 96000, 88200, 64000, 48000, + 44100, 32000, 24000, 22050, + 16000, 12000, 11025, 8000, + 7350, 0, 0, 0 + }; + switch (aac_sample_rates[dec.aac_sample_rate]) { + case 11025: + acodec->sound_rate = SrsCodecAudioSampleRate11025; + break; + case 22050: + acodec->sound_rate = SrsCodecAudioSampleRate22050; + break; + case 44100: + acodec->sound_rate = SrsCodecAudioSampleRate44100; + break; + default: + break; + }; + + if ((ret = write_audio_raw_frame((char*)sh.data(), (int)sh.length(), acodec, dts)) != ERROR_SUCCESS) { + return ret; + } + } + + return ret; +} + +int SrsRtspConn::write_h264_sps_pps(u_int32_t dts, u_int32_t pts) +{ + int ret = ERROR_SUCCESS; + + // h264 raw to h264 packet. + std::string sh; + if ((ret = avc->mux_sequence_header(h264_sps, h264_pps, dts, pts, sh)) != ERROR_SUCCESS) { + return ret; + } + + // h264 packet to flv packet. + int8_t frame_type = SrsCodecVideoAVCFrameKeyFrame; + int8_t avc_packet_type = SrsCodecVideoAVCTypeSequenceHeader; + char* flv = NULL; + int nb_flv = 0; + if ((ret = avc->mux_avc2flv(sh, frame_type, avc_packet_type, dts, pts, &flv, &nb_flv)) != ERROR_SUCCESS) { + return ret; + } + + // the timestamp in rtmp message header is dts. + u_int32_t timestamp = dts; + if ((ret = rtmp_write_packet(SrsCodecFlvTagVideo, timestamp, flv, nb_flv)) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +int SrsRtspConn::write_h264_ipb_frame(char* frame, int frame_size, u_int32_t dts, u_int32_t pts) +{ + int ret = ERROR_SUCCESS; + + // 5bits, 7.3.1 NAL unit syntax, + // H.264-AVC-ISO_IEC_14496-10.pdf, page 44. + // 7: SPS, 8: PPS, 5: I Frame, 1: P Frame + SrsAvcNaluType nal_unit_type = (SrsAvcNaluType)(frame[0] & 0x1f); + + // for IDR frame, the frame is keyframe. + SrsCodecVideoAVCFrame frame_type = SrsCodecVideoAVCFrameInterFrame; + if (nal_unit_type == SrsAvcNaluTypeIDR) { + frame_type = SrsCodecVideoAVCFrameKeyFrame; + } + + std::string ibp; + if ((ret = avc->mux_ipb_frame(frame, frame_size, ibp)) != ERROR_SUCCESS) { + return ret; + } + + int8_t avc_packet_type = SrsCodecVideoAVCTypeNALU; + char* flv = NULL; + int nb_flv = 0; + if ((ret = avc->mux_avc2flv(ibp, frame_type, avc_packet_type, dts, pts, &flv, &nb_flv)) != ERROR_SUCCESS) { + return ret; + } + + // the timestamp in rtmp message header is dts. + u_int32_t timestamp = dts; + return rtmp_write_packet(SrsCodecFlvTagVideo, timestamp, flv, nb_flv); +} + +int SrsRtspConn::write_audio_raw_frame(char* frame, int frame_size, SrsRawAacStreamCodec* codec, u_int32_t dts) +{ + int ret = ERROR_SUCCESS; + + char* data = NULL; + int size = 0; + if ((ret = aac->mux_aac2flv(frame, frame_size, codec, dts, &data, &size)) != ERROR_SUCCESS) { + return ret; + } + + return rtmp_write_packet(SrsCodecFlvTagAudio, dts, data, size); +} + +int SrsRtspConn::rtmp_write_packet(char type, u_int32_t timestamp, char* data, int size) +{ + int ret = ERROR_SUCCESS; + + SrsSharedPtrMessage* msg = NULL; + + if ((ret = srs_rtmp_create_msg(type, timestamp, data, size, stream_id, &msg)) != ERROR_SUCCESS) { + srs_error("rtsp: create shared ptr msg failed. ret=%d", ret); + return ret; + } + srs_assert(msg); + + // send out encoded msg. + if ((ret = client->send_and_free_message(msg, stream_id)) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +// TODO: FIXME: merge all client code. +int SrsRtspConn::connect() +{ + int ret = ERROR_SUCCESS; + + // when ok, ignore. + if (io || client) { + return ret; + } + + // parse uri + if (!req) { + req = new SrsRequest(); + + std::string schema, host, vhost, app, port, param; + srs_discovery_tc_url(rtsp_tcUrl, schema, host, vhost, app, port, param); + + // generate output by template. + std::string output = output_template; + output = srs_string_replace(output, "[app]", app); + output = srs_string_replace(output, "[stream]", rtsp_stream); + + size_t pos = string::npos; + string uri = req->tcUrl = output; + + // tcUrl, stream + if ((pos = uri.rfind("/")) != string::npos) { + req->stream = uri.substr(pos + 1); + req->tcUrl = uri = uri.substr(0, pos); + } + + srs_discovery_tc_url(req->tcUrl, + req->schema, req->host, req->vhost, req->app, req->port, + req->param); + } + + // connect host. + if ((ret = srs_socket_connect(req->host, ::atoi(req->port.c_str()), ST_UTIME_NO_TIMEOUT, &stfd)) != ERROR_SUCCESS) { + srs_error("rtsp: connect server %s:%s failed. ret=%d", req->host.c_str(), req->port.c_str(), ret); + return ret; + } + io = new SrsStSocket(stfd); + client = new SrsRtmpClient(io); + + client->set_recv_timeout(SRS_CONSTS_RTMP_RECV_TIMEOUT_US); + client->set_send_timeout(SRS_CONSTS_RTMP_SEND_TIMEOUT_US); + + // connect to vhost/app + if ((ret = client->handshake()) != ERROR_SUCCESS) { + srs_error("rtsp: handshake with server failed. ret=%d", ret); + return ret; + } + if ((ret = connect_app(req->host, req->port)) != ERROR_SUCCESS) { + srs_error("rtsp: connect with server failed. ret=%d", ret); + return ret; + } + if ((ret = client->create_stream(stream_id)) != ERROR_SUCCESS) { + srs_error("rtsp: connect with server failed, stream_id=%d. ret=%d", stream_id, ret); + return ret; + } + + // publish. + if ((ret = client->publish(req->stream, stream_id)) != ERROR_SUCCESS) { + srs_error("rtsp: publish failed, stream=%s, stream_id=%d. ret=%d", + req->stream.c_str(), stream_id, ret); + return ret; + } + + return write_sequence_header(); +} + +// TODO: FIXME: refine the connect_app. +int SrsRtspConn::connect_app(string ep_server, string ep_port) +{ + int ret = ERROR_SUCCESS; + + // args of request takes the srs info. + if (req->args == NULL) { + req->args = SrsAmf0Any::object(); + } + + // notify server the edge identity, + // @see https://github.com/simple-rtmp-server/srs/issues/147 + SrsAmf0Object* data = req->args; + data->set("srs_sig", SrsAmf0Any::str(RTMP_SIG_SRS_KEY)); + data->set("srs_server", SrsAmf0Any::str(RTMP_SIG_SRS_KEY" "RTMP_SIG_SRS_VERSION" ("RTMP_SIG_SRS_URL_SHORT")")); + data->set("srs_license", SrsAmf0Any::str(RTMP_SIG_SRS_LICENSE)); + data->set("srs_role", SrsAmf0Any::str(RTMP_SIG_SRS_ROLE)); + data->set("srs_url", SrsAmf0Any::str(RTMP_SIG_SRS_URL)); + data->set("srs_version", SrsAmf0Any::str(RTMP_SIG_SRS_VERSION)); + data->set("srs_site", SrsAmf0Any::str(RTMP_SIG_SRS_WEB)); + data->set("srs_email", SrsAmf0Any::str(RTMP_SIG_SRS_EMAIL)); + data->set("srs_copyright", SrsAmf0Any::str(RTMP_SIG_SRS_COPYRIGHT)); + data->set("srs_primary", SrsAmf0Any::str(RTMP_SIG_SRS_PRIMARY)); + data->set("srs_authors", SrsAmf0Any::str(RTMP_SIG_SRS_AUTHROS)); + // for edge to directly get the id of client. + data->set("srs_pid", SrsAmf0Any::number(getpid())); + data->set("srs_id", SrsAmf0Any::number(_srs_context->get_id())); + + // local ip of edge + std::vector ips = srs_get_local_ipv4_ips(); + assert(_srs_config->get_stats_network() < (int)ips.size()); + std::string local_ip = ips[_srs_config->get_stats_network()]; + data->set("srs_server_ip", SrsAmf0Any::str(local_ip.c_str())); + + // generate the tcUrl + std::string param = ""; + std::string tc_url = srs_generate_tc_url(ep_server, req->vhost, req->app, ep_port, param); + + // upnode server identity will show in the connect_app of client. + // @see https://github.com/simple-rtmp-server/srs/issues/160 + // the debug_srs_upnode is config in vhost and default to true. + bool debug_srs_upnode = _srs_config->get_debug_srs_upnode(req->vhost); + if ((ret = client->connect_app(req->app, tc_url, req, debug_srs_upnode)) != ERROR_SUCCESS) { + srs_error("rtsp: connect with server failed, tcUrl=%s, dsu=%d. ret=%d", + tc_url.c_str(), debug_srs_upnode, ret); + return ret; + } + + return ret; +} + +void SrsRtspConn::close() +{ + srs_freep(client); + srs_freep(io); + srs_freep(req); + srs_close_stfd(stfd); +} + +SrsRtspCaster::SrsRtspCaster(SrsConfDirective* c) +{ + // TODO: FIXME: support reload. + output = _srs_config->get_stream_caster_output(c); + local_port_min = _srs_config->get_stream_caster_rtp_port_min(c); + local_port_max = _srs_config->get_stream_caster_rtp_port_max(c); +} + +SrsRtspCaster::~SrsRtspCaster() +{ + std::vector::iterator it; + for (it = clients.begin(); it != clients.end(); ++it) { + SrsRtspConn* conn = *it; + srs_freep(conn); + } + clients.clear(); + used_ports.clear(); +} + +int SrsRtspCaster::alloc_port(int* pport) +{ + int ret = ERROR_SUCCESS; + + // use a pair of port. + for (int i = local_port_min; i < local_port_max - 1; i += 2) { + if (!used_ports[i]) { + used_ports[i] = true; + used_ports[i + 1] = true; + *pport = i; + break; + } + } + srs_info("rtsp: alloc port=%d-%d", *pport, *pport + 1); + + return ret; +} + +void SrsRtspCaster::free_port(int lpmin, int lpmax) +{ + for (int i = lpmin; i < lpmax; i++) { + used_ports[i] = false; + } + srs_trace("rtsp: free rtp port=%d-%d", lpmin, lpmax); +} + +int SrsRtspCaster::on_tcp_client(st_netfd_t stfd) +{ + int ret = ERROR_SUCCESS; + + SrsRtspConn* conn = new SrsRtspConn(this, stfd, output); + + if ((ret = conn->serve()) != ERROR_SUCCESS) { + srs_error("rtsp: serve client failed. ret=%d", ret); + srs_freep(conn); + return ret; + } + + clients.push_back(conn); + srs_info("rtsp: start thread to serve client."); + + return ret; +} + +void SrsRtspCaster::remove(SrsRtspConn* conn) +{ + std::vector::iterator it = find(clients.begin(), clients.end(), conn); + if (it != clients.end()) { + clients.erase(it); + } + srs_info("rtsp: remove connection from caster."); + + srs_freep(conn); +} + +#endif + diff --git a/trunk/src/app/srs_app_rtsp.hpp b/trunk/src/app/srs_app_rtsp.hpp new file mode 100644 index 0000000000..86397c0332 --- /dev/null +++ b/trunk/src/app/srs_app_rtsp.hpp @@ -0,0 +1,225 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef SRS_APP_RTSP_HPP +#define SRS_APP_RTSP_HPP + +/* +#include +*/ + +#include + +#include +#include +#include + +#include +#include +#include + +#ifdef SRS_AUTO_STREAM_CASTER + +class SrsStSocket; +class SrsRtspConn; +class SrsRtspStack; +class SrsRtspCaster; +class SrsConfDirective; +class SrsRtpPacket; +class SrsRequest; +class SrsStSocket; +class SrsRtmpClient; +class SrsRawH264Stream; +class SrsRawAacStream; +struct SrsRawAacStreamCodec; +class SrsSharedPtrMessage; +class SrsCodecSample; +class SrsSimpleBuffer; +class SrsPithyPrint; + +/** +* a rtp connection which transport a stream. +*/ +class SrsRtpConn: public ISrsUdpHandler +{ +private: + SrsPithyPrint* pprint; + SrsUdpListener* listener; + SrsRtspConn* rtsp; + SrsRtpPacket* cache; + int stream_id; + int _port; +public: + SrsRtpConn(SrsRtspConn* r, int p, int sid); + virtual ~SrsRtpConn(); +public: + virtual int port(); + virtual int listen(); +// interface ISrsUdpHandler +public: + virtual int on_udp_packet(sockaddr_in* from, char* buf, int nb_buf); +}; + +/** +* audio is group by frames. +*/ +struct SrsRtspAudioCache +{ + int64_t dts; + SrsCodecSample* audio_samples; + SrsSimpleBuffer* payload; + + SrsRtspAudioCache(); + virtual ~SrsRtspAudioCache(); +}; + +/** +* the time jitter correct for rtsp. +*/ +class SrsRtspJitter +{ +private: + int64_t previous_timestamp; + int64_t pts; + int delta; +public: + SrsRtspJitter(); + virtual ~SrsRtspJitter(); +public: + virtual int64_t timestamp(); + virtual int correct(int64_t& ts); +}; + +/** +* the rtsp connection serve the fd. +*/ +class SrsRtspConn : public ISrsThreadHandler +{ +private: + std::string output_template; + std::string rtsp_tcUrl; + std::string rtsp_stream; +private: + std::string session; + // video stream. + int video_id; + std::string video_codec; + SrsRtpConn* video_rtp; + // audio stream. + int audio_id; + std::string audio_codec; + int audio_sample_rate; + int audio_channel; + SrsRtpConn* audio_rtp; +private: + st_netfd_t stfd; + SrsStSocket* skt; + SrsRtspStack* rtsp; + SrsRtspCaster* caster; + SrsThread* trd; +private: + SrsRequest* req; + SrsStSocket* io; + SrsRtmpClient* client; + SrsRtspJitter* vjitter; + SrsRtspJitter* ajitter; + int stream_id; +private: + SrsRawH264Stream* avc; + std::string h264_sps; + std::string h264_pps; +private: + SrsRawAacStream* aac; + SrsRawAacStreamCodec* acodec; + std::string aac_specific_config; + SrsRtspAudioCache* acache; +public: + SrsRtspConn(SrsRtspCaster* c, st_netfd_t fd, std::string o); + virtual ~SrsRtspConn(); +public: + virtual int serve(); +private: + virtual int do_cycle(); +// internal methods +public: + virtual int on_rtp_packet(SrsRtpPacket* pkt, int stream_id); +// interface ISrsThreadHandler +public: + virtual int cycle(); + virtual void on_thread_stop(); +private: + virtual int on_rtp_video(SrsRtpPacket* pkt, int64_t dts, int64_t pts); + virtual int on_rtp_audio(SrsRtpPacket* pkt, int64_t dts); + virtual int kickoff_audio_cache(SrsRtpPacket* pkt, int64_t dts); +private: + virtual int write_sequence_header(); + virtual int write_h264_sps_pps(u_int32_t dts, u_int32_t pts); + virtual int write_h264_ipb_frame(char* frame, int frame_size, u_int32_t dts, u_int32_t pts); + virtual int write_audio_raw_frame(char* frame, int frame_size, SrsRawAacStreamCodec* codec, u_int32_t dts); + virtual int rtmp_write_packet(char type, u_int32_t timestamp, char* data, int size); +private: + // connect to rtmp output url. + // @remark ignore when not connected, reconnect when disconnected. + virtual int connect(); + virtual int connect_app(std::string ep_server, std::string ep_port); + // close the connected io and rtmp to ready to be re-connect. + virtual void close(); +}; + +/** +* the caster for rtsp. +*/ +class SrsRtspCaster : public ISrsTcpHandler +{ +private: + std::string output; + int local_port_min; + int local_port_max; + // key: port, value: whether used. + std::map used_ports; +private: + std::vector clients; +public: + SrsRtspCaster(SrsConfDirective* c); + virtual ~SrsRtspCaster(); +public: + /** + * alloc a rtp port from local ports pool. + * @param pport output the rtp port. + */ + virtual int alloc_port(int* pport); + /** + * free the alloced rtp port. + */ + virtual void free_port(int lpmin, int lpmax); +// interface ISrsTcpHandler +public: + virtual int on_tcp_client(st_netfd_t stfd); +// internal methods. +public: + virtual void remove(SrsRtspConn* conn); +}; + +#endif + +#endif diff --git a/trunk/src/app/srs_app_security.cpp b/trunk/src/app/srs_app_security.cpp new file mode 100644 index 0000000000..aadcfcda67 --- /dev/null +++ b/trunk/src/app/srs_app_security.cpp @@ -0,0 +1,159 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include + +#include +#include + +using namespace std; + +SrsSecurity::SrsSecurity() +{ +} + +SrsSecurity::~SrsSecurity() +{ +} + +int SrsSecurity::check(SrsRtmpConnType type, string ip, SrsRequest* req) +{ + int ret = ERROR_SUCCESS; + + // allow all if security disabled. + if (!_srs_config->get_security_enabled(req->vhost)) { + return ret; + } + + // default to deny all when security enabled. + ret = ERROR_SYSTEM_SECURITY; + + // rules to apply + SrsConfDirective* rules = _srs_config->get_security_rules(req->vhost); + if (!rules) { + return ret; + } + + // allow if matches allow strategy. + if (allow_check(rules, type, ip) == ERROR_SYSTEM_SECURITY_ALLOW) { + ret = ERROR_SUCCESS; + } + + // deny if matches deny strategy. + if (deny_check(rules, type, ip) == ERROR_SYSTEM_SECURITY_DENY) { + ret = ERROR_SYSTEM_SECURITY_DENY; + } + + return ret; +} + +int SrsSecurity::allow_check(SrsConfDirective* rules, SrsRtmpConnType type, std::string ip) +{ + int ret = ERROR_SUCCESS; + + for (int i = 0; i < (int)rules->directives.size(); i++) { + SrsConfDirective* rule = rules->at(i); + + if (rule->name != "allow") { + continue; + } + + switch (type) { + case SrsRtmpConnPlay: + if (rule->arg0() != "play") { + break; + } + if (rule->arg1() == "all" || rule->arg1() == ip) { + ret = ERROR_SYSTEM_SECURITY_ALLOW; + break; + } + break; + case SrsRtmpConnFMLEPublish: + case SrsRtmpConnFlashPublish: + if (rule->arg0() != "publish") { + break; + } + if (rule->arg1() == "all" || rule->arg1() == ip) { + ret = ERROR_SYSTEM_SECURITY_ALLOW; + break; + } + break; + case SrsRtmpConnUnknown: + default: + break; + } + + // when matched, donot search more. + if (ret == ERROR_SYSTEM_SECURITY_ALLOW) { + break; + } + } + + return ret; +} + +int SrsSecurity::deny_check(SrsConfDirective* rules, SrsRtmpConnType type, std::string ip) +{ + int ret = ERROR_SUCCESS; + + for (int i = 0; i < (int)rules->directives.size(); i++) { + SrsConfDirective* rule = rules->at(i); + + if (rule->name != "deny") { + continue; + } + + switch (type) { + case SrsRtmpConnPlay: + if (rule->arg0() != "play") { + break; + } + if (rule->arg1() == "all" || rule->arg1() == ip) { + ret = ERROR_SYSTEM_SECURITY_DENY; + break; + } + break; + case SrsRtmpConnFMLEPublish: + case SrsRtmpConnFlashPublish: + if (rule->arg0() != "publish") { + break; + } + if (rule->arg1() == "all" || rule->arg1() == ip) { + ret = ERROR_SYSTEM_SECURITY_DENY; + break; + } + break; + case SrsRtmpConnUnknown: + default: + break; + } + + // when matched, donot search more. + if (ret == ERROR_SYSTEM_SECURITY_DENY) { + break; + } + } + + return ret; +} + diff --git a/trunk/src/app/srs_app_security.hpp b/trunk/src/app/srs_app_security.hpp new file mode 100644 index 0000000000..43e22e218e --- /dev/null +++ b/trunk/src/app/srs_app_security.hpp @@ -0,0 +1,70 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef SRS_APP_SECURITY_HPP +#define SRS_APP_SECURITY_HPP + +/* +#include +*/ + +#include + +#include + +#include + +class SrsConfDirective; + +/** +* the security apply on vhost. +* @see https://github.com/simple-rtmp-server/srs/issues/211 +*/ +class SrsSecurity +{ +public: + SrsSecurity(); + virtual ~SrsSecurity(); +public: + /** + * security check the client apply by vhost security strategy + * @param type the client type, publish or play. + * @param ip the ip address of client. + * @param req the request object of client. + */ + virtual int check(SrsRtmpConnType type, std::string ip, SrsRequest* req); +private: + /** + * security check the allow, + * @return, if allowed, ERROR_SYSTEM_SECURITY_ALLOW. + */ + virtual int allow_check(SrsConfDirective* rules, SrsRtmpConnType type, std::string ip); + /** + * security check the deny, + * @return, if denied, ERROR_SYSTEM_SECURITY_DENY. + */ + virtual int deny_check(SrsConfDirective* rules, SrsRtmpConnType type, std::string ip); +}; + +#endif + diff --git a/trunk/src/app/srs_app_server.cpp b/trunk/src/app/srs_app_server.cpp index e0834aae4c..9b06c8a9dd 100644 --- a/trunk/src/app/srs_app_server.cpp +++ b/trunk/src/app/srs_app_server.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -24,14 +24,13 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include -#include -#include #include #include #include #include #include +using namespace std; #include #include @@ -44,74 +43,85 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include #include +#include +#include +#include +#include // signal defines. #define SIGNAL_RELOAD SIGHUP -#define SERVER_LISTEN_BACKLOG 512 - -// system interval +// system interval in ms, // all resolution times should be times togother, -// for example, system-time is 3(300ms), -// then rusage can be 3*x, for instance, 3*10=30(3s), -// the meminfo canbe 30*x, for instance, 30*2=60(6s) -#define SRS_SYS_CYCLE_INTERVAL 100 +// for example, system-interval is x=1s(1000ms), +// then rusage can be 3*x, for instance, 3*1=3s, +// the meminfo canbe 6*x, for instance, 6*1=6s, +// for performance refine, @see: https://github.com/simple-rtmp-server/srs/issues/194 +// @remark, recomment to 1000ms. +#define SRS_SYS_CYCLE_INTERVAL 1000 // update time interval: // SRS_SYS_CYCLE_INTERVAL * SRS_SYS_TIME_RESOLUTION_MS_TIMES // @see SYS_TIME_RESOLUTION_US -#define SRS_SYS_TIME_RESOLUTION_MS_TIMES 3 +#define SRS_SYS_TIME_RESOLUTION_MS_TIMES 1 // update rusage interval: // SRS_SYS_CYCLE_INTERVAL * SRS_SYS_RUSAGE_RESOLUTION_TIMES -#define SRS_SYS_RUSAGE_RESOLUTION_TIMES 30 +#define SRS_SYS_RUSAGE_RESOLUTION_TIMES 3 // update network devices info interval: // SRS_SYS_CYCLE_INTERVAL * SRS_SYS_NETWORK_RTMP_SERVER_RESOLUTION_TIMES -#define SRS_SYS_NETWORK_RTMP_SERVER_RESOLUTION_TIMES 30 +#define SRS_SYS_NETWORK_RTMP_SERVER_RESOLUTION_TIMES 3 // update rusage interval: // SRS_SYS_CYCLE_INTERVAL * SRS_SYS_CPU_STAT_RESOLUTION_TIMES -#define SRS_SYS_CPU_STAT_RESOLUTION_TIMES 30 +#define SRS_SYS_CPU_STAT_RESOLUTION_TIMES 3 // update the disk iops interval: // SRS_SYS_CYCLE_INTERVAL * SRS_SYS_DISK_STAT_RESOLUTION_TIMES -#define SRS_SYS_DISK_STAT_RESOLUTION_TIMES 60 +#define SRS_SYS_DISK_STAT_RESOLUTION_TIMES 6 // update rusage interval: // SRS_SYS_CYCLE_INTERVAL * SRS_SYS_MEMINFO_RESOLUTION_TIMES -#define SRS_SYS_MEMINFO_RESOLUTION_TIMES 60 +#define SRS_SYS_MEMINFO_RESOLUTION_TIMES 6 // update platform info interval: // SRS_SYS_CYCLE_INTERVAL * SRS_SYS_PLATFORM_INFO_RESOLUTION_TIMES -#define SRS_SYS_PLATFORM_INFO_RESOLUTION_TIMES 90 +#define SRS_SYS_PLATFORM_INFO_RESOLUTION_TIMES 9 // update network devices info interval: // SRS_SYS_CYCLE_INTERVAL * SRS_SYS_NETWORK_DEVICE_RESOLUTION_TIMES -#define SRS_SYS_NETWORK_DEVICE_RESOLUTION_TIMES 90 +#define SRS_SYS_NETWORK_DEVICE_RESOLUTION_TIMES 9 + +std::string srs_listener_type2string(SrsListenerType type) +{ + switch (type) { + case SrsListenerRtmpStream: + return "RTMP"; + case SrsListenerHttpApi: + return "HTTP-API"; + case SrsListenerHttpStream: + return "HTTP-Server"; + case SrsListenerMpegTsOverUdp: + return "MPEG-TS over UDP"; + case SrsListenerRtsp: + return "RTSP"; + case SrsListenerFlv: + return "HTTP-FLV"; + default: + return "UNKONWN"; + } +} SrsListener::SrsListener(SrsServer* server, SrsListenerType type) { - fd = -1; - stfd = NULL; - _port = 0; _server = server; _type = type; - - pthread = new SrsThread(this, 0, true); } SrsListener::~SrsListener() { - srs_close_stfd(stfd); - - pthread->stop(); - srs_freep(pthread); - - // st does not close it sometimes, - // close it manually. - close(fd); } SrsListenerType SrsListener::type() @@ -119,90 +129,220 @@ SrsListenerType SrsListener::type() return _type; } -int SrsListener::listen(int port) +SrsStreamListener::SrsStreamListener(SrsServer* server, SrsListenerType type) : SrsListener(server, type) +{ + listener = NULL; +} + +SrsStreamListener::~SrsStreamListener() +{ + srs_freep(listener); +} + +int SrsStreamListener::listen(string ip, int port) { int ret = ERROR_SUCCESS; + _ip = ip; _port = port; - - if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { - ret = ERROR_SOCKET_CREATE; - srs_error("create linux socket error. ret=%d", ret); + + srs_freep(listener); + listener = new SrsTcpListener(this, ip, port); + + if ((ret = listener->listen()) != ERROR_SUCCESS) { + srs_error("tcp listen failed. ret=%d", ret); return ret; } - srs_verbose("create linux socket success. fd=%d", fd); - int reuse_socket = 1; - if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse_socket, sizeof(int)) == -1) { - ret = ERROR_SOCKET_SETREUSE; - srs_error("setsockopt reuse-addr error. ret=%d", ret); + srs_info("listen thread cid=%d, current_cid=%d, " + "listen at port=%d, type=%d, fd=%d started success, ep=%s:%d", + pthread->cid(), _srs_context->get_id(), _port, _type, fd, ip.c_str(), port); + + srs_trace("%s listen at tcp://%s:%d, fd=%d", srs_listener_type2string(_type).c_str(), ip.c_str(), _port, listener->fd()); + + return ret; +} + +int SrsStreamListener::on_tcp_client(st_netfd_t stfd) +{ + int ret = ERROR_SUCCESS; + + if ((ret = _server->accept_client(_type, stfd)) != ERROR_SUCCESS) { + srs_warn("accept client error. ret=%d", ret); return ret; } - srs_verbose("setsockopt reuse-addr success. fd=%d", fd); + + return ret; +} + +#ifdef SRS_AUTO_STREAM_CASTER +SrsRtspListener::SrsRtspListener(SrsServer* server, SrsListenerType type, SrsConfDirective* c) : SrsListener(server, type) +{ + listener = NULL; + + // the caller already ensure the type is ok, + // we just assert here for unknown stream caster. + srs_assert(_type == SrsListenerRtsp); + if (_type == SrsListenerRtsp) { + caster = new SrsRtspCaster(c); + } +} + +SrsRtspListener::~SrsRtspListener() +{ + srs_freep(caster); + srs_freep(listener); +} + +int SrsRtspListener::listen(string ip, int port) +{ + int ret = ERROR_SUCCESS; + + // the caller already ensure the type is ok, + // we just assert here for unknown stream caster. + srs_assert(_type == SrsListenerRtsp); - sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_port = htons(_port); - addr.sin_addr.s_addr = INADDR_ANY; - if (bind(fd, (const sockaddr*)&addr, sizeof(sockaddr_in)) == -1) { - ret = ERROR_SOCKET_BIND; - srs_error("bind socket error. ret=%d", ret); + _ip = ip; + _port = port; + + srs_freep(listener); + listener = new SrsTcpListener(this, ip, port); + + if ((ret = listener->listen()) != ERROR_SUCCESS) { + srs_error("rtsp caster listen failed. ret=%d", ret); return ret; } - srs_verbose("bind socket success. fd=%d", fd); - if (::listen(fd, SERVER_LISTEN_BACKLOG) == -1) { - ret = ERROR_SOCKET_LISTEN; - srs_error("listen socket error. ret=%d", ret); + srs_info("listen thread cid=%d, current_cid=%d, " + "listen at port=%d, type=%d, fd=%d started success, ep=%s:%d", + pthread->cid(), _srs_context->get_id(), _port, _type, fd, ip.c_str(), port); + + srs_trace("%s listen at tcp://%s:%d, fd=%d", srs_listener_type2string(_type).c_str(), ip.c_str(), _port, listener->fd()); + + return ret; +} + +int SrsRtspListener::on_tcp_client(st_netfd_t stfd) +{ + int ret = ERROR_SUCCESS; + + if ((ret = caster->on_tcp_client(stfd)) != ERROR_SUCCESS) { + srs_warn("accept client error. ret=%d", ret); return ret; } - srs_verbose("listen socket success. fd=%d", fd); + + return ret; +} + +SrsHttpFlvListener::SrsHttpFlvListener(SrsServer* server, SrsListenerType type, SrsConfDirective* c) : SrsListener(server, type) +{ + listener = NULL; + + // the caller already ensure the type is ok, + // we just assert here for unknown stream caster. + srs_assert(_type == SrsListenerFlv); + if (_type == SrsListenerFlv) { + caster = new SrsAppCasterFlv(c); + } +} + +SrsHttpFlvListener::~SrsHttpFlvListener() +{ + srs_freep(caster); + srs_freep(listener); +} + +int SrsHttpFlvListener::listen(string ip, int port) +{ + int ret = ERROR_SUCCESS; - if ((stfd = st_netfd_open_socket(fd)) == NULL){ - ret = ERROR_ST_OPEN_SOCKET; - srs_error("st_netfd_open_socket open socket failed. ret=%d", ret); + // the caller already ensure the type is ok, + // we just assert here for unknown stream caster. + srs_assert(_type == SrsListenerFlv); + + _ip = ip; + _port = port; + + if ((ret = caster->initialize()) != ERROR_SUCCESS) { return ret; } - srs_verbose("st open socket success. fd=%d", fd); - if ((ret = pthread->start()) != ERROR_SUCCESS) { - srs_error("st_thread_create listen thread error. ret=%d", ret); + srs_freep(listener); + listener = new SrsTcpListener(this, ip, port); + + if ((ret = listener->listen()) != ERROR_SUCCESS) { + srs_error("flv caster listen failed. ret=%d", ret); return ret; } - srs_verbose("create st listen thread success."); - srs_trace("listen thread cid=%d, current_cid=%d, " - "listen at port=%d, type=%d, fd=%d started success", - pthread->cid(), _srs_context->get_id(), _port, _type, fd); + srs_info("listen thread cid=%d, current_cid=%d, " + "listen at port=%d, type=%d, fd=%d started success, ep=%s:%d", + pthread->cid(), _srs_context->get_id(), _port, _type, fd, ip.c_str(), port); + + srs_trace("%s listen at tcp://%s:%d, fd=%d", srs_listener_type2string(_type).c_str(), ip.c_str(), _port, listener->fd()); return ret; } -void SrsListener::on_thread_start() -{ - srs_trace("listen cycle start, port=%d, type=%d, fd=%d", _port, _type, fd); -} - -int SrsListener::cycle() +int SrsHttpFlvListener::on_tcp_client(st_netfd_t stfd) { int ret = ERROR_SUCCESS; - st_netfd_t client_stfd = st_accept(stfd, NULL, NULL, ST_UTIME_NO_TIMEOUT); - - if(client_stfd == NULL){ - // ignore error. - srs_error("ignore accept thread stoppped for accept client error"); + if ((ret = caster->on_tcp_client(stfd)) != ERROR_SUCCESS) { + srs_warn("accept client error. ret=%d", ret); return ret; } - srs_verbose("get a client. fd=%d", st_netfd_fileno(client_stfd)); - if ((ret = _server->accept_client(_type, client_stfd)) != ERROR_SUCCESS) { - srs_warn("accept client error. ret=%d", ret); + return ret; +} + +SrsUdpCasterListener::SrsUdpCasterListener(SrsServer* server, SrsListenerType type, SrsConfDirective* c) : SrsListener(server, type) +{ + _type = type; + listener = NULL; + + // the caller already ensure the type is ok, + // we just assert here for unknown stream caster. + srs_assert(_type == SrsListenerMpegTsOverUdp); + if (_type == SrsListenerMpegTsOverUdp) { + caster = new SrsMpegtsOverUdp(c); + } +} + +SrsUdpCasterListener::~SrsUdpCasterListener() +{ + srs_freep(caster); + srs_freep(listener); +} + +int SrsUdpCasterListener::listen(string ip, int port) +{ + int ret = ERROR_SUCCESS; + + // the caller already ensure the type is ok, + // we just assert here for unknown stream caster. + srs_assert(_type == SrsListenerMpegTsOverUdp); + + _ip = ip; + _port = port; + + srs_freep(listener); + listener = new SrsUdpListener(caster, ip, port); + + if ((ret = listener->listen()) != ERROR_SUCCESS) { + srs_error("udp caster listen failed. ret=%d", ret); return ret; } + srs_info("listen thread cid=%d, current_cid=%d, " + "listen at port=%d, type=%d, fd=%d started success, ep=%s:%d", + pthread->cid(), _srs_context->get_id(), _port, _type, fd, ip.c_str(), port); + + srs_trace("%s listen at udp://%s:%d, fd=%d", srs_listener_type2string(_type).c_str(), ip.c_str(), _port, listener->fd()); + return ret; } +#endif SrsSignalManager* SrsSignalManager::instance = NULL; @@ -212,7 +352,7 @@ SrsSignalManager::SrsSignalManager(SrsServer* server) _server = server; sig_pipe[0] = sig_pipe[1] = -1; - pthread = new SrsThread(this, 0, true); + pthread = new SrsThread("signal", this, 0, true); signal_read_stfd = NULL; } @@ -314,6 +454,14 @@ void SrsSignalManager::sig_catcher(int signo) errno = err; } +ISrsServerCycle::ISrsServerCycle() +{ +} + +ISrsServerCycle::~ISrsServerCycle() +{ +} + SrsServer::SrsServer() { signal_reload = false; @@ -321,16 +469,17 @@ SrsServer::SrsServer() pid_fd = -1; signal_manager = NULL; - kbps = NULL; + + handler = NULL; // donot new object in constructor, // for some global instance is not ready now, // new these objects in initialize instead. #ifdef SRS_AUTO_HTTP_API - http_api_handler = NULL; + http_api_mux = new SrsHttpServeMux(); #endif #ifdef SRS_AUTO_HTTP_SERVER - http_stream_handler = NULL; + http_stream_mux = new SrsHttpServer(this); #endif #ifdef SRS_AUTO_HTTP_PARSER http_heartbeat = NULL; @@ -360,11 +509,11 @@ void SrsServer::destroy() #endif #ifdef SRS_AUTO_HTTP_API - srs_freep(http_api_handler); + srs_freep(http_api_mux); #endif #ifdef SRS_AUTO_HTTP_SERVER - srs_freep(http_stream_handler); + srs_freep(http_stream_mux); #endif #ifdef SRS_AUTO_HTTP_PARSER @@ -381,7 +530,8 @@ void SrsServer::destroy() } srs_freep(signal_manager); - srs_freep(kbps); + + srs_freep(handler); // @remark never destroy the connections, // for it's still alive. @@ -391,7 +541,7 @@ void SrsServer::destroy() // and segment fault. } -int SrsServer::initialize() +int SrsServer::initialize(ISrsServerCycle* cycle_handler) { int ret = ERROR_SUCCESS; @@ -407,38 +557,33 @@ int SrsServer::initialize() srs_assert(!signal_manager); signal_manager = new SrsSignalManager(this); - srs_assert(!kbps); - kbps = new SrsKbps(); - kbps->set_io(NULL, NULL); + handler = cycle_handler; + if(handler && (ret = handler->initialize()) != ERROR_SUCCESS){ + return ret; + } #ifdef SRS_AUTO_HTTP_API - srs_assert(!http_api_handler); - http_api_handler = SrsHttpHandler::create_http_api(); + if ((ret = http_api_mux->initialize()) != ERROR_SUCCESS) { + return ret; + } #endif + #ifdef SRS_AUTO_HTTP_SERVER - srs_assert(!http_stream_handler); - http_stream_handler = SrsHttpHandler::create_http_stream(); + srs_assert(http_stream_mux); + if ((ret = http_stream_mux->initialize()) != ERROR_SUCCESS) { + return ret; + } #endif + #ifdef SRS_AUTO_HTTP_PARSER srs_assert(!http_heartbeat); http_heartbeat = new SrsHttpHeartbeat(); #endif + #ifdef SRS_AUTO_INGEST srs_assert(!ingester); ingester = new SrsIngester(); #endif - -#ifdef SRS_AUTO_HTTP_API - if ((ret = http_api_handler->initialize()) != ERROR_SUCCESS) { - return ret; - } -#endif - -#ifdef SRS_AUTO_HTTP_SERVER - if ((ret = http_stream_handler->initialize()) != ERROR_SUCCESS) { - return ret; - } -#endif return ret; } @@ -568,6 +713,10 @@ int SrsServer::listen() return ret; } + if ((ret = listen_stream_caster()) != ERROR_SUCCESS) { + return ret; + } + return ret; } @@ -577,6 +726,56 @@ int SrsServer::register_signal() return signal_manager->start(); } +int SrsServer::http_handle() +{ + int ret = ERROR_SUCCESS; + +#ifdef SRS_AUTO_HTTP_API + srs_assert(http_api_mux); + if ((ret = http_api_mux->handle("/", new SrsGoApiRoot())) != ERROR_SUCCESS) { + return ret; + } + if ((ret = http_api_mux->handle("/api", new SrsGoApiApi())) != ERROR_SUCCESS) { + return ret; + } + if ((ret = http_api_mux->handle("/api/v1", new SrsGoApiV1())) != ERROR_SUCCESS) { + return ret; + } + if ((ret = http_api_mux->handle("/api/v1/versions", new SrsGoApiVersion())) != ERROR_SUCCESS) { + return ret; + } + if ((ret = http_api_mux->handle("/api/v1/summaries", new SrsGoApiSummaries())) != ERROR_SUCCESS) { + return ret; + } + if ((ret = http_api_mux->handle("/api/v1/rusages", new SrsGoApiRusages())) != ERROR_SUCCESS) { + return ret; + } + if ((ret = http_api_mux->handle("/api/v1/self_proc_stats", new SrsGoApiSelfProcStats())) != ERROR_SUCCESS) { + return ret; + } + if ((ret = http_api_mux->handle("/api/v1/system_proc_stats", new SrsGoApiSystemProcStats())) != ERROR_SUCCESS) { + return ret; + } + if ((ret = http_api_mux->handle("/api/v1/meminfos", new SrsGoApiMemInfos())) != ERROR_SUCCESS) { + return ret; + } + if ((ret = http_api_mux->handle("/api/v1/authors", new SrsGoApiAuthors())) != ERROR_SUCCESS) { + return ret; + } + if ((ret = http_api_mux->handle("/api/v1/requests", new SrsGoApiRequests())) != ERROR_SUCCESS) { + return ret; + } + if ((ret = http_api_mux->handle("/api/v1/vhosts", new SrsGoApiVhosts())) != ERROR_SUCCESS) { + return ret; + } + if ((ret = http_api_mux->handle("/api/v1/streams", new SrsGoApiStreams())) != ERROR_SUCCESS) { + return ret; + } +#endif + + return ret; +} + int SrsServer::ingest() { int ret = ERROR_SUCCESS; @@ -626,8 +825,8 @@ void SrsServer::remove(SrsConnection* conn) srs_info("conn removed. conns=%d", (int)conns.size()); - // resample the resource of specified connection. - resample_kbps(conn); + SrsStatistic* stat = SrsStatistic::instance(); + stat->kbps_add_delta(conn); // all connections are created by server, // so we free it here. @@ -678,14 +877,19 @@ int SrsServer::do_cycle() // the deamon thread, update the time cache while (true) { + if(handler && (ret = handler->on_cycle(conns.size())) != ERROR_SUCCESS){ + srs_error("cycle handle failed. ret=%d", ret); + return ret; + } + // the interval in config. int heartbeat_max_resolution = (int)(_srs_config->get_heartbeat_interval() / SRS_SYS_CYCLE_INTERVAL); // dynamic fetch the max. - int __max = max; - __max = srs_max(__max, heartbeat_max_resolution); + int temp_max = max; + temp_max = srs_max(temp_max, heartbeat_max_resolution); - for (int i = 0; i < __max; i++) { + for (int i = 0; i < temp_max; i++) { st_usleep(SRS_SYS_CYCLE_INTERVAL * 1000); // for gperf heap checker, @@ -711,7 +915,7 @@ int SrsServer::do_cycle() srs_trace("reload config success."); } - // update the cache time or rusage. + // update the cache time if ((i % SRS_SYS_TIME_RESOLUTION_MS_TIMES) == 0) { srs_info("update current time cache."); srs_update_system_time_ms(); @@ -743,9 +947,8 @@ int SrsServer::do_cycle() srs_update_network_devices(); } if ((i % SRS_SYS_NETWORK_RTMP_SERVER_RESOLUTION_TIMES) == 0) { - srs_info("update network rtmp server info."); - resample_kbps(NULL); - srs_update_rtmp_server((int)conns.size(), kbps); + srs_info("update network server kbps info."); + resample_kbps(); } #ifdef SRS_AUTO_HTTP_PARSER if (_srs_config->get_heartbeat_enabled()) { @@ -768,18 +971,21 @@ int SrsServer::listen_rtmp() int ret = ERROR_SUCCESS; // stream service port. - std::vector ports = _srs_config->get_listen(); - srs_assert((int)ports.size() > 0); + std::vector ip_ports = _srs_config->get_listens(); + srs_assert((int)ip_ports.size() > 0); close_listeners(SrsListenerRtmpStream); - for (int i = 0; i < (int)ports.size(); i++) { - SrsListener* listener = new SrsListener(this, SrsListenerRtmpStream); + for (int i = 0; i < (int)ip_ports.size(); i++) { + SrsListener* listener = new SrsStreamListener(this, SrsListenerRtmpStream); listeners.push_back(listener); - int port = ::atoi(ports[i].c_str()); - if ((ret = listener->listen(port)) != ERROR_SUCCESS) { - srs_error("RTMP stream listen at port %d failed. ret=%d", port, ret); + std::string ip; + int port; + srs_parse_endpoint(ip_ports[i], ip, port); + + if ((ret = listener->listen(ip, port)) != ERROR_SUCCESS) { + srs_error("RTMP stream listen at %s:%d failed. ret=%d", ip.c_str(), port, ret); return ret; } } @@ -794,12 +1000,17 @@ int SrsServer::listen_http_api() #ifdef SRS_AUTO_HTTP_API close_listeners(SrsListenerHttpApi); if (_srs_config->get_http_api_enabled()) { - SrsListener* listener = new SrsListener(this, SrsListenerHttpApi); + SrsListener* listener = new SrsStreamListener(this, SrsListenerHttpApi); listeners.push_back(listener); - int port = _srs_config->get_http_api_listen(); - if ((ret = listener->listen(port)) != ERROR_SUCCESS) { - srs_error("HTTP api listen at port %d failed. ret=%d", port, ret); + std::string ep = _srs_config->get_http_api_listen(); + + std::string ip; + int port; + srs_parse_endpoint(ep, ip, port); + + if ((ret = listener->listen(ip, port)) != ERROR_SUCCESS) { + srs_error("HTTP api listen at %s:%d failed. ret=%d", ip.c_str(), port, ret); return ret; } } @@ -815,12 +1026,69 @@ int SrsServer::listen_http_stream() #ifdef SRS_AUTO_HTTP_SERVER close_listeners(SrsListenerHttpStream); if (_srs_config->get_http_stream_enabled()) { - SrsListener* listener = new SrsListener(this, SrsListenerHttpStream); + SrsListener* listener = new SrsStreamListener(this, SrsListenerHttpStream); listeners.push_back(listener); - int port = _srs_config->get_http_stream_listen(); - if ((ret = listener->listen(port)) != ERROR_SUCCESS) { - srs_error("HTTP stream listen at port %d failed. ret=%d", port, ret); + std::string ep = _srs_config->get_http_stream_listen(); + + std::string ip; + int port; + srs_parse_endpoint(ep, ip, port); + + if ((ret = listener->listen(ip, port)) != ERROR_SUCCESS) { + srs_error("HTTP stream listen at %s:%d failed. ret=%d", ip.c_str(), port, ret); + return ret; + } + } +#endif + + return ret; +} + +int SrsServer::listen_stream_caster() +{ + int ret = ERROR_SUCCESS; + +#ifdef SRS_AUTO_STREAM_CASTER + close_listeners(SrsListenerMpegTsOverUdp); + + std::vector::iterator it; + std::vector stream_casters = _srs_config->get_stream_casters(); + + for (it = stream_casters.begin(); it != stream_casters.end(); ++it) { + SrsConfDirective* stream_caster = *it; + if (!_srs_config->get_stream_caster_enabled(stream_caster)) { + continue; + } + + SrsListener* listener = NULL; + + std::string caster = _srs_config->get_stream_caster_engine(stream_caster); + if (caster == SRS_CONF_DEFAULT_STREAM_CASTER_MPEGTS_OVER_UDP) { + listener = new SrsUdpCasterListener(this, SrsListenerMpegTsOverUdp, stream_caster); + } else if (caster == SRS_CONF_DEFAULT_STREAM_CASTER_RTSP) { + listener = new SrsRtspListener(this, SrsListenerRtsp, stream_caster); + } else if (caster == SRS_CONF_DEFAULT_STREAM_CASTER_FLV) { + listener = new SrsHttpFlvListener(this, SrsListenerFlv, stream_caster); + } else { + ret = ERROR_STREAM_CASTER_ENGINE; + srs_error("unsupported stream caster %s. ret=%d", caster.c_str(), ret); + return ret; + } + srs_assert(listener != NULL); + + listeners.push_back(listener); + + int port = _srs_config->get_stream_caster_listen(stream_caster); + if (port <= 0) { + ret = ERROR_STREAM_CASTER_PORT; + srs_error("invalid stream caster port %d. ret=%d", port, ret); + return ret; + } + + // TODO: support listen at <[ip:]port> + if ((ret = listener->listen("0.0.0.0", port)) != ERROR_SUCCESS) { + srs_error("StreamCaster listen at port %d failed. ret=%d", port, ret); return ret; } } @@ -845,31 +1113,25 @@ void SrsServer::close_listeners(SrsListenerType type) } } -void SrsServer::resample_kbps(SrsConnection* conn, bool do_resample) +void SrsServer::resample_kbps() { - // resample all when conn is NULL. - if (!conn) { - for (std::vector::iterator it = conns.begin(); it != conns.end(); ++it) { - SrsConnection* client = *it; - srs_assert(client); - - // only resample, do resample when all finished. - resample_kbps(client, false); - } + SrsStatistic* stat = SrsStatistic::instance(); + + // collect delta from all clients. + for (std::vector::iterator it = conns.begin(); it != conns.end(); ++it) { + SrsConnection* conn = *it; - kbps->sample(); - return; + // add delta of connection to server kbps., + // for next sample() of server kbps can get the stat. + stat->kbps_add_delta(conn); } - // resample for connection. - conn->kbps_resample(); - - kbps->add_delta(conn); + // TODO: FXME: support all other connections. + + // sample the kbps, get the stat. + SrsKbps* kbps = stat->kbps_sample(); - // resample for server. - if (do_resample) { - kbps->sample(); - } + srs_update_rtmp_server((int)conns.size(), kbps); } int SrsServer::accept_client(SrsListenerType type, st_netfd_t client_stfd) @@ -893,7 +1155,7 @@ int SrsServer::accept_client(SrsListenerType type, st_netfd_t client_stfd) conn = new SrsRtmpConn(this, client_stfd); } else if (type == SrsListenerHttpApi) { #ifdef SRS_AUTO_HTTP_API - conn = new SrsHttpApi(this, client_stfd, http_api_handler); + conn = new SrsHttpApi(this, client_stfd, http_api_mux); #else srs_warn("close http client for server not support http-api"); srs_close_stfd(client_stfd); @@ -901,7 +1163,7 @@ int SrsServer::accept_client(SrsListenerType type, st_netfd_t client_stfd) #endif } else if (type == SrsListenerHttpStream) { #ifdef SRS_AUTO_HTTP_SERVER - conn = new SrsHttpConn(this, client_stfd, http_stream_handler); + conn = new SrsStaticHttpConn(this, client_stfd, &http_stream_mux->mux); #else srs_warn("close http client for server not support http-server"); srs_close_stfd(client_stfd); @@ -973,22 +1235,6 @@ int SrsServer::on_reload_vhost_removed(std::string /*vhost*/) return ret; } -int SrsServer::on_reload_vhost_http_updated() -{ - int ret = ERROR_SUCCESS; - -#ifdef SRS_AUTO_HTTP_SERVER - srs_freep(http_stream_handler); - http_stream_handler = SrsHttpHandler::create_http_stream(); - - if ((ret = http_stream_handler->initialize()) != ERROR_SUCCESS) { - return ret; - } -#endif - - return ret; -} - int SrsServer::on_reload_http_api_enabled() { int ret = ERROR_SUCCESS; @@ -1050,3 +1296,73 @@ int SrsServer::on_reload_http_stream_updated() return ret; } +int SrsServer::on_publish(SrsSource* s, SrsRequest* r) +{ + int ret = ERROR_SUCCESS; + +#ifdef SRS_AUTO_HTTP_SERVER + if ((ret = http_stream_mux->http_mount(s, r)) != ERROR_SUCCESS) { + return ret; + } +#endif + + return ret; +} + +void SrsServer::on_unpublish(SrsSource* s, SrsRequest* r) +{ +#ifdef SRS_AUTO_HTTP_SERVER + http_stream_mux->http_unmount(s, r); +#endif +} + +int SrsServer::on_hls_publish(SrsRequest* r) +{ + int ret = ERROR_SUCCESS; + +#ifdef SRS_AUTO_HTTP_SERVER + if ((ret = http_stream_mux->mount_hls(r)) != ERROR_SUCCESS) { + return ret; + } +#endif + + return ret; +} + +int SrsServer::on_update_m3u8(SrsRequest* r, string m3u8) +{ + int ret = ERROR_SUCCESS; + +#ifdef SRS_AUTO_HTTP_SERVER + if ((ret = http_stream_mux->hls_update_m3u8(r, m3u8)) != ERROR_SUCCESS) { + return ret; + } +#endif + + return ret; +} + +int SrsServer::on_update_ts(SrsRequest* r, string uri, string ts) +{ + int ret = ERROR_SUCCESS; + +#ifdef SRS_AUTO_HTTP_SERVER + if ((ret = http_stream_mux->hls_update_ts(r, uri, ts)) != ERROR_SUCCESS) { + return ret; + } +#endif + + return ret; +} + +int SrsServer::on_hls_unpublish(SrsRequest* r) +{ + int ret = ERROR_SUCCESS; + +#ifdef SRS_AUTO_HTTP_SERVER + http_stream_mux->unmount_hls(r); +#endif + + return ret; +} + diff --git a/trunk/src/app/srs_app_server.hpp b/trunk/src/app/srs_app_server.hpp index 64726c1de6..1ff7cee90b 100644 --- a/trunk/src/app/srs_app_server.hpp +++ b/trunk/src/app/srs_app_server.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -31,52 +31,138 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include +#include #include #include -#include +#include +#include +#include +#include class SrsServer; class SrsConnection; -class SrsHttpHandler; +class SrsHttpServeMux; +class SrsHttpServer; class SrsIngester; class SrsHttpHeartbeat; class SrsKbps; +class SrsConfDirective; +class ISrsTcpHandler; +class ISrsUdpHandler; +class SrsUdpListener; +class SrsTcpListener; +#ifdef SRS_AUTO_STREAM_CASTER +class SrsAppCasterFlv; +#endif // listener type for server to identify the connection, // that is, use different type to process the connection. enum SrsListenerType { // RTMP client, - SrsListenerRtmpStream = 0, + SrsListenerRtmpStream = 0, // HTTP api, - SrsListenerHttpApi = 1, + SrsListenerHttpApi = 1, // HTTP stream, HDS/HLS/DASH - SrsListenerHttpStream = 2 + SrsListenerHttpStream = 2, + // UDP stream, MPEG-TS over udp. + SrsListenerMpegTsOverUdp = 3, + // TCP stream, RTSP stream. + SrsListenerRtsp = 4, + // TCP stream, FLV stream over HTTP. + SrsListenerFlv = 5, }; -class SrsListener : public ISrsThreadHandler +/** +* the common tcp listener, for RTMP/HTTP server. +*/ +class SrsListener { -public: +protected: SrsListenerType _type; -private: - int fd; - st_netfd_t stfd; +protected: + std::string _ip; int _port; SrsServer* _server; - SrsThread* pthread; public: SrsListener(SrsServer* server, SrsListenerType type); virtual ~SrsListener(); public: virtual SrsListenerType type(); - virtual int listen(int port); -// interface ISrsThreadHandler. + virtual int listen(std::string ip, int port) = 0; +}; + +/** +* tcp listener. +*/ +class SrsStreamListener : virtual public SrsListener, virtual public ISrsTcpHandler +{ +private: + SrsTcpListener* listener; public: - virtual void on_thread_start(); - virtual int cycle(); + SrsStreamListener(SrsServer* server, SrsListenerType type); + virtual ~SrsStreamListener(); +public: + virtual int listen(std::string ip, int port); +// ISrsTcpHandler +public: + virtual int on_tcp_client(st_netfd_t stfd); +}; + +#ifdef SRS_AUTO_STREAM_CASTER +/** +* the tcp listener, for rtsp server. +*/ +class SrsRtspListener : virtual public SrsListener, virtual public ISrsTcpHandler +{ +private: + SrsTcpListener* listener; + ISrsTcpHandler* caster; +public: + SrsRtspListener(SrsServer* server, SrsListenerType type, SrsConfDirective* c); + virtual ~SrsRtspListener(); +public: + virtual int listen(std::string ip, int port); +// ISrsTcpHandler +public: + virtual int on_tcp_client(st_netfd_t stfd); }; +/** + * the tcp listener, for flv stream server. + */ +class SrsHttpFlvListener : virtual public SrsListener, virtual public ISrsTcpHandler +{ +private: + SrsTcpListener* listener; + SrsAppCasterFlv* caster; +public: + SrsHttpFlvListener(SrsServer* server, SrsListenerType type, SrsConfDirective* c); + virtual ~SrsHttpFlvListener(); +public: + virtual int listen(std::string ip, int port); +// ISrsTcpHandler +public: + virtual int on_tcp_client(st_netfd_t stfd); +}; + +/** +* the udp listener, for udp server. +*/ +class SrsUdpCasterListener : public SrsListener +{ +private: + SrsUdpListener* listener; + ISrsUdpHandler* caster; +public: + SrsUdpCasterListener(SrsServer* server, SrsListenerType type, SrsConfDirective* c); + virtual ~SrsUdpCasterListener(); +public: + virtual int listen(std::string ip, int port); +}; +#endif + /** * convert signal to io, * @see: st-1.9/docs/notes.html @@ -108,18 +194,39 @@ class SrsSignalManager : public ISrsThreadHandler static void sig_catcher(int signo); }; +/** +* the handler to the handle cycle in SRS RTMP server. +*/ +class ISrsServerCycle +{ +public: + ISrsServerCycle(); + virtual ~ISrsServerCycle(); +public: + /** + * initialize the cycle handler. + */ + virtual int initialize() = 0; + /** + * do on_cycle while server doing cycle. + */ + virtual int on_cycle(int connections) = 0; +}; + /** * SRS RTMP server, initialize and listen, * start connection service thread, destroy client. */ -class SrsServer : public ISrsReloadHandler +class SrsServer : virtual public ISrsReloadHandler + , virtual public ISrsSourceHandler, virtual public ISrsHlsHandler + , virtual public IConnectionManager { private: #ifdef SRS_AUTO_HTTP_API - SrsHttpHandler* http_api_handler; + SrsHttpServeMux* http_api_mux; #endif #ifdef SRS_AUTO_HTTP_SERVER - SrsHttpHandler* http_stream_handler; + SrsHttpServer* http_stream_mux; #endif #ifdef SRS_AUTO_HTTP_PARSER SrsHttpHeartbeat* http_heartbeat; @@ -148,9 +255,9 @@ class SrsServer : public ISrsReloadHandler */ SrsSignalManager* signal_manager; /** - * server total kbps. + * handle in server cycle. */ - SrsKbps* kbps; + ISrsServerCycle* handler; /** * user send the signal, convert to variable. */ @@ -168,15 +275,16 @@ class SrsServer : public ISrsReloadHandler virtual void destroy(); // server startup workflow, @see run_master() public: - virtual int initialize(); + virtual int initialize(ISrsServerCycle* cycle_handler); virtual int initialize_signal(); virtual int acquire_pid_file(); virtual int initialize_st(); virtual int listen(); virtual int register_signal(); + virtual int http_handle(); virtual int ingest(); virtual int cycle(); -// server utility +// IConnectionManager public: /** * callback for connection to remove itself. @@ -184,6 +292,8 @@ class SrsServer : public ISrsReloadHandler * @see SrsConnection.on_thread_stop(). */ virtual void remove(SrsConnection* conn); +// server utilities. +public: /** * callback for signal manager got a signal. * the signal manager convert signal to io message, @@ -208,18 +318,16 @@ class SrsServer : public ISrsReloadHandler virtual int listen_rtmp(); virtual int listen_http_api(); virtual int listen_http_stream(); + virtual int listen_stream_caster(); /** * close the listeners for specified type, * remove the listen object from manager. */ virtual void close_listeners(SrsListenerType type); /** - * resample the server kbps. - * if conn is NULL, resample all connections delta, then calc the total kbps. - * @param conn, the connection to do resample the kbps. NULL to resample all connections. - * @param do_resample, whether resample the server kbps. always false when sample a connection. + * resample the server kbs. */ - virtual void resample_kbps(SrsConnection* conn, bool do_resample = true); + virtual void resample_kbps(); // internal only public: /** @@ -235,12 +343,22 @@ class SrsServer : public ISrsReloadHandler virtual int on_reload_pid(); virtual int on_reload_vhost_added(std::string vhost); virtual int on_reload_vhost_removed(std::string vhost); - virtual int on_reload_vhost_http_updated(); virtual int on_reload_http_api_enabled(); virtual int on_reload_http_api_disabled(); virtual int on_reload_http_stream_enabled(); virtual int on_reload_http_stream_disabled(); virtual int on_reload_http_stream_updated(); +// interface ISrsSourceHandler +public: + virtual int on_publish(SrsSource* s, SrsRequest* r); + virtual void on_unpublish(SrsSource* s, SrsRequest* r); +// interface ISrsHlsHandler +public: + virtual int on_hls_publish(SrsRequest* r); + virtual int on_update_m3u8(SrsRequest* r, std::string m3u8); + virtual int on_update_ts(SrsRequest* r, std::string uri, std::string ts); + virtual int on_hls_unpublish(SrsRequest* r); }; #endif + diff --git a/trunk/src/app/srs_app_source.cpp b/trunk/src/app/srs_app_source.cpp index cdc0f4ee97..8eb657f647 100644 --- a/trunk/src/app/srs_app_source.cpp +++ b/trunk/src/app/srs_app_source.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -28,26 +28,30 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using namespace std; #include -#include -#include +#include +#include #include #include #include #include #include -#include +#include #include #include #include #include -#include +#include +#include +#include +#include +#include #define CONST_MAX_JITTER_MS 500 #define DEFAULT_FRAME_TIME_MS 40 // for 26ms per audio packet, // 115 packets is 3s. -#define __SRS_PURE_AUDIO_GUESS_COUNT 115 +#define SRS_PURE_AUDIO_GUESS_COUNT 115 int _srs_time_jitter_string2int(std::string time_jitter) { @@ -84,10 +88,10 @@ int SrsRtmpJitter::correct(SrsSharedPtrMessage* msg, int tba, int tbv, SrsRtmpJi if (ag == SrsRtmpJitterAlgorithmZERO) { // for the first time, last_pkt_correct_time is zero. // while when timestamp overflow, the timestamp become smaller, reset the last_pkt_correct_time. - if (last_pkt_correct_time <= 0 || last_pkt_correct_time > msg->header.timestamp) { - last_pkt_correct_time = msg->header.timestamp; + if (last_pkt_correct_time <= 0 || last_pkt_correct_time > msg->timestamp) { + last_pkt_correct_time = msg->timestamp; } - msg->header.timestamp -= last_pkt_correct_time; + msg->timestamp -= last_pkt_correct_time; return ret; } @@ -98,8 +102,8 @@ int SrsRtmpJitter::correct(SrsSharedPtrMessage* msg, int tba, int tbv, SrsRtmpJi // full jitter algorithm, do jitter correct. // set to 0 for metadata. - if (!msg->header.is_audio() && !msg->header.is_video()) { - msg->header.timestamp = 0; + if (!msg->is_av()) { + msg->timestamp = 0; return ret; } @@ -116,15 +120,15 @@ int SrsRtmpJitter::correct(SrsSharedPtrMessage* msg, int tba, int tbv, SrsRtmpJi * 3. last_pkt_correct_time: simply add the positive delta, * and enforce the time monotonically. */ - int64_t time = msg->header.timestamp; + int64_t time = msg->timestamp; int64_t delta = time - last_pkt_time; // if jitter detected, reset the delta. if (delta < 0 || delta > CONST_MAX_JITTER_MS) { // calc the right diff by audio sample rate - if (msg->header.is_audio() && sample_rate > 0) { + if (msg->is_audio() && sample_rate > 0) { delta = (int64_t)(delta * 1000.0 / sample_rate); - } else if (msg->header.is_video() && frame_rate > 0) { + } else if (msg->is_video() && frame_rate > 0) { delta = (int64_t)(delta * 1.0 / frame_rate); } else { delta = DEFAULT_FRAME_TIME_MS; @@ -144,7 +148,7 @@ int SrsRtmpJitter::correct(SrsSharedPtrMessage* msg, int tba, int tbv, SrsRtmpJi last_pkt_correct_time = srs_max(0, last_pkt_correct_time + delta); - msg->header.timestamp = last_pkt_correct_time; + msg->timestamp = last_pkt_correct_time; last_pkt_time = time; return ret; @@ -155,8 +159,97 @@ int SrsRtmpJitter::get_time() return (int)last_pkt_correct_time; } -SrsMessageQueue::SrsMessageQueue() +#ifdef SRS_PERF_QUEUE_FAST_VECTOR +SrsFastVector::SrsFastVector() +{ + count = 0; + nb_msgs = SRS_PERF_MW_MSGS * 8; + msgs = new SrsSharedPtrMessage*[nb_msgs]; +} + +SrsFastVector::~SrsFastVector() +{ + free(); + srs_freep(msgs); +} + +int SrsFastVector::size() +{ + return count; +} + +int SrsFastVector::begin() +{ + return 0; +} + +int SrsFastVector::end() +{ + return count; +} + +SrsSharedPtrMessage** SrsFastVector::data() +{ + return msgs; +} + +SrsSharedPtrMessage* SrsFastVector::at(int index) +{ + srs_assert(index < count); + return msgs[index]; +} + +void SrsFastVector::clear() +{ + count = 0; +} + +void SrsFastVector::erase(int _begin, int _end) +{ + srs_assert(_begin < _end); + + // move all erased to previous. + for (int i = 0; i < count - _end; i++) { + msgs[_begin + i] = msgs[_end + i]; + } + + // update the count. + count -= _end - _begin; +} + +void SrsFastVector::push_back(SrsSharedPtrMessage* msg) +{ + // increase vector. + if (count >= nb_msgs) { + int size = nb_msgs * 2; + SrsSharedPtrMessage** buf = new SrsSharedPtrMessage*[size]; + for (int i = 0; i < nb_msgs; i++) { + buf[i] = msgs[i]; + } + srs_warn("fast vector incrase %d=>%d", nb_msgs, size); + + // use new array. + srs_freep(msgs); + msgs = buf; + nb_msgs = size; + } + + msgs[count++] = msg; +} + +void SrsFastVector::free() +{ + for (int i = 0; i < count; i++) { + SrsSharedPtrMessage* msg = msgs[i]; + srs_freep(msg); + } + count = 0; +} +#endif + +SrsMessageQueue::SrsMessageQueue(bool ignore_shrink) { + _ignore_shrink = ignore_shrink; queue_size_ms = 0; av_start_time = av_end_time = -1; } @@ -166,26 +259,41 @@ SrsMessageQueue::~SrsMessageQueue() clear(); } +int SrsMessageQueue::size() +{ + return (int)msgs.size(); +} + +int SrsMessageQueue::duration() +{ + return (int)(av_end_time - av_start_time); +} + void SrsMessageQueue::set_queue_size(double queue_size) { queue_size_ms = (int)(queue_size * 1000); } -int SrsMessageQueue::enqueue(SrsSharedPtrMessage* msg) +int SrsMessageQueue::enqueue(SrsSharedPtrMessage* msg, bool* is_overflow) { int ret = ERROR_SUCCESS; - if (msg->header.is_audio() || msg->header.is_video()) { + if (msg->is_av()) { if (av_start_time == -1) { - av_start_time = msg->header.timestamp; + av_start_time = msg->timestamp; } - av_end_time = msg->header.timestamp; + av_end_time = msg->timestamp; } msgs.push_back(msg); while (av_end_time - av_start_time > queue_size_ms) { + // notice the caller queue already overflow and shrinked. + if (is_overflow) { + *is_overflow = true; + } + shrink(); } @@ -196,27 +304,29 @@ int SrsMessageQueue::dump_packets(int max_count, SrsSharedPtrMessage** pmsgs, in { int ret = ERROR_SUCCESS; - if (msgs.empty()) { + int nb_msgs = (int)msgs.size(); + if (nb_msgs <= 0) { return ret; } srs_assert(max_count > 0); - count = srs_min(max_count, (int)msgs.size()); - + count = srs_min(max_count, nb_msgs); + + SrsSharedPtrMessage** omsgs = msgs.data(); for (int i = 0; i < count; i++) { - pmsgs[i] = msgs[i]; + pmsgs[i] = omsgs[i]; } - SrsSharedPtrMessage* last = msgs[count - 1]; - av_start_time = last->header.timestamp; + SrsSharedPtrMessage* last = omsgs[count - 1]; + av_start_time = last->timestamp; - if (count == (int)msgs.size()) { + if (count >= nb_msgs) { // the pmsgs is big enough and clear msgs at most time. msgs.clear(); } else { // erase some vector elements may cause memory copy, // maybe can use more efficient vector.swap to avoid copy. - // @remark for the pmsgs is big enough, for instance, SYS_MAX_PLAY_SEND_MSGS 128, + // @remark for the pmsgs is big enough, for instance, SRS_PERF_MW_MSGS 128, // the rtmp play client will get 128msgs once, so this branch rarely execute. msgs.erase(msgs.begin(), msgs.begin() + count); } @@ -224,6 +334,26 @@ int SrsMessageQueue::dump_packets(int max_count, SrsSharedPtrMessage** pmsgs, in return ret; } +int SrsMessageQueue::dump_packets(SrsConsumer* consumer, bool atc, int tba, int tbv, SrsRtmpJitterAlgorithm ag) +{ + int ret = ERROR_SUCCESS; + + int nb_msgs = (int)msgs.size(); + if (nb_msgs <= 0) { + return ret; + } + + SrsSharedPtrMessage** omsgs = msgs.data(); + for (int i = 0; i < nb_msgs; i++) { + SrsSharedPtrMessage* msg = omsgs[i]; + if ((ret = consumer->enqueue(msg, atc, tba, tbv, ag)) != ERROR_SUCCESS) { + return ret; + } + } + + return ret; +} + void SrsMessageQueue::shrink() { int iframe_index = -1; @@ -233,15 +363,15 @@ void SrsMessageQueue::shrink() // for when we shrinked, the first is the iframe, // we will directly remove the gop next time. for (int i = 1; i < (int)msgs.size(); i++) { - SrsSharedPtrMessage* msg = msgs[i]; + SrsSharedPtrMessage* msg = msgs.at(i); - if (msg->header.is_video()) { + if (msg->is_video()) { if (SrsFlvCodec::video_is_keyframe(msg->payload, msg->size)) { // the max frame index to remove. iframe_index = i; // set the start time, we will remove until this frame. - av_start_time = msg->header.timestamp; + av_start_time = msg->timestamp; break; } @@ -252,18 +382,23 @@ void SrsMessageQueue::shrink() // it is ok to clear for audio, for the shrink tell us the queue is full. // for video, we clear util the I-Frame, for the decoding must start from I-frame, // for audio, it's ok to clear any data, also we can clear the whole queue. - // @see: https://github.com/winlinvip/simple-rtmp-server/issues/134 + // @see: https://github.com/simple-rtmp-server/srs/issues/134 if (iframe_index < 0) { clear(); return; } - srs_trace("shrink the cache queue, size=%d, removed=%d, max=%.2f", - (int)msgs.size(), iframe_index, queue_size_ms / 1000.0); + if (_ignore_shrink) { + srs_info("shrink the cache queue, size=%d, removed=%d, max=%.2f", + (int)msgs.size(), iframe_index, queue_size_ms / 1000.0); + } else { + srs_trace("shrink the cache queue, size=%d, removed=%d, max=%.2f", + (int)msgs.size(), iframe_index, queue_size_ms / 1000.0); + } // remove the first gop from the front for (int i = 0; i < iframe_index; i++) { - SrsSharedPtrMessage* msg = msgs[i]; + SrsSharedPtrMessage* msg = msgs.at(i); srs_freep(msg); } msgs.erase(msgs.begin(), msgs.begin() + iframe_index); @@ -271,12 +406,16 @@ void SrsMessageQueue::shrink() void SrsMessageQueue::clear() { +#ifndef SRS_PERF_QUEUE_FAST_VECTOR std::vector::iterator it; for (it = msgs.begin(); it != msgs.end(); ++it) { SrsSharedPtrMessage* msg = *it; srs_freep(msg); } +#else + msgs.free(); +#endif msgs.clear(); @@ -290,6 +429,13 @@ SrsConsumer::SrsConsumer(SrsSource* _source) jitter = new SrsRtmpJitter(); queue = new SrsMessageQueue(); should_update_source_id = false; + +#ifdef SRS_PERF_QUEUE_COND_WAIT + mw_wait = st_cond_new(); + mw_min_msgs = 0; + mw_duration = 0; + mw_waiting = false; +#endif } SrsConsumer::~SrsConsumer() @@ -297,6 +443,10 @@ SrsConsumer::~SrsConsumer() source->on_consumer_destroy(this); srs_freep(jitter); srs_freep(queue); + +#ifdef SRS_PERF_QUEUE_COND_WAIT + st_cond_destroy(mw_wait); +#endif } void SrsConsumer::set_queue_size(double queue_size) @@ -314,10 +464,12 @@ int SrsConsumer::get_time() return jitter->get_time(); } -int SrsConsumer::enqueue(SrsSharedPtrMessage* msg, bool atc, int tba, int tbv, SrsRtmpJitterAlgorithm ag) +int SrsConsumer::enqueue(SrsSharedPtrMessage* shared_msg, bool atc, int tba, int tbv, SrsRtmpJitterAlgorithm ag) { int ret = ERROR_SUCCESS; + SrsSharedPtrMessage* msg = shared_msg->copy(); + if (!atc) { if ((ret = jitter->correct(msg, tba, tbv, ag)) != ERROR_SUCCESS) { srs_freep(msg); @@ -325,16 +477,35 @@ int SrsConsumer::enqueue(SrsSharedPtrMessage* msg, bool atc, int tba, int tbv, S } } - if ((ret = queue->enqueue(msg)) != ERROR_SUCCESS) { + if ((ret = queue->enqueue(msg, NULL)) != ERROR_SUCCESS) { return ret; } +#ifdef SRS_PERF_QUEUE_COND_WAIT + srs_verbose("enqueue msg, time=%"PRId64", size=%d, duration=%d, waiting=%d, min_msg=%d", + msg->timestamp, msg->size, queue->duration(), mw_waiting, mw_min_msgs); + + // fire the mw when msgs is enough. + if (mw_waiting) { + int duration_ms = queue->duration(); + bool match_min_msgs = queue->size() > mw_min_msgs; + + // when duration ok, signal to flush. + if (match_min_msgs && duration_ms > mw_duration) { + st_cond_signal(mw_wait); + mw_waiting = false; + } + } +#endif + return ret; } -int SrsConsumer::dump_packets(int max_count, SrsSharedPtrMessage** pmsgs, int& count) +int SrsConsumer::dump_packets(SrsMessageArray* msgs, int& count) { - srs_assert(max_count > 0); + int ret =ERROR_SUCCESS; + + srs_assert(msgs->max > 0); if (should_update_source_id) { srs_trace("update source_id=%d[%d]", source->source_id(), source->source_id()); @@ -343,12 +514,52 @@ int SrsConsumer::dump_packets(int max_count, SrsSharedPtrMessage** pmsgs, int& c // paused, return nothing. if (paused) { - return ERROR_SUCCESS; + return ret; + } + + // pump msgs from queue. + if ((ret = queue->dump_packets(msgs->max, msgs->msgs, count)) != ERROR_SUCCESS) { + return ret; } - return queue->dump_packets(max_count, pmsgs, count); + return ret; } +#ifdef SRS_PERF_QUEUE_COND_WAIT +void SrsConsumer::wait(int nb_msgs, int duration) +{ + if (paused) { + st_usleep(SRS_CONSTS_RTMP_PULSE_TIMEOUT_US); + return; + } + + mw_min_msgs = nb_msgs; + mw_duration = duration; + + int duration_ms = queue->duration(); + bool match_min_msgs = queue->size() > mw_min_msgs; + + // when duration ok, signal to flush. + if (match_min_msgs && duration_ms > mw_duration) { + return; + } + + // the enqueue will notify this cond. + mw_waiting = true; + + // use cond block wait for high performance mode. + st_cond_wait(mw_wait); +} + +void SrsConsumer::wakeup() +{ + if (mw_waiting) { + st_cond_signal(mw_wait); + mw_waiting = false; + } +} +#endif + int SrsConsumer::on_play_client_pause(bool is_pause) { int ret = ERROR_SUCCESS; @@ -384,7 +595,7 @@ void SrsGopCache::set(bool enabled) srs_info("enable gop cache"); } -int SrsGopCache::cache(SrsSharedPtrMessage* msg) +int SrsGopCache::cache(SrsSharedPtrMessage* shared_msg) { int ret = ERROR_SUCCESS; @@ -392,9 +603,18 @@ int SrsGopCache::cache(SrsSharedPtrMessage* msg) srs_verbose("gop cache is disabled."); return ret; } + + // the gop cache know when to gop it. + SrsSharedPtrMessage* msg = shared_msg; + + // disable gop cache when not h.264 + if (!SrsFlvCodec::video_is_h264(msg->payload, msg->size)) { + srs_info("gop donot cache video for none h.264"); + return ret; + } // got video, update the video count if acceptable - if (msg->header.is_video()) { + if (msg->is_video()) { cached_video_count++; audio_after_last_video_count = 0; } @@ -406,19 +626,19 @@ int SrsGopCache::cache(SrsSharedPtrMessage* msg) } // ok, gop cache enabled, and got an audio. - if (msg->header.is_audio()) { + if (msg->is_audio()) { audio_after_last_video_count++; } // clear gop cache when pure audio count overflow - if (audio_after_last_video_count > __SRS_PURE_AUDIO_GUESS_COUNT) { + if (audio_after_last_video_count > SRS_PURE_AUDIO_GUESS_COUNT) { srs_warn("clear gop cache for guess pure audio overflow"); clear(); return ret; } // clear gop cache when got key frame - if (msg->header.is_video() && SrsFlvCodec::video_is_keyframe(msg->payload, msg->size)) { + if (msg->is_video() && SrsFlvCodec::video_is_keyframe(msg->payload, msg->size)) { srs_info("clear gop cache when got keyframe. vcount=%d, count=%d", cached_video_count, (int)gop_cache.size()); @@ -454,8 +674,7 @@ int SrsGopCache::dump(SrsConsumer* consumer, bool atc, int tba, int tbv, SrsRtmp std::vector::iterator it; for (it = gop_cache.begin(); it != gop_cache.end(); ++it) { SrsSharedPtrMessage* msg = *it; - SrsSharedPtrMessage* copy = msg->copy(); - if ((ret = consumer->enqueue(copy, atc, tba, tbv, jitter_algorithm)) != ERROR_SUCCESS) { + if ((ret = consumer->enqueue(msg, atc, tba, tbv, jitter_algorithm)) != ERROR_SUCCESS) { srs_error("dispatch cached gop failed. ret=%d", ret); return ret; } @@ -479,7 +698,7 @@ int64_t SrsGopCache::start_time() SrsSharedPtrMessage* msg = gop_cache[0]; srs_assert(msg); - return msg->header.timestamp; + return msg->timestamp; } bool SrsGopCache::pure_audio() @@ -487,36 +706,57 @@ bool SrsGopCache::pure_audio() return cached_video_count == 0; } +ISrsSourceHandler::ISrsSourceHandler() +{ +} + +ISrsSourceHandler::~ISrsSourceHandler() +{ +} + std::map SrsSource::pool; -int SrsSource::find(SrsRequest* req, SrsSource** ppsource) +int SrsSource::create(SrsRequest* r, ISrsSourceHandler* h, ISrsHlsHandler* hh, SrsSource** pps) { int ret = ERROR_SUCCESS; - string stream_url = req->get_stream_url(); - string vhost = req->vhost; + string stream_url = r->get_stream_url(); + string vhost = r->vhost; - if (pool.find(stream_url) == pool.end()) { - SrsSource* source = new SrsSource(req); - if ((ret = source->initialize()) != ERROR_SUCCESS) { - srs_freep(source); - return ret; - } - - pool[stream_url] = source; - srs_info("create new source for url=%s, vhost=%s", stream_url.c_str(), vhost.c_str()); + // should always not exists for create a source. + srs_assert (pool.find(stream_url) == pool.end()); + + SrsSource* source = new SrsSource(); + if ((ret = source->initialize(r, h, hh)) != ERROR_SUCCESS) { + srs_freep(source); + return ret; } + + pool[stream_url] = source; + srs_info("create new source for url=%s, vhost=%s", stream_url.c_str(), vhost.c_str()); + + *pps = source; + return ret; +} + +SrsSource* SrsSource::fetch(SrsRequest* r) +{ + SrsSource* source = NULL; + + string stream_url = r->get_stream_url(); + if (pool.find(stream_url) == pool.end()) { + return NULL; + } + + source = pool[stream_url]; + // we always update the request of resource, // for origin auth is on, the token in request maybe invalid, // and we only need to update the token of request, it's simple. - if (true) { - SrsSource* source = pool[stream_url]; - source->_req->update_auth(req); - *ppsource = source; - } - - return ret; + source->_req->update_auth(r); + + return source; } void SrsSource::destroy() @@ -529,20 +769,75 @@ void SrsSource::destroy() pool.clear(); } -SrsSource::SrsSource(SrsRequest* req) +SrsMixQueue::SrsMixQueue() +{ + nb_videos = 0; +} + +SrsMixQueue::~SrsMixQueue() +{ + clear(); +} + +void SrsMixQueue::clear() { - _req = req->copy(); + std::multimap::iterator it; + for (it = msgs.begin(); it != msgs.end(); ++it) { + SrsSharedPtrMessage* msg = it->second; + srs_freep(msg); + } + msgs.clear(); + + nb_videos = 0; +} + +void SrsMixQueue::push(SrsSharedPtrMessage* msg) +{ + msgs.insert(std::make_pair(msg->timestamp, msg)); + + if (msg->is_video()) { + nb_videos++; + } +} + +SrsSharedPtrMessage* SrsMixQueue::pop() +{ + // always keep 2+ videos + if (nb_videos < 2) { + return NULL; + } + + // pop the first msg. + std::multimap::iterator it = msgs.begin(); + SrsSharedPtrMessage* msg = it->second; + msgs.erase(it); + + if (msg->is_video()) { + nb_videos--; + } + + return msg; +} + +SrsSource::SrsSource() +{ + _req = NULL; jitter_algorithm = SrsRtmpJitterAlgorithmOFF; + mix_correct = false; + mix_queue = new SrsMixQueue(); #ifdef SRS_AUTO_HLS - hls = new SrsHls(this); + hls = new SrsHls(); #endif #ifdef SRS_AUTO_DVR - dvr = new SrsDvr(this); + dvr = new SrsDvr(); #endif #ifdef SRS_AUTO_TRANSCODE encoder = new SrsEncoder(); #endif +#ifdef SRS_AUTO_HDS + hds = new SrsHds(this); +#endif cache_metadata = cache_sh_video = cache_sh_audio = NULL; @@ -556,7 +851,7 @@ SrsSource::SrsSource(SrsRequest* req) aggregate_stream = new SrsStream(); _srs_config->subscribe(this); - atc = _srs_config->get_atc(_req->vhost); + atc = false; } SrsSource::~SrsSource() @@ -576,6 +871,7 @@ SrsSource::~SrsSource() forwarders.clear(); } + srs_freep(mix_queue); srs_freep(cache_metadata); srs_freep(cache_sh_video); srs_freep(cache_sh_audio); @@ -594,16 +890,33 @@ SrsSource::~SrsSource() #ifdef SRS_AUTO_TRANSCODE srs_freep(encoder); #endif +#ifdef SRS_AUTO_HDS + srs_freep(hds); +#endif srs_freep(_req); } -int SrsSource::initialize() +int SrsSource::initialize(SrsRequest* r, ISrsSourceHandler* h, ISrsHlsHandler* hh) { int ret = ERROR_SUCCESS; + srs_assert(h); + srs_assert(hh); + srs_assert(!_req); + + handler = h; + _req = r->copy(); + atc = _srs_config->get_atc(_req->vhost); + +#ifdef SRS_AUTO_HLS + if ((ret = hls->initialize(this, hh)) != ERROR_SUCCESS) { + return ret; + } +#endif + #ifdef SRS_AUTO_DVR - if ((ret = dvr->initialize(_req)) != ERROR_SUCCESS) { + if ((ret = dvr->initialize(this, _req)) != ERROR_SUCCESS) { return ret; } #endif @@ -619,6 +932,7 @@ int SrsSource::initialize() publish_edge->set_queue_size(queue_size); jitter_algorithm = (SrsRtmpJitterAlgorithm)_srs_config->get_time_jitter(_req->vhost); + mix_correct = _srs_config->get_mix_correct(_req->vhost); return ret; } @@ -714,6 +1028,25 @@ int SrsSource::on_reload_vhost_time_jitter(string vhost) return ret; } +int SrsSource::on_reload_vhost_mix_correct(string vhost) +{ + int ret = ERROR_SUCCESS; + + if (_req->vhost != vhost) { + return ret; + } + + bool v = _srs_config->get_mix_correct(_req->vhost); + + // when changed, clear the mix queue. + if (v != mix_correct) { + mix_queue->clear(); + } + mix_correct = v; + + return ret; +} + int SrsSource::on_reload_vhost_forward(string vhost) { int ret = ERROR_SUCCESS; @@ -754,6 +1087,26 @@ int SrsSource::on_reload_vhost_hls(string vhost) return ret; } +int SrsSource::on_reload_vhost_hds(string vhost) +{ + int ret = ERROR_SUCCESS; + + if (_req->vhost != vhost) { + return ret; + } + +#ifdef SRS_AUTO_HDS + hds->on_unpublish(); + if ((ret = hds->on_publish(_req)) != ERROR_SUCCESS) { + srs_error("hds publish failed. ret=%d", ret); + return ret; + } + srs_trace("vhost %s hds reload success", vhost.c_str()); +#endif + + return ret; +} + int SrsSource::on_reload_vhost_dvr(string vhost) { int ret = ERROR_SUCCESS; @@ -767,7 +1120,7 @@ int SrsSource::on_reload_vhost_dvr(string vhost) dvr->on_unpublish(); // reinitialize the dvr, update plan. - if ((ret = dvr->initialize(_req)) != ERROR_SUCCESS) { + if ((ret = dvr->initialize(this, _req)) != ERROR_SUCCESS) { return ret; } @@ -809,15 +1162,15 @@ int SrsSource::on_forwarder_start(SrsForwarder* forwarder) // feed the forwarder the metadata/sequence header, // when reload to enable the forwarder. - if (cache_metadata && (ret = forwarder->on_meta_data(cache_metadata->copy())) != ERROR_SUCCESS) { + if (cache_metadata && (ret = forwarder->on_meta_data(cache_metadata)) != ERROR_SUCCESS) { srs_error("forwarder process onMetaData message failed. ret=%d", ret); return ret; } - if (cache_sh_video && (ret = forwarder->on_video(cache_sh_video->copy())) != ERROR_SUCCESS) { + if (cache_sh_video && (ret = forwarder->on_video(cache_sh_video)) != ERROR_SUCCESS) { srs_error("forwarder process video sequence header message failed. ret=%d", ret); return ret; } - if (cache_sh_audio && (ret = forwarder->on_audio(cache_sh_audio->copy())) != ERROR_SUCCESS) { + if (cache_sh_audio && (ret = forwarder->on_audio(cache_sh_audio)) != ERROR_SUCCESS) { srs_error("forwarder process audio sequence header message failed. ret=%d", ret); return ret; } @@ -834,11 +1187,11 @@ int SrsSource::on_hls_start() // when reload to start hls, hls will never get the sequence header in stream, // use the SrsSource.on_hls_start to push the sequence header to HLS. // TODO: maybe need to decode the metadata? - if (cache_sh_video && (ret = hls->on_video(cache_sh_video->copy())) != ERROR_SUCCESS) { + if (cache_sh_video && (ret = hls->on_video(cache_sh_video)) != ERROR_SUCCESS) { srs_error("hls process video sequence header message failed. ret=%d", ret); return ret; } - if (cache_sh_audio && (ret = hls->on_audio(cache_sh_audio->copy())) != ERROR_SUCCESS) { + if (cache_sh_audio && (ret = hls->on_audio(cache_sh_audio)) != ERROR_SUCCESS) { srs_error("hls process audio sequence header message failed. ret=%d", ret); return ret; } @@ -877,11 +1230,11 @@ int SrsSource::on_dvr_request_sh() } } - if (cache_sh_video && (ret = dvr->on_video(cache_sh_video->copy())) != ERROR_SUCCESS) { + if (cache_sh_video && (ret = dvr->on_video(cache_sh_video)) != ERROR_SUCCESS) { srs_error("dvr process video sequence header message failed. ret=%d", ret); return ret; } - if (cache_sh_audio && (ret = dvr->on_audio(cache_sh_audio->copy())) != ERROR_SUCCESS) { + if (cache_sh_audio && (ret = dvr->on_audio(cache_sh_audio)) != ERROR_SUCCESS) { srs_error("dvr process audio sequence header message failed. ret=%d", ret); return ret; } @@ -920,7 +1273,7 @@ bool SrsSource::can_publish() return _can_publish; } -int SrsSource::on_meta_data(SrsMessage* msg, SrsOnMetaDataPacket* metadata) +int SrsSource::on_meta_data(SrsCommonMessage* msg, SrsOnMetaDataPacket* metadata) { int ret = ERROR_SUCCESS; @@ -957,8 +1310,8 @@ int SrsSource::on_meta_data(SrsMessage* msg, SrsOnMetaDataPacket* metadata) // add server info to metadata metadata->metadata->set("server", SrsAmf0Any::str(RTMP_SIG_SRS_SERVER)); - metadata->metadata->set("primary", SrsAmf0Any::str(RTMP_SIG_SRS_PRIMARY)); - metadata->metadata->set("authors", SrsAmf0Any::str(RTMP_SIG_SRS_AUTHROS)); + metadata->metadata->set("srs_primary", SrsAmf0Any::str(RTMP_SIG_SRS_PRIMARY)); + metadata->metadata->set("srs_authors", SrsAmf0Any::str(RTMP_SIG_SRS_AUTHROS)); // version, for example, 1.0.0 // add version to metadata, please donot remove it, for debug. @@ -1017,8 +1370,7 @@ int SrsSource::on_meta_data(SrsMessage* msg, SrsOnMetaDataPacket* metadata) std::vector::iterator it; for (it = consumers.begin(); it != consumers.end(); ++it) { SrsConsumer* consumer = *it; - SrsSharedPtrMessage* copy = cache_metadata->copy(); - if ((ret = consumer->enqueue(copy, atc, sample_rate, frame_rate, jitter_algorithm)) != ERROR_SUCCESS) { + if ((ret = consumer->enqueue(cache_metadata, atc, sample_rate, frame_rate, jitter_algorithm)) != ERROR_SUCCESS) { srs_error("dispatch the metadata failed. ret=%d", ret); return ret; } @@ -1031,7 +1383,7 @@ int SrsSource::on_meta_data(SrsMessage* msg, SrsOnMetaDataPacket* metadata) std::vector::iterator it; for (it = forwarders.begin(); it != forwarders.end(); ++it) { SrsForwarder* forwarder = *it; - if ((ret = forwarder->on_meta_data(cache_metadata->copy())) != ERROR_SUCCESS) { + if ((ret = forwarder->on_meta_data(cache_metadata)) != ERROR_SUCCESS) { srs_error("forwarder process onMetaData message failed. ret=%d", ret); return ret; } @@ -1041,23 +1393,36 @@ int SrsSource::on_meta_data(SrsMessage* msg, SrsOnMetaDataPacket* metadata) return ret; } -int SrsSource::on_audio(SrsMessage* __audio) +int SrsSource::on_audio(SrsCommonMessage* shared_audio) { int ret = ERROR_SUCCESS; - // convert __audio to msg, user should not use __audio again. - // the payload is transfer to msg, and set to NULL in __audio. + // convert shared_audio to msg, user should not use shared_audio again. + // the payload is transfer to msg, and set to NULL in shared_audio. SrsSharedPtrMessage msg; - if ((ret = msg.create(__audio)) != ERROR_SUCCESS) { + if ((ret = msg.create(shared_audio)) != ERROR_SUCCESS) { srs_error("initialize the audio failed. ret=%d", ret); return ret; } - srs_verbose("initialize shared ptr audio success."); + srs_info("Audio dts=%"PRId64", size=%d", msg.timestamp, msg.size); + + if (!mix_correct) { + return on_audio_imp(&msg); + } + + return do_mix_correct(&msg); +} + +int SrsSource::on_audio_imp(SrsSharedPtrMessage* msg) +{ + int ret = ERROR_SUCCESS; + + srs_info("Audio dts=%"PRId64", size=%d", msg->timestamp, msg->size); #ifdef SRS_AUTO_HLS - if ((ret = hls->on_audio(msg.copy())) != ERROR_SUCCESS) { + if ((ret = hls->on_audio(msg)) != ERROR_SUCCESS) { // apply the error strategy for hls. - // @see https://github.com/winlinvip/simple-rtmp-server/issues/264 + // @see https://github.com/simple-rtmp-server/srs/issues/264 std::string hls_error_strategy = _srs_config->get_hls_on_error(_req->vhost); if (hls_error_strategy == SRS_CONF_DEFAULT_HLS_ON_ERROR_IGNORE) { srs_warn("hls process audio message failed, ignore and disable hls. ret=%d", ret); @@ -1069,7 +1434,7 @@ int SrsSource::on_audio(SrsMessage* __audio) ret = ERROR_SUCCESS; } else if (hls_error_strategy == SRS_CONF_DEFAULT_HLS_ON_ERROR_CONTINUE) { // compare the sequence header with audio, continue when it's actually an sequence header. - if (ret == ERROR_HLS_DECODE_ERROR && cache_sh_audio && cache_sh_audio->size == msg.size) { + if (ret == ERROR_HLS_DECODE_ERROR && cache_sh_audio && cache_sh_audio->size == msg->size) { srs_warn("the audio is actually a sequence header, ignore this packet."); ret = ERROR_SUCCESS; } else { @@ -1084,7 +1449,7 @@ int SrsSource::on_audio(SrsMessage* __audio) #endif #ifdef SRS_AUTO_DVR - if ((ret = dvr->on_audio(msg.copy())) != ERROR_SUCCESS) { + if ((ret = dvr->on_audio(msg)) != ERROR_SUCCESS) { srs_warn("dvr process audio message failed, ignore and disable dvr. ret=%d", ret); // unpublish, ignore ret. @@ -1094,13 +1459,25 @@ int SrsSource::on_audio(SrsMessage* __audio) ret = ERROR_SUCCESS; } #endif + +#ifdef SRS_AUTO_HDS + if ((ret = hds->on_audio(msg)) != ERROR_SUCCESS) { + srs_warn("hds process audio message failed, ignore and disable dvr. ret=%d", ret); + + // unpublish, ignore ret. + hds->on_unpublish(); + // ignore. + ret = ERROR_SUCCESS; + } +#endif // copy to all consumer - if (true) { - for (int i = 0; i < (int)consumers.size(); i++) { - SrsConsumer* consumer = consumers.at(i); - SrsSharedPtrMessage* copy = msg.copy(); - if ((ret = consumer->enqueue(copy, atc, sample_rate, frame_rate, jitter_algorithm)) != ERROR_SUCCESS) { + int nb_consumers = (int)consumers.size(); + if (nb_consumers > 0) { + SrsConsumer** pconsumer = consumers.data(); + for (int i = 0; i < nb_consumers; i++) { + SrsConsumer* consumer = pconsumer[i]; + if ((ret = consumer->enqueue(msg, atc, sample_rate, frame_rate, jitter_algorithm)) != ERROR_SUCCESS) { srs_error("dispatch the audio failed. ret=%d", ret); return ret; } @@ -1113,34 +1490,47 @@ int SrsSource::on_audio(SrsMessage* __audio) std::vector::iterator it; for (it = forwarders.begin(); it != forwarders.end(); ++it) { SrsForwarder* forwarder = *it; - if ((ret = forwarder->on_audio(msg.copy())) != ERROR_SUCCESS) { + if ((ret = forwarder->on_audio(msg)) != ERROR_SUCCESS) { srs_error("forwarder process audio message failed. ret=%d", ret); return ret; } } } - // cache the sequence header if h264 - // donot cache the sequence header to gop_cache, return here. - if (SrsFlvCodec::audio_is_sequence_header(msg.payload, msg.size)) { + // cache the sequence header of aac, or first packet of mp3. + // for example, the mp3 is used for hls to write the "right" audio codec. + // TODO: FIXME: to refine the stream info system. + bool is_aac_sequence_header = SrsFlvCodec::audio_is_sequence_header(msg->payload, msg->size); + if (is_aac_sequence_header || !cache_sh_audio) { srs_freep(cache_sh_audio); - cache_sh_audio = msg.copy(); - + cache_sh_audio = msg->copy(); + } + + // cache the sequence header if aac + // donot cache the sequence header to gop_cache, return here. + if (is_aac_sequence_header) { // parse detail audio codec SrsAvcAacCodec codec; SrsCodecSample sample; - if ((ret = codec.audio_aac_demux(msg.payload, msg.size, &sample)) != ERROR_SUCCESS) { + if ((ret = codec.audio_aac_demux(msg->payload, msg->size, &sample)) != ERROR_SUCCESS) { srs_error("source codec demux audio failed. ret=%d", ret); return ret; } static int flv_sample_sizes[] = {8, 16, 0}; static int flv_sound_types[] = {1, 2, 0}; + + // when got audio stream info. + SrsStatistic* stat = SrsStatistic::instance(); + if ((ret = stat->on_audio_info(_req, SrsCodecAudioAAC, sample.sound_rate, sample.sound_type, codec.aac_object)) != ERROR_SUCCESS) { + return ret; + } + srs_trace("%dB audio sh, " - "codec(%d, profile=%d, %dchannels, %dkbps, %dHZ), " + "codec(%d, profile=%s, %dchannels, %dkbps, %dHZ), " "flv(%dbits, %dchannels, %dHZ)", - msg.header.payload_length, codec.audio_codec_id, - codec.aac_profile, codec.aac_channels, + msg->size, codec.audio_codec_id, + srs_codec_aac_object2str(codec.aac_object).c_str(), codec.aac_channels, codec.audio_data_rate / 1000, aac_sample_rates[codec.aac_sample_rate], flv_sample_sizes[sample.sound_size], flv_sound_types[sample.sound_type], flv_sample_rates[sample.sound_rate]); @@ -1148,7 +1538,7 @@ int SrsSource::on_audio(SrsMessage* __audio) } // cache the last gop packets - if ((ret = gop_cache->cache(&msg)) != ERROR_SUCCESS) { + if ((ret = gop_cache->cache(msg)) != ERROR_SUCCESS) { srs_error("shrink gop cache failed. ret=%d", ret); return ret; } @@ -1157,33 +1547,46 @@ int SrsSource::on_audio(SrsMessage* __audio) // if atc, update the sequence header to abs time. if (atc) { if (cache_sh_audio) { - cache_sh_audio->header.timestamp = msg.header.timestamp; + cache_sh_audio->timestamp = msg->timestamp; } if (cache_metadata) { - cache_metadata->header.timestamp = msg.header.timestamp; + cache_metadata->timestamp = msg->timestamp; } } return ret; } -int SrsSource::on_video(SrsMessage* __video) +int SrsSource::on_video(SrsCommonMessage* shared_video) { int ret = ERROR_SUCCESS; - // convert __video to msg, user should not use __video again. - // the payload is transfer to msg, and set to NULL in __video. + // convert shared_video to msg, user should not use shared_video again. + // the payload is transfer to msg, and set to NULL in shared_video. SrsSharedPtrMessage msg; - if ((ret = msg.create(__video)) != ERROR_SUCCESS) { + if ((ret = msg.create(shared_video)) != ERROR_SUCCESS) { srs_error("initialize the video failed. ret=%d", ret); return ret; } - srs_verbose("initialize shared ptr video success."); + srs_info("Video dts=%"PRId64", size=%d", msg.timestamp, msg.size); + + if (!mix_correct) { + return on_video_imp(&msg); + } + + return do_mix_correct(&msg); +} + +int SrsSource::on_video_imp(SrsSharedPtrMessage* msg) +{ + int ret = ERROR_SUCCESS; + + srs_info("Video dts=%"PRId64", size=%d", msg->timestamp, msg->size); #ifdef SRS_AUTO_HLS - if ((ret = hls->on_video(msg.copy())) != ERROR_SUCCESS) { + if ((ret = hls->on_video(msg)) != ERROR_SUCCESS) { // apply the error strategy for hls. - // @see https://github.com/winlinvip/simple-rtmp-server/issues/264 + // @see https://github.com/simple-rtmp-server/srs/issues/264 std::string hls_error_strategy = _srs_config->get_hls_on_error(_req->vhost); if (hls_error_strategy == SRS_CONF_DEFAULT_HLS_ON_ERROR_IGNORE) { srs_warn("hls process video message failed, ignore and disable hls. ret=%d", ret); @@ -1195,7 +1598,7 @@ int SrsSource::on_video(SrsMessage* __video) ret = ERROR_SUCCESS; } else if (hls_error_strategy == SRS_CONF_DEFAULT_HLS_ON_ERROR_CONTINUE) { // compare the sequence header with video, continue when it's actually an sequence header. - if (ret == ERROR_HLS_DECODE_ERROR && cache_sh_video && cache_sh_video->size == msg.size) { + if (ret == ERROR_HLS_DECODE_ERROR && cache_sh_video && cache_sh_video->size == msg->size) { srs_warn("the video is actually a sequence header, ignore this packet."); ret = ERROR_SUCCESS; } else { @@ -1210,7 +1613,7 @@ int SrsSource::on_video(SrsMessage* __video) #endif #ifdef SRS_AUTO_DVR - if ((ret = dvr->on_video(msg.copy())) != ERROR_SUCCESS) { + if ((ret = dvr->on_video(msg)) != ERROR_SUCCESS) { srs_warn("dvr process video message failed, ignore and disable dvr. ret=%d", ret); // unpublish, ignore ret. @@ -1220,13 +1623,23 @@ int SrsSource::on_video(SrsMessage* __video) ret = ERROR_SUCCESS; } #endif + +#ifdef SRS_AUTO_HDS + if ((ret = hds->on_video(msg)) != ERROR_SUCCESS) { + srs_warn("hds process video message failed, ignore and disable dvr. ret=%d", ret); + + // unpublish, ignore ret. + hds->on_unpublish(); + // ignore. + ret = ERROR_SUCCESS; + } +#endif // copy to all consumer if (true) { for (int i = 0; i < (int)consumers.size(); i++) { SrsConsumer* consumer = consumers.at(i); - SrsSharedPtrMessage* copy = msg.copy(); - if ((ret = consumer->enqueue(copy, atc, sample_rate, frame_rate, jitter_algorithm)) != ERROR_SUCCESS) { + if ((ret = consumer->enqueue(msg, atc, sample_rate, frame_rate, jitter_algorithm)) != ERROR_SUCCESS) { srs_error("dispatch the video failed. ret=%d", ret); return ret; } @@ -1235,11 +1648,11 @@ int SrsSource::on_video(SrsMessage* __video) } // copy to all forwarders. - if (true) { + if (!forwarders.empty()) { std::vector::iterator it; for (it = forwarders.begin(); it != forwarders.end(); ++it) { SrsForwarder* forwarder = *it; - if ((ret = forwarder->on_video(msg.copy())) != ERROR_SUCCESS) { + if ((ret = forwarder->on_video(msg)) != ERROR_SUCCESS) { srs_error("forwarder process video message failed. ret=%d", ret); return ret; } @@ -1248,28 +1661,35 @@ int SrsSource::on_video(SrsMessage* __video) // cache the sequence header if h264 // donot cache the sequence header to gop_cache, return here. - if (SrsFlvCodec::video_is_sequence_header(msg.payload, msg.size)) { + if (SrsFlvCodec::video_is_sequence_header(msg->payload, msg->size)) { srs_freep(cache_sh_video); - cache_sh_video = msg.copy(); + cache_sh_video = msg->copy(); // parse detail audio codec SrsAvcAacCodec codec; SrsCodecSample sample; - if ((ret = codec.video_avc_demux(msg.payload, msg.size, &sample)) != ERROR_SUCCESS) { + if ((ret = codec.video_avc_demux(msg->payload, msg->size, &sample)) != ERROR_SUCCESS) { srs_error("source codec demux video failed. ret=%d", ret); return ret; } + // when got video stream info. + SrsStatistic* stat = SrsStatistic::instance(); + if ((ret = stat->on_video_info(_req, SrsCodecVideoAVC, codec.avc_profile, codec.avc_level)) != ERROR_SUCCESS) { + return ret; + } + srs_trace("%dB video sh, " - "codec(%d, profile=%d, level=%d, %dx%d, %dkbps, %dfps, %ds)", - msg.header.payload_length, codec.video_codec_id, - codec.avc_profile, codec.avc_level, codec.width, codec.height, + "codec(%d, profile=%s, level=%s, %dx%d, %dkbps, %dfps, %ds)", + msg->size, codec.video_codec_id, + srs_codec_avc_profile2str(codec.avc_profile).c_str(), + srs_codec_avc_level2str(codec.avc_level).c_str(), codec.width, codec.height, codec.video_data_rate / 1000, codec.frame_rate, codec.duration); return ret; } // cache the last gop packets - if ((ret = gop_cache->cache(&msg)) != ERROR_SUCCESS) { + if ((ret = gop_cache->cache(msg)) != ERROR_SUCCESS) { srs_error("gop cache msg failed. ret=%d", ret); return ret; } @@ -1278,17 +1698,40 @@ int SrsSource::on_video(SrsMessage* __video) // if atc, update the sequence header to abs time. if (atc) { if (cache_sh_video) { - cache_sh_video->header.timestamp = msg.header.timestamp; + cache_sh_video->timestamp = msg->timestamp; } if (cache_metadata) { - cache_metadata->header.timestamp = msg.header.timestamp; + cache_metadata->timestamp = msg->timestamp; } } return ret; } -int SrsSource::on_aggregate(SrsMessage* msg) +int SrsSource::do_mix_correct(SrsSharedPtrMessage* msg) +{ + int ret = ERROR_SUCCESS; + + // insert msg to the queue. + mix_queue->push(msg->copy()); + + // fetch someone from mix queue. + SrsSharedPtrMessage* m = mix_queue->pop(); + if (!m) { + return ret; + } + SrsAutoFree(SrsSharedPtrMessage, m); + + // consume the monotonically increase message. + if (m->is_audio()) { + return on_audio_imp(m); + } + + srs_assert(m->is_video()); + return on_video_imp(m); +} + +int SrsSource::on_aggregate(SrsCommonMessage* msg) { int ret = ERROR_SUCCESS; @@ -1297,6 +1740,9 @@ int SrsSource::on_aggregate(SrsMessage* msg) return ret; } + // the aggregate message always use abs time. + int delta = -1; + while (!stream->empty()) { if (!stream->require(1)) { ret = ERROR_RTMP_AGGREGATE; @@ -1335,6 +1781,12 @@ int SrsSource::on_aggregate(SrsMessage* msg) timestamp |= time_h<<24; timestamp &= 0x7FFFFFFF; + // adjust abs timestamp in aggregate msg. + if (delta < 0) { + delta = (int)msg->header.timestamp - (int)timestamp; + } + timestamp += delta; + if (!stream->require(3)) { ret = ERROR_RTMP_AGGREGATE; srs_error("invalid aggregate message stream_id. ret=%d", ret); @@ -1349,8 +1801,7 @@ int SrsSource::on_aggregate(SrsMessage* msg) } // to common message. - SrsCommonMessage __o; - SrsMessage& o = __o; + SrsCommonMessage o; o.header.message_type = type; o.header.payload_length = data_size; @@ -1420,6 +1871,9 @@ int SrsSource::on_publish() // save its id to srouce id. on_source_id_changed(_srs_context->get_id()); + // reset the mix queue. + mix_queue->clear(); + // create forwarders if ((ret = create_forwarders()) != ERROR_SUCCESS) { srs_error("create forwarders failed. ret=%d", ret); @@ -1450,6 +1904,20 @@ int SrsSource::on_publish() } #endif +#ifdef SRS_AUTO_HDS + if ((ret = hds->on_publish(_req)) != ERROR_SUCCESS) { + srs_error("start hds failed. ret=%d", ret); + return ret; + } +#endif + + // notify the handler. + srs_assert(handler); + if ((ret = handler->on_publish(this, _req)) != ERROR_SUCCESS) { + srs_error("handle on publish failed. ret=%d", ret); + return ret; + } + return ret; } @@ -1470,6 +1938,10 @@ void SrsSource::on_unpublish() dvr->on_unpublish(); #endif +#ifdef SRS_AUTO_HDS + hds->on_unpublish(); +#endif + gop_cache->clear(); srs_freep(cache_metadata); @@ -1483,9 +1955,13 @@ void SrsSource::on_unpublish() _can_publish = true; _source_id = -1; + + // notify the handler. + srs_assert(handler); + handler->on_unpublish(this, _req); } - int SrsSource::create_consumer(SrsConsumer*& consumer) +int SrsSource::create_consumer(SrsConsumer*& consumer, bool ds, bool dm, bool dg) { int ret = ERROR_SUCCESS; @@ -1498,13 +1974,13 @@ void SrsSource::on_unpublish() // if atc, update the sequence header to gop cache time. if (atc && !gop_cache->empty()) { if (cache_metadata) { - cache_metadata->header.timestamp = gop_cache->start_time(); + cache_metadata->timestamp = gop_cache->start_time(); } if (cache_sh_video) { - cache_sh_video->header.timestamp = gop_cache->start_time(); + cache_sh_video->timestamp = gop_cache->start_time(); } if (cache_sh_audio) { - cache_sh_audio->header.timestamp = gop_cache->start_time(); + cache_sh_audio->timestamp = gop_cache->start_time(); } } @@ -1513,31 +1989,38 @@ void SrsSource::on_unpublish() SrsRtmpJitterAlgorithm ag = jitter_algorithm; // copy metadata. - if (cache_metadata && (ret = consumer->enqueue(cache_metadata->copy(), atc, tba, tbv, ag)) != ERROR_SUCCESS) { + if (dm && cache_metadata && (ret = consumer->enqueue(cache_metadata, atc, tba, tbv, ag)) != ERROR_SUCCESS) { srs_error("dispatch metadata failed. ret=%d", ret); return ret; } srs_info("dispatch metadata success"); // copy sequence header - if (cache_sh_video && (ret = consumer->enqueue(cache_sh_video->copy(), atc, tba, tbv, ag)) != ERROR_SUCCESS) { - srs_error("dispatch video sequence header failed. ret=%d", ret); - return ret; - } - srs_info("dispatch video sequence header success"); - - if (cache_sh_audio && (ret = consumer->enqueue(cache_sh_audio->copy(), atc, tba, tbv, ag)) != ERROR_SUCCESS) { + // copy audio sequence first, for hls to fast parse the "right" audio codec. + // @see https://github.com/simple-rtmp-server/srs/issues/301 + if (ds && cache_sh_audio && (ret = consumer->enqueue(cache_sh_audio, atc, tba, tbv, ag)) != ERROR_SUCCESS) { srs_error("dispatch audio sequence header failed. ret=%d", ret); return ret; } srs_info("dispatch audio sequence header success"); + + if (ds && cache_sh_video && (ret = consumer->enqueue(cache_sh_video, atc, tba, tbv, ag)) != ERROR_SUCCESS) { + srs_error("dispatch video sequence header failed. ret=%d", ret); + return ret; + } + srs_info("dispatch video sequence header success"); // copy gop cache to client. - if ((ret = gop_cache->dump(consumer, atc, tba, tbv, ag)) != ERROR_SUCCESS) { + if (dg && (ret = gop_cache->dump(consumer, atc, tba, tbv, ag)) != ERROR_SUCCESS) { return ret; } - srs_trace("create consumer, queue_size=%.2f, tba=%d, tbv=%d", queue_size, sample_rate, frame_rate); + // print status. + if (dg) { + srs_trace("create consumer, queue_size=%.2f, tba=%d, tbv=%d", queue_size, sample_rate, frame_rate); + } else { + srs_trace("create consumer, ignore gop cache, tba=%d, tbv=%d", sample_rate, frame_rate); + } return ret; } @@ -1571,7 +2054,7 @@ int SrsSource::on_edge_start_publish() return publish_edge->on_client_publish(); } -int SrsSource::on_edge_proxy_publish(SrsMessage* msg) +int SrsSource::on_edge_proxy_publish(SrsCommonMessage* msg) { return publish_edge->on_proxy_publish(msg); } @@ -1623,4 +2106,3 @@ void SrsSource::destroy_forwarders() forwarders.clear(); } - diff --git a/trunk/src/app/srs_app_source.hpp b/trunk/src/app/srs_app_source.hpp index 1759ebf7e7..4d4799f704 100644 --- a/trunk/src/app/srs_app_source.hpp +++ b/trunk/src/app/srs_app_source.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -36,11 +36,13 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include +#include +class SrsConsumer; class SrsPlayEdge; class SrsPublishEdge; class SrsSource; -class SrsMessage; +class SrsCommonMessage; class SrsOnMetaDataPacket; class SrsSharedPtrMessage; class SrsForwarder; @@ -48,6 +50,7 @@ class SrsRequest; class SrsStSocket; class SrsRtmpServer; class SrsEdgeProxyContext; +class SrsMessageArray; #ifdef SRS_AUTO_HLS class SrsHls; #endif @@ -58,6 +61,10 @@ class SrsDvr; class SrsEncoder; #endif class SrsStream; +class ISrsHlsHandler; +#ifdef SRS_AUTO_HDS +class SrsHds; +#endif /** * the time jitter algorithm: @@ -100,6 +107,34 @@ class SrsRtmpJitter virtual int get_time(); }; +#ifdef SRS_PERF_QUEUE_FAST_VECTOR +/** +* to alloc and increase fixed space, +* fast remove and insert for msgs sender. +* @see https://github.com/simple-rtmp-server/srs/issues/251 +*/ +class SrsFastVector +{ +private: + SrsSharedPtrMessage** msgs; + int nb_msgs; + int count; +public: + SrsFastVector(); + virtual ~SrsFastVector(); +public: + virtual int size(); + virtual int begin(); + virtual int end(); + virtual SrsSharedPtrMessage** data(); + virtual SrsSharedPtrMessage* at(int index); + virtual void clear(); + virtual void erase(int _begin, int _end); + virtual void push_back(SrsSharedPtrMessage* msg); + virtual void free(); +}; +#endif + /** * the message queue for the consumer(client), forwarder. * we limit the size in seconds, drop old messages(the whole gop) if full. @@ -107,14 +142,27 @@ class SrsRtmpJitter class SrsMessageQueue { private: + bool _ignore_shrink; int64_t av_start_time; int64_t av_end_time; int queue_size_ms; +#ifdef SRS_PERF_QUEUE_FAST_VECTOR + SrsFastVector msgs; +#else std::vector msgs; +#endif public: - SrsMessageQueue(); + SrsMessageQueue(bool ignore_shrink = false); virtual ~SrsMessageQueue(); public: + /** + * get the size of queue. + */ + virtual int size(); + /** + * get the duration of queue. + */ + virtual int duration(); /** * set the queue size * @param queue_size the queue size in seconds. @@ -124,15 +172,21 @@ class SrsMessageQueue /** * enqueue the message, the timestamp always monotonically. * @param msg, the msg to enqueue, user never free it whatever the return code. + * @param is_overflow, whether overflow and shrinked. NULL to ignore. */ - virtual int enqueue(SrsSharedPtrMessage* msg); + virtual int enqueue(SrsSharedPtrMessage* msg, bool* is_overflow = NULL); /** * get packets in consumer queue. - * @pmsgs SrsMessages*[], used to store the msgs, user must alloc it. + * @pmsgs SrsSharedPtrMessage*[], used to store the msgs, user must alloc it. * @count the count in array, output param. * @max_count the max count to dequeue, must be positive. */ virtual int dump_packets(int max_count, SrsSharedPtrMessage** pmsgs, int& count); + /** + * dumps packets to consumer, use specified args. + * @remark the atc/tba/tbv/ag are same to SrsConsumer.enqueue(). + */ + virtual int dump_packets(SrsConsumer* consumer, bool atc, int tba, int tbv, SrsRtmpJitterAlgorithm ag); private: /** * remove a gop from the front. @@ -154,6 +208,14 @@ class SrsConsumer bool paused; // when source id changed, notice all consumers bool should_update_source_id; +#ifdef SRS_PERF_QUEUE_COND_WAIT + // the cond wait for mw. + // @see https://github.com/simple-rtmp-server/srs/issues/251 + st_cond_t mw_wait; + bool mw_waiting; + int mw_min_msgs; + int mw_duration; +#endif public: SrsConsumer(SrsSource* _source); virtual ~SrsConsumer(); @@ -173,6 +235,7 @@ class SrsConsumer virtual int get_time(); /** * enqueue an shared ptr message. + * @param shared_msg, directly ptr, copy it if need to save it. * @param whether atc, donot use jitter correct if true. * @param tba timebase of audio. * used to calc the audio time delta if time-jitter detected. @@ -180,14 +243,27 @@ class SrsConsumer * used to calc the video time delta if time-jitter detected. * @param ag the algorithm of time jitter. */ - virtual int enqueue(SrsSharedPtrMessage* msg, bool atc, int tba, int tbv, SrsRtmpJitterAlgorithm ag); + virtual int enqueue(SrsSharedPtrMessage* shared_msg, bool atc, int tba, int tbv, SrsRtmpJitterAlgorithm ag); /** * get packets in consumer queue. - * @pmsgs SrsMessages*[], used to store the msgs, user must alloc it. - * @count the count in array, output param. - * @max_count the max count to dequeue, must be positive. + * @param msgs the msgs array to dump packets to send. + * @param count the count in array, output param. */ - virtual int dump_packets(int max_count, SrsSharedPtrMessage** pmsgs, int& count); + virtual int dump_packets(SrsMessageArray* msgs, int& count); +#ifdef SRS_PERF_QUEUE_COND_WAIT + /** + * wait for messages incomming, atleast nb_msgs and in duration. + * @param nb_msgs the messages count to wait. + * @param duration the messgae duration to wait. + */ + virtual void wait(int nb_msgs, int duration); + /** + * when the consumer(for player) got msg from recv thread, + * it must be processed for maybe it's a close msg, so the cond + * wait must be wakeup. + */ + virtual void wakeup(); +#endif /** * when client send the pause message. */ @@ -222,7 +298,7 @@ class SrsGopCache * * @remark, it is ok for performance, for when we clear the gop cache, * gop cache is disabled for pure audio stream. - * @see: https://github.com/winlinvip/simple-rtmp-server/issues/124 + * @see: https://github.com/simple-rtmp-server/srs/issues/124 */ int audio_after_last_video_count; /** @@ -241,8 +317,9 @@ class SrsGopCache * only for h264 codec * 1. cache the gop when got h264 video packet. * 2. clear gop when got keyframe. + * @param shared_msg, directly ptr, copy it if need to save it. */ - virtual int cache(SrsSharedPtrMessage* msg); + virtual int cache(SrsSharedPtrMessage* shared_msg); /** * clear the gop cache. */ @@ -270,6 +347,44 @@ class SrsGopCache virtual bool pure_audio(); }; +/** +* the handler to handle the event of srs source. +* for example, the http flv streaming module handle the event and +* mount http when rtmp start publishing. +*/ +class ISrsSourceHandler +{ +public: + ISrsSourceHandler(); + virtual ~ISrsSourceHandler(); +public: + /** + * when stream start publish, mount stream. + */ + virtual int on_publish(SrsSource* s, SrsRequest* r) = 0; + /** + * when stream stop publish, unmount stream. + */ + virtual void on_unpublish(SrsSource* s, SrsRequest* r) = 0; +}; + +/** + * the mix queue to correct the timestamp for mix_correct algorithm. + */ +class SrsMixQueue +{ +private: + u_int32_t nb_videos; + std::multimap msgs; +public: + SrsMixQueue(); + virtual ~SrsMixQueue(); +public: + virtual void clear(); + virtual void push(SrsSharedPtrMessage* msg); + virtual SrsSharedPtrMessage* pop(); +}; + /** * live streaming source. */ @@ -280,11 +395,17 @@ class SrsSource : public ISrsReloadHandler public: /** * find stream by vhost/app/stream. - * @param req the client request. - * @param ppsource the matched source, if success never be NULL. - * @remark stream_url should without port and schema. + * @param r the client request. + * @param h the event handler for source. + * @param hh the event handler for hls. + * @param pps the matched source, if success never be NULL. */ - static int find(SrsRequest* req, SrsSource** ppsource); + static int create(SrsRequest* r, ISrsSourceHandler* h, ISrsHlsHandler* hh, SrsSource** pps); + /** + * get the exists source, NULL when not exists. + * update the request and return the exists source. + */ + static SrsSource* fetch(SrsRequest* r); /** * when system exit, destroy the sources, * for gmc to analysis mem leaks. @@ -303,6 +424,9 @@ class SrsSource : public ISrsReloadHandler std::vector consumers; // the time jitter algorithm for vhost. SrsRtmpJitterAlgorithm jitter_algorithm; + // whether use interlaced/mixed algorithm to correct timestamp. + bool mix_correct; + SrsMixQueue* mix_queue; // hls handler. #ifdef SRS_AUTO_HLS SrsHls* hls; @@ -314,6 +438,9 @@ class SrsSource : public ISrsReloadHandler // transcoding handler. #ifdef SRS_AUTO_TRANSCODE SrsEncoder* encoder; +#endif +#ifdef SRS_AUTO_HDS + SrsHds *hds; #endif // edge control service SrsPlayEdge* play_edge; @@ -324,6 +451,8 @@ class SrsSource : public ISrsReloadHandler std::vector forwarders; // for aggregate message SrsStream* aggregate_stream; + // the event handler. + ISrsSourceHandler* handler; private: /** * the sample rate of audio in metadata. @@ -351,22 +480,24 @@ class SrsSource : public ISrsReloadHandler // the cached audio sequence header. SrsSharedPtrMessage* cache_sh_audio; public: - /** - * @param _req the client request object, - * this object will deep copy it for reload. - */ - SrsSource(SrsRequest* req); + SrsSource(); virtual ~SrsSource(); +// initialize, get and setter. public: - virtual int initialize(); + /** + * initialize the hls with handlers. + */ + virtual int initialize(SrsRequest* r, ISrsSourceHandler* h, ISrsHlsHandler* hh); // interface ISrsReloadHandler public: virtual int on_reload_vhost_atc(std::string vhost); virtual int on_reload_vhost_gop_cache(std::string vhost); virtual int on_reload_vhost_queue_length(std::string vhost); virtual int on_reload_vhost_time_jitter(std::string vhost); + virtual int on_reload_vhost_mix_correct(std::string vhost); virtual int on_reload_vhost_forward(std::string vhost); virtual int on_reload_vhost_hls(std::string vhost); + virtual int on_reload_vhost_hds(std::string vhost); virtual int on_reload_vhost_dvr(std::string vhost); virtual int on_reload_vhost_transcode(std::string vhost); // for the tools callback @@ -384,10 +515,19 @@ class SrsSource : public ISrsReloadHandler // logic data methods public: virtual bool can_publish(); - virtual int on_meta_data(SrsMessage* msg, SrsOnMetaDataPacket* metadata); - virtual int on_audio(SrsMessage* audio); - virtual int on_video(SrsMessage* video); - virtual int on_aggregate(SrsMessage* msg); + virtual int on_meta_data(SrsCommonMessage* msg, SrsOnMetaDataPacket* metadata); +public: + virtual int on_audio(SrsCommonMessage* audio); +private: + virtual int on_audio_imp(SrsSharedPtrMessage* audio); +public: + virtual int on_video(SrsCommonMessage* video); +private: + virtual int on_video_imp(SrsSharedPtrMessage* video); +private: + virtual int do_mix_correct(SrsSharedPtrMessage* msg); +public: + virtual int on_aggregate(SrsCommonMessage* msg); /** * the pre-publish is we are very sure we are * trying to publish stream, please lock the resource, @@ -404,7 +544,17 @@ class SrsSource : public ISrsReloadHandler virtual void on_unpublish(); // consumer methods public: - virtual int create_consumer(SrsConsumer*& consumer); + /** + * create consumer and dumps packets in cache. + * @param consumer, output the create consumer. + * @param ds, whether dumps the sequence header. + * @param dm, whether dumps the metadata. + * @param dg, whether dumps the gop cache. + */ + virtual int create_consumer( + SrsConsumer*& consumer, + bool ds = true, bool dm = true, bool dg = true + ); virtual void on_consumer_destroy(SrsConsumer* consumer); virtual void set_cache(bool enabled); // internal @@ -414,7 +564,7 @@ class SrsSource : public ISrsReloadHandler // for edge, when publish edge stream, check the state virtual int on_edge_start_publish(); // for edge, proxy the publish - virtual int on_edge_proxy_publish(SrsMessage* msg); + virtual int on_edge_proxy_publish(SrsCommonMessage* msg); // for edge, proxy stop publish virtual void on_edge_proxy_unpublish(); private: diff --git a/trunk/src/app/srs_app_st.cpp b/trunk/src/app/srs_app_st.cpp index 7c0e41678e..bf73a4bb20 100644 --- a/trunk/src/app/srs_app_st.cpp +++ b/trunk/src/app/srs_app_st.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -26,7 +26,9 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include +#ifdef __linux__ #include + bool srs_st_epoll_is_supported(void) { struct epoll_event ev; @@ -38,33 +40,37 @@ bool srs_st_epoll_is_supported(void) return (errno != ENOSYS); } +#endif int srs_init_st() { int ret = ERROR_SUCCESS; +#ifdef __linux__ // check epoll, some old linux donot support epoll. - // @see https://github.com/winlinvip/simple-rtmp-server/issues/162 + // @see https://github.com/simple-rtmp-server/srs/issues/162 if (!srs_st_epoll_is_supported()) { ret = ERROR_ST_SET_EPOLL; - srs_error("epoll required. ret=%d", ret); + srs_error("epoll required on Linux. ret=%d", ret); return ret; } +#endif - // use linux epoll. + // Select the best event system available on the OS. In Linux this is + // epoll(). On BSD it will be kqueue. if (st_set_eventsys(ST_EVENTSYS_ALT) == -1) { ret = ERROR_ST_SET_EPOLL; - srs_error("st_set_eventsys use linux epoll failed. ret=%d", ret); + srs_error("st_set_eventsys use %s failed. ret=%d", st_get_eventsys_name(), ret); return ret; } - srs_verbose("st_set_eventsys use linux epoll success"); - + srs_trace("st_set_eventsys to %s", st_get_eventsys_name()); + if(st_init() != 0){ ret = ERROR_ST_INITIALIZE; srs_error("st_init failed. ret=%d", ret); return ret; } - srs_verbose("st_init success"); + srs_trace("st_init success, use %s", st_get_eventsys_name()); return ret; } diff --git a/trunk/src/app/srs_app_st.hpp b/trunk/src/app/srs_app_st.hpp index 366abca0ae..545547058b 100644 --- a/trunk/src/app/srs_app_st.hpp +++ b/trunk/src/app/srs_app_st.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/trunk/src/app/srs_app_st_socket.cpp b/trunk/src/app/srs_app_st_socket.cpp index 92618fdfcc..d4da0369cc 100644 --- a/trunk/src/app/srs_app_st_socket.cpp +++ b/trunk/src/app/srs_app_st_socket.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -84,7 +84,7 @@ int SrsStSocket::read(void* buf, size_t size, ssize_t* nread) // (a value of 0 means the network connection is closed or end of file is reached). // Otherwise, a value of -1 is returned and errno is set to indicate the error. if (nb_read <= 0) { - // @see https://github.com/winlinvip/simple-rtmp-server/issues/200 + // @see https://github.com/simple-rtmp-server/srs/issues/200 if (nb_read < 0 && errno == ETIME) { return ERROR_SOCKET_TIMEOUT; } @@ -114,7 +114,7 @@ int SrsStSocket::read_fully(void* buf, size_t size, ssize_t* nread) // (a value less than nbyte means the network connection is closed or end of file is reached) // Otherwise, a value of -1 is returned and errno is set to indicate the error. if (nb_read != (ssize_t)size) { - // @see https://github.com/winlinvip/simple-rtmp-server/issues/200 + // @see https://github.com/simple-rtmp-server/srs/issues/200 if (nb_read < 0 && errno == ETIME) { return ERROR_SOCKET_TIMEOUT; } @@ -143,7 +143,7 @@ int SrsStSocket::write(void* buf, size_t size, ssize_t* nwrite) // On success a non-negative integer equal to nbyte is returned. // Otherwise, a value of -1 is returned and errno is set to indicate the error. if (nb_write <= 0) { - // @see https://github.com/winlinvip/simple-rtmp-server/issues/200 + // @see https://github.com/simple-rtmp-server/srs/issues/200 if (nb_write < 0 && errno == ETIME) { return ERROR_SOCKET_TIMEOUT; } @@ -168,7 +168,7 @@ int SrsStSocket::writev(const iovec *iov, int iov_size, ssize_t* nwrite) // On success a non-negative integer equal to nbyte is returned. // Otherwise, a value of -1 is returned and errno is set to indicate the error. if (nb_write <= 0) { - // @see https://github.com/winlinvip/simple-rtmp-server/issues/200 + // @see https://github.com/simple-rtmp-server/srs/issues/200 if (nb_write < 0 && errno == ETIME) { return ERROR_SOCKET_TIMEOUT; } diff --git a/trunk/src/app/srs_app_st_socket.hpp b/trunk/src/app/srs_app_st_socket.hpp index e0f2531831..6e074fb0eb 100644 --- a/trunk/src/app/srs_app_st_socket.hpp +++ b/trunk/src/app/srs_app_st_socket.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -31,7 +31,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include -#include +#include /** * the socket provides TCP socket over st, diff --git a/trunk/src/app/srs_app_statistic.cpp b/trunk/src/app/srs_app_statistic.cpp new file mode 100644 index 0000000000..2df8c74601 --- /dev/null +++ b/trunk/src/app/srs_app_statistic.cpp @@ -0,0 +1,382 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include + +#include +#include +using namespace std; + +#include +#include +#include +#include +#include +#include + +int64_t srs_gvid = getpid(); + +int64_t srs_generate_id() +{ + return srs_gvid++; +} + +SrsStatisticVhost::SrsStatisticVhost() +{ + id = srs_generate_id(); + + kbps = new SrsKbps(); + kbps->set_io(NULL, NULL); +} + +SrsStatisticVhost::~SrsStatisticVhost() +{ + srs_freep(kbps); +} + +SrsStatisticStream::SrsStatisticStream() +{ + id = srs_generate_id(); + vhost = NULL; + + has_video = false; + vcodec = SrsCodecVideoReserved; + avc_profile = SrsAvcProfileReserved; + avc_level = SrsAvcLevelReserved; + + has_audio = false; + acodec = SrsCodecAudioReserved1; + asample_rate = SrsCodecAudioSampleRateReserved; + asound_type = SrsCodecAudioSoundTypeReserved; + aac_object = SrsAacObjectTypeReserved; + + kbps = new SrsKbps(); + kbps->set_io(NULL, NULL); +} + +SrsStatisticStream::~SrsStatisticStream() +{ + srs_freep(kbps); +} + +void SrsStatisticStream::close() +{ + has_video = false; + has_audio = false; +} + +SrsStatistic* SrsStatistic::_instance = new SrsStatistic(); + +SrsStatistic::SrsStatistic() +{ + _server_id = srs_generate_id(); + + kbps = new SrsKbps(); + kbps->set_io(NULL, NULL); +} + +SrsStatistic::~SrsStatistic() +{ + srs_freep(kbps); + + if (true) { + std::map::iterator it; + for (it = vhosts.begin(); it != vhosts.end(); it++) { + SrsStatisticVhost* vhost = it->second; + srs_freep(vhost); + } + } + if (true) { + std::map::iterator it; + for (it = streams.begin(); it != streams.end(); it++) { + SrsStatisticStream* stream = it->second; + srs_freep(stream); + } + } + if (true) { + std::map::iterator it; + for (it = clients.begin(); it != clients.end(); it++) { + SrsStatisticClient* client = it->second; + srs_freep(client); + } + } +} + +SrsStatistic* SrsStatistic::instance() +{ + return _instance; +} + +int SrsStatistic::on_video_info(SrsRequest* req, + SrsCodecVideo vcodec, SrsAvcProfile avc_profile, SrsAvcLevel avc_level +) { + int ret = ERROR_SUCCESS; + + SrsStatisticVhost* vhost = create_vhost(req); + SrsStatisticStream* stream = create_stream(vhost, req); + + stream->has_video = true; + stream->vcodec = vcodec; + stream->avc_profile = avc_profile; + stream->avc_level = avc_level; + + return ret; +} + +int SrsStatistic::on_audio_info(SrsRequest* req, + SrsCodecAudio acodec, SrsCodecAudioSampleRate asample_rate, SrsCodecAudioSoundType asound_type, + SrsAacObjectType aac_object +) { + int ret = ERROR_SUCCESS; + + SrsStatisticVhost* vhost = create_vhost(req); + SrsStatisticStream* stream = create_stream(vhost, req); + + stream->has_audio = true; + stream->acodec = acodec; + stream->asample_rate = asample_rate; + stream->asound_type = asound_type; + stream->aac_object = aac_object; + + return ret; +} + +void SrsStatistic::on_stream_close(SrsRequest* req) +{ + SrsStatisticVhost* vhost = create_vhost(req); + SrsStatisticStream* stream = create_stream(vhost, req); + + stream->close(); +} + +int SrsStatistic::on_client(int id, SrsRequest* req) +{ + int ret = ERROR_SUCCESS; + + SrsStatisticVhost* vhost = create_vhost(req); + SrsStatisticStream* stream = create_stream(vhost, req); + + // create client if not exists + SrsStatisticClient* client = NULL; + if (clients.find(id) == clients.end()) { + client = new SrsStatisticClient(); + client->stream = stream; + clients[id] = client; + } else { + client = clients[id]; + } + + return ret; +} + +void SrsStatistic::on_disconnect(int id) +{ + std::map::iterator it; + it = clients.find(id); + if (it != clients.end()) { + SrsStatisticClient* client = it->second; + srs_freep(client); + clients.erase(it); + } +} + +void SrsStatistic::kbps_add_delta(SrsConnection* conn) +{ + int id = conn->srs_id(); + if (clients.find(id) == clients.end()) { + return; + } + + SrsStatisticClient* client = clients[id]; + + // resample the kbps to collect the delta. + conn->resample(); + + // add delta of connection to kbps. + // for next sample() of server kbps can get the stat. + kbps->add_delta(conn); + client->stream->kbps->add_delta(conn); + client->stream->vhost->kbps->add_delta(conn); + + // cleanup the delta. + conn->cleanup(); +} + +SrsKbps* SrsStatistic::kbps_sample() +{ + kbps->sample(); + if (true) { + std::map::iterator it; + for (it = vhosts.begin(); it != vhosts.end(); it++) { + SrsStatisticVhost* vhost = it->second; + vhost->kbps->sample(); + } + } + if (true) { + std::map::iterator it; + for (it = streams.begin(); it != streams.end(); it++) { + SrsStatisticStream* stream = it->second; + stream->kbps->sample(); + } + } + + return kbps; +} + +int64_t SrsStatistic::server_id() +{ + return _server_id; +} + +int SrsStatistic::dumps_vhosts(stringstream& ss) +{ + int ret = ERROR_SUCCESS; + + ss << SRS_JARRAY_START; + std::map::iterator it; + for (it = vhosts.begin(); it != vhosts.end(); it++) { + SrsStatisticVhost* vhost = it->second; + if (it != vhosts.begin()) { + ss << SRS_JFIELD_CONT; + } + + // dumps the config of vhost. + bool hls_enabled = _srs_config->get_hls_enabled(vhost->vhost); + + ss << SRS_JOBJECT_START + << SRS_JFIELD_ORG("id", vhost->id) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("name", vhost->vhost) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("send_bytes", vhost->kbps->get_send_bytes()) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("recv_bytes", vhost->kbps->get_recv_bytes()) << SRS_JFIELD_CONT + << SRS_JFIELD_NAME("hls") << SRS_JOBJECT_START + << SRS_JFIELD_BOOL("enabled", hls_enabled) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("fragment", _srs_config->get_hls_fragment(vhost->vhost)) + << SRS_JOBJECT_END + << SRS_JOBJECT_END; + } + ss << SRS_JARRAY_END; + + return ret; +} + +int SrsStatistic::dumps_streams(stringstream& ss) +{ + int ret = ERROR_SUCCESS; + + ss << SRS_JARRAY_START; + std::map::iterator it; + for (it = streams.begin(); it != streams.end(); it++) { + SrsStatisticStream* stream = it->second; + if (it != streams.begin()) { + ss << SRS_JFIELD_CONT; + } + + int client_num = 0; + std::map::iterator it_client; + for (it_client = clients.begin(); it_client != clients.end(); it_client++) { + SrsStatisticClient* client = it_client->second; + if (client->stream == stream) { + client_num++; + } + } + + ss << SRS_JOBJECT_START + << SRS_JFIELD_ORG("id", stream->id) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("name", stream->stream) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("vhost", stream->vhost->id) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("app", stream->app) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("clients", client_num) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("send_bytes", stream->kbps->get_send_bytes()) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("recv_bytes", stream->kbps->get_recv_bytes()) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("live_ms", srs_get_system_time_ms()) << SRS_JFIELD_CONT; + + if (!stream->has_video) { + ss << SRS_JFIELD_NULL("video") << SRS_JFIELD_CONT; + } else { + ss << SRS_JFIELD_NAME("video") + << SRS_JOBJECT_START + << SRS_JFIELD_STR("codec", srs_codec_video2str(stream->vcodec)) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("profile", srs_codec_avc_profile2str(stream->avc_profile)) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("level", srs_codec_avc_level2str(stream->avc_level)) + << SRS_JOBJECT_END + << SRS_JFIELD_CONT; + } + + if (!stream->has_audio) { + ss << SRS_JFIELD_NULL("audio"); + } else { + ss << SRS_JFIELD_NAME("audio") + << SRS_JOBJECT_START + << SRS_JFIELD_STR("codec", srs_codec_audio2str(stream->acodec)) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("sample_rate", (int)flv_sample_rates[stream->asample_rate]) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("channel", (int)stream->asound_type + 1) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("profile", srs_codec_aac_object2str(stream->aac_object)) + << SRS_JOBJECT_END; + } + + ss << SRS_JOBJECT_END; + } + ss << SRS_JARRAY_END; + + return ret; +} + +SrsStatisticVhost* SrsStatistic::create_vhost(SrsRequest* req) +{ + SrsStatisticVhost* vhost = NULL; + + // create vhost if not exists. + if (vhosts.find(req->vhost) == vhosts.end()) { + vhost = new SrsStatisticVhost(); + vhost->vhost = req->vhost; + vhosts[req->vhost] = vhost; + return vhost; + } + + vhost = vhosts[req->vhost]; + + return vhost; +} + +SrsStatisticStream* SrsStatistic::create_stream(SrsStatisticVhost* vhost, SrsRequest* req) +{ + std::string url = req->get_stream_url(); + + SrsStatisticStream* stream = NULL; + + // create stream if not exists. + if (streams.find(url) == streams.end()) { + stream = new SrsStatisticStream(); + stream->vhost = vhost; + stream->stream = req->stream; + stream->app = req->app; + stream->url = url; + streams[url] = stream; + return stream; + } + + stream = streams[url]; + + return stream; +} + diff --git a/trunk/src/app/srs_app_statistic.hpp b/trunk/src/app/srs_app_statistic.hpp new file mode 100644 index 0000000000..66daaa1309 --- /dev/null +++ b/trunk/src/app/srs_app_statistic.hpp @@ -0,0 +1,183 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef SRS_APP_STATISTIC_HPP +#define SRS_APP_STATISTIC_HPP + +/* +#include +*/ + +#include + +#include +#include + +#include + +class SrsKbps; +class SrsRequest; +class SrsConnection; + +struct SrsStatisticVhost +{ +public: + int64_t id; + std::string vhost; +public: + /** + * vhost total kbps. + */ + SrsKbps* kbps; +public: + SrsStatisticVhost(); + virtual ~SrsStatisticVhost(); +}; + +struct SrsStatisticStream +{ +public: + int64_t id; + SrsStatisticVhost* vhost; + std::string app; + std::string stream; + std::string url; +public: + /** + * stream total kbps. + */ + SrsKbps* kbps; +public: + bool has_video; + SrsCodecVideo vcodec; + // profile_idc, H.264-AVC-ISO_IEC_14496-10.pdf, page 45. + SrsAvcProfile avc_profile; + // level_idc, H.264-AVC-ISO_IEC_14496-10.pdf, page 45. + SrsAvcLevel avc_level; +public: + bool has_audio; + SrsCodecAudio acodec; + SrsCodecAudioSampleRate asample_rate; + SrsCodecAudioSoundType asound_type; + /** + * audio specified + * audioObjectType, in 1.6.2.1 AudioSpecificConfig, page 33, + * 1.5.1.1 Audio object type definition, page 23, + * in aac-mp4a-format-ISO_IEC_14496-3+2001.pdf. + */ + SrsAacObjectType aac_object; +public: + SrsStatisticStream(); + virtual ~SrsStatisticStream(); +public: + /** + * close the stream. + */ + virtual void close(); +}; + +struct SrsStatisticClient +{ +public: + SrsStatisticStream* stream; + int id; +}; + +class SrsStatistic +{ +private: + static SrsStatistic *_instance; + // the id to identify the sever. + int64_t _server_id; + // key: vhost name, value: vhost object. + std::map vhosts; + // key: stream url, value: stream object. + std::map streams; + // key: client id, value: stream object. + std::map clients; + // server total kbps. + SrsKbps* kbps; +private: + SrsStatistic(); + virtual ~SrsStatistic(); +public: + static SrsStatistic* instance(); +public: + /** + * when got video info for stream. + */ + virtual int on_video_info(SrsRequest* req, + SrsCodecVideo vcodec, SrsAvcProfile avc_profile, SrsAvcLevel avc_level + ); + /** + * when got audio info for stream. + */ + virtual int on_audio_info(SrsRequest* req, + SrsCodecAudio acodec, SrsCodecAudioSampleRate asample_rate, SrsCodecAudioSoundType asound_type, + SrsAacObjectType aac_object + ); + /** + * when close stream. + */ + virtual void on_stream_close(SrsRequest* req); +public: + /** + * when got a client to publish/play stream, + * @param id, the client srs id. + * @param req, the client request object. + */ + virtual int on_client(int id, SrsRequest* req); + /** + * client disconnect + */ + virtual void on_disconnect(int id); + /** + * sample the kbps, add delta bytes of conn. + * use kbps_sample() to get all result of kbps stat. + */ + virtual void kbps_add_delta(SrsConnection* conn); + /** + * calc the result for all kbps. + * @return the server kbps. + */ + virtual SrsKbps* kbps_sample(); +public: + /** + * get the server id, used to identify the server. + * for example, when restart, the server id must changed. + */ + virtual int64_t server_id(); + /** + * dumps the vhosts to sstream in json. + */ + virtual int dumps_vhosts(std::stringstream& ss); + /** + * dumps the streams to sstream in json. + */ + virtual int dumps_streams(std::stringstream& ss); +private: + virtual SrsStatisticVhost* create_vhost(SrsRequest* req); + virtual SrsStatisticStream* create_stream(SrsStatisticVhost* vhost, SrsRequest* req); +}; + +#endif diff --git a/trunk/src/app/srs_app_thread.cpp b/trunk/src/app/srs_app_thread.cpp index 4785d6987f..a7e89c85ec 100644 --- a/trunk/src/app/srs_app_thread.cpp +++ b/trunk/src/app/srs_app_thread.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -54,19 +54,21 @@ void ISrsThreadHandler::on_thread_stop() { } -SrsThread::SrsThread(ISrsThreadHandler* thread_handler, int64_t interval_us, bool joinable) +SrsThread::SrsThread(const char* name, ISrsThreadHandler* thread_handler, int64_t interval_us, bool joinable) { + _name = name; handler = thread_handler; cycle_interval_us = interval_us; tid = NULL; loop = false; + really_terminated = true; _cid = -1; _joinable = joinable; // in start(), the thread cycle method maybe stop and remove the thread itself, // and the thread start() is waiting for the _cid, and segment fault then. - // @see https://github.com/winlinvip/simple-rtmp-server/issues/110 + // @see https://github.com/simple-rtmp-server/srs/issues/110 // thread will set _cid, callback on_thread_start(), then wait for the can_run signal. can_run = false; } @@ -86,7 +88,7 @@ int SrsThread::start() int ret = ERROR_SUCCESS; if(tid) { - srs_info("thread already running."); + srs_info("thread %s already running.", _name); return ret; } @@ -119,10 +121,27 @@ void SrsThread::stop() // which will terminate the cycle thread. st_thread_interrupt(tid); - // wait the thread to exit. - int ret = st_thread_join(tid, NULL); - // TODO: FIXME: the join maybe failed, should use a variable to ensure thread terminated. - srs_assert(ret == 0); + // when joinable, wait util quit. + if (_joinable) { + // wait the thread to exit. + int ret = st_thread_join(tid, NULL); + if (ret) { + srs_warn("core: ignore join thread failed."); + } + + // wait the thread actually terminated. + // sometimes the thread join return -1, for example, + // when thread use st_recvfrom, the thread join return -1. + // so here, we use a variable to ensure the thread stopped. + while (!really_terminated) { + st_usleep(10 * 1000); + + if (really_terminated) { + break; + } + srs_warn("core: wait thread to actually terminated"); + } + } tid = NULL; } @@ -143,12 +162,15 @@ void SrsThread::thread_cycle() int ret = ERROR_SUCCESS; _srs_context->generate_id(); - srs_info("thread cycle start"); + srs_info("thread %s cycle start", _name); _cid = _srs_context->get_id(); srs_assert(handler); handler->on_thread_start(); + + // thread is running now. + really_terminated = false; // wait for cid to ready, for parent thread to get the cid. while (!can_run && loop) { @@ -157,33 +179,42 @@ void SrsThread::thread_cycle() while (loop) { if ((ret = handler->on_before_cycle()) != ERROR_SUCCESS) { - srs_warn("thread on before cycle failed, ignored and retry, ret=%d", ret); + srs_warn("thread %s on before cycle failed, ignored and retry, ret=%d", _name, ret); goto failed; } - srs_info("thread on before cycle success"); + srs_info("thread %s on before cycle success"); if ((ret = handler->cycle()) != ERROR_SUCCESS) { - srs_warn("thread cycle failed, ignored and retry, ret=%d", ret); + if (!srs_is_client_gracefully_close(ret)) { + srs_warn("thread %s cycle failed, ignored and retry, ret=%d", _name, ret); + } goto failed; } - srs_info("thread cycle success"); + srs_info("thread %s cycle success", _name); if ((ret = handler->on_end_cycle()) != ERROR_SUCCESS) { - srs_warn("thread on end cycle failed, ignored and retry, ret=%d", ret); + srs_warn("thread %s on end cycle failed, ignored and retry, ret=%d", _name, ret); goto failed; } - srs_info("thread on end cycle success"); + srs_info("thread %s on end cycle success", _name); failed: if (!loop) { break; } - st_usleep(cycle_interval_us); + // to improve performance, donot sleep when interval is zero. + // @see: https://github.com/simple-rtmp-server/srs/issues/237 + if (cycle_interval_us != 0) { + st_usleep(cycle_interval_us); + } } + // readly terminated now. + really_terminated = true; + handler->on_thread_stop(); - srs_info("thread cycle finished"); + srs_info("thread %s cycle finished", _name); } void* SrsThread::thread_fun(void* arg) diff --git a/trunk/src/app/srs_app_thread.hpp b/trunk/src/app/srs_app_thread.hpp index 34801824e0..5d73f2f953 100644 --- a/trunk/src/app/srs_app_thread.hpp +++ b/trunk/src/app/srs_app_thread.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -46,24 +46,78 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * which will cause the socket to return error and * terminate the cycle thread. * -* when thread interrupt, the socket maybe not got EINT, -* espectially on st_usleep(), so the cycle must check the loop, -* when handler->cycle() has loop itself, for example: -* while (true): -* st_usleep(0); -* if (read_from_socket(skt) < 0) break; -* if thread stop when read_from_socket, it's ok, the loop will break, -* but when thread stop interrupt the s_usleep(0), then the loop is -* death loop. -* in a word, the handler->cycle() must: -* while (pthread->can_loop()): -* st_usleep(0); -* if (read_from_socket(skt) < 0) break; -* check the loop, then it works. +* Usage 1: stop by other thread. +* user can create thread and stop then start again and again, +* generally must provides a start and stop method, @see SrsIngester. +* the step to create a thread stop by other thread: +* 1. create SrsThread field, with joinable true. +* 2. must use stop to stop and join the thread. +* for example: +* class SrsIngester : public ISrsThreadHandler { +* public: SrsIngester() { pthread = new SrsThread("ingest", this, SRS_AUTO_INGESTER_SLEEP_US, true); } +* public: virtual int start() { return pthread->start(); } +* public: virtual void stop() { pthread->stop(); } +* public: virtual int cycle() { +* // check status, start ffmpeg when stopped. +* } +* }; * -* in the thread itself, that is the cycle method, -* if itself want to terminate the thread, should never use stop(), -* but use stop_loop() to set the loop to false and terminate normally. +* Usage 2: stop by thread itself. +* user can create thread which stop itself, +* generally only need to provides a start method, +* the object will destroy itself then terminate the thread, @see SrsConnection +* 1. create SrsThread field, with joinable false. +* 2. owner stop thread loop, destroy itself when thread stop. +* for example: +* class SrsConnection : public ISrsThreadHandler { +* public: SrsConnection() { pthread = new SrsThread("conn", this, 0, false); } +* public: virtual int start() { return pthread->start(); } +* public: virtual int cycle() { +* // serve client. +* // set loop to stop to quit, stop thread itself. +* pthread->stop_loop(); +* } +* public: virtual int on_thread_stop() { +* // remove the connection in thread itself. +* server->remove(this); +* } +* }; +* +* Usage 3: loop in the cycle method. +* user can use loop code in the cycle method, @see SrsForwarder +* 1. create SrsThread field, with or without joinable is ok. +* 2. loop code in cycle method, check the can_loop() for thread to quit. +* for example: +* class SrsForwarder : public ISrsThreadHandler { +* public: virtual int cycle() { +* while (pthread->can_loop()) { +* // read msgs from queue and forward to server. +* } +* } +* }; +* +* @remark why should check can_loop() in cycle method? +* when thread interrupt, the socket maybe not got EINT, +* espectially on st_usleep(), so the cycle must check the loop, +* when handler->cycle() has loop itself, for example: +* while (true): +* if (read_from_socket(skt) < 0) break; +* if thread stop when read_from_socket, it's ok, the loop will break, +* but when thread stop interrupt the s_usleep(0), then the loop is +* death loop. +* in a word, the handler->cycle() must: +* while (pthread->can_loop()): +* if (read_from_socket(skt) < 0) break; +* check the loop, then it works. +* +* @remark why should use stop_loop() to terminate thread in itself? +* in the thread itself, that is the cycle method, +* if itself want to terminate the thread, should never use stop(), +* but use stop_loop() to set the loop to false and terminate normally. +* +* @remark when should set the interval_us, and when not? +* the cycle will invoke util cannot loop, eventhough the return code of cycle is error, +* so the interval_us used to sleep for each cycle. */ class ISrsThreadHandler { @@ -89,24 +143,28 @@ class SrsThread int _cid; bool loop; bool can_run; + bool really_terminated; bool _joinable; + const char* _name; private: ISrsThreadHandler* handler; int64_t cycle_interval_us; public: /** * initialize the thread. + * @param name, human readable name for st debug. * @param thread_handler, the cycle handler for the thread. * @param interval_us, the sleep interval when cycle finished. * @param joinable, if joinable, other thread must stop the thread. * @remark if joinable, thread never quit itself, or memory leak. - * @see: https://github.com/winlinvip/simple-rtmp-server/issues/78 + * @see: https://github.com/simple-rtmp-server/srs/issues/78 + * @remark about st debug, see st-1.9/README, _st_iterate_threads_flag */ /** * TODO: FIXME: maybe all thread must be reap by others threads, - * @see: https://github.com/winlinvip/simple-rtmp-server/issues/77 + * @see: https://github.com/simple-rtmp-server/srs/issues/77 */ - SrsThread(ISrsThreadHandler* thread_handler, int64_t interval_us, bool joinable); + SrsThread(const char* name, ISrsThreadHandler* thread_handler, int64_t interval_us, bool joinable); virtual ~SrsThread(); public: /** diff --git a/trunk/src/app/srs_app_utility.cpp b/trunk/src/app/srs_app_utility.cpp index b4d910f9ef..fc1d812378 100644 --- a/trunk/src/app/srs_app_utility.cpp +++ b/trunk/src/app/srs_app_utility.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -28,6 +28,11 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include +#ifdef SRS_OSX +#include +#endif +#include +#include using namespace std; #include @@ -36,8 +41,9 @@ using namespace std; #include #include #include +#include -int srs_socket_connect(std::string server, int port, int64_t timeout, st_netfd_t* pstfd) +int srs_socket_connect(string server, int port, int64_t timeout, st_netfd_t* pstfd) { int ret = ERROR_SUCCESS; @@ -89,7 +95,7 @@ int srs_socket_connect(std::string server, int port, int64_t timeout, st_netfd_t return ret; } -int srs_get_log_level(std::string level) +int srs_get_log_level(string level) { if ("verbose" == level) { return SrsLogLevel::Verbose; @@ -106,6 +112,116 @@ int srs_get_log_level(std::string level) } } +string srs_path_build_stream(string template_path, string vhost, string app, string stream) +{ + std::string path = template_path; + + // variable [vhost] + path = srs_string_replace(path, "[vhost]", vhost); + // variable [app] + path = srs_string_replace(path, "[app]", app); + // variable [stream] + path = srs_string_replace(path, "[stream]", stream); + + return path; +} + +string srs_path_build_timestamp(string template_path) +{ + std::string path = template_path; + + + // date and time substitude + // clock time + timeval tv; + if (gettimeofday(&tv, NULL) == -1) { + return path; + } + + // to calendar time + struct tm* tm; + if (_srs_config->get_utc_time()) { + if ((tm = gmtime(&tv.tv_sec)) == NULL) { + return path; + } + } else { + if ((tm = localtime(&tv.tv_sec)) == NULL) { + return path; + } + } + + // the buffer to format the date and time. + char buf[64]; + + // [2006], replace with current year. + if (true) { + snprintf(buf, sizeof(buf), "%d", 1900 + tm->tm_year); + path = srs_string_replace(path, "[2006]", buf); + } + // [2006], replace with current year. + if (true) { + snprintf(buf, sizeof(buf), "%04d", 1900 + tm->tm_year); + path = srs_string_replace(path, "[2006]", buf); + } + // [01], replace this const to current month. + if (true) { + snprintf(buf, sizeof(buf), "%02d", 1 + tm->tm_mon); + path = srs_string_replace(path, "[01]", buf); + } + // [02], replace this const to current date. + if (true) { + snprintf(buf, sizeof(buf), "%02d", tm->tm_mday); + path = srs_string_replace(path, "[02]", buf); + } + // [15], replace this const to current hour. + if (true) { + snprintf(buf, sizeof(buf), "%02d", tm->tm_hour); + path = srs_string_replace(path, "[15]", buf); + } + // [04], repleace this const to current minute. + if (true) { + snprintf(buf, sizeof(buf), "%02d", tm->tm_min); + path = srs_string_replace(path, "[04]", buf); + } + // [05], repleace this const to current second. + if (true) { + snprintf(buf, sizeof(buf), "%02d", tm->tm_sec); + path = srs_string_replace(path, "[05]", buf); + } + // [999], repleace this const to current millisecond. + if (true) { + snprintf(buf, sizeof(buf), "%03d", (int)(tv.tv_usec / 1000)); + path = srs_string_replace(path, "[999]", buf); + } + // [timestamp],replace this const to current UNIX timestamp in ms. + if (true) { + int64_t now_us = ((int64_t)tv.tv_sec) * 1000 * 1000 + (int64_t)tv.tv_usec; + snprintf(buf, sizeof(buf), "%"PRId64, now_us / 1000); + path = srs_string_replace(path, "[timestamp]", buf); + } + + return path; +} + +void srs_parse_endpoint(string ip_port, string& ip, string& port) +{ + ip = "0.0.0.0"; + port = ip_port; + + size_t pos = string::npos; + if ((pos = port.find(":")) != string::npos) { + ip = port.substr(0, pos); + port = port.substr(pos + 1); + } +} + +void srs_parse_endpoint(string ip_port, string& ip, int& port) +{ + std::string the_port; + srs_parse_endpoint(ip_port, ip, the_port); + port = ::atoi(the_port.c_str()); +} + static SrsRusage _srs_system_rusage; SrsRusage::SrsRusage() @@ -143,7 +259,7 @@ SrsProcSelfStat::SrsProcSelfStat() pid = 0; memset(comm, 0, sizeof(comm)); - state = 0; + state = '0'; ppid = 0; pgrp = 0; session = 0; @@ -221,6 +337,7 @@ SrsProcSystemStat* srs_get_system_proc_stat() bool get_proc_system_stat(SrsProcSystemStat& r) { +#ifndef SRS_OSX FILE* f = fopen("/proc/stat", "r"); if (f == NULL) { srs_warn("open system cpu stat failed, ignore"); @@ -250,6 +367,10 @@ bool get_proc_system_stat(SrsProcSystemStat& r) } fclose(f); +#else + // TODO: FIXME: impelments it. + // Fuck all of you who use osx for a long time and never patch the osx features for srs. +#endif r.ok = true; @@ -258,6 +379,7 @@ bool get_proc_system_stat(SrsProcSystemStat& r) bool get_proc_self_stat(SrsProcSelfStat& r) { +#ifndef SRS_OSX FILE* f = fopen("/proc/self/stat", "r"); if (f == NULL) { srs_warn("open self cpu stat failed, ignore"); @@ -284,6 +406,10 @@ bool get_proc_self_stat(SrsProcSelfStat& r) &r.guest_time, &r.cguest_time); fclose(f); +#else + // TODO: FIXME: impelments it. + // Fuck all of you who use osx for a long time and never patch the osx features for srs. +#endif r.ok = true; @@ -381,6 +507,7 @@ SrsDiskStat* srs_get_disk_stat() bool srs_get_disk_vmstat_stat(SrsDiskStat& r) { +#ifndef SRS_OSX FILE* f = fopen("/proc/vmstat", "r"); if (f == NULL) { srs_warn("open vmstat failed, ignore"); @@ -400,6 +527,10 @@ bool srs_get_disk_vmstat_stat(SrsDiskStat& r) } fclose(f); +#else + // TODO: FIXME: impelments it. + // Fuck all of you who use osx for a long time and never patch the osx features for srs. +#endif r.ok = true; @@ -417,6 +548,7 @@ bool srs_get_disk_diskstats_stat(SrsDiskStat& r) return true; } +#ifndef SRS_OSX FILE* f = fopen("/proc/diskstats", "r"); if (f == NULL) { srs_warn("open vmstat failed, ignore"); @@ -439,7 +571,7 @@ bool srs_get_disk_diskstats_stat(SrsDiskStat& r) unsigned int nb_current = 0; unsigned int ticks = 0; unsigned int aveq = 0; - memset(name, sizeof(name), 0); + memset(name, 0, sizeof(name)); sscanf(buf, "%4d %4d %31s %u %u %llu %u %u %u %llu %u %u %u %u", &major, @@ -481,6 +613,10 @@ bool srs_get_disk_diskstats_stat(SrsDiskStat& r) } fclose(f); +#else + // TODO: FIXME: impelments it. + // Fuck all of you who use osx for a long time and never patch the osx features for srs. +#endif r.ok = true; @@ -571,14 +707,15 @@ SrsMemInfo* srs_get_meminfo() void srs_update_meminfo() { + SrsMemInfo& r = _srs_system_meminfo; + +#ifndef SRS_OSX FILE* f = fopen("/proc/meminfo", "r"); if (f == NULL) { srs_warn("open meminfo failed, ignore"); return; } - SrsMemInfo& r = _srs_system_meminfo; - static char buf[1024]; while (fgets(buf, sizeof(buf), f)) { // @see: read_meminfo() from https://github.com/sysstat/sysstat/blob/master/rd_stats.c#L227 @@ -598,6 +735,10 @@ void srs_update_meminfo() } fclose(f); +#else + // TODO: FIXME: impelments it. + // Fuck all of you who use osx for a long time and never patch the osx features for srs. +#endif r.sample_time = srs_get_system_time_ms(); r.MemActive = r.MemTotal - r.MemFree; @@ -665,6 +806,7 @@ void srs_update_platform_info() r.srs_startup_time = srs_get_system_startup_time_ms(); +#ifndef SRS_OSX if (true) { FILE* f = fopen("/proc/uptime", "r"); if (f == NULL) { @@ -693,6 +835,43 @@ void srs_update_platform_info() fclose(f); } +#else + // man 3 sysctl + if (true) { + struct timeval tv; + size_t len = sizeof(timeval); + + int mib[2]; + mib[0] = CTL_KERN; + mib[1] = KERN_BOOTTIME; + if (sysctl(mib, 2, &tv, &len, NULL, 0) < 0) { + srs_warn("sysctl boottime failed, ignore"); + return; + } + + time_t bsec = tv.tv_sec; + time_t csec = ::time(NULL); + r.os_uptime = difftime(csec, bsec); + } + + // man 3 sysctl + if (true) { + struct loadavg la; + size_t len = sizeof(loadavg); + + int mib[2]; + mib[0] = CTL_VM; + mib[1] = VM_LOADAVG; + if (sysctl(mib, 2, &la, &len, NULL, 0) < 0) { + srs_warn("sysctl loadavg failed, ignore"); + return; + } + + r.load_one_minutes = (double)la.ldavg[0] / la.fscale; + r.load_five_minutes = (double)la.ldavg[1] / la.fscale; + r.load_fifteen_minutes = (double)la.ldavg[2] / la.fscale; + } +#endif r.ok = true; } @@ -739,6 +918,7 @@ int srs_get_network_devices_count() void srs_update_network_devices() { +#ifndef SRS_OSX if (true) { FILE* f = fopen("/proc/net/dev", "r"); if (f == NULL) { @@ -773,6 +953,10 @@ void srs_update_network_devices() fclose(f); } +#else + // TODO: FIXME: impelments it. + // Fuck all of you who use osx for a long time and never patch the osx features for srs. +#endif } SrsNetworkRtmpServer::SrsNetworkRtmpServer() @@ -820,6 +1004,7 @@ void srs_update_rtmp_server(int nb_conn, SrsKbps* kbps) int nb_tcp_mem = 0; int nb_udp4 = 0; +#ifndef SRS_OSX if (true) { FILE* f = fopen("/proc/net/sockstat", "r"); if (f == NULL) { @@ -849,9 +1034,21 @@ void srs_update_rtmp_server(int nb_conn, SrsKbps* kbps) fclose(f); } +#else + // TODO: FIXME: impelments it. + // Fuck all of you who use osx for a long time and never patch the osx features for srs. + nb_socks = 0; + nb_tcp4_hashed = 0; + nb_tcp_orphans = 0; + nb_tcp_tws = 0; + nb_tcp_total = 0; + nb_tcp_mem = 0; + nb_udp4 = 0; +#endif int nb_tcp_estab = 0; +#ifndef SRS_OSX if (true) { FILE* f = fopen("/proc/net/snmp", "r"); if (f == NULL) { @@ -881,6 +1078,10 @@ void srs_update_rtmp_server(int nb_conn, SrsKbps* kbps) fclose(f); } +#else + // TODO: FIXME: impelments it. + // Fuck all of you who use osx for a long time and never patch the osx features for srs. +#endif // @see: https://github.com/shemminger/iproute2/blob/master/misc/ss.c // TODO: FIXME: ignore the slabstat, @see: get_slabstat() @@ -931,7 +1132,7 @@ void retrieve_local_ipv4_ips() // retrieve ipv4 addr // ignore the tun0 network device, // which addr is NULL. - // @see: https://github.com/winlinvip/simple-rtmp-server/issues/141 + // @see: https://github.com/simple-rtmp-server/srs/issues/141 if (addr && addr->sa_family == AF_INET) { in_addr* inaddr = &((sockaddr_in*)addr)->sin_addr; @@ -1057,7 +1258,6 @@ void srs_api_dump_summaries(std::stringstream& ss) int64_t now = srs_get_system_time_ms(); double srs_uptime = (now - p->srs_startup_time) / 100 / 10.0; - bool n_ok = false; int64_t n_sample_time = 0; int64_t nr_bytes = 0; int64_t ns_bytes = 0; @@ -1071,7 +1271,6 @@ void srs_api_dump_summaries(std::stringstream& ss) continue; } - n_ok = true; nr_bytes += o.rbytes; ns_bytes += o.sbytes; n_sample_time = o.sample_time; @@ -1079,55 +1278,53 @@ void srs_api_dump_summaries(std::stringstream& ss) // all data is ok? bool ok = (r->ok && u->ok && s->ok && c->ok - && d->ok && m->ok && p->ok && n_ok && nrs->ok); - - ss << __SRS_JOBJECT_START - << __SRS_JFIELD_ERROR(ERROR_SUCCESS) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("data", __SRS_JOBJECT_START) - << __SRS_JFIELD_ORG("ok", (ok? "true":"false")) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("now_ms", now) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("self", __SRS_JOBJECT_START) - << __SRS_JFIELD_STR("version", RTMP_SIG_SRS_VERSION) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("pid", getpid()) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("ppid", u->ppid) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("argv", _srs_config->argv()) << __SRS_JFIELD_CONT - << __SRS_JFIELD_STR("cwd", _srs_config->cwd()) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("mem_kbyte", r->r.ru_maxrss) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("mem_percent", self_mem_percent) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("cpu_percent", u->percent) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("srs_uptime", srs_uptime) - << __SRS_JOBJECT_END << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("system", __SRS_JOBJECT_START) - << __SRS_JFIELD_ORG("cpu_percent", s->percent) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("disk_read_KBps", d->in_KBps) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("disk_write_KBps", d->out_KBps) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("disk_busy_percent", d->busy) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("mem_ram_kbyte", m->MemTotal) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("mem_ram_percent", m->percent_ram) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("mem_swap_kbyte", m->SwapTotal) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("mem_swap_percent", m->percent_swap) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("cpus", c->nb_processors) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("cpus_online", c->nb_processors_online) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("uptime", p->os_uptime) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("ilde_time", p->os_ilde_time) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("load_1m", p->load_one_minutes) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("load_5m", p->load_five_minutes) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("load_15m", p->load_fifteen_minutes) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("net_sample_time", n_sample_time) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("net_recv_bytes", nr_bytes) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("net_send_bytes", ns_bytes) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("srs_sample_time", nrs->sample_time) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("srs_recv_bytes", nrs->rbytes) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("srs_recv_kbps", nrs->rkbps) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("srs_send_bytes", nrs->sbytes) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("srs_send_kbps", nrs->skbps) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("conn_sys", nrs->nb_conn_sys) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("conn_sys_et", nrs->nb_conn_sys_et) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("conn_sys_tw", nrs->nb_conn_sys_tw) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("conn_sys_udp", nrs->nb_conn_sys_udp) << __SRS_JFIELD_CONT - << __SRS_JFIELD_ORG("conn_srs", nrs->nb_conn_srs) - << __SRS_JOBJECT_END - << __SRS_JOBJECT_END - << __SRS_JOBJECT_END; + && d->ok && m->ok && p->ok && nrs->ok); + + ss << SRS_JOBJECT_START + << SRS_JFIELD_ERROR(ERROR_SUCCESS) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("data", SRS_JOBJECT_START) + << SRS_JFIELD_ORG("ok", (ok? "true":"false")) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("now_ms", now) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("self", SRS_JOBJECT_START) + << SRS_JFIELD_STR("version", RTMP_SIG_SRS_VERSION) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("pid", getpid()) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("ppid", u->ppid) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("argv", _srs_config->argv()) << SRS_JFIELD_CONT + << SRS_JFIELD_STR("cwd", _srs_config->cwd()) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("mem_kbyte", r->r.ru_maxrss) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("mem_percent", self_mem_percent) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("cpu_percent", u->percent) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("srs_uptime", srs_uptime) + << SRS_JOBJECT_END << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("system", SRS_JOBJECT_START) + << SRS_JFIELD_ORG("cpu_percent", s->percent) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("disk_read_KBps", d->in_KBps) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("disk_write_KBps", d->out_KBps) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("disk_busy_percent", d->busy) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("mem_ram_kbyte", m->MemTotal) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("mem_ram_percent", m->percent_ram) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("mem_swap_kbyte", m->SwapTotal) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("mem_swap_percent", m->percent_swap) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("cpus", c->nb_processors) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("cpus_online", c->nb_processors_online) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("uptime", p->os_uptime) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("ilde_time", p->os_ilde_time) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("load_1m", p->load_one_minutes) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("load_5m", p->load_five_minutes) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("load_15m", p->load_fifteen_minutes) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("net_sample_time", n_sample_time) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("net_recv_bytes", nr_bytes) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("net_send_bytes", ns_bytes) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("srs_sample_time", nrs->sample_time) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("srs_recv_bytes", nrs->rbytes) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("srs_send_bytes", nrs->sbytes) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("conn_sys", nrs->nb_conn_sys) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("conn_sys_et", nrs->nb_conn_sys_et) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("conn_sys_tw", nrs->nb_conn_sys_tw) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("conn_sys_udp", nrs->nb_conn_sys_udp) << SRS_JFIELD_CONT + << SRS_JFIELD_ORG("conn_srs", nrs->nb_conn_srs) + << SRS_JOBJECT_END + << SRS_JOBJECT_END + << SRS_JOBJECT_END; } diff --git a/trunk/src/app/srs_app_utility.hpp b/trunk/src/app/srs_app_utility.hpp index cd05b5959b..4c3e6fb8c4 100644 --- a/trunk/src/app/srs_app_utility.hpp +++ b/trunk/src/app/srs_app_utility.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -39,6 +39,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include class SrsKbps; +class SrsStream; // client open socket and connect to server. extern int srs_socket_connect(std::string server, int port, int64_t timeout, st_netfd_t* pstfd); @@ -49,6 +50,36 @@ extern int srs_socket_connect(std::string server, int port, int64_t timeout, st_ */ extern int srs_get_log_level(std::string level); +/** +* build the path according to vhost/app/stream, where replace variables: +* [vhost], the vhost of stream. +* [app], the app of stream. +* [stream], the stream name of stream. +* @return the replaced path. +*/ +extern std::string srs_path_build_stream(std::string template_path, std::string vhost, std::string app, std::string stream); + +/** +* build the path according to timestamp, where replace variables: +* [2006], replace this const to current year. +* [01], replace this const to current month. +* [02], replace this const to current date. +* [15], replace this const to current hour. +* [04], repleace this const to current minute. +* [05], repleace this const to current second. +* [999], repleace this const to current millisecond. +* [timestamp],replace this const to current UNIX timestamp in ms. +* @return the replaced path. +*/ +extern std::string srs_path_build_timestamp(std::string template_path); + +/** +* parse the endpoint to ip and port. +* @param ip_port the ip and port which formats in <[ip:]port> + */ +extern void srs_parse_endpoint(std::string ip_port, std::string& ip, std::string& port); +extern void srs_parse_endpoint(std::string ip_port, std::string& ip, int& port); + // current process resouce usage. // @see: man getrusage class SrsRusage @@ -377,7 +408,7 @@ class SrsDiskStat unsigned long long rd_sectors; // Number of milliseconds spent reading. // This is the total number of milliseconds spent by all reads - // (as measured from __make_request() to end_that_request_last()). + // (as measured from make_request() to end_that_request_last()). // Time in queue + service for read unsigned int rd_ticks; // @@ -398,7 +429,7 @@ class SrsDiskStat unsigned long long wr_sectors; // Number of milliseconds spent writing . // This is the total number of milliseconds spent by all writes - // (as measured from __make_request() to end_that_request_last()). + // (as measured from make_request() to end_that_request_last()). // Time in queue + service for write unsigned int wr_ticks; // diff --git a/trunk/src/core/srs_core.cpp b/trunk/src/core/srs_core.cpp index f19cb903d1..464a50fa76 100644 --- a/trunk/src/core/srs_core.cpp +++ b/trunk/src/core/srs_core.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/trunk/src/core/srs_core.hpp b/trunk/src/core/srs_core.hpp index 89e2f80710..0a419c5aa9 100644 --- a/trunk/src/core/srs_core.hpp +++ b/trunk/src/core/srs_core.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -29,34 +29,38 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ // current release version -#define VERSION_MAJOR 1 +#define VERSION_MAJOR 2 #define VERSION_MINOR 0 -#define VERSION_REVISION 28 +#define VERSION_REVISION 161 // server info. #define RTMP_SIG_SRS_KEY "SRS" -#define RTMP_SIG_SRS_CODE "HuKaiqun" +#define RTMP_SIG_SRS_CODE "ZhouGuowen" #define RTMP_SIG_SRS_ROLE "origin/edge server" #define RTMP_SIG_SRS_NAME RTMP_SIG_SRS_KEY"(Simple RTMP Server)" -#define RTMP_SIG_SRS_URL_SHORT "github.com/winlinvip/simple-rtmp-server" +#define RTMP_SIG_SRS_URL_SHORT "github.com/simple-rtmp-server/srs" #define RTMP_SIG_SRS_URL "https://"RTMP_SIG_SRS_URL_SHORT -#define RTMP_SIG_SRS_WEB "http://blog.csdn.net/win_lin" +#define RTMP_SIG_SRS_WEB "http://ossrs.net" #define RTMP_SIG_SRS_EMAIL "winlin@vip.126.com" #define RTMP_SIG_SRS_LICENSE "The MIT License (MIT)" -#define RTMP_SIG_SRS_COPYRIGHT "Copyright (c) 2013-2014 winlin" -#define RTMP_SIG_SRS_PRIMARY "winlin" -#define RTMP_SIG_SRS_AUTHROS "wenjie.zhao" +#define RTMP_SIG_SRS_COPYRIGHT "Copyright (c) 2013-2015 SRS(simple-rtmp-server)" +#define RTMP_SIG_SRS_PRIMARY "SRS/"VERSION_STABLE_BRANCH +#define RTMP_SIG_SRS_AUTHROS "winlin,wenjie.zhao" #define RTMP_SIG_SRS_CONTRIBUTORS_URL RTMP_SIG_SRS_URL"/blob/master/AUTHORS.txt" #define RTMP_SIG_SRS_HANDSHAKE RTMP_SIG_SRS_KEY"("RTMP_SIG_SRS_VERSION")" -#define RTMP_SIG_SRS_RELEASE "https://github.com/winlinvip/simple-rtmp-server/tree/1.0release" -#define RTMP_SIG_SRS_HTTP_SERVER "https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_HTTPServer#feature" -#define RTMP_SIG_SRS_VERSION __SRS_XSTR(VERSION_MAJOR)"."__SRS_XSTR(VERSION_MINOR)"."__SRS_XSTR(VERSION_REVISION) +#define RTMP_SIG_SRS_RELEASE RTMP_SIG_SRS_URL"/tree/1.0release" +#define RTMP_SIG_SRS_ISSUES(id) RTMP_SIG_SRS_URL"/issues/"#id +#define RTMP_SIG_SRS_VERSION SRS_XSTR(VERSION_MAJOR)"."SRS_XSTR(VERSION_MINOR)"."SRS_XSTR(VERSION_REVISION) #define RTMP_SIG_SRS_SERVER RTMP_SIG_SRS_KEY"/"RTMP_SIG_SRS_VERSION"("RTMP_SIG_SRS_CODE")" +// stable major version +#define VERSION_STABLE 1 +#define VERSION_STABLE_BRANCH SRS_XSTR(VERSION_STABLE)".0release" + // internal macros, covert macro values to str, // see: read https://gcc.gnu.org/onlinedocs/cpp/Stringification.html#Stringification -#define __SRS_XSTR(v) __SRS_STR(v) -#define __SRS_STR(v) #v +#define SRS_XSTR(v) SRS_INTERNAL_STR(v) +#define SRS_INTERNAL_STR(v) #v /** * the core provides the common defined macros, utilities, @@ -74,7 +78,11 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #ifndef __STDC_FORMAT_MACROS #define __STDC_FORMAT_MACROS #endif + +// for srs-librtmp, @see https://github.com/simple-rtmp-server/srs/issues/213 +#ifndef _WIN32 #include +#endif #include #define srs_assert(expression) assert(expression) @@ -84,6 +92,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // generated by configure. #include +// important performance options. +#include // free the p and set to NULL. // p must be a T*. diff --git a/trunk/src/core/srs_core_autofree.cpp b/trunk/src/core/srs_core_autofree.cpp index e9e4538c74..8617132063 100644 --- a/trunk/src/core/srs_core_autofree.cpp +++ b/trunk/src/core/srs_core_autofree.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/trunk/src/core/srs_core_autofree.hpp b/trunk/src/core/srs_core_autofree.hpp index 98c9761b84..72ff6f72e9 100644 --- a/trunk/src/core/srs_core_autofree.hpp +++ b/trunk/src/core/srs_core_autofree.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -41,9 +41,9 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * SrsAutoFree(MyClass, po); */ #define SrsAutoFree(className, instance) \ - __SrsAutoFree _auto_free_##instance(&instance) + impl__SrsAutoFree _auto_free_##instance(&instance) template -class __SrsAutoFree +class impl__SrsAutoFree { private: T** ptr; @@ -51,11 +51,11 @@ class __SrsAutoFree /** * auto delete the ptr. */ - __SrsAutoFree(T** _ptr) { + impl__SrsAutoFree(T** _ptr) { ptr = _ptr; } - virtual ~__SrsAutoFree() { + virtual ~impl__SrsAutoFree() { if (ptr == NULL || *ptr == NULL) { return; } diff --git a/trunk/src/rtmp/srs_protocol_msg_array.cpp b/trunk/src/core/srs_core_performance.cpp similarity index 65% rename from trunk/src/rtmp/srs_protocol_msg_array.cpp rename to trunk/src/core/srs_core_performance.cpp index c936fdb2cb..7ec9d9451f 100644 --- a/trunk/src/rtmp/srs_protocol_msg_array.cpp +++ b/trunk/src/core/srs_core_performance.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -21,32 +21,5 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include - -#include - -SrsSharedPtrMessageArray::SrsSharedPtrMessageArray(int _size) -{ - srs_assert(_size > 0); - - msgs = new SrsSharedPtrMessage*[_size]; - size = _size; - - // initialize - for (int i = 0; i < _size; i++) { - msgs[i] = NULL; - } -} - -SrsSharedPtrMessageArray::~SrsSharedPtrMessageArray() -{ - // cleanup - for (int i = 0; i < size; i++) { - SrsSharedPtrMessage* msg = msgs[i]; - srs_freep(msg); - } - - srs_freep(msgs); -} - +#include diff --git a/trunk/src/core/srs_core_performance.hpp b/trunk/src/core/srs_core_performance.hpp new file mode 100644 index 0000000000..b6aff9f0a0 --- /dev/null +++ b/trunk/src/core/srs_core_performance.hpp @@ -0,0 +1,185 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef SRS_CORE_PERFORMANCE_HPP +#define SRS_CORE_PERFORMANCE_HPP + +/* +#include +*/ + +#include + +/** +* this file defines the perfromance options. +*/ + +/** +* to improve read performance, merge some packets then read, +* when it on and read small bytes, we sleep to wait more data., +* that is, we merge some data to read together. +* @see SrsConfig::get_mr_enabled() +* @see SrsConfig::get_mr_sleep_ms() +* @see https://github.com/simple-rtmp-server/srs/issues/241 +* @example, for the default settings, this algorithm will use: +* that is, when got nread bytes smaller than 4KB, sleep(780ms). +*/ +/** +* https://github.com/simple-rtmp-server/srs/issues/241#issuecomment-65554690 +* The merged read algorithm is ok and can be simplified for: +* 1. Suppose the client network is ok. All algorithm go wrong when netowrk is not ok. +* 2. Suppose the client send each packet one by one. Although send some together, it's same. +* 3. SRS MR algorithm will read all data then sleep. +* So, the MR algorithm is: +* while true: +* read all data from socket. +* sleep a while +* For example, sleep 120ms. Then there is, and always 120ms data in buffer. +* That is, the latency is 120ms(the sleep time). +*/ +#define SRS_PERF_MERGED_READ +// the default config of mr. +#define SRS_PERF_MR_ENABLED false +#define SRS_PERF_MR_SLEEP 350 + +/** +* the MW(merged-write) send cache time in ms. +* the default value, user can override it in config. +* to improve send performance, cache msgs and send in a time. +* for example, cache 500ms videos and audios, then convert all these +* msgs to iovecs, finally use writev to send. +* @remark this largely improve performance, from 3.5k+ to 7.5k+. +* the latency+ when cache+. +* @remark the socket send buffer default to 185KB, it large enough. +* @see https://github.com/simple-rtmp-server/srs/issues/194 +* @see SrsConfig::get_mw_sleep_ms() +* @remark the mw sleep and msgs to send, maybe: +* mw_sleep msgs iovs +* 350 43 86 +* 400 44 88 +* 500 46 92 +* 600 46 92 +* 700 82 164 +* 800 81 162 +* 900 80 160 +* 1000 88 176 +* 1100 91 182 +* 1200 89 178 +* 1300 119 238 +* 1400 120 240 +* 1500 119 238 +* 1600 131 262 +* 1700 131 262 +* 1800 133 266 +* 1900 141 282 +* 2000 150 300 +*/ +// the default config of mw. +#define SRS_PERF_MW_SLEEP 350 +/** +* how many msgs can be send entirely. +* for play clients to get msgs then totally send out. +* for the mw sleep set to 1800, the msgs is about 133. +* @remark, recomment to 128. +*/ +#define SRS_PERF_MW_MSGS 128 + +/** +* whether set the socket send buffer size. +* @see https://github.com/simple-rtmp-server/srs/issues/251 +*/ +#define SRS_PERF_MW_SO_SNDBUF + +/** +* whether set the socket recv buffer size. +* @see https://github.com/simple-rtmp-server/srs/issues/251 +*/ +#undef SRS_PERF_MW_SO_RCVBUF +/** +* whether enable the fast vector for qeueue. +* @see https://github.com/simple-rtmp-server/srs/issues/251 +*/ +#define SRS_PERF_QUEUE_FAST_VECTOR +/** +* whether use cond wait to send messages. +* @remark this improve performance for large connectios. +* @see https://github.com/simple-rtmp-server/srs/issues/251 +*/ +#define SRS_PERF_QUEUE_COND_WAIT +#ifdef SRS_PERF_QUEUE_COND_WAIT + #define SRS_PERF_MW_MIN_MSGS 8 +#endif +/** +* the default value of vhost for +* SRS whether use the min latency mode. +* for min latence mode: +* 1. disable the mr for vhost. +* 2. use timeout for cond wait for consumer queue. +* @see https://github.com/simple-rtmp-server/srs/issues/257 +*/ +#define SRS_PERF_MIN_LATENCY_ENABLED false + +/** +* how many chunk stream to cache, [0, N]. +* to imporove about 10% performance when chunk size small, and 5% for large chunk. +* @see https://github.com/simple-rtmp-server/srs/issues/249 +* @remark 0 to disable the chunk stream cache. +*/ +#define SRS_PERF_CHUNK_STREAM_CACHE 16 + +/** +* the gop cache and play cache queue. +*/ +// whether gop cache is on. +#define SRS_PERF_GOP_CACHE true +// in seconds, the live queue length. +#define SRS_PERF_PLAY_QUEUE 30 + +/** +* whether always use complex send algorithm. +* for some network does not support the complex send, +* @see https://github.com/simple-rtmp-server/srs/issues/320 +*/ +//#undef SRS_PERF_COMPLEX_SEND +#define SRS_PERF_COMPLEX_SEND +/** +* whether enable the TCP_NODELAY +* user maybe need send small tcp packet for some network. +* @see https://github.com/simple-rtmp-server/srs/issues/320 +*/ +//#define SRS_PERF_TCP_NODELAY +#undef SRS_PERF_TCP_NODELAY +/** +* set the socket send buffer, +* to force the server to send smaller tcp packet. +* @see https://github.com/simple-rtmp-server/srs/issues/320 +* @remark undef it to auto calc it by merged write sleep ms. +* @remark only apply it when SRS_PERF_MW_SO_RCVBUF is defined. +*/ +#ifdef SRS_PERF_MW_SO_SNDBUF + //#define SRS_PERF_SO_SNDBUF_SIZE 1024 + #undef SRS_PERF_SO_SNDBUF_SIZE +#endif + +#endif + diff --git a/trunk/src/kernel/srs_kernel_aac.cpp b/trunk/src/kernel/srs_kernel_aac.cpp new file mode 100644 index 0000000000..8c75e6a4cb --- /dev/null +++ b/trunk/src/kernel/srs_kernel_aac.cpp @@ -0,0 +1,220 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include + +// for srs-librtmp, @see https://github.com/simple-rtmp-server/srs/issues/213 +#ifndef _WIN32 +#include +#endif + +#include +#include +using namespace std; + +#include +#include +#include +#include +#include + +SrsAacEncoder::SrsAacEncoder() +{ + _fs = NULL; + got_sequence_header = false; + tag_stream = new SrsStream(); + aac_object = SrsAacObjectTypeReserved; +} + +SrsAacEncoder::~SrsAacEncoder() +{ + srs_freep(tag_stream); +} + +int SrsAacEncoder::initialize(SrsFileWriter* fs) +{ + int ret = ERROR_SUCCESS; + + srs_assert(fs); + + if (!fs->is_open()) { + ret = ERROR_KERNEL_AAC_STREAM_CLOSED; + srs_warn("stream is not open for encoder. ret=%d", ret); + return ret; + } + + _fs = fs; + + return ret; +} + +int SrsAacEncoder::write_audio(int64_t timestamp, char* data, int size) +{ + int ret = ERROR_SUCCESS; + + srs_assert(data); + + timestamp &= 0x7fffffff; + + SrsStream* stream = tag_stream; + if ((ret = stream->initialize(data, size)) != ERROR_SUCCESS) { + return ret; + } + + // audio decode + if (!stream->require(1)) { + ret = ERROR_AAC_DECODE_ERROR; + srs_error("aac decode audio sound_format failed. ret=%d", ret); + return ret; + } + + // @see: E.4.2 Audio Tags, video_file_format_spec_v10_1.pdf, page 76 + int8_t sound_format = stream->read_1bytes(); + + // @see: SrsAvcAacCodec::audio_aac_demux + //int8_t sound_type = sound_format & 0x01; + //int8_t sound_size = (sound_format >> 1) & 0x01; + //int8_t sound_rate = (sound_format >> 2) & 0x03; + sound_format = (sound_format >> 4) & 0x0f; + + if ((SrsCodecAudio)sound_format != SrsCodecAudioAAC) { + ret = ERROR_AAC_DECODE_ERROR; + srs_error("aac required, format=%d. ret=%d", sound_format, ret); + return ret; + } + + if (!stream->require(1)) { + ret = ERROR_AAC_DECODE_ERROR; + srs_error("aac decode aac_packet_type failed. ret=%d", ret); + return ret; + } + + SrsCodecAudioType aac_packet_type = (SrsCodecAudioType)stream->read_1bytes(); + if (aac_packet_type == SrsCodecAudioTypeSequenceHeader) { + // AudioSpecificConfig + // 1.6.2.1 AudioSpecificConfig, in aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 33. + // + // only need to decode the first 2bytes: + // audioObjectType, 5bits. + // samplingFrequencyIndex, aac_sample_rate, 4bits. + // channelConfiguration, aac_channels, 4bits + if (!stream->require(2)) { + ret = ERROR_AAC_DECODE_ERROR; + srs_error("aac decode sequence header failed. ret=%d", ret); + return ret; + } + + int8_t audioObjectType = stream->read_1bytes(); + aac_sample_rate = stream->read_1bytes(); + + aac_channels = (aac_sample_rate >> 3) & 0x0f; + aac_sample_rate = ((audioObjectType << 1) & 0x0e) | ((aac_sample_rate >> 7) & 0x01); + + audioObjectType = (audioObjectType >> 3) & 0x1f; + aac_object = (SrsAacObjectType)audioObjectType; + + got_sequence_header = true; + + return ret; + } + + if (!got_sequence_header) { + ret = ERROR_AAC_DECODE_ERROR; + srs_error("aac no sequence header. ret=%d", ret); + return ret; + } + + // the left is the aac raw frame data. + int16_t aac_raw_length = stream->size() - stream->pos(); + + // write the ADTS header. + // @see aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 75, + // 1.A.2.2 Audio_Data_Transport_Stream frame, ADTS + // @see https://github.com/simple-rtmp-server/srs/issues/212#issuecomment-64145885 + // byte_alignment() + + // adts_fixed_header: + // 12bits syncword, + // 16bits left. + // adts_variable_header: + // 28bits + // 12+16+28=56bits + // adts_error_check: + // 16bits if protection_absent + // 56+16=72bits + // if protection_absent: + // require(7bytes)=56bits + // else + // require(9bytes)=72bits + char aac_fixed_header[7]; + if(true) { + char* pp = aac_fixed_header; + int16_t aac_frame_length = aac_raw_length + 7; + + // Syncword 12 bslbf + *pp++ = 0xff; + // 4bits left. + // adts_fixed_header(), 1.A.2.2.1 Fixed Header of ADTS + // ID 1 bslbf + // Layer 2 uimsbf + // protection_absent 1 bslbf + *pp++ = 0xf1; + + // profile 2 uimsbf + // sampling_frequency_index 4 uimsbf + // private_bit 1 bslbf + // channel_configuration 3 uimsbf + // original/copy 1 bslbf + // home 1 bslbf + SrsAacProfile aac_profile = srs_codec_aac_rtmp2ts(aac_object); + *pp++ = ((aac_profile << 6) & 0xc0) | ((aac_sample_rate << 2) & 0x3c) | ((aac_channels >> 2) & 0x01); + // 4bits left. + // adts_variable_header(), 1.A.2.2.2 Variable Header of ADTS + // copyright_identification_bit 1 bslbf + // copyright_identification_start 1 bslbf + *pp++ = ((aac_channels << 6) & 0xc0) | ((aac_frame_length >> 11) & 0x03); + + // aac_frame_length 13 bslbf: Length of the frame including headers and error_check in bytes. + // use the left 2bits as the 13 and 12 bit, + // the aac_frame_length is 13bits, so we move 13-2=11. + *pp++ = aac_frame_length >> 3; + // adts_buffer_fullness 11 bslbf + *pp++ = (aac_frame_length << 5) & 0xe0; + + // no_raw_data_blocks_in_frame 2 uimsbf + *pp++ = 0xfc; + } + + // write 7bytes fixed header. + if ((ret = _fs->write(aac_fixed_header, 7, NULL)) != ERROR_SUCCESS) { + return ret; + } + + // write aac frame body. + if ((ret = _fs->write(data + stream->pos(), aac_raw_length, NULL)) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + diff --git a/trunk/src/kernel/srs_kernel_aac.hpp b/trunk/src/kernel/srs_kernel_aac.hpp new file mode 100644 index 0000000000..ac57823a41 --- /dev/null +++ b/trunk/src/kernel/srs_kernel_aac.hpp @@ -0,0 +1,73 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef SRS_KERNEL_AAC_HPP +#define SRS_KERNEL_AAC_HPP + +/* +#include +*/ +#include + +#include + +#include + +class SrsStream; +class SrsFileWriter; +class SrsFileReader; + +/** +* encode data to aac file. +*/ +class SrsAacEncoder +{ +private: + SrsFileWriter* _fs; +private: + SrsAacObjectType aac_object; + int8_t aac_sample_rate; + int8_t aac_channels; + bool got_sequence_header; +private: + SrsStream* tag_stream; +public: + SrsAacEncoder(); + virtual ~SrsAacEncoder(); +public: + /** + * initialize the underlayer file stream. + * @remark user can initialize multiple times to encode multiple aac files. + * @remark, user must free the fs, aac encoder never close/free it. + */ + virtual int initialize(SrsFileWriter* fs); +public: + /** + * write audio/video packet. + * @remark assert data is not NULL. + */ + virtual int write_audio(int64_t timestamp, char* data, int size); +}; + +#endif + diff --git a/trunk/src/kernel/srs_kernel_buffer.cpp b/trunk/src/kernel/srs_kernel_buffer.cpp index d60690de4f..83f3123962 100644 --- a/trunk/src/kernel/srs_kernel_buffer.cpp +++ b/trunk/src/kernel/srs_kernel_buffer.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -25,38 +25,30 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include +#include +#include -#define SOCKET_READ_SIZE 4096 - -ISrsBufferReader::ISrsBufferReader() +SrsSimpleBuffer::SrsSimpleBuffer() { } -ISrsBufferReader::~ISrsBufferReader() +SrsSimpleBuffer::~SrsSimpleBuffer() { } -SrsBuffer::SrsBuffer() -{ -} - -SrsBuffer::~SrsBuffer() -{ -} - -int SrsBuffer::length() +int SrsSimpleBuffer::length() { int len = (int)data.size(); srs_assert(len >= 0); return len; } -char* SrsBuffer::bytes() +char* SrsSimpleBuffer::bytes() { return (length() == 0)? NULL : &data.at(0); } -void SrsBuffer::erase(int size) +void SrsSimpleBuffer::erase(int size) { if (size <= 0) { return; @@ -70,36 +62,9 @@ void SrsBuffer::erase(int size) data.erase(data.begin(), data.begin() + size); } -void SrsBuffer::append(const char* bytes, int size) +void SrsSimpleBuffer::append(const char* bytes, int size) { srs_assert(size > 0); data.insert(data.end(), bytes, bytes + size); } - -int SrsBuffer::grow(ISrsBufferReader* reader, int required_size) -{ - int ret = ERROR_SUCCESS; - - if (required_size < 0) { - ret = ERROR_SYSTEM_SIZE_NEGATIVE; - srs_error("size is negative. size=%d, ret=%d", required_size, ret); - return ret; - } - - while (length() < required_size) { - char buffer[SOCKET_READ_SIZE]; - - ssize_t nread; - if ((ret = reader->read(buffer, SOCKET_READ_SIZE, &nread)) != ERROR_SUCCESS) { - return ret; - } - - srs_assert((int)nread > 0); - append(buffer, (int)nread); - } - - return ret; -} - - diff --git a/trunk/src/kernel/srs_kernel_buffer.hpp b/trunk/src/kernel/srs_kernel_buffer.hpp index 93d3a56832..3997b8064a 100644 --- a/trunk/src/kernel/srs_kernel_buffer.hpp +++ b/trunk/src/kernel/srs_kernel_buffer.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -33,29 +33,16 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include /** -* the reader for the buffer to read from whatever channel. +* the simple buffer use vector to append bytes, +* it's for hls and http, and need to be refined in future. */ -class ISrsBufferReader -{ -public: - ISrsBufferReader(); - virtual ~ISrsBufferReader(); -// for protocol/amf0/msg-codec -public: - virtual int read(void* buf, size_t size, ssize_t* nread) = 0; -}; - -/** -* the buffer provices bytes cache for protocol. generally, -* protocol recv data from socket, put into buffer, decode to RTMP message. -*/ -class SrsBuffer +class SrsSimpleBuffer { private: std::vector data; public: - SrsBuffer(); - virtual ~SrsBuffer(); + SrsSimpleBuffer(); + virtual ~SrsSimpleBuffer(); public: /** * get the length of buffer. empty if zero. @@ -80,15 +67,6 @@ class SrsBuffer * @remark assert size is positive. */ virtual void append(const char* bytes, int size); -public: - /** - * grow buffer to the required size, loop to read from skt to fill. - * @param reader, read more bytes from reader to fill the buffer to required size. - * @param required_size, loop to fill to ensure buffer size to required. - * @return an int error code, error if required_size negative. - * @remark, we actually maybe read more than required_size, maybe 4k for example. - */ - virtual int grow(ISrsBufferReader* reader, int required_size); }; #endif diff --git a/trunk/src/kernel/srs_kernel_codec.cpp b/trunk/src/kernel/srs_kernel_codec.cpp index 45990dc701..21981506c0 100644 --- a/trunk/src/kernel/srs_kernel_codec.cpp +++ b/trunk/src/kernel/srs_kernel_codec.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -25,6 +25,161 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include +using namespace std; + +#include +#include +#include +#include +#include + +string srs_codec_video2str(SrsCodecVideo codec) +{ + switch (codec) { + case SrsCodecVideoAVC: + return "H264"; + case SrsCodecVideoOn2VP6: + case SrsCodecVideoOn2VP6WithAlphaChannel: + return "VP6"; + case SrsCodecVideoReserved: + case SrsCodecVideoReserved1: + case SrsCodecVideoReserved2: + case SrsCodecVideoDisabled: + case SrsCodecVideoSorensonH263: + case SrsCodecVideoScreenVideo: + case SrsCodecVideoScreenVideoVersion2: + default: + return "Other"; + } +} + +string srs_codec_audio2str(SrsCodecAudio codec) +{ + switch (codec) { + case SrsCodecAudioAAC: + return "AAC"; + case SrsCodecAudioMP3: + return "MP3"; + case SrsCodecAudioReserved1: + case SrsCodecAudioLinearPCMPlatformEndian: + case SrsCodecAudioADPCM: + case SrsCodecAudioLinearPCMLittleEndian: + case SrsCodecAudioNellymoser16kHzMono: + case SrsCodecAudioNellymoser8kHzMono: + case SrsCodecAudioNellymoser: + case SrsCodecAudioReservedG711AlawLogarithmicPCM: + case SrsCodecAudioReservedG711MuLawLogarithmicPCM: + case SrsCodecAudioReserved: + case SrsCodecAudioSpeex: + case SrsCodecAudioReservedMP3_8kHz: + case SrsCodecAudioReservedDeviceSpecificSound: + default: + return "Other"; + } +} + +string srs_codec_aac_profile2str(SrsAacProfile aac_profile) +{ + switch (aac_profile) { + case SrsAacProfileMain: return "Main"; + case SrsAacProfileLC: return "LC"; + case SrsAacProfileSSR: return "SSR"; + default: return "Other"; + } +} + +string srs_codec_aac_object2str(SrsAacObjectType aac_object) +{ + switch (aac_object) { + case SrsAacObjectTypeAacMain: return "Main"; + case SrsAacObjectTypeAacHE: return "HE"; + case SrsAacObjectTypeAacHEV2: return "HEv2"; + case SrsAacObjectTypeAacLC: return "LC"; + case SrsAacObjectTypeAacSSR: return "SSR"; + default: return "Other"; + } +} + +SrsAacObjectType srs_codec_aac_ts2rtmp(SrsAacProfile profile) +{ + switch (profile) { + case SrsAacProfileMain: return SrsAacObjectTypeAacMain; + case SrsAacProfileLC: return SrsAacObjectTypeAacLC; + case SrsAacProfileSSR: return SrsAacObjectTypeAacSSR; + default: return SrsAacObjectTypeReserved; + } +} + +SrsAacProfile srs_codec_aac_rtmp2ts(SrsAacObjectType object_type) +{ + switch (object_type) { + case SrsAacObjectTypeAacMain: return SrsAacProfileMain; + case SrsAacObjectTypeAacHE: + case SrsAacObjectTypeAacHEV2: + case SrsAacObjectTypeAacLC: return SrsAacProfileLC; + case SrsAacObjectTypeAacSSR: return SrsAacProfileSSR; + default: return SrsAacProfileReserved; + } +} + +string srs_codec_avc_profile2str(SrsAvcProfile profile) +{ + switch (profile) { + case SrsAvcProfileBaseline: return "Baseline"; + case SrsAvcProfileConstrainedBaseline: return "Baseline(Constrained)"; + case SrsAvcProfileMain: return "Main"; + case SrsAvcProfileExtended: return "Extended"; + case SrsAvcProfileHigh: return "High"; + case SrsAvcProfileHigh10: return "High(10)"; + case SrsAvcProfileHigh10Intra: return "High(10+Intra)"; + case SrsAvcProfileHigh422: return "High(422)"; + case SrsAvcProfileHigh422Intra: return "High(422+Intra)"; + case SrsAvcProfileHigh444: return "High(444)"; + case SrsAvcProfileHigh444Predictive: return "High(444+Predictive)"; + case SrsAvcProfileHigh444Intra: return "High(444+Intra)"; + default: return "Other"; + } +} + +string srs_codec_avc_level2str(SrsAvcLevel level) +{ + switch (level) { + case SrsAvcLevel_1: return "1"; + case SrsAvcLevel_11: return "1.1"; + case SrsAvcLevel_12: return "1.2"; + case SrsAvcLevel_13: return "1.3"; + case SrsAvcLevel_2: return "2"; + case SrsAvcLevel_21: return "2.1"; + case SrsAvcLevel_22: return "2.2"; + case SrsAvcLevel_3: return "3"; + case SrsAvcLevel_31: return "3.1"; + case SrsAvcLevel_32: return "3.2"; + case SrsAvcLevel_4: return "4"; + case SrsAvcLevel_41: return "4.1"; + case SrsAvcLevel_5: return "5"; + case SrsAvcLevel_51: return "5.1"; + default: return "Other"; + } +} + +/** +* the public data, event HLS disable, others can use it. +*/ +// 0 = 5.5 kHz = 5512 Hz +// 1 = 11 kHz = 11025 Hz +// 2 = 22 kHz = 22050 Hz +// 3 = 44 kHz = 44100 Hz +int flv_sample_rates[] = {5512, 11025, 22050, 44100}; + +// the sample rates in the codec, +// in the sequence header. +int aac_sample_rates[] = +{ + 96000, 88200, 64000, 48000, + 44100, 32000, 24000, 22050, + 16000, 12000, 11025, 8000, + 7350, 0, 0, 0 +}; SrsFlvCodec::SrsFlvCodec() { @@ -111,3 +266,915 @@ bool SrsFlvCodec::audio_is_aac(char* data, int size) return sound_format == SrsCodecAudioAAC; } +string srs_codec_avc_nalu2str(SrsAvcNaluType nalu_type) +{ + switch (nalu_type) { + case SrsAvcNaluTypeNonIDR: return "NonIDR"; + case SrsAvcNaluTypeDataPartitionA: return "DataPartitionA"; + case SrsAvcNaluTypeDataPartitionB: return "DataPartitionB"; + case SrsAvcNaluTypeDataPartitionC: return "DataPartitionC"; + case SrsAvcNaluTypeIDR: return "IDR"; + case SrsAvcNaluTypeSEI: return "SEI"; + case SrsAvcNaluTypeSPS: return "SPS"; + case SrsAvcNaluTypePPS: return "PPS"; + case SrsAvcNaluTypeAccessUnitDelimiter: return "AccessUnitDelimiter"; + case SrsAvcNaluTypeEOSequence: return "EOSequence"; + case SrsAvcNaluTypeEOStream: return "EOStream"; + case SrsAvcNaluTypeFilterData: return "FilterData"; + case SrsAvcNaluTypeSPSExt: return "SPSExt"; + case SrsAvcNaluTypePrefixNALU: return "PrefixNALU"; + case SrsAvcNaluTypeSubsetSPS: return "SubsetSPS"; + case SrsAvcNaluTypeLayerWithoutPartition: return "LayerWithoutPartition"; + case SrsAvcNaluTypeCodedSliceExt: return "CodedSliceExt"; + case SrsAvcNaluTypeReserved: default: return "Other"; + } +} + +SrsCodecSampleUnit::SrsCodecSampleUnit() +{ + size = 0; + bytes = NULL; +} + +SrsCodecSampleUnit::~SrsCodecSampleUnit() +{ +} + +SrsCodecSample::SrsCodecSample() +{ + clear(); +} + +SrsCodecSample::~SrsCodecSample() +{ +} + +void SrsCodecSample::clear() +{ + is_video = false; + nb_sample_units = 0; + + cts = 0; + frame_type = SrsCodecVideoAVCFrameReserved; + avc_packet_type = SrsCodecVideoAVCTypeReserved; + has_idr = false; + first_nalu_type = SrsAvcNaluTypeReserved; + + acodec = SrsCodecAudioReserved1; + sound_rate = SrsCodecAudioSampleRateReserved; + sound_size = SrsCodecAudioSampleSizeReserved; + sound_type = SrsCodecAudioSoundTypeReserved; + aac_packet_type = SrsCodecAudioTypeReserved; +} + +int SrsCodecSample::add_sample_unit(char* bytes, int size) +{ + int ret = ERROR_SUCCESS; + + if (nb_sample_units >= SRS_SRS_MAX_CODEC_SAMPLE) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("hls decode samples error, " + "exceed the max count: %d, ret=%d", SRS_SRS_MAX_CODEC_SAMPLE, ret); + return ret; + } + + SrsCodecSampleUnit* sample_unit = &sample_units[nb_sample_units++]; + sample_unit->bytes = bytes; + sample_unit->size = size; + + // for video, parse the nalu type, set the IDR flag. + if (is_video) { + SrsAvcNaluType nal_unit_type = (SrsAvcNaluType)(bytes[0] & 0x1f); + + if (nal_unit_type == SrsAvcNaluTypeIDR) { + has_idr = true; + } + + if (first_nalu_type == SrsAvcNaluTypeReserved) { + first_nalu_type = nal_unit_type; + } + } + + return ret; +} + +SrsAvcAacCodec::SrsAvcAacCodec() +{ + width = 0; + height = 0; + duration = 0; + NAL_unit_length = 0; + frame_rate = 0; + + video_data_rate = 0; + video_codec_id = 0; + + audio_data_rate = 0; + audio_codec_id = 0; + + avc_profile = SrsAvcProfileReserved; + avc_level = SrsAvcLevelReserved; + aac_object = SrsAacObjectTypeReserved; + aac_sample_rate = SRS_AAC_SAMPLE_RATE_UNSET; // sample rate ignored + aac_channels = 0; + avc_extra_size = 0; + avc_extra_data = NULL; + aac_extra_size = 0; + aac_extra_data = NULL; + + sequenceParameterSetLength = 0; + sequenceParameterSetNALUnit = NULL; + pictureParameterSetLength = 0; + pictureParameterSetNALUnit = NULL; + + payload_format = SrsAvcPayloadFormatGuess; + stream = new SrsStream(); +} + +SrsAvcAacCodec::~SrsAvcAacCodec() +{ + srs_freep(avc_extra_data); + srs_freep(aac_extra_data); + + srs_freep(stream); + srs_freep(sequenceParameterSetNALUnit); + srs_freep(pictureParameterSetNALUnit); +} + +int SrsAvcAacCodec::audio_aac_demux(char* data, int size, SrsCodecSample* sample) +{ + int ret = ERROR_SUCCESS; + + sample->is_video = false; + + if (!data || size <= 0) { + srs_trace("no audio present, ignore it."); + return ret; + } + + if ((ret = stream->initialize(data, size)) != ERROR_SUCCESS) { + return ret; + } + + // audio decode + if (!stream->require(1)) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("audio codec decode sound_format failed. ret=%d", ret); + return ret; + } + + // @see: E.4.2 Audio Tags, video_file_format_spec_v10_1.pdf, page 76 + int8_t sound_format = stream->read_1bytes(); + + int8_t sound_type = sound_format & 0x01; + int8_t sound_size = (sound_format >> 1) & 0x01; + int8_t sound_rate = (sound_format >> 2) & 0x03; + sound_format = (sound_format >> 4) & 0x0f; + + audio_codec_id = sound_format; + sample->acodec = (SrsCodecAudio)audio_codec_id; + + sample->sound_type = (SrsCodecAudioSoundType)sound_type; + sample->sound_rate = (SrsCodecAudioSampleRate)sound_rate; + sample->sound_size = (SrsCodecAudioSampleSize)sound_size; + + // we support h.264+mp3 for hls. + if (audio_codec_id == SrsCodecAudioMP3) { + return ERROR_HLS_TRY_MP3; + } + + // only support aac + if (audio_codec_id != SrsCodecAudioAAC) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("audio codec only support mp3/aac codec. actual=%d, ret=%d", audio_codec_id, ret); + return ret; + } + + if (!stream->require(1)) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("audio codec decode aac_packet_type failed. ret=%d", ret); + return ret; + } + + int8_t aac_packet_type = stream->read_1bytes(); + sample->aac_packet_type = (SrsCodecAudioType)aac_packet_type; + + if (aac_packet_type == SrsCodecAudioTypeSequenceHeader) { + // AudioSpecificConfig + // 1.6.2.1 AudioSpecificConfig, in aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 33. + aac_extra_size = stream->size() - stream->pos(); + if (aac_extra_size > 0) { + srs_freep(aac_extra_data); + aac_extra_data = new char[aac_extra_size]; + memcpy(aac_extra_data, stream->data() + stream->pos(), aac_extra_size); + + // demux the sequence header. + if ((ret = audio_aac_sequence_header_demux(aac_extra_data, aac_extra_size)) != ERROR_SUCCESS) { + return ret; + } + } + } else if (aac_packet_type == SrsCodecAudioTypeRawData) { + // ensure the sequence header demuxed + if (aac_extra_size <= 0 || !aac_extra_data) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("audio codec decode aac failed, sequence header not found. ret=%d", ret); + return ret; + } + + // Raw AAC frame data in UI8 [] + // 6.3 Raw Data, aac-iso-13818-7.pdf, page 28 + if ((ret = sample->add_sample_unit(stream->data() + stream->pos(), stream->size() - stream->pos())) != ERROR_SUCCESS) { + srs_error("audio codec add sample failed. ret=%d", ret); + return ret; + } + } else { + // ignored. + } + + // reset the sample rate by sequence header + if (aac_sample_rate != SRS_AAC_SAMPLE_RATE_UNSET) { + static int aac_sample_rates[] = { + 96000, 88200, 64000, 48000, + 44100, 32000, 24000, 22050, + 16000, 12000, 11025, 8000, + 7350, 0, 0, 0 + }; + switch (aac_sample_rates[aac_sample_rate]) { + case 11025: + sample->sound_rate = SrsCodecAudioSampleRate11025; + break; + case 22050: + sample->sound_rate = SrsCodecAudioSampleRate22050; + break; + case 44100: + sample->sound_rate = SrsCodecAudioSampleRate44100; + break; + default: + break; + }; + } + + srs_info("audio decoded, type=%d, codec=%d, asize=%d, rate=%d, format=%d, size=%d", + sound_type, audio_codec_id, sound_size, sound_rate, sound_format, size); + + return ret; +} + +int SrsAvcAacCodec::audio_mp3_demux(char* data, int size, SrsCodecSample* sample) +{ + int ret = ERROR_SUCCESS; + + // we always decode aac then mp3. + srs_assert(sample->acodec == SrsCodecAudioMP3); + + // @see: E.4.2 Audio Tags, video_file_format_spec_v10_1.pdf, page 76 + if (!data || size <= 1) { + srs_trace("no mp3 audio present, ignore it."); + return ret; + } + + // mp3 payload. + if ((ret = sample->add_sample_unit(data + 1, size - 1)) != ERROR_SUCCESS) { + srs_error("audio codec add mp3 sample failed. ret=%d", ret); + return ret; + } + + srs_info("audio decoded, type=%d, codec=%d, asize=%d, rate=%d, format=%d, size=%d", + sample->sound_type, audio_codec_id, sample->sound_size, sample->sound_rate, sample->acodec, size); + + return ret; +} + +int SrsAvcAacCodec::audio_aac_sequence_header_demux(char* data, int size) +{ + int ret = ERROR_SUCCESS; + + if ((ret = stream->initialize(data, size)) != ERROR_SUCCESS) { + return ret; + } + + // only need to decode the first 2bytes: + // audioObjectType, aac_profile, 5bits. + // samplingFrequencyIndex, aac_sample_rate, 4bits. + // channelConfiguration, aac_channels, 4bits + if (!stream->require(2)) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("audio codec decode aac sequence header failed. ret=%d", ret); + return ret; + } + u_int8_t profile_ObjectType = stream->read_1bytes(); + u_int8_t samplingFrequencyIndex = stream->read_1bytes(); + + aac_channels = (samplingFrequencyIndex >> 3) & 0x0f; + samplingFrequencyIndex = ((profile_ObjectType << 1) & 0x0e) | ((samplingFrequencyIndex >> 7) & 0x01); + profile_ObjectType = (profile_ObjectType >> 3) & 0x1f; + + // set the aac sample rate. + aac_sample_rate = samplingFrequencyIndex; + + // convert the object type in sequence header to aac profile of ADTS. + aac_object = (SrsAacObjectType)profile_ObjectType; + if (aac_object == SrsAacObjectTypeReserved) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("audio codec decode aac sequence header failed, " + "adts object=%d invalid. ret=%d", profile_ObjectType, ret); + return ret; + } + + // TODO: FIXME: to support aac he/he-v2, see: ngx_rtmp_codec_parse_aac_header + // @see: https://github.com/winlinvip/nginx-rtmp-module/commit/3a5f9eea78fc8d11e8be922aea9ac349b9dcbfc2 + // + // donot force to LC, @see: https://github.com/simple-rtmp-server/srs/issues/81 + // the source will print the sequence header info. + //if (aac_profile > 3) { + // Mark all extended profiles as LC + // to make Android as happy as possible. + // @see: ngx_rtmp_hls_parse_aac_header + //aac_profile = 1; + //} + + return ret; +} + +int SrsAvcAacCodec::video_avc_demux(char* data, int size, SrsCodecSample* sample) +{ + int ret = ERROR_SUCCESS; + + sample->is_video = true; + + if (!data || size <= 0) { + srs_trace("no video present, ignore it."); + return ret; + } + + if ((ret = stream->initialize(data, size)) != ERROR_SUCCESS) { + return ret; + } + + // video decode + if (!stream->require(1)) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("video codec decode frame_type failed. ret=%d", ret); + return ret; + } + + // @see: E.4.3 Video Tags, video_file_format_spec_v10_1.pdf, page 78 + int8_t frame_type = stream->read_1bytes(); + int8_t codec_id = frame_type & 0x0f; + frame_type = (frame_type >> 4) & 0x0f; + + sample->frame_type = (SrsCodecVideoAVCFrame)frame_type; + + // ignore info frame without error, + // @see https://github.com/simple-rtmp-server/srs/issues/288#issuecomment-69863909 + if (sample->frame_type == SrsCodecVideoAVCFrameVideoInfoFrame) { + srs_warn("video codec igone the info frame, ret=%d", ret); + return ret; + } + + // only support h.264/avc + if (codec_id != SrsCodecVideoAVC) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("video codec only support video h.264/avc codec. actual=%d, ret=%d", codec_id, ret); + return ret; + } + video_codec_id = codec_id; + + if (!stream->require(4)) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("video codec decode avc_packet_type failed. ret=%d", ret); + return ret; + } + int8_t avc_packet_type = stream->read_1bytes(); + int32_t composition_time = stream->read_3bytes(); + + // pts = dts + cts. + sample->cts = composition_time; + sample->avc_packet_type = (SrsCodecVideoAVCType)avc_packet_type; + + if (avc_packet_type == SrsCodecVideoAVCTypeSequenceHeader) { + if ((ret = avc_demux_sps_pps(stream)) != ERROR_SUCCESS) { + return ret; + } + } else if (avc_packet_type == SrsCodecVideoAVCTypeNALU){ + // ensure the sequence header demuxed + if (avc_extra_size <= 0 || !avc_extra_data) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("avc decode failed, sequence header not found. ret=%d", ret); + return ret; + } + + // guess for the first time. + if (payload_format == SrsAvcPayloadFormatGuess) { + // One or more NALUs (Full frames are required) + // try "AnnexB" from H.264-AVC-ISO_IEC_14496-10.pdf, page 211. + if ((ret = avc_demux_annexb_format(stream, sample)) != ERROR_SUCCESS) { + // stop try when system error. + if (ret != ERROR_HLS_AVC_TRY_OTHERS) { + srs_error("avc demux for annexb failed. ret=%d", ret); + return ret; + } + + // try "ISO Base Media File Format" from H.264-AVC-ISO_IEC_14496-15.pdf, page 20 + if ((ret = avc_demux_ibmf_format(stream, sample)) != ERROR_SUCCESS) { + return ret; + } else { + payload_format = SrsAvcPayloadFormatIbmf; + srs_info("hls guess avc payload is ibmf format."); + } + } else { + payload_format = SrsAvcPayloadFormatAnnexb; + srs_info("hls guess avc payload is annexb format."); + } + } else if (payload_format == SrsAvcPayloadFormatIbmf) { + // try "ISO Base Media File Format" from H.264-AVC-ISO_IEC_14496-15.pdf, page 20 + if ((ret = avc_demux_ibmf_format(stream, sample)) != ERROR_SUCCESS) { + return ret; + } + srs_info("hls decode avc payload in ibmf format."); + } else { + // One or more NALUs (Full frames are required) + // try "AnnexB" from H.264-AVC-ISO_IEC_14496-10.pdf, page 211. + if ((ret = avc_demux_annexb_format(stream, sample)) != ERROR_SUCCESS) { + // ok, we guess out the payload is annexb, but maybe changed to ibmf. + if (ret != ERROR_HLS_AVC_TRY_OTHERS) { + srs_error("avc demux for annexb failed. ret=%d", ret); + return ret; + } + + // try "ISO Base Media File Format" from H.264-AVC-ISO_IEC_14496-15.pdf, page 20 + if ((ret = avc_demux_ibmf_format(stream, sample)) != ERROR_SUCCESS) { + return ret; + } else { + payload_format = SrsAvcPayloadFormatIbmf; + srs_warn("hls avc payload change from annexb to ibmf format."); + } + } + srs_info("hls decode avc payload in annexb format."); + } + } else { + // ignored. + } + + srs_info("video decoded, type=%d, codec=%d, avc=%d, cts=%d, size=%d", + frame_type, video_codec_id, avc_packet_type, composition_time, size); + + return ret; +} + +int SrsAvcAacCodec::avc_demux_sps_pps(SrsStream* stream) +{ + int ret = ERROR_SUCCESS; + + // AVCDecoderConfigurationRecord + // 5.2.4.1.1 Syntax, H.264-AVC-ISO_IEC_14496-15.pdf, page 16 + avc_extra_size = stream->size() - stream->pos(); + if (avc_extra_size > 0) { + srs_freep(avc_extra_data); + avc_extra_data = new char[avc_extra_size]; + memcpy(avc_extra_data, stream->data() + stream->pos(), avc_extra_size); + } + + if (!stream->require(6)) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("avc decode sequenc header failed. ret=%d", ret); + return ret; + } + //int8_t configurationVersion = stream->read_1bytes(); + stream->read_1bytes(); + //int8_t AVCProfileIndication = stream->read_1bytes(); + avc_profile = (SrsAvcProfile)stream->read_1bytes(); + //int8_t profile_compatibility = stream->read_1bytes(); + stream->read_1bytes(); + //int8_t AVCLevelIndication = stream->read_1bytes(); + avc_level = (SrsAvcLevel)stream->read_1bytes(); + + // parse the NALU size. + int8_t lengthSizeMinusOne = stream->read_1bytes(); + lengthSizeMinusOne &= 0x03; + NAL_unit_length = lengthSizeMinusOne; + + // 5.3.4.2.1 Syntax, H.264-AVC-ISO_IEC_14496-15.pdf, page 16 + // 5.2.4.1 AVC decoder configuration record + // 5.2.4.1.2 Semantics + // The value of this field shall be one of 0, 1, or 3 corresponding to a + // length encoded with 1, 2, or 4 bytes, respectively. + if (NAL_unit_length == 2) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("sps lengthSizeMinusOne should never be 2. ret=%d", ret); + return ret; + } + + // 1 sps, 7.3.2.1 Sequence parameter set RBSP syntax + // H.264-AVC-ISO_IEC_14496-10.pdf, page 45. + if (!stream->require(1)) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("avc decode sequenc header sps failed. ret=%d", ret); + return ret; + } + int8_t numOfSequenceParameterSets = stream->read_1bytes(); + numOfSequenceParameterSets &= 0x1f; + if (numOfSequenceParameterSets != 1) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("avc decode sequenc header sps failed. ret=%d", ret); + return ret; + } + if (!stream->require(2)) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("avc decode sequenc header sps size failed. ret=%d", ret); + return ret; + } + sequenceParameterSetLength = stream->read_2bytes(); + if (!stream->require(sequenceParameterSetLength)) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("avc decode sequenc header sps data failed. ret=%d", ret); + return ret; + } + if (sequenceParameterSetLength > 0) { + srs_freep(sequenceParameterSetNALUnit); + sequenceParameterSetNALUnit = new char[sequenceParameterSetLength]; + stream->read_bytes(sequenceParameterSetNALUnit, sequenceParameterSetLength); + } + // 1 pps + if (!stream->require(1)) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("avc decode sequenc header pps failed. ret=%d", ret); + return ret; + } + int8_t numOfPictureParameterSets = stream->read_1bytes(); + numOfPictureParameterSets &= 0x1f; + if (numOfPictureParameterSets != 1) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("avc decode sequenc header pps failed. ret=%d", ret); + return ret; + } + if (!stream->require(2)) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("avc decode sequenc header pps size failed. ret=%d", ret); + return ret; + } + pictureParameterSetLength = stream->read_2bytes(); + if (!stream->require(pictureParameterSetLength)) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("avc decode sequenc header pps data failed. ret=%d", ret); + return ret; + } + if (pictureParameterSetLength > 0) { + srs_freep(pictureParameterSetNALUnit); + pictureParameterSetNALUnit = new char[pictureParameterSetLength]; + stream->read_bytes(pictureParameterSetNALUnit, pictureParameterSetLength); + } + + return avc_demux_sps(); +} + +int SrsAvcAacCodec::avc_demux_sps() +{ + int ret = ERROR_SUCCESS; + + if (!sequenceParameterSetLength) { + return ret; + } + + SrsStream stream; + if ((ret = stream.initialize(sequenceParameterSetNALUnit, sequenceParameterSetLength)) != ERROR_SUCCESS) { + return ret; + } + + // for NALU, 7.3.1 NAL unit syntax + // H.264-AVC-ISO_IEC_14496-10-2012.pdf, page 61. + if (!stream.require(1)) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("avc decode sps failed. ret=%d", ret); + return ret; + } + int8_t nutv = stream.read_1bytes(); + + // forbidden_zero_bit shall be equal to 0. + int8_t forbidden_zero_bit = (nutv >> 7) & 0x01; + if (forbidden_zero_bit) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("forbidden_zero_bit shall be equal to 0. ret=%d", ret); + return ret; + } + + // nal_ref_idc not equal to 0 specifies that the content of the NAL unit contains a sequence parameter set or a picture + // parameter set or a slice of a reference picture or a slice data partition of a reference picture. + int8_t nal_ref_idc = (nutv >> 5) & 0x03; + if (!nal_ref_idc) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("for sps, nal_ref_idc shall be not be equal to 0. ret=%d", ret); + return ret; + } + + // 7.4.1 NAL unit semantics + // H.264-AVC-ISO_IEC_14496-10-2012.pdf, page 61. + // nal_unit_type specifies the type of RBSP data structure contained in the NAL unit as specified in Table 7-1. + SrsAvcNaluType nal_unit_type = (SrsAvcNaluType)(nutv & 0x1f); + if (nal_unit_type != 7) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("for sps, nal_unit_type shall be equal to 7. ret=%d", ret); + return ret; + } + + // decode the rbsp from sps. + // rbsp[ i ] a raw byte sequence payload is specified as an ordered sequence of bytes. + int8_t* rbsp = new int8_t[sequenceParameterSetLength]; + SrsAutoFree(int8_t, rbsp); + + int nb_rbsp = 0; + while (!stream.empty()) { + rbsp[nb_rbsp] = stream.read_1bytes(); + + // XX 00 00 03 XX, the 03 byte should be drop. + if (nb_rbsp > 2 && rbsp[nb_rbsp - 2] == 0 && rbsp[nb_rbsp - 1] == 0 && rbsp[nb_rbsp] == 3) { + // read 1byte more. + if (stream.empty()) { + break; + } + rbsp[nb_rbsp] = stream.read_1bytes(); + nb_rbsp++; + + continue; + } + + nb_rbsp++; + } + + return avc_demux_sps_rbsp((char*)rbsp, nb_rbsp); +} + + +int SrsAvcAacCodec::avc_demux_sps_rbsp(char* rbsp, int nb_rbsp) +{ + int ret = ERROR_SUCCESS; + + // reparse the rbsp. + SrsStream stream; + if ((ret = stream.initialize(rbsp, nb_rbsp)) != ERROR_SUCCESS) { + return ret; + } + + // for SPS, 7.3.2.1.1 Sequence parameter set data syntax + // H.264-AVC-ISO_IEC_14496-10-2012.pdf, page 62. + if (!stream.require(3)) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("sps shall atleast 3bytes. ret=%d", ret); + return ret; + } + u_int8_t profile_idc = stream.read_1bytes(); + if (!profile_idc) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("sps the profile_idc invalid. ret=%d", ret); + return ret; + } + + int8_t flags = stream.read_1bytes(); + if (flags & 0x03) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("sps the flags invalid. ret=%d", ret); + return ret; + } + + u_int8_t level_idc = stream.read_1bytes(); + if (!level_idc) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("sps the level_idc invalid. ret=%d", ret); + return ret; + } + + SrsBitStream bs; + if ((ret = bs.initialize(&stream)) != ERROR_SUCCESS) { + return ret; + } + + int32_t seq_parameter_set_id = -1; + if ((ret = srs_avc_nalu_read_uev(&bs, seq_parameter_set_id)) != ERROR_SUCCESS) { + return ret; + } + if (seq_parameter_set_id < 0) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("sps the seq_parameter_set_id invalid. ret=%d", ret); + return ret; + } + srs_info("sps parse profile=%d, level=%d, sps_id=%d", profile_idc, level_idc, seq_parameter_set_id); + + if (profile_idc == 100 || profile_idc == 110 || profile_idc == 122 || profile_idc == 244 + || profile_idc == 44 || profile_idc == 83 || profile_idc == 86 || profile_idc == 118 + || profile_idc == 128 + ) { + int32_t chroma_format_idc = -1; + if ((ret = srs_avc_nalu_read_uev(&bs, chroma_format_idc)) != ERROR_SUCCESS) { + return ret; + } + if (chroma_format_idc == 3) { + int8_t separate_colour_plane_flag = -1; + if ((ret = srs_avc_nalu_read_bit(&bs, separate_colour_plane_flag)) != ERROR_SUCCESS) { + return ret; + } + } + + int32_t bit_depth_luma_minus8 = -1; + if ((ret = srs_avc_nalu_read_uev(&bs, bit_depth_luma_minus8)) != ERROR_SUCCESS) { + return ret; + } + + int32_t bit_depth_chroma_minus8 = -1; + if ((ret = srs_avc_nalu_read_uev(&bs, bit_depth_chroma_minus8)) != ERROR_SUCCESS) { + return ret; + } + + int8_t qpprime_y_zero_transform_bypass_flag = -1; + if ((ret = srs_avc_nalu_read_bit(&bs, qpprime_y_zero_transform_bypass_flag)) != ERROR_SUCCESS) { + return ret; + } + + int8_t seq_scaling_matrix_present_flag = -1; + if ((ret = srs_avc_nalu_read_bit(&bs, seq_scaling_matrix_present_flag)) != ERROR_SUCCESS) { + return ret; + } + if (seq_scaling_matrix_present_flag) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("sps the seq_scaling_matrix_present_flag invalid. ret=%d", ret); + return ret; + } + } + + int32_t log2_max_frame_num_minus4 = -1; + if ((ret = srs_avc_nalu_read_uev(&bs, log2_max_frame_num_minus4)) != ERROR_SUCCESS) { + return ret; + } + + int32_t pic_order_cnt_type = -1; + if ((ret = srs_avc_nalu_read_uev(&bs, pic_order_cnt_type)) != ERROR_SUCCESS) { + return ret; + } + + if (pic_order_cnt_type == 0) { + int32_t log2_max_pic_order_cnt_lsb_minus4 = -1; + if ((ret = srs_avc_nalu_read_uev(&bs, log2_max_pic_order_cnt_lsb_minus4)) != ERROR_SUCCESS) { + return ret; + } + } else if (pic_order_cnt_type == 1) { + int8_t delta_pic_order_always_zero_flag = -1; + if ((ret = srs_avc_nalu_read_bit(&bs, delta_pic_order_always_zero_flag)) != ERROR_SUCCESS) { + return ret; + } + + int32_t offset_for_non_ref_pic = -1; + if ((ret = srs_avc_nalu_read_uev(&bs, offset_for_non_ref_pic)) != ERROR_SUCCESS) { + return ret; + } + + int32_t offset_for_top_to_bottom_field = -1; + if ((ret = srs_avc_nalu_read_uev(&bs, offset_for_top_to_bottom_field)) != ERROR_SUCCESS) { + return ret; + } + + int32_t num_ref_frames_in_pic_order_cnt_cycle = -1; + if ((ret = srs_avc_nalu_read_uev(&bs, num_ref_frames_in_pic_order_cnt_cycle)) != ERROR_SUCCESS) { + return ret; + } + if (num_ref_frames_in_pic_order_cnt_cycle) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("sps the num_ref_frames_in_pic_order_cnt_cycle invalid. ret=%d", ret); + return ret; + } + } + + int32_t max_num_ref_frames = -1; + if ((ret = srs_avc_nalu_read_uev(&bs, max_num_ref_frames)) != ERROR_SUCCESS) { + return ret; + } + + int8_t gaps_in_frame_num_value_allowed_flag = -1; + if ((ret = srs_avc_nalu_read_bit(&bs, gaps_in_frame_num_value_allowed_flag)) != ERROR_SUCCESS) { + return ret; + } + + int32_t pic_width_in_mbs_minus1 = -1; + if ((ret = srs_avc_nalu_read_uev(&bs, pic_width_in_mbs_minus1)) != ERROR_SUCCESS) { + return ret; + } + + int32_t pic_height_in_map_units_minus1 = -1; + if ((ret = srs_avc_nalu_read_uev(&bs, pic_height_in_map_units_minus1)) != ERROR_SUCCESS) { + return ret; + } + + width = (int)(pic_width_in_mbs_minus1 + 1) * 16; + height = (int)(pic_height_in_map_units_minus1 + 1) * 16; + + return ret; +} + +int SrsAvcAacCodec::avc_demux_annexb_format(SrsStream* stream, SrsCodecSample* sample) +{ + int ret = ERROR_SUCCESS; + + // not annexb, try others + if (!srs_avc_startswith_annexb(stream, NULL)) { + return ERROR_HLS_AVC_TRY_OTHERS; + } + + // AnnexB + // B.1.1 Byte stream NAL unit syntax, + // H.264-AVC-ISO_IEC_14496-10.pdf, page 211. + while (!stream->empty()) { + // find start code + int nb_start_code = 0; + if (!srs_avc_startswith_annexb(stream, &nb_start_code)) { + return ret; + } + + // skip the start code. + if (nb_start_code > 0) { + stream->skip(nb_start_code); + } + + // the NALU start bytes. + char* p = stream->data() + stream->pos(); + + // get the last matched NALU + while (!stream->empty()) { + if (srs_avc_startswith_annexb(stream, NULL)) { + break; + } + + stream->skip(1); + } + + char* pp = stream->data() + stream->pos(); + + // skip the empty. + if (pp - p <= 0) { + continue; + } + + // got the NALU. + if ((ret = sample->add_sample_unit(p, pp - p)) != ERROR_SUCCESS) { + srs_error("annexb add video sample failed. ret=%d", ret); + return ret; + } + } + + return ret; +} + +int SrsAvcAacCodec::avc_demux_ibmf_format(SrsStream* stream, SrsCodecSample* sample) +{ + int ret = ERROR_SUCCESS; + + int PictureLength = stream->size() - stream->pos(); + + // 5.3.4.2.1 Syntax, H.264-AVC-ISO_IEC_14496-15.pdf, page 16 + // 5.2.4.1 AVC decoder configuration record + // 5.2.4.1.2 Semantics + // The value of this field shall be one of 0, 1, or 3 corresponding to a + // length encoded with 1, 2, or 4 bytes, respectively. + srs_assert(NAL_unit_length != 2); + + // 5.3.4.2.1 Syntax, H.264-AVC-ISO_IEC_14496-15.pdf, page 20 + for (int i = 0; i < PictureLength;) { + // unsigned int((NAL_unit_length+1)*8) NALUnitLength; + if (!stream->require(NAL_unit_length + 1)) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("avc decode NALU size failed. ret=%d", ret); + return ret; + } + int32_t NALUnitLength = 0; + if (NAL_unit_length == 3) { + NALUnitLength = stream->read_4bytes(); + } else if (NAL_unit_length == 1) { + NALUnitLength = stream->read_2bytes(); + } else { + NALUnitLength = stream->read_1bytes(); + } + + // maybe stream is invalid format. + // see: https://github.com/simple-rtmp-server/srs/issues/183 + if (NALUnitLength < 0) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("maybe stream is AnnexB format. ret=%d", ret); + return ret; + } + + // NALUnit + if (!stream->require(NALUnitLength)) { + ret = ERROR_HLS_DECODE_ERROR; + srs_error("avc decode NALU data failed. ret=%d", ret); + return ret; + } + // 7.3.1 NAL unit syntax, H.264-AVC-ISO_IEC_14496-10.pdf, page 44. + if ((ret = sample->add_sample_unit(stream->data() + stream->pos(), NALUnitLength)) != ERROR_SUCCESS) { + srs_error("avc add video sample failed. ret=%d", ret); + return ret; + } + stream->skip(NALUnitLength); + + i += NAL_unit_length + 1 + NALUnitLength; + } + + return ret; +} + diff --git a/trunk/src/kernel/srs_kernel_codec.hpp b/trunk/src/kernel/srs_kernel_codec.hpp index 32b019bd5d..5f654db3e1 100644 --- a/trunk/src/kernel/srs_kernel_codec.hpp +++ b/trunk/src/kernel/srs_kernel_codec.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -30,6 +30,10 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include +#include + +class SrsStream; + // AACPacketType IF SoundFormat == 10 UI8 // The following values are defined: // 0 = AAC sequence header @@ -53,7 +57,7 @@ enum SrsCodecAudioType // 5 = video info/command frame enum SrsCodecVideoAVCFrame { - // set to the max value to reserved, for array map. + // set to the zero to reserved, for array map. SrsCodecVideoAVCFrameReserved = 0, SrsCodecVideoAVCFrameReserved1 = 6, @@ -91,10 +95,13 @@ enum SrsCodecVideoAVCType // 7 = AVC enum SrsCodecVideo { - // set to the max value to reserved, for array map. + // set to the zero to reserved, for array map. SrsCodecVideoReserved = 0, SrsCodecVideoReserved1 = 1, - SrsCodecVideoReserved2 = 8, + SrsCodecVideoReserved2 = 9, + + // for user to disable video, for example, use pure audio hls. + SrsCodecVideoDisabled = 8, SrsCodecVideoSorensonH263 = 2, SrsCodecVideoScreenVideo = 3, @@ -103,6 +110,7 @@ enum SrsCodecVideo SrsCodecVideoScreenVideoVersion2 = 6, SrsCodecVideoAVC = 7, }; +std::string srs_codec_video2str(SrsCodecVideo codec); // SoundFormat UB [4] // Format of SoundData. The following values are defined: @@ -143,6 +151,42 @@ enum SrsCodecAudio SrsCodecAudioReservedMP3_8kHz = 14, SrsCodecAudioReservedDeviceSpecificSound = 15, }; +std::string srs_codec_audio2str(SrsCodecAudio codec); + +/** +* the FLV/RTMP supported audio sample rate. +* Sampling rate. The following values are defined: +* 0 = 5.5 kHz = 5512 Hz +* 1 = 11 kHz = 11025 Hz +* 2 = 22 kHz = 22050 Hz +* 3 = 44 kHz = 44100 Hz +*/ +enum SrsCodecAudioSampleRate +{ + // set to the max value to reserved, for array map. + SrsCodecAudioSampleRateReserved = 4, + + SrsCodecAudioSampleRate5512 = 0, + SrsCodecAudioSampleRate11025 = 1, + SrsCodecAudioSampleRate22050 = 2, + SrsCodecAudioSampleRate44100 = 3, +}; + +/** +* E.4.1 FLV Tag, page 75 +*/ +enum SrsCodecFlvTag +{ + // set to the zero to reserved, for array map. + SrsCodecFlvTagReserved = 0, + + // 8 = audio + SrsCodecFlvTagAudio = 8, + // 9 = video + SrsCodecFlvTagVideo = 9, + // 18 = script data + SrsCodecFlvTagScript = 18, +}; /** * Annex E. The FLV File Format @@ -177,4 +221,429 @@ class SrsFlvCodec static bool audio_is_aac(char* data, int size); }; +/** +* the public data, event HLS disable, others can use it. +*/ +/** +* the flv sample rate map +*/ +extern int flv_sample_rates[]; + +/** +* the aac sample rate map +*/ +extern int aac_sample_rates[]; + +#define SRS_SRS_MAX_CODEC_SAMPLE 128 +#define SRS_AAC_SAMPLE_RATE_UNSET 15 + +// in ms, for HLS aac flush the audio +#define SRS_CONF_DEFAULT_AAC_DELAY 60 + +// max PES packets size to flush the video. +#define SRS_AUTO_HLS_AUDIO_CACHE_SIZE 128 * 1024 + +/** +* the FLV/RTMP supported audio sample size. +* Size of each audio sample. This parameter only pertains to +* uncompressed formats. Compressed formats always decode +* to 16 bits internally. +* 0 = 8-bit samples +* 1 = 16-bit samples +*/ +enum SrsCodecAudioSampleSize +{ + // set to the max value to reserved, for array map. + SrsCodecAudioSampleSizeReserved = 2, + + SrsCodecAudioSampleSize8bit = 0, + SrsCodecAudioSampleSize16bit = 1, +}; + +/** +* the FLV/RTMP supported audio sound type/channel. +* Mono or stereo sound +* 0 = Mono sound +* 1 = Stereo sound +*/ +enum SrsCodecAudioSoundType +{ + // set to the max value to reserved, for array map. + SrsCodecAudioSoundTypeReserved = 2, + + SrsCodecAudioSoundTypeMono = 0, + SrsCodecAudioSoundTypeStereo = 1, +}; + +/** + * Table 7-1 – NAL unit type codes, syntax element categories, and NAL unit type classes + * H.264-AVC-ISO_IEC_14496-10-2012.pdf, page 83. + */ +enum SrsAvcNaluType +{ + // Unspecified + SrsAvcNaluTypeReserved = 0, + + // Coded slice of a non-IDR picture slice_layer_without_partitioning_rbsp( ) + SrsAvcNaluTypeNonIDR = 1, + // Coded slice data partition A slice_data_partition_a_layer_rbsp( ) + SrsAvcNaluTypeDataPartitionA = 2, + // Coded slice data partition B slice_data_partition_b_layer_rbsp( ) + SrsAvcNaluTypeDataPartitionB = 3, + // Coded slice data partition C slice_data_partition_c_layer_rbsp( ) + SrsAvcNaluTypeDataPartitionC = 4, + // Coded slice of an IDR picture slice_layer_without_partitioning_rbsp( ) + SrsAvcNaluTypeIDR = 5, + // Supplemental enhancement information (SEI) sei_rbsp( ) + SrsAvcNaluTypeSEI = 6, + // Sequence parameter set seq_parameter_set_rbsp( ) + SrsAvcNaluTypeSPS = 7, + // Picture parameter set pic_parameter_set_rbsp( ) + SrsAvcNaluTypePPS = 8, + // Access unit delimiter access_unit_delimiter_rbsp( ) + SrsAvcNaluTypeAccessUnitDelimiter = 9, + // End of sequence end_of_seq_rbsp( ) + SrsAvcNaluTypeEOSequence = 10, + // End of stream end_of_stream_rbsp( ) + SrsAvcNaluTypeEOStream = 11, + // Filler data filler_data_rbsp( ) + SrsAvcNaluTypeFilterData = 12, + // Sequence parameter set extension seq_parameter_set_extension_rbsp( ) + SrsAvcNaluTypeSPSExt = 13, + // Prefix NAL unit prefix_nal_unit_rbsp( ) + SrsAvcNaluTypePrefixNALU = 14, + // Subset sequence parameter set subset_seq_parameter_set_rbsp( ) + SrsAvcNaluTypeSubsetSPS = 15, + // Coded slice of an auxiliary coded picture without partitioning slice_layer_without_partitioning_rbsp( ) + SrsAvcNaluTypeLayerWithoutPartition = 19, + // Coded slice extension slice_layer_extension_rbsp( ) + SrsAvcNaluTypeCodedSliceExt = 20, +}; +std::string srs_codec_avc_nalu2str(SrsAvcNaluType nalu_type); + +/** +* the codec sample unit. +* for h.264 video packet, a NALU is a sample unit. +* for aac raw audio packet, a NALU is the entire aac raw data. +* for sequence header, it's not a sample unit. +*/ +class SrsCodecSampleUnit +{ +public: + /** + * the sample bytes is directly ptr to packet bytes, + * user should never use it when packet destroyed. + */ + int size; + char* bytes; +public: + SrsCodecSampleUnit(); + virtual ~SrsCodecSampleUnit(); +}; + +/** +* the samples in the flv audio/video packet. +* the sample used to analysis a video/audio packet, +* split the h.264 NALUs to buffers, or aac raw data to a buffer, +* and decode the video/audio specified infos. +* +* the sample unit: +* a video packet codec in h.264 contains many NALUs, each is a sample unit. +* a audio packet codec in aac is a sample unit. +* @remark, the video/audio sequence header is not sample unit, +* all sequence header stores as extra data, +* @see SrsAvcAacCodec.avc_extra_data and SrsAvcAacCodec.aac_extra_data +* @remark, user must clear all samples before decode a new video/audio packet. +*/ +class SrsCodecSample +{ +public: + /** + * each audio/video raw data packet will dumps to one or multiple buffers, + * the buffers will write to hls and clear to reset. + * generally, aac audio packet corresponding to one buffer, + * where avc/h264 video packet may contains multiple buffer. + */ + int nb_sample_units; + SrsCodecSampleUnit sample_units[SRS_SRS_MAX_CODEC_SAMPLE]; +public: + /** + * whether the sample is video sample which demux from video packet. + */ + bool is_video; + /** + * CompositionTime, video_file_format_spec_v10_1.pdf, page 78. + * cts = pts - dts, where dts = flvheader->timestamp. + */ + int32_t cts; +public: + // video specified + SrsCodecVideoAVCFrame frame_type; + SrsCodecVideoAVCType avc_packet_type; + // whether sample_units contains IDR frame. + bool has_idr; + SrsAvcNaluType first_nalu_type; +public: + // audio specified + SrsCodecAudio acodec; + // audio aac specified. + SrsCodecAudioSampleRate sound_rate; + SrsCodecAudioSampleSize sound_size; + SrsCodecAudioSoundType sound_type; + SrsCodecAudioType aac_packet_type; +public: + SrsCodecSample(); + virtual ~SrsCodecSample(); +public: + /** + * clear all samples. + * the sample units never copy the bytes, it directly use the ptr, + * so when video/audio packet is destroyed, the sample must be clear. + * in a word, user must clear sample before demux it. + * @remark demux sample use SrsAvcAacCodec.audio_aac_demux or video_avc_demux. + */ + void clear(); + /** + * add the a sample unit, it's a h.264 NALU or aac raw data. + * the sample unit directly use the ptr of packet bytes, + * so user must never use sample unit when packet is destroyed. + * in a word, user must clear sample before demux it. + */ + int add_sample_unit(char* bytes, int size); +}; + +/** +* the avc payload format, must be ibmf or annexb format. +* we guess by annexb first, then ibmf for the first time, +* and we always use the guessed format for the next time. +*/ +enum SrsAvcPayloadFormat +{ + SrsAvcPayloadFormatGuess = 0, + SrsAvcPayloadFormatAnnexb, + SrsAvcPayloadFormatIbmf, +}; + +/** +* the aac profile, for ADTS(HLS/TS) +* @see https://github.com/simple-rtmp-server/srs/issues/310 +*/ +enum SrsAacProfile +{ + SrsAacProfileReserved = 3, + + // @see 7.1 Profiles, aac-iso-13818-7.pdf, page 40 + SrsAacProfileMain = 0, + SrsAacProfileLC = 1, + SrsAacProfileSSR = 2, +}; +std::string srs_codec_aac_profile2str(SrsAacProfile aac_profile); + +/** +* the aac object type, for RTMP sequence header +* for AudioSpecificConfig, @see aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 33 +* for audioObjectType, @see aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 23 +*/ +enum SrsAacObjectType +{ + SrsAacObjectTypeReserved = 0, + + // Table 1.1 – Audio Object Type definition + // @see @see aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 23 + SrsAacObjectTypeAacMain = 1, + SrsAacObjectTypeAacLC = 2, + SrsAacObjectTypeAacSSR = 3, + + // AAC HE = LC+SBR + SrsAacObjectTypeAacHE = 5, + // AAC HEv2 = LC+SBR+PS + SrsAacObjectTypeAacHEV2 = 29, +}; +std::string srs_codec_aac_object2str(SrsAacObjectType aac_object); +// ts/hls/adts audio header profile to RTMP sequence header object type. +SrsAacObjectType srs_codec_aac_ts2rtmp(SrsAacProfile profile); +// RTMP sequence header object type to ts/hls/adts audio header profile. +SrsAacProfile srs_codec_aac_rtmp2ts(SrsAacObjectType object_type); + +/** +* the profile for avc/h.264. +* @see Annex A Profiles and levels, H.264-AVC-ISO_IEC_14496-10.pdf, page 205. +*/ +enum SrsAvcProfile +{ + SrsAvcProfileReserved = 0, + + // @see ffmpeg, libavcodec/avcodec.h:2713 + SrsAvcProfileBaseline = 66, + // FF_PROFILE_H264_CONSTRAINED (1<<9) // 8+1; constraint_set1_flag + // FF_PROFILE_H264_CONSTRAINED_BASELINE (66|FF_PROFILE_H264_CONSTRAINED) + SrsAvcProfileConstrainedBaseline = 578, + SrsAvcProfileMain = 77, + SrsAvcProfileExtended = 88, + SrsAvcProfileHigh = 100, + SrsAvcProfileHigh10 = 110, + SrsAvcProfileHigh10Intra = 2158, + SrsAvcProfileHigh422 = 122, + SrsAvcProfileHigh422Intra = 2170, + SrsAvcProfileHigh444 = 144, + SrsAvcProfileHigh444Predictive = 244, + SrsAvcProfileHigh444Intra = 2192, +}; +std::string srs_codec_avc_profile2str(SrsAvcProfile profile); + +/** +* the level for avc/h.264. +* @see Annex A Profiles and levels, H.264-AVC-ISO_IEC_14496-10.pdf, page 207. +*/ +enum SrsAvcLevel +{ + SrsAvcLevelReserved = 0, + + SrsAvcLevel_1 = 10, + SrsAvcLevel_11 = 11, + SrsAvcLevel_12 = 12, + SrsAvcLevel_13 = 13, + SrsAvcLevel_2 = 20, + SrsAvcLevel_21 = 21, + SrsAvcLevel_22 = 22, + SrsAvcLevel_3 = 30, + SrsAvcLevel_31 = 31, + SrsAvcLevel_32 = 32, + SrsAvcLevel_4 = 40, + SrsAvcLevel_41 = 41, + SrsAvcLevel_5 = 50, + SrsAvcLevel_51 = 51, +}; +std::string srs_codec_avc_level2str(SrsAvcLevel level); + +/** +* the h264/avc and aac codec, for media stream. +* +* to demux the FLV/RTMP video/audio packet to sample, +* add each NALUs of h.264 as a sample unit to sample, +* while the entire aac raw data as a sample unit. +* +* for sequence header, +* demux it and save it in the avc_extra_data and aac_extra_data, +* +* for the codec info, such as audio sample rate, +* decode from FLV/RTMP header, then use codec info in sequence +* header to override it. +*/ +class SrsAvcAacCodec +{ +private: + SrsStream* stream; +public: + /** + * metadata specified + */ + int duration; + int width; + int height; + int frame_rate; + // @see: SrsCodecVideo + int video_codec_id; + int video_data_rate; // in bps + // @see: SrsCod ecAudioType + int audio_codec_id; + int audio_data_rate; // in bps +public: + /** + * video specified + */ + // profile_idc, H.264-AVC-ISO_IEC_14496-10.pdf, page 45. + SrsAvcProfile avc_profile; + // level_idc, H.264-AVC-ISO_IEC_14496-10.pdf, page 45. + SrsAvcLevel avc_level; + // lengthSizeMinusOne, H.264-AVC-ISO_IEC_14496-15.pdf, page 16 + int8_t NAL_unit_length; + u_int16_t sequenceParameterSetLength; + char* sequenceParameterSetNALUnit; + u_int16_t pictureParameterSetLength; + char* pictureParameterSetNALUnit; +private: + // the avc payload format. + SrsAvcPayloadFormat payload_format; +public: + /** + * audio specified + * audioObjectType, in 1.6.2.1 AudioSpecificConfig, page 33, + * 1.5.1.1 Audio object type definition, page 23, + * in aac-mp4a-format-ISO_IEC_14496-3+2001.pdf. + */ + SrsAacObjectType aac_object; + /** + * samplingFrequencyIndex + */ + u_int8_t aac_sample_rate; + /** + * channelConfiguration + */ + u_int8_t aac_channels; +public: + /** + * the avc extra data, the AVC sequence header, + * without the flv codec header, + * @see: ffmpeg, AVCodecContext::extradata + */ + int avc_extra_size; + char* avc_extra_data; + /** + * the aac extra data, the AAC sequence header, + * without the flv codec header, + * @see: ffmpeg, AVCodecContext::extradata + */ + int aac_extra_size; + char* aac_extra_data; +public: + SrsAvcAacCodec(); + virtual ~SrsAvcAacCodec(); +// the following function used for hls to build the sample and codec. +public: + /** + * demux the audio packet in aac codec. + * the packet mux in FLV/RTMP format defined in flv specification. + * demux the audio speicified data(sound_format, sound_size, ...) to sample. + * demux the aac specified data(aac_profile, ...) to codec from sequence header. + * demux the aac raw to sample units. + */ + virtual int audio_aac_demux(char* data, int size, SrsCodecSample* sample); + virtual int audio_mp3_demux(char* data, int size, SrsCodecSample* sample); + /** + * demux the video packet in h.264 codec. + * the packet mux in FLV/RTMP format defined in flv specification. + * demux the video specified data(frame_type, codec_id, ...) to sample. + * demux the h.264 sepcified data(avc_profile, ...) to codec from sequence header. + * demux the h.264 NALUs to sampe units. + */ + virtual int video_avc_demux(char* data, int size, SrsCodecSample* sample); +public: + /** + * directly demux the sequence header, without RTMP packet header. + */ + virtual int audio_aac_sequence_header_demux(char* data, int size); +private: + /** + * when avc packet type is SrsCodecVideoAVCTypeSequenceHeader, + * decode the sps and pps. + */ + virtual int avc_demux_sps_pps(SrsStream* stream); + /** + * decode the sps rbsp stream. + */ + virtual int avc_demux_sps(); + virtual int avc_demux_sps_rbsp(char* rbsp, int nb_rbsp); + /** + * demux the avc NALU in "AnnexB" + * from H.264-AVC-ISO_IEC_14496-10.pdf, page 211. + */ + virtual int avc_demux_annexb_format(SrsStream* stream, SrsCodecSample* sample); + /** + * demux the avc NALU in "ISO Base Media File Format" + * from H.264-AVC-ISO_IEC_14496-15.pdf, page 20 + */ + virtual int avc_demux_ibmf_format(SrsStream* stream, SrsCodecSample* sample); +}; + #endif diff --git a/trunk/src/kernel/srs_kernel_consts.cpp b/trunk/src/kernel/srs_kernel_consts.cpp index f0c5a53589..8d00050238 100644 --- a/trunk/src/kernel/srs_kernel_consts.cpp +++ b/trunk/src/kernel/srs_kernel_consts.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/trunk/src/kernel/srs_kernel_consts.hpp b/trunk/src/kernel/srs_kernel_consts.hpp index 3f66203ef7..c4a09831da 100644 --- a/trunk/src/kernel/srs_kernel_consts.hpp +++ b/trunk/src/kernel/srs_kernel_consts.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -67,18 +67,19 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // the following is the timeout for rtmp protocol, // to avoid death connection. -// the timeout to wait client data, +// the timeout to send data to client, // if timeout, close the connection. #define SRS_CONSTS_RTMP_SEND_TIMEOUT_US (int64_t)(30*1000*1000LL) -// the timeout to send data to client, +// the timeout to wait client data, // if timeout, close the connection. #define SRS_CONSTS_RTMP_RECV_TIMEOUT_US (int64_t)(30*1000*1000LL) // the timeout to wait for client control message, // if timeout, we generally ignore and send the data to client, // generally, it's the pulse time for data seding. -#define SRS_CONSTS_RTMP_PULSE_TIMEOUT_US (int64_t)(200*1000LL) +// @remark, recomment to 500ms. +#define SRS_CONSTS_RTMP_PULSE_TIMEOUT_US (int64_t)(500*1000LL) /** * max rtmp header size: @@ -95,7 +96,30 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * that is, 1+4=5bytes. */ // always use fmt0 as cache. -//#define SRS_CONSTS_RTMP_MAX_FMT3_HEADER_SIZE 5 +#define SRS_CONSTS_RTMP_MAX_FMT3_HEADER_SIZE 5 + +/** +* for performance issue, +* the iovs cache, @see https://github.com/simple-rtmp-server/srs/issues/194 +* iovs cache for multiple messages for each connections. +* suppose the chunk size is 64k, each message send in a chunk which needs only 2 iovec, +* so the iovs max should be (SRS_PERF_MW_MSGS * 2) +* +* @remark, SRS will realloc when the iovs not enough. +*/ +#define SRS_CONSTS_IOVS_MAX (SRS_PERF_MW_MSGS * 2) +/** +* for performance issue, +* the c0c3 cache, @see https://github.com/simple-rtmp-server/srs/issues/194 +* c0c3 cache for multiple messages for each connections. +* each c0 <= 16byes, suppose the chunk size is 64k, +* each message send in a chunk which needs only a c0 header, +* so the c0c3 cache should be (SRS_PERF_MW_MSGS * 16) +* +* @remark, SRS will try another loop when c0c3 cache dry, for we cannot realloc it. +* so we use larger c0c3 cache, that is (SRS_PERF_MW_MSGS * 32) +*/ +#define SRS_CONSTS_C0C3_HEADERS_MAX (SRS_PERF_MW_MSGS * 32) /////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////// @@ -138,6 +162,12 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define SRS_CONSTS_LOG_HLS "HLS" // encoder log id. #define SRS_CONSTS_LOG_ENCODER "ENC" +// http stream log id. +#define SRS_CONSTS_LOG_HTTP_STREAM "HTS" +// http stream cache log id. +#define SRS_CONSTS_LOG_HTTP_STREAM_CACHE "HTC" +// stream caster log id. +#define SRS_CONSTS_LOG_STREAM_CASTER "SCS" /////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////// @@ -147,29 +177,21 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. /////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////// -// pithy-print consts values -/////////////////////////////////////////////////////////// -// the pithy stage for all play clients. -#define SRS_CONSTS_STAGE_PLAY_USER 1 -// the pithy stage for all publish clients. -#define SRS_CONSTS_STAGE_PUBLISH_USER 2 -// the pithy stage for all forward clients. -#define SRS_CONSTS_STAGE_FORWARDER 3 -// the pithy stage for all encoders. -#define SRS_CONSTS_STAGE_ENCODER 4 -// the pithy stage for all hls. -#define SRS_CONSTS_STAGE_HLS 5 -// the pithy stage for all ingesters. -#define SRS_CONSTS_STAGE_INGESTER 6 -// the pithy stage for all edge. -#define SRS_CONSTS_STAGE_EDGE 7 - -/////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////// +// RTMP consts values /////////////////////////////////////////////////////////// +#define SRS_CONSTS_RTMP_SET_DATAFRAME "@setDataFrame" +#define SRS_CONSTS_RTMP_ON_METADATA "onMetaData" + /////////////////////////////////////////////////////////// +// HTTP/HLS consts values /////////////////////////////////////////////////////////// +// @see hls-m3u8-draft-pantos-http-live-streaming-12.pdf, page 4 +// Lines are terminated by either a single LF character or a CR +// character followed by an LF character. +// CR = +#define SRS_CONSTS_CR '\r' // 0x0D +// LF = +#define SRS_CONSTS_LF '\n' // 0x0A /////////////////////////////////////////////////////////// // HTTP consts values @@ -224,43 +246,137 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define SRS_CONSTS_HTTP_Continue_str "Continue" #define SRS_CONSTS_HTTP_SwitchingProtocols_str "Switching Protocols" #define SRS_CONSTS_HTTP_OK_str "OK" -#define SRS_CONSTS_HTTP_Created_str "Created " +#define SRS_CONSTS_HTTP_Created_str "Created" #define SRS_CONSTS_HTTP_Accepted_str "Accepted" -#define SRS_CONSTS_HTTP_NonAuthoritativeInformation_str "Non Authoritative Information " -#define SRS_CONSTS_HTTP_NoContent_str "No Content " +#define SRS_CONSTS_HTTP_NonAuthoritativeInformation_str "Non Authoritative Information" +#define SRS_CONSTS_HTTP_NoContent_str "No Content" #define SRS_CONSTS_HTTP_ResetContent_str "Reset Content" #define SRS_CONSTS_HTTP_PartialContent_str "Partial Content" -#define SRS_CONSTS_HTTP_MultipleChoices_str "Multiple Choices " +#define SRS_CONSTS_HTTP_MultipleChoices_str "Multiple Choices" #define SRS_CONSTS_HTTP_MovedPermanently_str "Moved Permanently" #define SRS_CONSTS_HTTP_Found_str "Found" #define SRS_CONSTS_HTTP_SeeOther_str "See Other" -#define SRS_CONSTS_HTTP_NotModified_str "Not Modified " +#define SRS_CONSTS_HTTP_NotModified_str "Not Modified" #define SRS_CONSTS_HTTP_UseProxy_str "Use Proxy" -#define SRS_CONSTS_HTTP_TemporaryRedirect_str "Temporary Redirect " +#define SRS_CONSTS_HTTP_TemporaryRedirect_str "Temporary Redirect" #define SRS_CONSTS_HTTP_BadRequest_str "Bad Request" #define SRS_CONSTS_HTTP_Unauthorized_str "Unauthorized" -#define SRS_CONSTS_HTTP_PaymentRequired_str "Payment Required " -#define SRS_CONSTS_HTTP_Forbidden_str "Forbidden " +#define SRS_CONSTS_HTTP_PaymentRequired_str "Payment Required" +#define SRS_CONSTS_HTTP_Forbidden_str "Forbidden" #define SRS_CONSTS_HTTP_NotFound_str "Not Found" #define SRS_CONSTS_HTTP_MethodNotAllowed_str "Method Not Allowed" -#define SRS_CONSTS_HTTP_NotAcceptable_str "Not Acceptable " -#define SRS_CONSTS_HTTP_ProxyAuthenticationRequired_str "Proxy Authentication Required " +#define SRS_CONSTS_HTTP_NotAcceptable_str "Not Acceptable" +#define SRS_CONSTS_HTTP_ProxyAuthenticationRequired_str "Proxy Authentication Required" #define SRS_CONSTS_HTTP_RequestTimeout_str "Request Timeout" #define SRS_CONSTS_HTTP_Conflict_str "Conflict" #define SRS_CONSTS_HTTP_Gone_str "Gone" #define SRS_CONSTS_HTTP_LengthRequired_str "Length Required" #define SRS_CONSTS_HTTP_PreconditionFailed_str "Precondition Failed" -#define SRS_CONSTS_HTTP_RequestEntityTooLarge_str "Request Entity Too Large " +#define SRS_CONSTS_HTTP_RequestEntityTooLarge_str "Request Entity Too Large" #define SRS_CONSTS_HTTP_RequestURITooLarge_str "Request URI Too Large" #define SRS_CONSTS_HTTP_UnsupportedMediaType_str "Unsupported Media Type" #define SRS_CONSTS_HTTP_RequestedRangeNotSatisfiable_str "Requested Range Not Satisfiable" -#define SRS_CONSTS_HTTP_ExpectationFailed_str "Expectation Failed " -#define SRS_CONSTS_HTTP_InternalServerError_str "Internal Server Error " +#define SRS_CONSTS_HTTP_ExpectationFailed_str "Expectation Failed" +#define SRS_CONSTS_HTTP_InternalServerError_str "Internal Server Error" #define SRS_CONSTS_HTTP_NotImplemented_str "Not Implemented" #define SRS_CONSTS_HTTP_BadGateway_str "Bad Gateway" #define SRS_CONSTS_HTTP_ServiceUnavailable_str "Service Unavailable" #define SRS_CONSTS_HTTP_GatewayTimeout_str "Gateway Timeout" #define SRS_CONSTS_HTTP_HTTPVersionNotSupported_str "HTTP Version Not Supported" +/////////////////////////////////////////////////////////// +// RTSP consts values +/////////////////////////////////////////////////////////// +// 7.1.1 Status Code and Reason Phrase +#define SRS_CONSTS_RTSP_Continue 100 +#define SRS_CONSTS_RTSP_OK 200 +#define SRS_CONSTS_RTSP_Created 201 +#define SRS_CONSTS_RTSP_LowOnStorageSpace 250 +#define SRS_CONSTS_RTSP_MultipleChoices 300 +#define SRS_CONSTS_RTSP_MovedPermanently 301 +#define SRS_CONSTS_RTSP_MovedTemporarily 302 +#define SRS_CONSTS_RTSP_SeeOther 303 +#define SRS_CONSTS_RTSP_NotModified 304 +#define SRS_CONSTS_RTSP_UseProxy 305 +#define SRS_CONSTS_RTSP_BadRequest 400 +#define SRS_CONSTS_RTSP_Unauthorized 401 +#define SRS_CONSTS_RTSP_PaymentRequired 402 +#define SRS_CONSTS_RTSP_Forbidden 403 +#define SRS_CONSTS_RTSP_NotFound 404 +#define SRS_CONSTS_RTSP_MethodNotAllowed 405 +#define SRS_CONSTS_RTSP_NotAcceptable 406 +#define SRS_CONSTS_RTSP_ProxyAuthenticationRequired 407 +#define SRS_CONSTS_RTSP_RequestTimeout 408 +#define SRS_CONSTS_RTSP_Gone 410 +#define SRS_CONSTS_RTSP_LengthRequired 411 +#define SRS_CONSTS_RTSP_PreconditionFailed 412 +#define SRS_CONSTS_RTSP_RequestEntityTooLarge 413 +#define SRS_CONSTS_RTSP_RequestURITooLarge 414 +#define SRS_CONSTS_RTSP_UnsupportedMediaType 415 +#define SRS_CONSTS_RTSP_ParameterNotUnderstood 451 +#define SRS_CONSTS_RTSP_ConferenceNotFound 452 +#define SRS_CONSTS_RTSP_NotEnoughBandwidth 453 +#define SRS_CONSTS_RTSP_SessionNotFound 454 +#define SRS_CONSTS_RTSP_MethodNotValidInThisState 455 +#define SRS_CONSTS_RTSP_HeaderFieldNotValidForResource 456 +#define SRS_CONSTS_RTSP_InvalidRange 457 +#define SRS_CONSTS_RTSP_ParameterIsReadOnly 458 +#define SRS_CONSTS_RTSP_AggregateOperationNotAllowed 459 +#define SRS_CONSTS_RTSP_OnlyAggregateOperationAllowed 460 +#define SRS_CONSTS_RTSP_UnsupportedTransport 461 +#define SRS_CONSTS_RTSP_DestinationUnreachable 462 +#define SRS_CONSTS_RTSP_InternalServerError 500 +#define SRS_CONSTS_RTSP_NotImplemented 501 +#define SRS_CONSTS_RTSP_BadGateway 502 +#define SRS_CONSTS_RTSP_ServiceUnavailable 503 +#define SRS_CONSTS_RTSP_GatewayTimeout 504 +#define SRS_CONSTS_RTSP_RTSPVersionNotSupported 505 +#define SRS_CONSTS_RTSP_OptionNotSupported 551 + +#define SRS_CONSTS_RTSP_Continue_str "Continue" +#define SRS_CONSTS_RTSP_OK_str "OK" +#define SRS_CONSTS_RTSP_Created_str "Created" +#define SRS_CONSTS_RTSP_LowOnStorageSpace_str "Low on Storage Space" +#define SRS_CONSTS_RTSP_MultipleChoices_str "Multiple Choices" +#define SRS_CONSTS_RTSP_MovedPermanently_str "Moved Permanently" +#define SRS_CONSTS_RTSP_MovedTemporarily_str "Moved Temporarily" +#define SRS_CONSTS_RTSP_SeeOther_str "See Other" +#define SRS_CONSTS_RTSP_NotModified_str "Not Modified" +#define SRS_CONSTS_RTSP_UseProxy_str "Use Proxy" +#define SRS_CONSTS_RTSP_BadRequest_str "Bad Request" +#define SRS_CONSTS_RTSP_Unauthorized_str "Unauthorized" +#define SRS_CONSTS_RTSP_PaymentRequired_str "Payment Required" +#define SRS_CONSTS_RTSP_Forbidden_str "Forbidden" +#define SRS_CONSTS_RTSP_NotFound_str "Not Found" +#define SRS_CONSTS_RTSP_MethodNotAllowed_str "Method Not Allowed" +#define SRS_CONSTS_RTSP_NotAcceptable_str "Not Acceptable" +#define SRS_CONSTS_RTSP_ProxyAuthenticationRequired_str "Proxy Authentication Required" +#define SRS_CONSTS_RTSP_RequestTimeout_str "Request Timeout" +#define SRS_CONSTS_RTSP_Gone_str "Gone" +#define SRS_CONSTS_RTSP_LengthRequired_str "Length Required" +#define SRS_CONSTS_RTSP_PreconditionFailed_str "Precondition Failed" +#define SRS_CONSTS_RTSP_RequestEntityTooLarge_str "Request Entity Too Large" +#define SRS_CONSTS_RTSP_RequestURITooLarge_str "Request URI Too Large" +#define SRS_CONSTS_RTSP_UnsupportedMediaType_str "Unsupported Media Type" +#define SRS_CONSTS_RTSP_ParameterNotUnderstood_str "Invalid parameter" +#define SRS_CONSTS_RTSP_ConferenceNotFound_str "Illegal Conference Identifier" +#define SRS_CONSTS_RTSP_NotEnoughBandwidth_str "Not Enough Bandwidth" +#define SRS_CONSTS_RTSP_SessionNotFound_str "Session Not Found" +#define SRS_CONSTS_RTSP_MethodNotValidInThisState_str "Method Not Valid In This State" +#define SRS_CONSTS_RTSP_HeaderFieldNotValidForResource_str "Header Field Not Valid" +#define SRS_CONSTS_RTSP_InvalidRange_str "Invalid Range" +#define SRS_CONSTS_RTSP_ParameterIsReadOnly_str "Parameter Is Read-Only" +#define SRS_CONSTS_RTSP_AggregateOperationNotAllowed_str "Aggregate Operation Not Allowed" +#define SRS_CONSTS_RTSP_OnlyAggregateOperationAllowed_str "Only Aggregate Operation Allowed" +#define SRS_CONSTS_RTSP_UnsupportedTransport_str "Unsupported Transport" +#define SRS_CONSTS_RTSP_DestinationUnreachable_str "Destination Unreachable" +#define SRS_CONSTS_RTSP_InternalServerError_str "Internal Server Error" +#define SRS_CONSTS_RTSP_NotImplemented_str "Not Implemented" +#define SRS_CONSTS_RTSP_BadGateway_str "Bad Gateway" +#define SRS_CONSTS_RTSP_ServiceUnavailable_str "Service Unavailable" +#define SRS_CONSTS_RTSP_GatewayTimeout_str "Gateway Timeout" +#define SRS_CONSTS_RTSP_RTSPVersionNotSupported_str "RTSP Version Not Supported" +#define SRS_CONSTS_RTSP_OptionNotSupported_str "Option not support" + #endif diff --git a/trunk/src/kernel/srs_kernel_error.cpp b/trunk/src/kernel/srs_kernel_error.cpp index 873bac5ce9..0118ef9357 100644 --- a/trunk/src/kernel/srs_kernel_error.cpp +++ b/trunk/src/kernel/srs_kernel_error.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/trunk/src/kernel/srs_kernel_error.hpp b/trunk/src/kernel/srs_kernel_error.hpp index 27218bcac5..bfcdad6b64 100644 --- a/trunk/src/kernel/srs_kernel_error.hpp +++ b/trunk/src/kernel/srs_kernel_error.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -30,8 +30,10 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include -// success, ok +// for srs-librtmp, @see https://github.com/simple-rtmp-server/srs/issues/213 +#ifndef _WIN32 #define ERROR_SUCCESS 0 +#endif /////////////////////////////////////////////////////// // system error. @@ -58,7 +60,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define ERROR_SYSTEM_PACKET_INVALID 1019 #define ERROR_SYSTEM_CLIENT_INVALID 1020 #define ERROR_SYSTEM_ASSERT_FAILED 1021 -#define ERROR_SYSTEM_SIZE_NEGATIVE 1022 +#define ERROR_READER_BUFFER_OVERFLOW 1022 #define ERROR_SYSTEM_CONFIG_INVALID 1023 #define ERROR_SYSTEM_CONFIG_DIRECTIVE 1024 #define ERROR_SYSTEM_CONFIG_BLOCK_START 1025 @@ -88,6 +90,12 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define ERROR_SYSTEM_FILE_SEEK 1049 #define ERROR_SYSTEM_IO_INVALID 1050 #define ERROR_ST_EXCEED_THREADS 1051 +#define ERROR_SYSTEM_SECURITY 1052 +#define ERROR_SYSTEM_SECURITY_DENY 1053 +#define ERROR_SYSTEM_SECURITY_ALLOW 1054 +#define ERROR_SYSTEM_TIME 1055 +#define ERROR_SYSTEM_DIR_EXISTS 1056 +#define ERROR_SYSTEM_CREATE_DIR 1057 /////////////////////////////////////////////////////// // RTMP protocol error. @@ -132,6 +140,14 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define ERROR_OpenSslSha256DigestSize 2037 #define ERROR_OpenSslGetPeerPublicKey 2038 #define ERROR_OpenSslComputeSharedKey 2039 +#define ERROR_RTMP_MIC_CHUNKSIZE_CHANGED 2040 +#define ERROR_RTMP_MIC_CACHE_OVERFLOW 2041 +#define ERROR_RTSP_TOKEN_NOT_NORMAL 2042 +#define ERROR_RTSP_REQUEST_HEADER_EOF 2043 +#define ERROR_RTP_HEADER_CORRUPT 2044 +#define ERROR_RTP_TYPE96_CORRUPT 2045 +#define ERROR_RTP_TYPE97_CORRUPT 2046 +#define ERROR_RTSP_AUDIO_CONFIG 2047 // // system control message, // not an error, but special control logic. @@ -145,7 +161,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. /////////////////////////////////////////////////////// #define ERROR_HLS_METADATA 3000 #define ERROR_HLS_DECODE_ERROR 3001 -#define ERROR_HLS_CREATE_DIR 3002 +//#define ERROR_HLS_CREATE_DIR 3002 #define ERROR_HLS_OPEN_FAILED 3003 #define ERROR_HLS_WRITE_FAILED 3004 #define ERROR_HLS_AAC_FRAME_LENGTH 3005 @@ -156,8 +172,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define ERROR_HTTP_HANDLER_MATCH_URL 3010 #define ERROR_HTTP_HANDLER_INVALID 3011 #define ERROR_HTTP_API_LOGS 3012 -#define ERROR_HTTP_FLV_SEQUENCE_HEADER 3013 -#define ERROR_HTTP_FLV_OFFSET_OVERFLOW 3014 +#define ERROR_HTTP_REMUX_SEQUENCE_HEADER 3013 +#define ERROR_HTTP_REMUX_OFFSET_OVERFLOW 3014 #define ERROR_ENCODER_VCODEC 3015 #define ERROR_ENCODER_OUTPUT 3016 #define ERROR_ENCODER_ACHANNELS 3017 @@ -183,6 +199,69 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define ERROR_KERNEL_FLV_STREAM_CLOSED 3037 #define ERROR_KERNEL_STREAM_INIT 3038 #define ERROR_EDGE_VHOST_REMOVED 3039 +#define ERROR_HLS_AVC_TRY_OTHERS 3040 +#define ERROR_H264_API_NO_PREFIXED 3041 +#define ERROR_FLV_INVALID_VIDEO_TAG 3042 +#define ERROR_H264_DROP_BEFORE_SPS_PPS 3043 +#define ERROR_H264_DUPLICATED_SPS 3044 +#define ERROR_H264_DUPLICATED_PPS 3045 +#define ERROR_AAC_REQUIRED_ADTS 3046 +#define ERROR_AAC_ADTS_HEADER 3047 +#define ERROR_AAC_DATA_INVALID 3048 +#define ERROR_HLS_TRY_MP3 3049 +#define ERROR_HTTP_DVR_DISABLED 3050 +#define ERROR_HTTP_DVR_REQUEST 3051 +#define ERROR_HTTP_JSON_REQUIRED 3052 +#define ERROR_HTTP_DVR_CREATE_REQUEST 3053 +#define ERROR_HTTP_DVR_NO_TAEGET 3054 +#define ERROR_ADTS_ID_NOT_AAC 3055 +// HDS error code +#define ERROR_HDS_OPEN_F4M_FAILED 3056 +#define ERROR_HDS_WRITE_F4M_FAILED 3057 +#define ERROR_HDS_OPEN_BOOTSTRAP_FAILED 3058 +#define ERROR_HDS_WRITE_BOOTSTRAP_FAILED 3059 +#define ERROR_HDS_OPEN_FRAGMENT_FAILED 3060 +#define ERROR_HDS_WRITE_FRAGMENT_FAILED 3061 + +/////////////////////////////////////////////////////// +// HTTP/StreamCaster protocol error. +/////////////////////////////////////////////////////// +#define ERROR_HTTP_PATTERN_EMPTY 4000 +#define ERROR_HTTP_PATTERN_DUPLICATED 4001 +#define ERROR_HTTP_URL_NOT_CLEAN 4002 +#define ERROR_HTTP_CONTENT_LENGTH 4003 +#define ERROR_HTTP_LIVE_STREAM_EXT 4004 +#define ERROR_HTTP_STATUS_INVLIAD 4005 +#define ERROR_KERNEL_AAC_STREAM_CLOSED 4006 +#define ERROR_AAC_DECODE_ERROR 4007 +#define ERROR_KERNEL_MP3_STREAM_CLOSED 4008 +#define ERROR_MP3_DECODE_ERROR 4009 +#define ERROR_STREAM_CASTER_ENGINE 4010 +#define ERROR_STREAM_CASTER_PORT 4011 +#define ERROR_STREAM_CASTER_TS_HEADER 4012 +#define ERROR_STREAM_CASTER_TS_SYNC_BYTE 4013 +#define ERROR_STREAM_CASTER_TS_AF 4014 +#define ERROR_STREAM_CASTER_TS_CRC32 4015 +#define ERROR_STREAM_CASTER_TS_PSI 4016 +#define ERROR_STREAM_CASTER_TS_PAT 4017 +#define ERROR_STREAM_CASTER_TS_PMT 4018 +#define ERROR_STREAM_CASTER_TS_PSE 4019 +#define ERROR_STREAM_CASTER_TS_ES 4020 +#define ERROR_STREAM_CASTER_TS_CODEC 4021 +#define ERROR_STREAM_CASTER_AVC_SPS 4022 +#define ERROR_STREAM_CASTER_AVC_PPS 4023 +#define ERROR_STREAM_CASTER_FLV_TAG 4024 +#define ERROR_HTTP_RESPONSE_EOF 4025 +#define ERROR_HTTP_INVALID_CHUNK_HEADER 4026 +#define ERROR_AVC_NALU_UEV 4027 +#define ERROR_AAC_BYTES_INVALID 4028 +#define ERROR_HTTP_REQUEST_EOF 4029 + +/////////////////////////////////////////////////////// +// user-define error. +/////////////////////////////////////////////////////// +#define ERROR_USER_START 9000 +#define ERROR_USER_END 9999 /** * whether the error code is an system control error. diff --git a/trunk/src/kernel/srs_kernel_file.cpp b/trunk/src/kernel/srs_kernel_file.cpp index 17f92586fe..40ef01305b 100644 --- a/trunk/src/kernel/srs_kernel_file.cpp +++ b/trunk/src/kernel/srs_kernel_file.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -23,8 +23,12 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include -#include +// for srs-librtmp, @see https://github.com/simple-rtmp-server/srs/issues/213 +#ifndef _WIN32 #include +#endif + +#include #include using namespace std; @@ -65,6 +69,30 @@ int SrsFileWriter::open(string file) return ret; } +int SrsFileWriter::open_append(string file) +{ + int ret = ERROR_SUCCESS; + + if (fd > 0) { + ret = ERROR_SYSTEM_FILE_ALREADY_OPENED; + srs_error("file %s already opened. ret=%d", _file.c_str(), ret); + return ret; + } + + int flags = O_APPEND|O_WRONLY; + mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH; + + if ((fd = ::open(file.c_str(), flags, mode)) < 0) { + ret = ERROR_SYSTEM_FILE_OPENE; + srs_error("open file %s failed. ret=%d", file.c_str(), ret); + return ret; + } + + _file = file; + + return ret; +} + void SrsFileWriter::close() { int ret = ERROR_SUCCESS; @@ -88,6 +116,11 @@ bool SrsFileWriter::is_open() return fd > 0; } +void SrsFileWriter::lseek(int64_t offset) +{ + ::lseek(fd, (off_t)offset, SEEK_SET); +} + int64_t SrsFileWriter::tellg() { return (int64_t)::lseek(fd, 0, SEEK_CUR); @@ -173,19 +206,19 @@ int64_t SrsFileReader::tellg() void SrsFileReader::skip(int64_t size) { - ::lseek(fd, size, SEEK_CUR); + ::lseek(fd, (off_t)size, SEEK_CUR); } int64_t SrsFileReader::lseek(int64_t offset) { - return (int64_t)::lseek(fd, offset, SEEK_SET); + return (int64_t)::lseek(fd, (off_t)offset, SEEK_SET); } int64_t SrsFileReader::filesize() { int64_t cur = tellg(); int64_t size = (int64_t)::lseek(fd, 0, SEEK_END); - ::lseek(fd, cur, SEEK_SET); + ::lseek(fd, (off_t)cur, SEEK_SET); return size; } diff --git a/trunk/src/kernel/srs_kernel_file.hpp b/trunk/src/kernel/srs_kernel_file.hpp index 033dd32083..4943c1ed7e 100644 --- a/trunk/src/kernel/srs_kernel_file.hpp +++ b/trunk/src/kernel/srs_kernel_file.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -47,9 +47,14 @@ class SrsFileWriter * open file writer, can open then close then open... */ virtual int open(std::string file); + /** + * open file writer in append mode. + */ + virtual int open_append(std::string file); virtual void close(); public: virtual bool is_open(); + virtual void lseek(int64_t offset); virtual int64_t tellg(); public: /** @@ -77,6 +82,7 @@ class SrsFileReader virtual int open(std::string file); virtual void close(); public: + // TODO: FIXME: extract interface. virtual bool is_open(); virtual int64_t tellg(); virtual void skip(int64_t size); diff --git a/trunk/src/kernel/srs_kernel_flv.cpp b/trunk/src/kernel/srs_kernel_flv.cpp index c2b93461e9..8a29c6c760 100644 --- a/trunk/src/kernel/srs_kernel_flv.cpp +++ b/trunk/src/kernel/srs_kernel_flv.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -23,8 +23,12 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include -#include +// for srs-librtmp, @see https://github.com/simple-rtmp-server/srs/issues/213 +#ifndef _WIN32 #include +#endif + +#include #include using namespace std; @@ -32,6 +36,7 @@ using namespace std; #include #include #include +#include #define SRS_FLV_TAG_HEADER_SIZE 11 #define SRS_FLV_PREVIOUS_TAG_SIZE 4 @@ -55,7 +60,7 @@ int SrsFlvEncoder::initialize(SrsFileWriter* fs) if (!fs->is_open()) { ret = ERROR_KERNEL_FLV_STREAM_CLOSED; - srs_warn("stream is not open for decoder. ret=%d", ret); + srs_warn("stream is not open for encoder. ret=%d", ret); return ret; } @@ -69,7 +74,7 @@ int SrsFlvEncoder::write_header() int ret = ERROR_SUCCESS; // 9bytes header and 4bytes first previous-tag-size - static char flv_header[] = { + char flv_header[] = { 'F', 'L', 'V', // Signatures "FLV" (char)0x01, // File version (for example, 0x01 for FLV version 1) (char)0x00, // 4, audio; 1, video; 5 audio+video. @@ -98,6 +103,7 @@ int SrsFlvEncoder::write_header(char flv_header[9]) return ret; } + // previous tag size. char pts[] = { (char)0x00, (char)0x00, (char)0x00, (char)0x00 }; if ((ret = _fs->write(pts, 4, NULL)) != ERROR_SUCCESS) { return ret; @@ -106,15 +112,15 @@ int SrsFlvEncoder::write_header(char flv_header[9]) return ret; } -int SrsFlvEncoder::write_metadata(char* data, int size) +int SrsFlvEncoder::write_metadata(char type, char* data, int size) { int ret = ERROR_SUCCESS; srs_assert(data); // 11 bytes tag header - static char tag_header[] = { - (char)18, // TagType UB [5], 18 = script data + char tag_header[] = { + (char)type, // TagType UB [5], 18 = script data (char)0x00, (char)0x00, (char)0x00, // DataSize UI24 Length of the message. (char)0x00, (char)0x00, (char)0x00, // Timestamp UI24 Time in milliseconds at which the data in this tag applies. (char)0x00, // TimestampExtended UI8 @@ -144,8 +150,8 @@ int SrsFlvEncoder::write_audio(int64_t timestamp, char* data, int size) timestamp &= 0x7fffffff; // 11bytes tag header - static char tag_header[] = { - (char)8, // TagType UB [5], 8 = audio + char tag_header[] = { + (char)SrsCodecFlvTagAudio, // TagType UB [5], 8 = audio (char)0x00, (char)0x00, (char)0x00, // DataSize UI24 Length of the message. (char)0x00, (char)0x00, (char)0x00, // Timestamp UI24 Time in milliseconds at which the data in this tag applies. (char)0x00, // TimestampExtended UI8 @@ -157,7 +163,7 @@ int SrsFlvEncoder::write_audio(int64_t timestamp, char* data, int size) return ret; } tag_stream->write_3bytes(size); - tag_stream->write_3bytes(timestamp); + tag_stream->write_3bytes((int32_t)timestamp); // default to little-endian tag_stream->write_1bytes((timestamp >> 24) & 0xFF); @@ -178,8 +184,8 @@ int SrsFlvEncoder::write_video(int64_t timestamp, char* data, int size) timestamp &= 0x7fffffff; // 11bytes tag header - static char tag_header[] = { - (char)9, // TagType UB [5], 9 = video + char tag_header[] = { + (char)SrsCodecFlvTagVideo, // TagType UB [5], 9 = video (char)0x00, (char)0x00, (char)0x00, // DataSize UI24 Length of the message. (char)0x00, (char)0x00, (char)0x00, // Timestamp UI24 Time in milliseconds at which the data in this tag applies. (char)0x00, // TimestampExtended UI8 @@ -191,7 +197,7 @@ int SrsFlvEncoder::write_video(int64_t timestamp, char* data, int size) return ret; } tag_stream->write_3bytes(size); - tag_stream->write_3bytes(timestamp); + tag_stream->write_3bytes((int32_t)timestamp); // default to little-endian tag_stream->write_1bytes((timestamp >> 24) & 0xFF); @@ -226,7 +232,7 @@ int SrsFlvEncoder::write_tag(char* header, int header_size, char* tag, int tag_s } // PreviousTagSizeN UI32 Size of last tag, including its header, in bytes. - static char pre_size[SRS_FLV_PREVIOUS_TAG_SIZE]; + char pre_size[SRS_FLV_PREVIOUS_TAG_SIZE]; if ((ret = tag_stream->initialize(pre_size, SRS_FLV_PREVIOUS_TAG_SIZE)) != ERROR_SUCCESS) { return ret; } @@ -312,6 +318,7 @@ int SrsFlvDecoder::read_tag_header(char* ptype, int32_t* pdata_size, u_int32_t* // DataSize UI24 char* pp = (char*)pdata_size; + pp[3] = 0; pp[2] = th[1]; pp[1] = th[2]; pp[0] = th[3]; @@ -420,7 +427,7 @@ int SrsFlvVodStreamDecoder::read_sequence_header_summary(int64_t* pstart, int* p // and must be a sequence video and audio. // 11bytes tag header - static char tag_header[] = { + char tag_header[] = { (char)0x00, // TagType UB [5], 9 = video, 8 = audio, 18 = script data (char)0x00, (char)0x00, (char)0x00, // DataSize UI24 Length of the message. (char)0x00, (char)0x00, (char)0x00, // Timestamp UI24 Time in milliseconds at which the data in this tag applies. diff --git a/trunk/src/kernel/srs_kernel_flv.hpp b/trunk/src/kernel/srs_kernel_flv.hpp index 845f7b61be..e3564f7dbd 100644 --- a/trunk/src/kernel/srs_kernel_flv.hpp +++ b/trunk/src/kernel/srs_kernel_flv.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -66,12 +66,14 @@ class SrsFlvEncoder virtual int write_header(char flv_header[9]); /** * write flv metadata. + * @param type, the type of data, or other message type. + * @see SrsCodecFlvTag * @param data, the amf0 metadata which serialize from: * AMF0 string: onMetaData, * AMF0 object: the metadata object. * @remark assert data is not NULL. */ - virtual int write_metadata(char* data, int size); + virtual int write_metadata(char type, char* data, int size); /** * write audio/video packet. * @remark assert data is not NULL. diff --git a/trunk/src/kernel/srs_kernel_log.cpp b/trunk/src/kernel/srs_kernel_log.cpp index e832acc074..78cf3fad06 100644 --- a/trunk/src/kernel/srs_kernel_log.cpp +++ b/trunk/src/kernel/srs_kernel_log.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/trunk/src/kernel/srs_kernel_log.hpp b/trunk/src/kernel/srs_kernel_log.hpp index 9ebfcea3de..93f6b1d2b0 100644 --- a/trunk/src/kernel/srs_kernel_log.hpp +++ b/trunk/src/kernel/srs_kernel_log.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/trunk/src/kernel/srs_kernel_mp3.cpp b/trunk/src/kernel/srs_kernel_mp3.cpp new file mode 100644 index 0000000000..339ca1a47a --- /dev/null +++ b/trunk/src/kernel/srs_kernel_mp3.cpp @@ -0,0 +1,127 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include + +// for srs-librtmp, @see https://github.com/simple-rtmp-server/srs/issues/213 +#ifndef _WIN32 +#include +#endif + +#include +#include +using namespace std; + +#include +#include +#include +#include +#include + +SrsMp3Encoder::SrsMp3Encoder() +{ + _fs = NULL; + tag_stream = new SrsStream(); +} + +SrsMp3Encoder::~SrsMp3Encoder() +{ + srs_freep(tag_stream); +} + +int SrsMp3Encoder::initialize(SrsFileWriter* fs) +{ + int ret = ERROR_SUCCESS; + + srs_assert(fs); + + if (!fs->is_open()) { + ret = ERROR_KERNEL_MP3_STREAM_CLOSED; + srs_warn("stream is not open for encoder. ret=%d", ret); + return ret; + } + + _fs = fs; + + return ret; +} + +int SrsMp3Encoder::write_header() +{ + char id3[] = { + (char)0x49, (char)0x44, (char)0x33, // ID3 + (char)0x03, (char)0x00, // version + (char)0x00, // flags + (char)0x00, (char)0x00, (char)0x00, (char)0x0a, // size + + (char)0x00, (char)0x00, (char)0x00, (char)0x00, // FrameID + (char)0x00, (char)0x00, (char)0x00, (char)0x00, // FrameSize + (char)0x00, (char)0x00 // Flags + }; + return _fs->write(id3, sizeof(id3), NULL); +} + +int SrsMp3Encoder::write_audio(int64_t timestamp, char* data, int size) +{ + int ret = ERROR_SUCCESS; + + srs_assert(data); + + timestamp &= 0x7fffffff; + + SrsStream* stream = tag_stream; + if ((ret = stream->initialize(data, size)) != ERROR_SUCCESS) { + return ret; + } + + // audio decode + if (!stream->require(1)) { + ret = ERROR_MP3_DECODE_ERROR; + srs_error("mp3 decode audio sound_format failed. ret=%d", ret); + return ret; + } + + // @see: E.4.2 Audio Tags, video_file_format_spec_v10_1.pdf, page 76 + int8_t sound_format = stream->read_1bytes(); + + // @see: SrsAvcAacCodec::audio_aac_demux + //int8_t sound_type = sound_format & 0x01; + //int8_t sound_size = (sound_format >> 1) & 0x01; + //int8_t sound_rate = (sound_format >> 2) & 0x03; + sound_format = (sound_format >> 4) & 0x0f; + + if ((SrsCodecAudio)sound_format != SrsCodecAudioMP3) { + ret = ERROR_MP3_DECODE_ERROR; + srs_error("mp3 required, format=%d. ret=%d", sound_format, ret); + return ret; + } + + if (!stream->require(1)) { + ret = ERROR_MP3_DECODE_ERROR; + srs_error("mp3 decode aac_packet_type failed. ret=%d", ret); + return ret; + } + + return _fs->write(data + stream->pos(), size - stream->pos(), NULL); +} + diff --git a/trunk/src/kernel/srs_kernel_mp3.hpp b/trunk/src/kernel/srs_kernel_mp3.hpp new file mode 100644 index 0000000000..64a37020a6 --- /dev/null +++ b/trunk/src/kernel/srs_kernel_mp3.hpp @@ -0,0 +1,71 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef SRS_KERNEL_MP3_HPP +#define SRS_KERNEL_MP3_HPP + +/* +#include +*/ +#include + +#include + +class SrsStream; +class SrsFileWriter; +class SrsFileReader; + +/** +* encode data to aac file. +*/ +class SrsMp3Encoder +{ +private: + SrsFileWriter* _fs; +private: + SrsStream* tag_stream; +public: + SrsMp3Encoder(); + virtual ~SrsMp3Encoder(); +public: + /** + * initialize the underlayer file stream. + * @remark user can initialize multiple times to encode multiple mp3 files. + * @remark, user must free the fs, mp3 encoder never close/free it. + */ + virtual int initialize(SrsFileWriter* fs); +public: + /** + * write mp3 id3 v2.3 header. + * @see mp3.id3v2.3.0.pdf, http://id3.org/id3v2.3.0 + */ + virtual int write_header(); + /** + * write audio/video packet. + * @remark assert data is not NULL. + */ + virtual int write_audio(int64_t timestamp, char* data, int size); +}; + +#endif + diff --git a/trunk/src/kernel/srs_kernel_stream.cpp b/trunk/src/kernel/srs_kernel_stream.cpp index 115285dd1a..48a1dc8dcf 100644 --- a/trunk/src/kernel/srs_kernel_stream.cpp +++ b/trunk/src/kernel/srs_kernel_stream.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -252,4 +252,38 @@ void SrsStream::write_bytes(char* data, int size) p += size; } +SrsBitStream::SrsBitStream() +{ + cb = 0; + cb_left = 0; + stream = NULL; +} + +SrsBitStream::~SrsBitStream() +{ +} + +int SrsBitStream::initialize(SrsStream* s) { + stream = s; + return ERROR_SUCCESS; +} + +bool SrsBitStream::empty() { + if (cb_left) { + return false; + } + return stream->empty(); +} + +int8_t SrsBitStream::read_bit() { + if (!cb_left) { + srs_assert(!stream->empty()); + cb = stream->read_1bytes(); + cb_left = 8; + } + + int8_t v = (cb >> (cb_left - 1)) & 0x01; + cb_left--; + return v; +} diff --git a/trunk/src/kernel/srs_kernel_stream.hpp b/trunk/src/kernel/srs_kernel_stream.hpp index 161f085242..1c17cdad26 100644 --- a/trunk/src/kernel/srs_kernel_stream.hpp +++ b/trunk/src/kernel/srs_kernel_stream.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -154,4 +154,22 @@ class SrsStream virtual void write_bytes(char* data, int size); }; +/** + * the bit stream. + */ +class SrsBitStream +{ +private: + int8_t cb; + u_int8_t cb_left; + SrsStream* stream; +public: + SrsBitStream(); + virtual ~SrsBitStream(); +public: + virtual int initialize(SrsStream* s); + virtual bool empty(); + virtual int8_t read_bit(); +}; + #endif diff --git a/trunk/src/kernel/srs_kernel_ts.cpp b/trunk/src/kernel/srs_kernel_ts.cpp new file mode 100644 index 0000000000..8eb3c7bcb0 --- /dev/null +++ b/trunk/src/kernel/srs_kernel_ts.cpp @@ -0,0 +1,3192 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include + +// for srs-librtmp, @see https://github.com/simple-rtmp-server/srs/issues/213 +#ifndef _WIN32 +#include +#endif + +#include +#include +using namespace std; + +#include +#include +#include +#include +#include +#include +#include +#include + +// in ms, for HLS aac sync time. +#define SRS_CONF_DEFAULT_AAC_SYNC 100 + +// @see: ngx_rtmp_hls_audio +/* We assume here AAC frame size is 1024 + * Need to handle AAC frames with frame size of 960 */ +#define _SRS_AAC_SAMPLE_SIZE 1024 + +// the mpegts header specifed the video/audio pid. +#define TS_PMT_NUMBER 1 +#define TS_PMT_PID 0x1001 +#define TS_VIDEO_AVC_PID 0x100 +#define TS_AUDIO_AAC_PID 0x101 +#define TS_AUDIO_MP3_PID 0x102 + +string srs_ts_stream2string(SrsTsStream stream) +{ + switch (stream) { + case SrsTsStreamReserved: return "Reserved"; + case SrsTsStreamAudioMp3: return "MP3"; + case SrsTsStreamAudioAAC: return "AAC"; + case SrsTsStreamAudioAC3: return "AC3"; + case SrsTsStreamAudioDTS: return "AudioDTS"; + case SrsTsStreamVideoH264: return "H.264"; + case SrsTsStreamVideoMpeg4: return "MP4"; + case SrsTsStreamAudioMpeg4: return "MP4A"; + default: return "Other"; + } +} + +SrsTsChannel::SrsTsChannel() +{ + pid = 0; + apply = SrsTsPidApplyReserved; + stream = SrsTsStreamReserved; + msg = NULL; + continuity_counter = 0; + context = NULL; +} + +SrsTsChannel::~SrsTsChannel() +{ + srs_freep(msg); +} + +SrsTsMessage::SrsTsMessage(SrsTsChannel* c, SrsTsPacket* p) +{ + channel = c; + packet = p; + + dts = pts = 0; + sid = (SrsTsPESStreamId)0x00; + continuity_counter = 0; + PES_packet_length = 0; + payload = new SrsSimpleBuffer(); + is_discontinuity = false; + + start_pts = 0; + write_pcr = false; +} + +SrsTsMessage::~SrsTsMessage() +{ + srs_freep(payload); +} + +int SrsTsMessage::dump(SrsStream* stream, int* pnb_bytes) +{ + int ret = ERROR_SUCCESS; + + if (stream->empty()) { + return ret; + } + + // xB + int nb_bytes = stream->size() - stream->pos(); + if (PES_packet_length > 0) { + nb_bytes = srs_min(nb_bytes, PES_packet_length - payload->length()); + } + + if (nb_bytes > 0) { + if (!stream->require(nb_bytes)) { + ret = ERROR_STREAM_CASTER_TS_PSE; + srs_error("ts: dump PSE bytes failed, requires=%dB. ret=%d", nb_bytes, ret); + return ret; + } + + payload->append(stream->data() + stream->pos(), nb_bytes); + stream->skip(nb_bytes); + } + + *pnb_bytes = nb_bytes; + + return ret; +} + +bool SrsTsMessage::completed(int8_t payload_unit_start_indicator) +{ + if (PES_packet_length == 0) { + return payload_unit_start_indicator; + } + return payload->length() >= PES_packet_length; +} + +bool SrsTsMessage::fresh() +{ + return payload->length() == 0; +} + +bool SrsTsMessage::is_audio() +{ + return ((sid >> 5) & 0x07) == SrsTsPESStreamIdAudioChecker; +} + +bool SrsTsMessage::is_video() +{ + return ((sid >> 4) & 0x0f) == SrsTsPESStreamIdVideoChecker; +} + +int SrsTsMessage::stream_number() +{ + if (is_audio()) { + return sid & 0x1f; + } else if (is_video()) { + return sid & 0x0f; + } + return -1; +} + +SrsTsMessage* SrsTsMessage::detach() +{ + // @remark the packet cannot be used, but channel is ok. + SrsTsMessage* cp = new SrsTsMessage(channel, NULL); + cp->start_pts = start_pts; + cp->write_pcr = write_pcr; + cp->is_discontinuity = is_discontinuity; + cp->dts = dts; + cp->pts = pts; + cp->sid = sid; + cp->PES_packet_length = PES_packet_length; + cp->continuity_counter = continuity_counter; + cp->payload = payload; + payload = NULL; + return cp; +} + +ISrsTsHandler::ISrsTsHandler() +{ +} + +ISrsTsHandler::~ISrsTsHandler() +{ +} + +SrsTsContext::SrsTsContext() +{ + pure_audio = false; + vcodec = SrsCodecVideoReserved; + acodec = SrsCodecAudioReserved1; +} + +SrsTsContext::~SrsTsContext() +{ + std::map::iterator it; + for (it = pids.begin(); it != pids.end(); ++it) { + SrsTsChannel* channel = it->second; + srs_freep(channel); + } + pids.clear(); +} + +bool SrsTsContext::is_pure_audio() +{ + return pure_audio; +} + +void SrsTsContext::on_pmt_parsed() +{ + pure_audio = true; + + std::map::iterator it; + for (it = pids.begin(); it != pids.end(); ++it) { + SrsTsChannel* channel = it->second; + if (channel->apply == SrsTsPidApplyVideo) { + pure_audio = false; + } + } +} + +void SrsTsContext::reset() +{ + vcodec = SrsCodecVideoReserved; + acodec = SrsCodecAudioReserved1; +} + +SrsTsChannel* SrsTsContext::get(int pid) +{ + if (pids.find(pid) == pids.end()) { + return NULL; + } + return pids[pid]; +} + +void SrsTsContext::set(int pid, SrsTsPidApply apply_pid, SrsTsStream stream) +{ + SrsTsChannel* channel = NULL; + + if (pids.find(pid) == pids.end()) { + channel = new SrsTsChannel(); + channel->context = this; + pids[pid] = channel; + } else { + channel = pids[pid]; + } + + channel->pid = pid; + channel->apply = apply_pid; + channel->stream = stream; +} + +int SrsTsContext::decode(SrsStream* stream, ISrsTsHandler* handler) +{ + int ret = ERROR_SUCCESS; + + // parse util EOF of stream. + // for example, parse multiple times for the PES_packet_length(0) packet. + while (!stream->empty()) { + SrsTsPacket* packet = new SrsTsPacket(this); + SrsAutoFree(SrsTsPacket, packet); + + SrsTsMessage* msg = NULL; + if ((ret = packet->decode(stream, &msg)) != ERROR_SUCCESS) { + srs_error("mpegts: decode ts packet failed. ret=%d", ret); + return ret; + } + + if (!msg) { + continue; + } + SrsAutoFree(SrsTsMessage, msg); + + if ((ret = handler->on_ts_message(msg)) != ERROR_SUCCESS) { + srs_error("mpegts: handler ts message failed. ret=%d", ret); + return ret; + } + } + + return ret; +} + +int SrsTsContext::encode(SrsFileWriter* writer, SrsTsMessage* msg, SrsCodecVideo vc, SrsCodecAudio ac) +{ + int ret = ERROR_SUCCESS; + + SrsTsStream vs, as; + int16_t video_pid = 0, audio_pid = 0; + switch (vc) { + case SrsCodecVideoAVC: + vs = SrsTsStreamVideoH264; + video_pid = TS_VIDEO_AVC_PID; + break; + case SrsCodecVideoReserved: + case SrsCodecVideoReserved1: + case SrsCodecVideoReserved2: + case SrsCodecVideoDisabled: + case SrsCodecVideoSorensonH263: + case SrsCodecVideoScreenVideo: + case SrsCodecVideoOn2VP6: + case SrsCodecVideoOn2VP6WithAlphaChannel: + case SrsCodecVideoScreenVideoVersion2: + vs = SrsTsStreamReserved; + break; + } + switch (ac) { + case SrsCodecAudioAAC: + as = SrsTsStreamAudioAAC; + audio_pid = TS_AUDIO_AAC_PID; + break; + case SrsCodecAudioMP3: + as = SrsTsStreamAudioMp3; + audio_pid = TS_AUDIO_MP3_PID; + break; + case SrsCodecAudioReserved1: + case SrsCodecAudioLinearPCMPlatformEndian: + case SrsCodecAudioADPCM: + case SrsCodecAudioLinearPCMLittleEndian: + case SrsCodecAudioNellymoser16kHzMono: + case SrsCodecAudioNellymoser8kHzMono: + case SrsCodecAudioNellymoser: + case SrsCodecAudioReservedG711AlawLogarithmicPCM: + case SrsCodecAudioReservedG711MuLawLogarithmicPCM: + case SrsCodecAudioReserved: + case SrsCodecAudioSpeex: + case SrsCodecAudioReservedMP3_8kHz: + case SrsCodecAudioReservedDeviceSpecificSound: + as = SrsTsStreamReserved; + break; + } + + // when any codec changed, write PAT/PMT table. + if (vcodec != vc || acodec != ac) { + vcodec = vc; + acodec = ac; + if ((ret = encode_pat_pmt(writer, video_pid, vs, audio_pid, as)) != ERROR_SUCCESS) { + return ret; + } + } + + // encode the media frame to PES packets over TS. + if (msg->is_audio()) { + return encode_pes(writer, msg, audio_pid, as, vs == SrsTsStreamReserved); + } else { + return encode_pes(writer, msg, video_pid, vs, vs == SrsTsStreamReserved); + } +} + +int SrsTsContext::encode_pat_pmt(SrsFileWriter* writer, int16_t vpid, SrsTsStream vs, int16_t apid, SrsTsStream as) +{ + int ret = ERROR_SUCCESS; + + int16_t pmt_number = TS_PMT_NUMBER; + int16_t pmt_pid = TS_PMT_PID; + if (true) { + SrsTsPacket* pkt = SrsTsPacket::create_pat(this, pmt_number, pmt_pid); + SrsAutoFree(SrsTsPacket, pkt); + + char* buf = new char[SRS_TS_PACKET_SIZE]; + SrsAutoFree(char, buf); + + // set the left bytes with 0xFF. + int nb_buf = pkt->size(); + srs_assert(nb_buf < SRS_TS_PACKET_SIZE); + memset(buf + nb_buf, 0xFF, SRS_TS_PACKET_SIZE - nb_buf); + + SrsStream stream; + if ((ret = stream.initialize(buf, nb_buf)) != ERROR_SUCCESS) { + return ret; + } + if ((ret = pkt->encode(&stream)) != ERROR_SUCCESS) { + srs_error("ts encode ts packet failed. ret=%d", ret); + return ret; + } + if ((ret = writer->write(buf, SRS_TS_PACKET_SIZE, NULL)) != ERROR_SUCCESS) { + srs_error("ts write ts packet failed. ret=%d", ret); + return ret; + } + } + if (true) { + SrsTsPacket* pkt = SrsTsPacket::create_pmt(this, pmt_number, pmt_pid, vpid, vs, apid, as); + SrsAutoFree(SrsTsPacket, pkt); + + char* buf = new char[SRS_TS_PACKET_SIZE]; + SrsAutoFree(char, buf); + + // set the left bytes with 0xFF. + int nb_buf = pkt->size(); + srs_assert(nb_buf < SRS_TS_PACKET_SIZE); + memset(buf + nb_buf, 0xFF, SRS_TS_PACKET_SIZE - nb_buf); + + SrsStream stream; + if ((ret = stream.initialize(buf, nb_buf)) != ERROR_SUCCESS) { + return ret; + } + if ((ret = pkt->encode(&stream)) != ERROR_SUCCESS) { + srs_error("ts encode ts packet failed. ret=%d", ret); + return ret; + } + if ((ret = writer->write(buf, SRS_TS_PACKET_SIZE, NULL)) != ERROR_SUCCESS) { + srs_error("ts write ts packet failed. ret=%d", ret); + return ret; + } + } + + return ret; +} + +int SrsTsContext::encode_pes(SrsFileWriter* writer, SrsTsMessage* msg, int16_t pid, SrsTsStream sid, bool pure_audio) +{ + int ret = ERROR_SUCCESS; + + if (msg->payload->length() == 0) { + return ret; + } + + if (sid != SrsTsStreamVideoH264 && sid != SrsTsStreamAudioMp3 && sid != SrsTsStreamAudioAAC) { + srs_info("ts: ignore the unknown stream, sid=%d", sid); + return ret; + } + + SrsTsChannel* channel = get(pid); + srs_assert(channel); + + char* start = msg->payload->bytes(); + char* end = start + msg->payload->length(); + char* p = start; + + while (p < end) { + SrsTsPacket* pkt = NULL; + if (p == start) { + // for pure audio stream, always write pcr. + bool write_pcr = msg->write_pcr; + if (pure_audio && msg->is_audio()) { + write_pcr = true; + } + + // it's ok to set pcr equals to dts, + // @see https://github.com/simple-rtmp-server/srs/issues/311 + int64_t pcr = write_pcr? msg->dts : -1; + + // TODO: FIXME: finger it why use discontinuity of msg. + pkt = SrsTsPacket::create_pes_first(this, + pid, msg->sid, channel->continuity_counter++, msg->is_discontinuity, + pcr, msg->dts, msg->pts, msg->payload->length() + ); + } else { + pkt = SrsTsPacket::create_pes_continue(this, + pid, msg->sid, channel->continuity_counter++ + ); + } + SrsAutoFree(SrsTsPacket, pkt); + + char* buf = new char[SRS_TS_PACKET_SIZE]; + SrsAutoFree(char, buf); + + // set the left bytes with 0xFF. + int nb_buf = pkt->size(); + srs_assert(nb_buf < SRS_TS_PACKET_SIZE); + + int left = (int)srs_min(end - p, SRS_TS_PACKET_SIZE - nb_buf); + int nb_stuffings = SRS_TS_PACKET_SIZE - nb_buf - left; + if (nb_stuffings > 0) { + // set all bytes to stuffings. + memset(buf, 0xFF, SRS_TS_PACKET_SIZE); + + // padding with stuffings. + pkt->padding(nb_stuffings); + + // size changed, recalc it. + nb_buf = pkt->size(); + srs_assert(nb_buf < SRS_TS_PACKET_SIZE); + + left = (int)srs_min(end - p, SRS_TS_PACKET_SIZE - nb_buf); + nb_stuffings = SRS_TS_PACKET_SIZE - nb_buf - left; + srs_assert(nb_stuffings == 0); + } + memcpy(buf + nb_buf, p, left); + p += left; + + SrsStream stream; + if ((ret = stream.initialize(buf, nb_buf)) != ERROR_SUCCESS) { + return ret; + } + if ((ret = pkt->encode(&stream)) != ERROR_SUCCESS) { + srs_error("ts encode ts packet failed. ret=%d", ret); + return ret; + } + if ((ret = writer->write(buf, SRS_TS_PACKET_SIZE, NULL)) != ERROR_SUCCESS) { + srs_error("ts write ts packet failed. ret=%d", ret); + return ret; + } + } + + return ret; +} + +SrsTsPacket::SrsTsPacket(SrsTsContext* c) +{ + context = c; + + sync_byte = 0; + transport_error_indicator = 0; + payload_unit_start_indicator = 0; + transport_priority = 0; + pid = SrsTsPidPAT; + transport_scrambling_control = SrsTsScrambledDisabled; + adaption_field_control = SrsTsAdaptationFieldTypeReserved; + continuity_counter = 0; + adaptation_field = NULL; + payload = NULL; +} + +SrsTsPacket::~SrsTsPacket() +{ + srs_freep(adaptation_field); + srs_freep(payload); +} + +int SrsTsPacket::decode(SrsStream* stream, SrsTsMessage** ppmsg) +{ + int ret = ERROR_SUCCESS; + + int pos = stream->pos(); + + // 4B ts packet header. + if (!stream->require(4)) { + ret = ERROR_STREAM_CASTER_TS_HEADER; + srs_error("ts: demux header failed. ret=%d", ret); + return ret; + } + + sync_byte = stream->read_1bytes(); + if (sync_byte != 0x47) { + ret = ERROR_STREAM_CASTER_TS_SYNC_BYTE; + srs_error("ts: sync_bytes must be 0x47, actual=%#x. ret=%d", sync_byte, ret); + return ret; + } + + int16_t pidv = stream->read_2bytes(); + transport_error_indicator = (pidv >> 15) & 0x01; + payload_unit_start_indicator = (pidv >> 14) & 0x01; + transport_priority = (pidv >> 13) & 0x01; + pid = (SrsTsPid)(pidv & 0x1FFF); + + int8_t ccv = stream->read_1bytes(); + transport_scrambling_control = (SrsTsScrambled)((ccv >> 6) & 0x03); + adaption_field_control = (SrsTsAdaptationFieldType)((ccv >> 4) & 0x03); + continuity_counter = ccv & 0x0F; + + // TODO: FIXME: create pids map when got new pid. + + srs_info("ts: header sync=%#x error=%d unit_start=%d priotiry=%d pid=%d scrambling=%d adaption=%d counter=%d", + sync_byte, transport_error_indicator, payload_unit_start_indicator, transport_priority, pid, + transport_scrambling_control, adaption_field_control, continuity_counter); + + // optional: adaptation field + if (adaption_field_control == SrsTsAdaptationFieldTypeAdaptionOnly || adaption_field_control == SrsTsAdaptationFieldTypeBoth) { + srs_freep(adaptation_field); + adaptation_field = new SrsTsAdaptationField(this); + + if ((ret = adaptation_field->decode(stream)) != ERROR_SUCCESS) { + srs_error("ts: demux af faield. ret=%d", ret); + return ret; + } + srs_verbose("ts: demux af ok."); + } + + // calc the user defined data size for payload. + int nb_payload = SRS_TS_PACKET_SIZE - (stream->pos() - pos); + + // optional: payload. + if (adaption_field_control == SrsTsAdaptationFieldTypePayloadOnly || adaption_field_control == SrsTsAdaptationFieldTypeBoth) { + if (pid == SrsTsPidPAT) { + // 2.4.4.3 Program association Table + srs_freep(payload); + payload = new SrsTsPayloadPAT(this); + } else { + SrsTsChannel* channel = context->get(pid); + if (channel && channel->apply == SrsTsPidApplyPMT) { + // 2.4.4.8 Program Map Table + srs_freep(payload); + payload = new SrsTsPayloadPMT(this); + } else if (channel && (channel->apply == SrsTsPidApplyVideo || channel->apply == SrsTsPidApplyAudio)) { + // 2.4.3.6 PES packet + srs_freep(payload); + payload = new SrsTsPayloadPES(this); + } else { + // left bytes as reserved. + stream->skip(nb_payload); + } + } + + if (payload && (ret = payload->decode(stream, ppmsg)) != ERROR_SUCCESS) { + srs_error("ts: demux payload failed. ret=%d", ret); + return ret; + } + } + + return ret; +} + +int SrsTsPacket::size() +{ + int sz = 4; + + sz += adaptation_field? adaptation_field->size() : 0; + sz += payload? payload->size() : 0; + + return sz; +} + +int SrsTsPacket::encode(SrsStream* stream) +{ + int ret = ERROR_SUCCESS; + + // 4B ts packet header. + if (!stream->require(4)) { + ret = ERROR_STREAM_CASTER_TS_HEADER; + srs_error("ts: mux header failed. ret=%d", ret); + return ret; + } + + stream->write_1bytes(sync_byte); + + int16_t pidv = pid & 0x1FFF; + pidv |= (transport_priority << 13) & 0x2000; + pidv |= (transport_error_indicator << 15) & 0x8000; + pidv |= (payload_unit_start_indicator << 14) & 0x4000; + stream->write_2bytes(pidv); + + int8_t ccv = continuity_counter & 0x0F; + ccv |= (transport_scrambling_control << 6) & 0xC0; + ccv |= (adaption_field_control << 4) & 0x30; + stream->write_1bytes(ccv); + + srs_info("ts: header sync=%#x error=%d unit_start=%d priotiry=%d pid=%d scrambling=%d adaption=%d counter=%d", + sync_byte, transport_error_indicator, payload_unit_start_indicator, transport_priority, pid, + transport_scrambling_control, adaption_field_control, continuity_counter); + + // optional: adaptation field + if (adaptation_field) { + if ((ret = adaptation_field->encode(stream)) != ERROR_SUCCESS) { + srs_error("ts: mux af faield. ret=%d", ret); + return ret; + } + srs_verbose("ts: mux af ok."); + } + + // optional: payload. + if (payload) { + if ((ret = payload->encode(stream)) != ERROR_SUCCESS) { + srs_error("ts: mux payload failed. ret=%d", ret); + return ret; + } + srs_verbose("ts: mux payload ok."); + } + + return ret; +} + +void SrsTsPacket::padding(int nb_stuffings) +{ + if (!adaptation_field) { + SrsTsAdaptationField* af = new SrsTsAdaptationField(this); + adaptation_field = af; + + af->adaption_field_length = 0; // calc in size. + af->discontinuity_indicator = 0; + af->random_access_indicator = 0; + af->elementary_stream_priority_indicator = 0; + af->PCR_flag = 0; + af->OPCR_flag = 0; + af->splicing_point_flag = 0; + af->transport_private_data_flag = 0; + af->adaptation_field_extension_flag = 0; + + // consume the af size if possible. + nb_stuffings = srs_max(0, nb_stuffings - af->size()); + } + + adaptation_field->nb_af_reserved = nb_stuffings; + + // set payload with af. + if (adaption_field_control == SrsTsAdaptationFieldTypePayloadOnly) { + adaption_field_control = SrsTsAdaptationFieldTypeBoth; + } +} + +SrsTsPacket* SrsTsPacket::create_pat(SrsTsContext* context, int16_t pmt_number, int16_t pmt_pid) +{ + SrsTsPacket* pkt = new SrsTsPacket(context); + pkt->sync_byte = 0x47; + pkt->transport_error_indicator = 0; + pkt->payload_unit_start_indicator = 1; + pkt->transport_priority = 0; + pkt->pid = SrsTsPidPAT; + pkt->transport_scrambling_control = SrsTsScrambledDisabled; + pkt->adaption_field_control = SrsTsAdaptationFieldTypePayloadOnly; + pkt->continuity_counter = 0; + pkt->adaptation_field = NULL; + SrsTsPayloadPAT* pat = new SrsTsPayloadPAT(pkt); + pkt->payload = pat; + + pat->pointer_field = 0; + pat->table_id = SrsTsPsiIdPas; + pat->section_syntax_indicator = 1; + pat->section_length = 0; // calc in size. + pat->transport_stream_id = 1; + pat->version_number = 0; + pat->current_next_indicator = 1; + pat->section_number = 0; + pat->last_section_number = 0; + pat->programs.push_back(new SrsTsPayloadPATProgram(pmt_number, pmt_pid)); + pat->CRC_32 = 0; // calc in encode. + return pkt; +} + +SrsTsPacket* SrsTsPacket::create_pmt(SrsTsContext* context, int16_t pmt_number, int16_t pmt_pid, int16_t vpid, SrsTsStream vs, int16_t apid, SrsTsStream as) +{ + SrsTsPacket* pkt = new SrsTsPacket(context); + pkt->sync_byte = 0x47; + pkt->transport_error_indicator = 0; + pkt->payload_unit_start_indicator = 1; + pkt->transport_priority = 0; + pkt->pid = (SrsTsPid)pmt_pid; + pkt->transport_scrambling_control = SrsTsScrambledDisabled; + pkt->adaption_field_control = SrsTsAdaptationFieldTypePayloadOnly; + // TODO: FIXME: maybe should continuous in channel. + pkt->continuity_counter = 0; + pkt->adaptation_field = NULL; + SrsTsPayloadPMT* pmt = new SrsTsPayloadPMT(pkt); + pkt->payload = pmt; + + pmt->pointer_field = 0; + pmt->table_id = SrsTsPsiIdPms; + pmt->section_syntax_indicator = 1; + pmt->section_length = 0; // calc in size. + pmt->program_number = pmt_number; + pmt->version_number = 0; + pmt->current_next_indicator = 1; + pmt->section_number = 0; + pmt->last_section_number = 0; + pmt->program_info_length = 0; + + // use audio to carray pcr by default. + // for hls, there must be atleast one audio channel. + pmt->PCR_PID = apid; + pmt->infos.push_back(new SrsTsPayloadPMTESInfo(as, apid)); + + // if h.264 specified, use video to carry pcr. + if (vs == SrsTsStreamVideoH264) { + pmt->PCR_PID = vpid; + pmt->infos.push_back(new SrsTsPayloadPMTESInfo(vs, vpid)); + } + + pmt->CRC_32 = 0; // calc in encode. + return pkt; +} + +SrsTsPacket* SrsTsPacket::create_pes_first(SrsTsContext* context, + int16_t pid, SrsTsPESStreamId sid, u_int8_t continuity_counter, bool discontinuity, + int64_t pcr, int64_t dts, int64_t pts, int size +) { + SrsTsPacket* pkt = new SrsTsPacket(context); + pkt->sync_byte = 0x47; + pkt->transport_error_indicator = 0; + pkt->payload_unit_start_indicator = 1; + pkt->transport_priority = 0; + pkt->pid = (SrsTsPid)pid; + pkt->transport_scrambling_control = SrsTsScrambledDisabled; + pkt->adaption_field_control = SrsTsAdaptationFieldTypePayloadOnly; + pkt->continuity_counter = continuity_counter; + pkt->adaptation_field = NULL; + SrsTsPayloadPES* pes = new SrsTsPayloadPES(pkt); + pkt->payload = pes; + + if (pcr >= 0) { + SrsTsAdaptationField* af = new SrsTsAdaptationField(pkt); + pkt->adaptation_field = af; + pkt->adaption_field_control = SrsTsAdaptationFieldTypeBoth; + + af->adaption_field_length = 0; // calc in size. + af->discontinuity_indicator = discontinuity; + af->random_access_indicator = 0; + af->elementary_stream_priority_indicator = 0; + af->PCR_flag = 1; + af->OPCR_flag = 0; + af->splicing_point_flag = 0; + af->transport_private_data_flag = 0; + af->adaptation_field_extension_flag = 0; + af->program_clock_reference_base = pcr; + af->program_clock_reference_extension = 0; + } + + pes->packet_start_code_prefix = 0x01; + pes->stream_id = (u_int8_t)sid; + pes->PES_packet_length = (size > 0xFFFF)? 0:size; + pes->PES_scrambling_control = 0; + pes->PES_priority = 0; + pes->data_alignment_indicator = 0; + pes->copyright = 0; + pes->original_or_copy = 0; + pes->PTS_DTS_flags = (dts == pts)? 0x02:0x03; + pes->ESCR_flag = 0; + pes->ES_rate_flag = 0; + pes->DSM_trick_mode_flag = 0; + pes->additional_copy_info_flag = 0; + pes->PES_CRC_flag = 0; + pes->PES_extension_flag = 0; + pes->PES_header_data_length = 0; // calc in size. + pes->pts = pts; + pes->dts = dts; + return pkt; +} + +SrsTsPacket* SrsTsPacket::create_pes_continue(SrsTsContext* context, + int16_t pid, SrsTsPESStreamId sid, u_int8_t continuity_counter +) { + SrsTsPacket* pkt = new SrsTsPacket(context); + pkt->sync_byte = 0x47; + pkt->transport_error_indicator = 0; + pkt->payload_unit_start_indicator = 0; + pkt->transport_priority = 0; + pkt->pid = (SrsTsPid)pid; + pkt->transport_scrambling_control = SrsTsScrambledDisabled; + pkt->adaption_field_control = SrsTsAdaptationFieldTypePayloadOnly; + pkt->continuity_counter = continuity_counter; + pkt->adaptation_field = NULL; + pkt->payload = NULL; + + return pkt; +} + +SrsTsAdaptationField::SrsTsAdaptationField(SrsTsPacket* pkt) +{ + packet = pkt; + + adaption_field_length = 0; + discontinuity_indicator = 0; + random_access_indicator = 0; + elementary_stream_priority_indicator = 0; + PCR_flag = 0; + OPCR_flag = 0; + splicing_point_flag = 0; + transport_private_data_flag = 0; + adaptation_field_extension_flag = 0; + program_clock_reference_base = 0; + program_clock_reference_extension = 0; + original_program_clock_reference_base = 0; + original_program_clock_reference_extension = 0; + splice_countdown = 0; + transport_private_data_length = 0; + transport_private_data = NULL; + adaptation_field_extension_length = 0; + ltw_flag = 0; + piecewise_rate_flag = 0; + seamless_splice_flag = 0; + ltw_valid_flag = 0; + ltw_offset = 0; + piecewise_rate = 0; + splice_type = 0; + DTS_next_AU0 = 0; + marker_bit0 = 0; + DTS_next_AU1 = 0; + marker_bit1 = 0; + DTS_next_AU2 = 0; + marker_bit2 = 0; + nb_af_ext_reserved = 0; + nb_af_reserved = 0; + + const1_value0 = 0x3F; + const1_value1 = 0x1F; + const1_value2 = 0x3F; +} + +SrsTsAdaptationField::~SrsTsAdaptationField() +{ + srs_freep(transport_private_data); +} + +int SrsTsAdaptationField::decode(SrsStream* stream) +{ + int ret = ERROR_SUCCESS; + + if (!stream->require(2)) { + ret = ERROR_STREAM_CASTER_TS_AF; + srs_error("ts: demux af failed. ret=%d", ret); + return ret; + } + adaption_field_length = stream->read_1bytes(); + + // When the adaptation_field_control value is '11', the value of the adaptation_field_length shall + // be in the range 0 to 182. + if (packet->adaption_field_control == SrsTsAdaptationFieldTypeBoth && adaption_field_length > 182) { + ret = ERROR_STREAM_CASTER_TS_AF; + srs_error("ts: demux af length failed, must in [0, 182], actual=%d. ret=%d", adaption_field_length, ret); + return ret; + } + // When the adaptation_field_control value is '10', the value of the adaptation_field_length shall + // be 183. + if (packet->adaption_field_control == SrsTsAdaptationFieldTypeAdaptionOnly && adaption_field_length != 183) { + ret = ERROR_STREAM_CASTER_TS_AF; + srs_error("ts: demux af length failed, must be 183, actual=%d. ret=%d", adaption_field_length, ret); + return ret; + } + + // no adaptation field. + if (adaption_field_length == 0) { + srs_info("ts: demux af empty."); + return ret; + } + + // the adaptation field start at here. + int pos_af = stream->pos(); + int8_t tmpv = stream->read_1bytes(); + + discontinuity_indicator = (tmpv >> 7) & 0x01; + random_access_indicator = (tmpv >> 6) & 0x01; + elementary_stream_priority_indicator = (tmpv >> 5) & 0x01; + PCR_flag = (tmpv >> 4) & 0x01; + OPCR_flag = (tmpv >> 3) & 0x01; + splicing_point_flag = (tmpv >> 2) & 0x01; + transport_private_data_flag = (tmpv >> 1) & 0x01; + adaptation_field_extension_flag = tmpv & 0x01; + + if (PCR_flag) { + if (!stream->require(6)) { + ret = ERROR_STREAM_CASTER_TS_AF; + srs_error("ts: demux af PCR_flag failed. ret=%d", ret); + return ret; + } + + char* pp = NULL; + char* p = stream->data() + stream->pos(); + stream->skip(6); + + int64_t pcrv = 0; + pp = (char*)&pcrv; + pp[5] = *p++; + pp[4] = *p++; + pp[3] = *p++; + pp[2] = *p++; + pp[1] = *p++; + pp[0] = *p++; + + // @remark, use pcr base and ignore the extension + // @see https://github.com/simple-rtmp-server/srs/issues/250#issuecomment-71349370 + program_clock_reference_extension = pcrv & 0x1ff; + const1_value0 = (pcrv >> 9) & 0x3F; + program_clock_reference_base = (pcrv >> 15) & 0x1ffffffffLL; + } + + if (OPCR_flag) { + if (!stream->require(6)) { + ret = ERROR_STREAM_CASTER_TS_AF; + srs_error("ts: demux af OPCR_flag failed. ret=%d", ret); + return ret; + } + + char* pp = NULL; + char* p = stream->data() + stream->pos(); + stream->skip(6); + + int64_t opcrv = 0; + pp = (char*)&opcrv; + pp[5] = *p++; + pp[4] = *p++; + pp[3] = *p++; + pp[2] = *p++; + pp[1] = *p++; + pp[0] = *p++; + + // @remark, use pcr base and ignore the extension + // @see https://github.com/simple-rtmp-server/srs/issues/250#issuecomment-71349370 + original_program_clock_reference_extension = opcrv & 0x1ff; + const1_value2 = (opcrv >> 9) & 0x3F; + original_program_clock_reference_base = (opcrv >> 15) & 0x1ffffffffLL; + } + + if (splicing_point_flag) { + if (!stream->require(1)) { + ret = ERROR_STREAM_CASTER_TS_AF; + srs_error("ts: demux af splicing_point_flag failed. ret=%d", ret); + return ret; + } + splice_countdown = stream->read_1bytes(); + } + + if (transport_private_data_flag) { + if (!stream->require(1)) { + ret = ERROR_STREAM_CASTER_TS_AF; + srs_error("ts: demux af transport_private_data_flag failed. ret=%d", ret); + return ret; + } + transport_private_data_length = (u_int8_t)stream->read_1bytes(); + + if (transport_private_data_length> 0) { + if (!stream->require(transport_private_data_length)) { + ret = ERROR_STREAM_CASTER_TS_AF; + srs_error("ts: demux af transport_private_data_flag failed. ret=%d", ret); + return ret; + } + srs_freep(transport_private_data); + transport_private_data = new char[transport_private_data_length]; + stream->read_bytes(transport_private_data, transport_private_data_length); + } + } + + if (adaptation_field_extension_flag) { + int pos_af_ext = stream->pos(); + + if (!stream->require(2)) { + ret = ERROR_STREAM_CASTER_TS_AF; + srs_error("ts: demux af adaptation_field_extension_flag failed. ret=%d", ret); + return ret; + } + adaptation_field_extension_length = (u_int8_t)stream->read_1bytes(); + int8_t ltwfv = stream->read_1bytes(); + + piecewise_rate_flag = (ltwfv >> 6) & 0x01; + seamless_splice_flag = (ltwfv >> 5) & 0x01; + ltw_flag = (ltwfv >> 7) & 0x01; + const1_value1 = ltwfv & 0x1F; + + if (ltw_flag) { + if (!stream->require(2)) { + ret = ERROR_STREAM_CASTER_TS_AF; + srs_error("ts: demux af ltw_flag failed. ret=%d", ret); + return ret; + } + ltw_offset = stream->read_2bytes(); + + ltw_valid_flag = (ltw_offset >> 15) &0x01; + ltw_offset &= 0x7FFF; + } + + if (piecewise_rate_flag) { + if (!stream->require(3)) { + ret = ERROR_STREAM_CASTER_TS_AF; + srs_error("ts: demux af piecewise_rate_flag failed. ret=%d", ret); + return ret; + } + piecewise_rate = stream->read_3bytes(); + + piecewise_rate &= 0x3FFFFF; + } + + if (seamless_splice_flag) { + if (!stream->require(5)) { + ret = ERROR_STREAM_CASTER_TS_AF; + srs_error("ts: demux af seamless_splice_flag failed. ret=%d", ret); + return ret; + } + marker_bit0 = stream->read_1bytes(); + DTS_next_AU1 = stream->read_2bytes(); + DTS_next_AU2 = stream->read_2bytes(); + + splice_type = (marker_bit0 >> 4) & 0x0F; + DTS_next_AU0 = (marker_bit0 >> 1) & 0x07; + marker_bit0 &= 0x01; + + marker_bit1 = DTS_next_AU1 & 0x01; + DTS_next_AU1 = (DTS_next_AU1 >> 1) & 0x7FFF; + + marker_bit2 = DTS_next_AU2 & 0x01; + DTS_next_AU2 = (DTS_next_AU2 >> 1) & 0x7FFF; + } + + nb_af_ext_reserved = adaptation_field_extension_length - (stream->pos() - pos_af_ext); + stream->skip(nb_af_ext_reserved); + } + + nb_af_reserved = adaption_field_length - (stream->pos() - pos_af); + stream->skip(nb_af_reserved); + + srs_info("ts: af parsed, discontinuity=%d random=%d priority=%d PCR=%d OPCR=%d slicing=%d private=%d extension=%d/%d pcr=%"PRId64"/%d opcr=%"PRId64"/%d", + discontinuity_indicator, random_access_indicator, elementary_stream_priority_indicator, PCR_flag, OPCR_flag, splicing_point_flag, + transport_private_data_flag, adaptation_field_extension_flag, adaptation_field_extension_length, program_clock_reference_base, + program_clock_reference_extension, original_program_clock_reference_base, original_program_clock_reference_extension); + + return ret; +} + +int SrsTsAdaptationField::size() +{ + int sz = 2; + + sz += PCR_flag? 6 : 0; + sz += OPCR_flag? 6 : 0; + sz += splicing_point_flag? 1 : 0; + sz += transport_private_data_flag? 1 + transport_private_data_length : 0; + sz += adaptation_field_extension_flag? 2 + adaptation_field_extension_length : 0; + sz += nb_af_ext_reserved; + sz += nb_af_reserved; + + adaption_field_length = sz - 1; + + return sz; +} + +int SrsTsAdaptationField::encode(SrsStream* stream) +{ + int ret = ERROR_SUCCESS; + + if (!stream->require(2)) { + ret = ERROR_STREAM_CASTER_TS_AF; + srs_error("ts: mux af failed. ret=%d", ret); + return ret; + } + stream->write_1bytes(adaption_field_length); + + // When the adaptation_field_control value is '11', the value of the adaptation_field_length shall + // be in the range 0 to 182. + if (packet->adaption_field_control == SrsTsAdaptationFieldTypeBoth && adaption_field_length > 182) { + ret = ERROR_STREAM_CASTER_TS_AF; + srs_error("ts: mux af length failed, must in [0, 182], actual=%d. ret=%d", adaption_field_length, ret); + return ret; + } + // When the adaptation_field_control value is '10', the value of the adaptation_field_length shall + // be 183. + if (packet->adaption_field_control == SrsTsAdaptationFieldTypeAdaptionOnly && adaption_field_length != 183) { + ret = ERROR_STREAM_CASTER_TS_AF; + srs_error("ts: mux af length failed, must be 183, actual=%d. ret=%d", adaption_field_length, ret); + return ret; + } + + // no adaptation field. + if (adaption_field_length == 0) { + srs_info("ts: mux af empty."); + return ret; + } + int8_t tmpv = adaptation_field_extension_flag & 0x01; + tmpv |= (discontinuity_indicator << 7) & 0x80; + tmpv |= (random_access_indicator << 6) & 0x40; + tmpv |= (elementary_stream_priority_indicator << 5) & 0x20; + tmpv |= (PCR_flag << 4) & 0x10; + tmpv |= (OPCR_flag << 3) & 0x08; + tmpv |= (splicing_point_flag << 2) & 0x04; + tmpv |= (transport_private_data_flag << 1) & 0x02; + stream->write_1bytes(tmpv); + + if (PCR_flag) { + if (!stream->require(6)) { + ret = ERROR_STREAM_CASTER_TS_AF; + srs_error("ts: mux af PCR_flag failed. ret=%d", ret); + return ret; + } + + char* pp = NULL; + char* p = stream->data() + stream->pos(); + stream->skip(6); + + // @remark, use pcr base and ignore the extension + // @see https://github.com/simple-rtmp-server/srs/issues/250#issuecomment-71349370 + int64_t pcrv = program_clock_reference_extension & 0x1ff; + pcrv |= (const1_value0 << 9) & 0x7E00; + pcrv |= (program_clock_reference_base << 15) & 0x1FFFFFFFF000000LL; + + pp = (char*)&pcrv; + *p++ = pp[5]; + *p++ = pp[4]; + *p++ = pp[3]; + *p++ = pp[2]; + *p++ = pp[1]; + *p++ = pp[0]; + } + + if (OPCR_flag) { + if (!stream->require(6)) { + ret = ERROR_STREAM_CASTER_TS_AF; + srs_error("ts: demux af OPCR_flag failed. ret=%d", ret); + return ret; + } + stream->skip(6); + srs_warn("ts: mux af ignore OPCR"); + } + + if (splicing_point_flag) { + if (!stream->require(1)) { + ret = ERROR_STREAM_CASTER_TS_AF; + srs_error("ts: mux af splicing_point_flag failed. ret=%d", ret); + return ret; + } + stream->write_1bytes(splice_countdown); + } + + if (transport_private_data_flag) { + if (!stream->require(1)) { + ret = ERROR_STREAM_CASTER_TS_AF; + srs_error("ts: mux af transport_private_data_flag failed. ret=%d", ret); + return ret; + } + stream->write_1bytes(transport_private_data_length); + + if (transport_private_data_length> 0) { + if (!stream->require(transport_private_data_length)) { + ret = ERROR_STREAM_CASTER_TS_AF; + srs_error("ts: mux af transport_private_data_flag failed. ret=%d", ret); + return ret; + } + stream->write_bytes(transport_private_data, transport_private_data_length); + } + } + + if (adaptation_field_extension_flag) { + if (!stream->require(2)) { + ret = ERROR_STREAM_CASTER_TS_AF; + srs_error("ts: mux af adaptation_field_extension_flag failed. ret=%d", ret); + return ret; + } + stream->write_1bytes(adaptation_field_extension_length); + int8_t ltwfv = const1_value1 & 0x1F; + ltwfv |= (ltw_flag << 7) & 0x80; + ltwfv |= (piecewise_rate_flag << 6) & 0x40; + ltwfv |= (seamless_splice_flag << 5) & 0x20; + stream->write_1bytes(ltwfv); + + if (ltw_flag) { + if (!stream->require(2)) { + ret = ERROR_STREAM_CASTER_TS_AF; + srs_error("ts: mux af ltw_flag failed. ret=%d", ret); + return ret; + } + stream->skip(2); + srs_warn("ts: mux af ignore ltw"); + } + + if (piecewise_rate_flag) { + if (!stream->require(3)) { + ret = ERROR_STREAM_CASTER_TS_AF; + srs_error("ts: mux af piecewise_rate_flag failed. ret=%d", ret); + return ret; + } + stream->skip(3); + srs_warn("ts: mux af ignore piecewise_rate"); + } + + if (seamless_splice_flag) { + if (!stream->require(5)) { + ret = ERROR_STREAM_CASTER_TS_AF; + srs_error("ts: mux af seamless_splice_flag failed. ret=%d", ret); + return ret; + } + stream->skip(5); + srs_warn("ts: mux af ignore seamless_splice"); + } + + if (nb_af_ext_reserved) { + stream->skip(nb_af_ext_reserved); + } + } + + if (nb_af_reserved) { + stream->skip(nb_af_reserved); + } + + srs_info("ts: af parsed, discontinuity=%d random=%d priority=%d PCR=%d OPCR=%d slicing=%d private=%d extension=%d/%d pcr=%"PRId64"/%d opcr=%"PRId64"/%d", + discontinuity_indicator, random_access_indicator, elementary_stream_priority_indicator, PCR_flag, OPCR_flag, splicing_point_flag, + transport_private_data_flag, adaptation_field_extension_flag, adaptation_field_extension_length, program_clock_reference_base, + program_clock_reference_extension, original_program_clock_reference_base, original_program_clock_reference_extension); + + return ret; +} + +SrsTsPayload::SrsTsPayload(SrsTsPacket* p) +{ + packet = p; +} + +SrsTsPayload::~SrsTsPayload() +{ +} + +SrsTsPayloadPES::SrsTsPayloadPES(SrsTsPacket* p) : SrsTsPayload(p) +{ + PES_private_data = NULL; + pack_field = NULL; + PES_extension_field = NULL; + nb_stuffings = 0; + nb_bytes = 0; + nb_paddings = 0; + const2bits = 0x02; + const1_value0 = 0x07; +} + +SrsTsPayloadPES::~SrsTsPayloadPES() +{ + srs_freep(PES_private_data); + srs_freep(pack_field); + srs_freep(PES_extension_field); +} + +int SrsTsPayloadPES::decode(SrsStream* stream, SrsTsMessage** ppmsg) +{ + int ret = ERROR_SUCCESS; + + // find the channel from chunk. + SrsTsChannel* channel = packet->context->get(packet->pid); + if (!channel) { + ret = ERROR_STREAM_CASTER_TS_PSE; + srs_error("ts: demux PES no channel for pid=%#x. ret=%d", packet->pid, ret); + return ret; + } + + // init msg. + SrsTsMessage* msg = channel->msg; + if (!msg) { + msg = new SrsTsMessage(channel, packet); + channel->msg = msg; + } + + // we must cache the fresh state of msg, + // for the PES_packet_length is 0, the first payload_unit_start_indicator always 1, + // so should check for the fresh and not completed it. + bool is_fresh_msg = msg->fresh(); + + // check when fresh, the payload_unit_start_indicator + // should be 1 for the fresh msg. + if (is_fresh_msg && !packet->payload_unit_start_indicator) { + ret = ERROR_STREAM_CASTER_TS_PSE; + srs_error("ts: PES fresh packet length=%d, us=%d, cc=%d. ret=%d", + msg->PES_packet_length, packet->payload_unit_start_indicator, packet->continuity_counter, + ret); + return ret; + } + + // check when not fresh and PES_packet_length>0, + // the payload_unit_start_indicator should never be 1 when not completed. + if (!is_fresh_msg && msg->PES_packet_length > 0 + && !msg->completed(packet->payload_unit_start_indicator) + && packet->payload_unit_start_indicator + ) { + ret = ERROR_STREAM_CASTER_TS_PSE; + srs_error("ts: PES packet length=%d, payload=%d, us=%d, cc=%d. ret=%d", + msg->PES_packet_length, msg->payload->length(), packet->payload_unit_start_indicator, + packet->continuity_counter, ret); + + // reparse current msg. + stream->skip(stream->pos() * -1); + srs_freep(msg); + channel->msg = NULL; + return ERROR_SUCCESS; + } + + // check the continuity counter + if (!is_fresh_msg) { + // late-incoming or duplicated continuity, drop message. + // @remark check overflow, the counter plus 1 should greater when invalid. + if (msg->continuity_counter >= packet->continuity_counter + && ((msg->continuity_counter + 1) & 0x0f) > packet->continuity_counter + ) { + srs_warn("ts: drop PES %dB for duplicated cc=%#x", msg->continuity_counter); + stream->skip(stream->size() - stream->pos()); + return ret; + } + + // when got partially message, the continous count must be continuous, or drop it. + if (((msg->continuity_counter + 1) & 0x0f) != packet->continuity_counter) { + ret = ERROR_STREAM_CASTER_TS_PSE; + srs_error("ts: continuity must be continous, msg=%#x, packet=%#x. ret=%d", + msg->continuity_counter, packet->continuity_counter, ret); + + // reparse current msg. + stream->skip(stream->pos() * -1); + srs_freep(msg); + channel->msg = NULL; + return ERROR_SUCCESS; + } + } + msg->continuity_counter = packet->continuity_counter; + + // for the PES_packet_length(0), reap when completed. + if (!is_fresh_msg && msg->completed(packet->payload_unit_start_indicator)) { + // reap previous PES packet. + *ppmsg = msg; + channel->msg = NULL; + + // reparse current msg. + stream->skip(stream->pos() * -1); + return ret; + } + + // contious packet, append bytes for unit start is 0 + if (!packet->payload_unit_start_indicator) { + if ((ret = msg->dump(stream, &nb_bytes)) != ERROR_SUCCESS) { + return ret; + } + } + + // when unit start, parse the fresh msg. + if (packet->payload_unit_start_indicator) { + // 6B fixed header. + if (!stream->require(6)) { + ret = ERROR_STREAM_CASTER_TS_PSE; + srs_error("ts: demux PSE failed. ret=%d", ret); + return ret; + } + // 3B + packet_start_code_prefix = stream->read_3bytes(); + // 1B + stream_id = stream->read_1bytes(); + // 2B + PES_packet_length = stream->read_2bytes(); + + // check the packet start prefix. + packet_start_code_prefix &= 0xFFFFFF; + if (packet_start_code_prefix != 0x01) { + ret = ERROR_STREAM_CASTER_TS_PSE; + srs_error("ts: demux PES start code failed, expect=0x01, actual=%#x. ret=%d", packet_start_code_prefix, ret); + return ret; + } + int pos_packet = stream->pos(); + + // @remark the sid indicates the elementary stream format. + // the SrsTsPESStreamIdAudio and SrsTsPESStreamIdVideo is start by 0b110 or 0b1110 + SrsTsPESStreamId sid = (SrsTsPESStreamId)stream_id; + msg->sid = sid; + + if (sid != SrsTsPESStreamIdProgramStreamMap + && sid != SrsTsPESStreamIdPaddingStream + && sid != SrsTsPESStreamIdPrivateStream2 + && sid != SrsTsPESStreamIdEcmStream + && sid != SrsTsPESStreamIdEmmStream + && sid != SrsTsPESStreamIdProgramStreamDirectory + && sid != SrsTsPESStreamIdDsmccStream + && sid != SrsTsPESStreamIdH2221TypeE + ) { + // 3B flags. + if (!stream->require(3)) { + ret = ERROR_STREAM_CASTER_TS_PSE; + srs_error("ts: demux PES flags failed. ret=%d", ret); + return ret; + } + // 1B + int8_t oocv = stream->read_1bytes(); + // 1B + int8_t pefv = stream->read_1bytes(); + // 1B + PES_header_data_length = stream->read_1bytes(); + // position of header start. + int pos_header = stream->pos(); + + const2bits = (oocv >> 6) & 0x03; + PES_scrambling_control = (oocv >> 4) & 0x03; + PES_priority = (oocv >> 3) & 0x01; + data_alignment_indicator = (oocv >> 2) & 0x01; + copyright = (oocv >> 1) & 0x01; + original_or_copy = oocv & 0x01; + + PTS_DTS_flags = (pefv >> 6) & 0x03; + ESCR_flag = (pefv >> 5) & 0x01; + ES_rate_flag = (pefv >> 4) & 0x01; + DSM_trick_mode_flag = (pefv >> 3) & 0x01; + additional_copy_info_flag = (pefv >> 2) & 0x01; + PES_CRC_flag = (pefv >> 1) & 0x01; + PES_extension_flag = pefv & 0x01; + + // check required together. + int nb_required = 0; + nb_required += (PTS_DTS_flags == 0x2)? 5:0; + nb_required += (PTS_DTS_flags == 0x3)? 10:0; + nb_required += ESCR_flag? 6:0; + nb_required += ES_rate_flag? 3:0; + nb_required += DSM_trick_mode_flag? 1:0; + nb_required += additional_copy_info_flag? 1:0; + nb_required += PES_CRC_flag? 2:0; + nb_required += PES_extension_flag? 1:0; + if (!stream->require(nb_required)) { + ret = ERROR_STREAM_CASTER_TS_PSE; + srs_error("ts: demux PES payload failed. ret=%d", ret); + return ret; + } + + // 5B + if (PTS_DTS_flags == 0x2) { + if ((ret = decode_33bits_dts_pts(stream, &pts)) != ERROR_SUCCESS) { + return ret; + } + dts = pts; + + // update the dts and pts of message. + msg->dts = dts; + msg->pts = pts; + } + + // 10B + if (PTS_DTS_flags == 0x3) { + if ((ret = decode_33bits_dts_pts(stream, &pts)) != ERROR_SUCCESS) { + return ret; + } + if ((ret = decode_33bits_dts_pts(stream, &dts)) != ERROR_SUCCESS) { + return ret; + } + + // check sync, the diff of dts and pts should never greater than 1s. + if (dts - pts > 90000 || pts - dts > 90000) { + srs_warn("ts: sync dts=%"PRId64", pts=%"PRId64, dts, pts); + } + + // update the dts and pts of message. + msg->dts = dts; + msg->pts = pts; + } + + // 6B + if (ESCR_flag) { + ESCR_extension = 0; + ESCR_base = 0; + + stream->skip(6); + srs_warn("ts: demux PES, ignore the escr."); + } + + // 3B + if (ES_rate_flag) { + ES_rate = stream->read_3bytes(); + + ES_rate = ES_rate >> 1; + ES_rate &= 0x3FFFFF; + } + + // 1B + if (DSM_trick_mode_flag) { + trick_mode_control = stream->read_1bytes(); + + trick_mode_value = trick_mode_control & 0x1f; + trick_mode_control = (trick_mode_control >> 5) & 0x03; + } + + // 1B + if (additional_copy_info_flag) { + additional_copy_info = stream->read_1bytes(); + + additional_copy_info &= 0x7f; + } + + // 2B + if (PES_CRC_flag) { + previous_PES_packet_CRC = stream->read_2bytes(); + } + + // 1B + if (PES_extension_flag) { + int8_t efv = stream->read_1bytes(); + + PES_private_data_flag = (efv >> 7) & 0x01; + pack_header_field_flag = (efv >> 6) & 0x01; + program_packet_sequence_counter_flag = (efv >> 5) & 0x01; + P_STD_buffer_flag = (efv >> 4) & 0x01; + const1_value0 = (efv >> 1) & 0x07; + PES_extension_flag_2 = efv & 0x01; + + nb_required = 0; + nb_required += PES_private_data_flag? 16:0; + nb_required += pack_header_field_flag? 1:0; // 1+x bytes. + nb_required += program_packet_sequence_counter_flag? 2:0; + nb_required += P_STD_buffer_flag? 2:0; + nb_required += PES_extension_flag_2? 1:0; // 1+x bytes. + if (!stream->require(nb_required)) { + ret = ERROR_STREAM_CASTER_TS_PSE; + srs_error("ts: demux PSE ext payload failed. ret=%d", ret); + return ret; + } + + // 16B + if (PES_private_data_flag) { + srs_freep(PES_private_data); + PES_private_data = new char[16]; + stream->read_bytes(PES_private_data, 16); + } + + // (1+x)B + if (pack_header_field_flag) { + pack_field_length = stream->read_1bytes(); + if (pack_field_length > 0) { + // the adjust required bytes. + nb_required = nb_required - 16 - 1 + pack_field_length; + if (!stream->require(nb_required)) { + ret = ERROR_STREAM_CASTER_TS_PSE; + srs_error("ts: demux PSE ext pack failed. ret=%d", ret); + return ret; + } + srs_freep(pack_field); + pack_field = new char[pack_field_length]; + stream->read_bytes(pack_field, pack_field_length); + } + } + + // 2B + if (program_packet_sequence_counter_flag) { + program_packet_sequence_counter = stream->read_1bytes(); + program_packet_sequence_counter &= 0x7f; + + original_stuff_length = stream->read_1bytes(); + MPEG1_MPEG2_identifier = (original_stuff_length >> 6) & 0x01; + original_stuff_length &= 0x3f; + } + + // 2B + if (P_STD_buffer_flag) { + P_STD_buffer_size = stream->read_2bytes(); + + // '01' + //int8_t const2bits = (P_STD_buffer_scale >>14) & 0x03; + + P_STD_buffer_scale = (P_STD_buffer_scale >>13) & 0x01; + P_STD_buffer_size &= 0x1FFF; + } + + // (1+x)B + if (PES_extension_flag_2) { + PES_extension_field_length = stream->read_1bytes(); + PES_extension_field_length &= 0x07; + + if (PES_extension_field_length > 0) { + if (!stream->require(PES_extension_field_length)) { + ret = ERROR_STREAM_CASTER_TS_PSE; + srs_error("ts: demux PSE ext field failed. ret=%d", ret); + return ret; + } + srs_freep(PES_extension_field); + PES_extension_field = new char[PES_extension_field_length]; + stream->read_bytes(PES_extension_field, PES_extension_field_length); + } + } + } + + // stuffing_byte + nb_stuffings = PES_header_data_length - (stream->pos() - pos_header); + if (nb_stuffings > 0) { + if (!stream->require(nb_stuffings)) { + ret = ERROR_STREAM_CASTER_TS_PSE; + srs_error("ts: demux PSE stuffings failed. ret=%d", ret); + return ret; + } + stream->skip(nb_stuffings); + } + + // PES_packet_data_byte, page58. + // the packet size contains the header size. + // The number of PES_packet_data_bytes, N, is specified by the + // PES_packet_length field. N shall be equal to the value + // indicated in the PES_packet_length minus the number of bytes + // between the last byte of the PES_packet_length field and the + // first PES_packet_data_byte. + /** + * when actual packet length > 0xffff(65535), + * which exceed the max u_int16_t packet length, + * use 0 packet length, the next unit start indicates the end of packet. + */ + if (PES_packet_length > 0) { + int nb_packet = PES_packet_length - (stream->pos() - pos_packet); + msg->PES_packet_length = srs_max(0, nb_packet); + } + + // xB + if ((ret = msg->dump(stream, &nb_bytes)) != ERROR_SUCCESS) { + return ret; + } + } else if (sid == SrsTsPESStreamIdProgramStreamMap + || sid == SrsTsPESStreamIdPrivateStream2 + || sid == SrsTsPESStreamIdEcmStream + || sid == SrsTsPESStreamIdEmmStream + || sid == SrsTsPESStreamIdProgramStreamDirectory + || sid == SrsTsPESStreamIdDsmccStream + || sid == SrsTsPESStreamIdH2221TypeE + ) { + // for (i = 0; i < PES_packet_length; i++) { + // PES_packet_data_byte + // } + + // xB + if ((ret = msg->dump(stream, &nb_bytes)) != ERROR_SUCCESS) { + return ret; + } + } else if (sid == SrsTsPESStreamIdPaddingStream) { + // for (i = 0; i < PES_packet_length; i++) { + // padding_byte + // } + nb_paddings = stream->size() - stream->pos(); + stream->skip(nb_paddings); + srs_info("ts: drop %dB padding bytes", nb_paddings); + } else { + int nb_drop = stream->size() - stream->pos(); + stream->skip(nb_drop); + srs_warn("ts: drop the pes packet %dB for stream_id=%#x", nb_drop, stream_id); + } + } + + // when fresh and the PES_packet_length is 0, + // the payload_unit_start_indicator always be 1, + // the message should never EOF for the first packet. + if (is_fresh_msg && msg->PES_packet_length == 0) { + return ret; + } + + // check msg, reap when completed. + if (msg->completed(packet->payload_unit_start_indicator)) { + *ppmsg = msg; + channel->msg = NULL; + srs_info("ts: reap msg for completed."); + } + + return ret; +} + +int SrsTsPayloadPES::size() +{ + int sz = 0; + + PES_header_data_length = 0; + SrsTsPESStreamId sid = (SrsTsPESStreamId)stream_id; + + if (sid != SrsTsPESStreamIdProgramStreamMap + && sid != SrsTsPESStreamIdPaddingStream + && sid != SrsTsPESStreamIdPrivateStream2 + && sid != SrsTsPESStreamIdEcmStream + && sid != SrsTsPESStreamIdEmmStream + && sid != SrsTsPESStreamIdProgramStreamDirectory + && sid != SrsTsPESStreamIdDsmccStream + && sid != SrsTsPESStreamIdH2221TypeE + ) { + sz += 6; + sz += 3; + PES_header_data_length = sz; + + sz += (PTS_DTS_flags == 0x2)? 5:0; + sz += (PTS_DTS_flags == 0x3)? 10:0; + sz += ESCR_flag? 6:0; + sz += ES_rate_flag? 3:0; + sz += DSM_trick_mode_flag? 1:0; + sz += additional_copy_info_flag? 1:0; + sz += PES_CRC_flag? 2:0; + sz += PES_extension_flag? 1:0; + + if (PES_extension_flag) { + sz += PES_private_data_flag? 16:0; + sz += pack_header_field_flag? 1 + pack_field_length:0; // 1+x bytes. + sz += program_packet_sequence_counter_flag? 2:0; + sz += P_STD_buffer_flag? 2:0; + sz += PES_extension_flag_2? 1 + PES_extension_field_length:0; // 1+x bytes. + } + PES_header_data_length = sz - PES_header_data_length; + + sz += nb_stuffings; + + // packet bytes + } else if (sid == SrsTsPESStreamIdProgramStreamMap + || sid == SrsTsPESStreamIdPrivateStream2 + || sid == SrsTsPESStreamIdEcmStream + || sid == SrsTsPESStreamIdEmmStream + || sid == SrsTsPESStreamIdProgramStreamDirectory + || sid == SrsTsPESStreamIdDsmccStream + || sid == SrsTsPESStreamIdH2221TypeE + ) { + // packet bytes + } else { + // nb_drop + } + + return sz; +} + +int SrsTsPayloadPES::encode(SrsStream* stream) +{ + int ret = ERROR_SUCCESS; + + // 6B fixed header. + if (!stream->require(6)) { + ret = ERROR_STREAM_CASTER_TS_PSE; + srs_error("ts: mux PSE failed. ret=%d", ret); + return ret; + } + + // 3B + stream->write_3bytes(packet_start_code_prefix); + // 1B + stream->write_1bytes(stream_id); + // 2B + // the PES_packet_length is the actual bytes size, the pplv write to ts + // is the actual bytes plus the header size. + int32_t pplv = 0; + if (PES_packet_length > 0) { + pplv = PES_packet_length + 3 + PES_header_data_length; + pplv = (pplv > 0xFFFF)? 0 : pplv; + } + stream->write_2bytes(pplv); + + // check the packet start prefix. + packet_start_code_prefix &= 0xFFFFFF; + if (packet_start_code_prefix != 0x01) { + ret = ERROR_STREAM_CASTER_TS_PSE; + srs_error("ts: mux PSE start code failed, expect=0x01, actual=%#x. ret=%d", packet_start_code_prefix, ret); + return ret; + } + + // 3B flags. + if (!stream->require(3)) { + ret = ERROR_STREAM_CASTER_TS_PSE; + srs_error("ts: mux PSE flags failed. ret=%d", ret); + return ret; + } + // 1B + int8_t oocv = original_or_copy & 0x01; + oocv |= (const2bits << 6) & 0xC0; + oocv |= (PES_scrambling_control << 4) & 0x30; + oocv |= (PES_priority << 3) & 0x08; + oocv |= (data_alignment_indicator << 2) & 0x04; + oocv |= (copyright << 1) & 0x02; + stream->write_1bytes(oocv); + // 1B + int8_t pefv = PES_extension_flag & 0x01; + pefv |= (PTS_DTS_flags << 6) & 0xC0; + pefv |= (ESCR_flag << 5) & 0x20; + pefv |= (ES_rate_flag << 4) & 0x10; + pefv |= (DSM_trick_mode_flag << 3) & 0x08; + pefv |= (additional_copy_info_flag << 2) & 0x04; + pefv |= (PES_CRC_flag << 1) & 0x02; + stream->write_1bytes(pefv); + // 1B + stream->write_1bytes(PES_header_data_length); + + // check required together. + int nb_required = 0; + nb_required += (PTS_DTS_flags == 0x2)? 5:0; + nb_required += (PTS_DTS_flags == 0x3)? 10:0; + nb_required += ESCR_flag? 6:0; + nb_required += ES_rate_flag? 3:0; + nb_required += DSM_trick_mode_flag? 1:0; + nb_required += additional_copy_info_flag? 1:0; + nb_required += PES_CRC_flag? 2:0; + nb_required += PES_extension_flag? 1:0; + if (!stream->require(nb_required)) { + ret = ERROR_STREAM_CASTER_TS_PSE; + srs_error("ts: mux PSE payload failed. ret=%d", ret); + return ret; + } + + // 5B + if (PTS_DTS_flags == 0x2) { + if ((ret = encode_33bits_dts_pts(stream, 0x02, pts)) != ERROR_SUCCESS) { + return ret; + } + } + + // 10B + if (PTS_DTS_flags == 0x3) { + if ((ret = encode_33bits_dts_pts(stream, 0x03, pts)) != ERROR_SUCCESS) { + return ret; + } + if ((ret = encode_33bits_dts_pts(stream, 0x01, dts)) != ERROR_SUCCESS) { + return ret; + } + + // check sync, the diff of dts and pts should never greater than 1s. + if (dts - pts > 90000 || pts - dts > 90000) { + srs_warn("ts: sync dts=%"PRId64", pts=%"PRId64, dts, pts); + } + } + + // 6B + if (ESCR_flag) { + stream->skip(6); + srs_warn("ts: demux PES, ignore the escr."); + } + + // 3B + if (ES_rate_flag) { + stream->skip(3); + srs_warn("ts: demux PES, ignore the ES_rate."); + } + + // 1B + if (DSM_trick_mode_flag) { + stream->skip(1); + srs_warn("ts: demux PES, ignore the DSM_trick_mode."); + } + + // 1B + if (additional_copy_info_flag) { + stream->skip(1); + srs_warn("ts: demux PES, ignore the additional_copy_info."); + } + + // 2B + if (PES_CRC_flag) { + stream->skip(2); + srs_warn("ts: demux PES, ignore the PES_CRC."); + } + + // 1B + if (PES_extension_flag) { + int8_t efv = PES_extension_flag_2 & 0x01; + efv |= (PES_private_data_flag << 7) & 0x80; + efv |= (pack_header_field_flag << 6) & 0x40; + efv |= (program_packet_sequence_counter_flag << 5) & 0x20; + efv |= (P_STD_buffer_flag << 4) & 0x10; + efv |= (const1_value0 << 1) & 0xE0; + stream->write_1bytes(efv); + + nb_required = 0; + nb_required += PES_private_data_flag? 16:0; + nb_required += pack_header_field_flag? 1+pack_field_length:0; // 1+x bytes. + nb_required += program_packet_sequence_counter_flag? 2:0; + nb_required += P_STD_buffer_flag? 2:0; + nb_required += PES_extension_flag_2? 1+PES_extension_field_length:0; // 1+x bytes. + if (!stream->require(nb_required)) { + ret = ERROR_STREAM_CASTER_TS_PSE; + srs_error("ts: mux PSE ext payload failed. ret=%d", ret); + return ret; + } + stream->skip(nb_required); + srs_warn("ts: demux PES, ignore the PES_extension."); + } + + // stuffing_byte + if (nb_stuffings) { + stream->skip(nb_stuffings); + srs_warn("ts: demux PES, ignore the stuffings."); + } + + return ret; +} + +int SrsTsPayloadPES::decode_33bits_dts_pts(SrsStream* stream, int64_t* pv) +{ + int ret = ERROR_SUCCESS; + + if (!stream->require(5)) { + ret = ERROR_STREAM_CASTER_TS_PSE; + srs_error("ts: demux PSE dts/pts failed. ret=%d", ret); + return ret; + } + + // decode the 33bits schema. + // ===========1B + // 4bits const maybe '0001', '0010' or '0011'. + // 3bits DTS/PTS [32..30] + // 1bit const '1' + int64_t dts_pts_30_32 = stream->read_1bytes(); + if ((dts_pts_30_32 & 0x01) != 0x01) { + ret = ERROR_STREAM_CASTER_TS_PSE; + srs_error("ts: demux PSE dts/pts 30-32 failed. ret=%d", ret); + return ret; + } + // @remark, we donot check the high 4bits, maybe '0001', '0010' or '0011'. + // so we just ensure the high 4bits is not 0x00. + if (((dts_pts_30_32 >> 4) & 0x0f) == 0x00) { + ret = ERROR_STREAM_CASTER_TS_PSE; + srs_error("ts: demux PSE dts/pts 30-32 failed. ret=%d", ret); + return ret; + } + dts_pts_30_32 = (dts_pts_30_32 >> 1) & 0x07; + + // ===========2B + // 15bits DTS/PTS [29..15] + // 1bit const '1' + int64_t dts_pts_15_29 = stream->read_2bytes(); + if ((dts_pts_15_29 & 0x01) != 0x01) { + ret = ERROR_STREAM_CASTER_TS_PSE; + srs_error("ts: demux PSE dts/pts 15-29 failed. ret=%d", ret); + return ret; + } + dts_pts_15_29 = (dts_pts_15_29 >> 1) & 0x7fff; + + // ===========2B + // 15bits DTS/PTS [14..0] + // 1bit const '1' + int64_t dts_pts_0_14 = stream->read_2bytes(); + if ((dts_pts_0_14 & 0x01) != 0x01) { + ret = ERROR_STREAM_CASTER_TS_PSE; + srs_error("ts: demux PSE dts/pts 0-14 failed. ret=%d", ret); + return ret; + } + dts_pts_0_14 = (dts_pts_0_14 >> 1) & 0x7fff; + + int64_t v = 0x00; + v |= (dts_pts_30_32 << 30) & 0x1c0000000LL; + v |= (dts_pts_15_29 << 15) & 0x3fff8000LL; + v |= dts_pts_0_14 & 0x7fff; + *pv = v; + + return ret; +} + +int SrsTsPayloadPES::encode_33bits_dts_pts(SrsStream* stream, u_int8_t fb, int64_t v) +{ + int ret = ERROR_SUCCESS; + + if (!stream->require(5)) { + ret = ERROR_STREAM_CASTER_TS_PSE; + srs_error("ts: mux PSE dts/pts failed. ret=%d", ret); + return ret; + } + + char* p = stream->data() + stream->pos(); + stream->skip(5); + + int32_t val = 0; + + val = fb << 4 | (((v >> 30) & 0x07) << 1) | 1; + *p++ = val; + + val = (((v >> 15) & 0x7fff) << 1) | 1; + *p++ = (val >> 8); + *p++ = val; + + val = (((v) & 0x7fff) << 1) | 1; + *p++ = (val >> 8); + *p++ = val; + + return ret; +} + +SrsTsPayloadPSI::SrsTsPayloadPSI(SrsTsPacket* p) : SrsTsPayload(p) +{ + pointer_field = 0; + const0_value = 0; + const1_value = 3; + CRC_32 = 0; +} + +SrsTsPayloadPSI::~SrsTsPayloadPSI() +{ +} + +int SrsTsPayloadPSI::decode(SrsStream* stream, SrsTsMessage** /*ppmsg*/) +{ + int ret = ERROR_SUCCESS; + + /** + * When the payload of the Transport Stream packet contains PSI data, the payload_unit_start_indicator has the following + * significance: if the Transport Stream packet carries the first byte of a PSI section, the payload_unit_start_indicator value + * shall be '1', indicating that the first byte of the payload of this Transport Stream packet carries the pointer_field. If the + * Transport Stream packet does not carry the first byte of a PSI section, the payload_unit_start_indicator value shall be '0', + * indicating that there is no pointer_field in the payload. Refer to 2.4.4.1 and 2.4.4.2. This also applies to private streams of + * stream_type 5 (refer to Table 2-29). + */ + if (packet->payload_unit_start_indicator) { + if (!stream->require(1)) { + ret = ERROR_STREAM_CASTER_TS_PSI; + srs_error("ts: demux PSI failed. ret=%d", ret); + return ret; + } + pointer_field = stream->read_1bytes(); + } + + // to calc the crc32 + char* ppat = stream->data() + stream->pos(); + int pat_pos = stream->pos(); + + // atleast 3B for all psi. + if (!stream->require(3)) { + ret = ERROR_STREAM_CASTER_TS_PSI; + srs_error("ts: demux PSI failed. ret=%d", ret); + return ret; + } + // 1B + table_id = (SrsTsPsiId)stream->read_1bytes(); + + // 2B + int16_t slv = stream->read_2bytes(); + + section_syntax_indicator = (slv >> 15) & 0x01; + const0_value = (slv >> 14) & 0x01; + const1_value = (slv >> 12) & 0x03; + section_length = slv & 0x0FFF; + + // no section, ignore. + if (section_length == 0) { + srs_warn("ts: demux PAT ignore empty section"); + return ret; + } + + if (!stream->require(section_length)) { + ret = ERROR_STREAM_CASTER_TS_PSI; + srs_error("ts: demux PAT section failed. ret=%d", ret); + return ret; + } + + // call the virtual method of actual PSI. + if ((ret = psi_decode(stream)) != ERROR_SUCCESS) { + return ret; + } + + // 4B + if (!stream->require(4)) { + ret = ERROR_STREAM_CASTER_TS_PSI; + srs_error("ts: demux PSI crc32 failed. ret=%d", ret); + return ret; + } + CRC_32 = stream->read_4bytes(); + + // verify crc32. + int32_t crc32 = srs_crc32(ppat, stream->pos() - pat_pos - 4); + if (crc32 != CRC_32) { + ret = ERROR_STREAM_CASTER_TS_CRC32; + srs_error("ts: verify PSI crc32 failed. ret=%d", ret); + return ret; + } + + // consume left stuffings + if (!stream->empty()) { + int nb_stuffings = stream->size() - stream->pos(); + char* stuffing = stream->data() + stream->pos(); + + // all stuffing must be 0xff. + // TODO: FIXME: maybe need to remove the following. + for (int i = 0; i < nb_stuffings; i++) { + if ((u_int8_t)stuffing[i] != 0xff) { + srs_warn("ts: stuff is not 0xff, actual=%#x", stuffing[i]); + break; + } + } + + stream->skip(nb_stuffings); + } + + return ret; +} + +int SrsTsPayloadPSI::size() +{ + int sz = 0; + + // section size is the sl plus the crc32 + section_length = psi_size() + 4; + + sz += packet->payload_unit_start_indicator? 1:0; + sz += 3; + sz += section_length; + + return sz; +} + +int SrsTsPayloadPSI::encode(SrsStream* stream) +{ + int ret = ERROR_SUCCESS; + + if (packet->payload_unit_start_indicator) { + if (!stream->require(1)) { + ret = ERROR_STREAM_CASTER_TS_PSI; + srs_error("ts: mux PSI failed. ret=%d", ret); + return ret; + } + stream->write_1bytes(pointer_field); + } + + // to calc the crc32 + char* ppat = stream->data() + stream->pos(); + int pat_pos = stream->pos(); + + // atleast 3B for all psi. + if (!stream->require(3)) { + ret = ERROR_STREAM_CASTER_TS_PSI; + srs_error("ts: mux PSI failed. ret=%d", ret); + return ret; + } + // 1B + stream->write_1bytes(table_id); + + // 2B + int16_t slv = section_length & 0x0FFF; + slv |= (section_syntax_indicator << 15) & 0x8000; + slv |= (const0_value << 14) & 0x4000; + slv |= (const1_value << 12) & 0x3000; + stream->write_2bytes(slv); + + // no section, ignore. + if (section_length == 0) { + srs_warn("ts: mux PAT ignore empty section"); + return ret; + } + + if (!stream->require(section_length)) { + ret = ERROR_STREAM_CASTER_TS_PSI; + srs_error("ts: mux PAT section failed. ret=%d", ret); + return ret; + } + + // call the virtual method of actual PSI. + if ((ret = psi_encode(stream)) != ERROR_SUCCESS) { + return ret; + } + + // 4B + if (!stream->require(4)) { + ret = ERROR_STREAM_CASTER_TS_PSI; + srs_error("ts: mux PSI crc32 failed. ret=%d", ret); + return ret; + } + CRC_32 = srs_crc32(ppat, stream->pos() - pat_pos); + stream->write_4bytes(CRC_32); + + return ret; +} + +SrsTsPayloadPATProgram::SrsTsPayloadPATProgram(int16_t n, int16_t p) +{ + number = n; + pid = p; + const1_value = 0x07; +} + +SrsTsPayloadPATProgram::~SrsTsPayloadPATProgram() +{ +} + +int SrsTsPayloadPATProgram::decode(SrsStream* stream) +{ + int ret = ERROR_SUCCESS; + + // atleast 4B for PAT program specified + if (!stream->require(4)) { + ret = ERROR_STREAM_CASTER_TS_PAT; + srs_error("ts: demux PAT failed. ret=%d", ret); + return ret; + } + + int tmpv = stream->read_4bytes(); + number = (int16_t)((tmpv >> 16) & 0xFFFF); + const1_value = (int16_t)((tmpv >> 13) & 0x07); + pid = (int16_t)(tmpv & 0x1FFF); + + return ret; +} + +int SrsTsPayloadPATProgram::size() +{ + return 4; +} + +int SrsTsPayloadPATProgram::encode(SrsStream* stream) +{ + int ret = ERROR_SUCCESS; + + // atleast 4B for PAT program specified + if (!stream->require(4)) { + ret = ERROR_STREAM_CASTER_TS_PAT; + srs_error("ts: mux PAT failed. ret=%d", ret); + return ret; + } + + int tmpv = pid & 0x1FFF; + tmpv |= (number << 16) & 0xFFFF0000; + tmpv |= (const1_value << 13) & 0xE000; + stream->write_4bytes(tmpv); + + return ret; +} + +SrsTsPayloadPAT::SrsTsPayloadPAT(SrsTsPacket* p) : SrsTsPayloadPSI(p) +{ + const3_value = 3; +} + +SrsTsPayloadPAT::~SrsTsPayloadPAT() +{ + std::vector::iterator it; + for (it = programs.begin(); it != programs.end(); ++it) { + SrsTsPayloadPATProgram* program = *it; + srs_freep(program); + } + programs.clear(); +} + +int SrsTsPayloadPAT::psi_decode(SrsStream* stream) +{ + int ret = ERROR_SUCCESS; + + // atleast 5B for PAT specified + if (!stream->require(5)) { + ret = ERROR_STREAM_CASTER_TS_PAT; + srs_error("ts: demux PAT failed. ret=%d", ret); + return ret; + } + + int pos = stream->pos(); + + // 2B + transport_stream_id = stream->read_2bytes(); + + // 1B + int8_t cniv = stream->read_1bytes(); + + const3_value = (cniv >> 6) & 0x03; + version_number = (cniv >> 1) & 0x1F; + current_next_indicator = cniv & 0x01; + + // TODO: FIXME: check the indicator. + + // 1B + section_number = stream->read_1bytes(); + // 1B + last_section_number = stream->read_1bytes(); + + // multiple 4B program data. + int program_bytes = section_length - 4 - (stream->pos() - pos); + for (int i = 0; i < program_bytes; i += 4) { + SrsTsPayloadPATProgram* program = new SrsTsPayloadPATProgram(); + + if ((ret = program->decode(stream)) != ERROR_SUCCESS) { + return ret; + } + + // update the apply pid table. + packet->context->set(program->pid, SrsTsPidApplyPMT); + + programs.push_back(program); + } + + // update the apply pid table. + packet->context->set(packet->pid, SrsTsPidApplyPAT); + packet->context->on_pmt_parsed(); + + return ret; +} + +int SrsTsPayloadPAT::psi_size() +{ + int sz = 5; + for (int i = 0; i < (int)programs.size(); i ++) { + SrsTsPayloadPATProgram* program = programs.at(i); + sz += program->size(); + } + return sz; +} + +int SrsTsPayloadPAT::psi_encode(SrsStream* stream) +{ + int ret = ERROR_SUCCESS; + + // atleast 5B for PAT specified + if (!stream->require(5)) { + ret = ERROR_STREAM_CASTER_TS_PAT; + srs_error("ts: mux PAT failed. ret=%d", ret); + return ret; + } + + // 2B + stream->write_2bytes(transport_stream_id); + + // 1B + int8_t cniv = current_next_indicator & 0x01; + cniv |= (version_number << 1) & 0x3E; + cniv |= (const1_value << 6) & 0xC0; + stream->write_1bytes(cniv); + + // 1B + stream->write_1bytes(section_number); + // 1B + stream->write_1bytes(last_section_number); + + // multiple 4B program data. + for (int i = 0; i < (int)programs.size(); i ++) { + SrsTsPayloadPATProgram* program = programs.at(i); + if ((ret = program->encode(stream)) != ERROR_SUCCESS) { + return ret; + } + + // update the apply pid table. + packet->context->set(program->pid, SrsTsPidApplyPMT); + } + + // update the apply pid table. + packet->context->set(packet->pid, SrsTsPidApplyPAT); + + return ret; +} + +SrsTsPayloadPMTESInfo::SrsTsPayloadPMTESInfo(SrsTsStream st, int16_t epid) +{ + stream_type = st; + elementary_PID = epid; + + const1_value0 = 7; + const1_value1 = 0x0f; + ES_info_length = 0; + ES_info = NULL; +} + +SrsTsPayloadPMTESInfo::~SrsTsPayloadPMTESInfo() +{ + srs_freep(ES_info); +} + +int SrsTsPayloadPMTESInfo::decode(SrsStream* stream) +{ + int ret = ERROR_SUCCESS; + + // 5B + if (!stream->require(5)) { + ret = ERROR_STREAM_CASTER_TS_PMT; + srs_error("ts: demux PMT es info failed. ret=%d", ret); + return ret; + } + + stream_type = (SrsTsStream)stream->read_1bytes(); + + int16_t epv = stream->read_2bytes(); + const1_value0 = (epv >> 13) & 0x07; + elementary_PID = epv & 0x1FFF; + + int16_t eilv = stream->read_2bytes(); + const1_value1 = (epv >> 12) & 0x0f; + ES_info_length = eilv & 0x0FFF; + + if (ES_info_length > 0) { + if (!stream->require(ES_info_length)) { + ret = ERROR_STREAM_CASTER_TS_PMT; + srs_error("ts: demux PMT es info data failed. ret=%d", ret); + return ret; + } + srs_freep(ES_info); + ES_info = new char[ES_info_length]; + stream->read_bytes(ES_info, ES_info_length); + } + + return ret; +} + +int SrsTsPayloadPMTESInfo::size() +{ + return 5 + ES_info_length; +} + +int SrsTsPayloadPMTESInfo::encode(SrsStream* stream) +{ + int ret = ERROR_SUCCESS; + + // 5B + if (!stream->require(5)) { + ret = ERROR_STREAM_CASTER_TS_PMT; + srs_error("ts: mux PMT es info failed. ret=%d", ret); + return ret; + } + + stream->write_1bytes(stream_type); + + int16_t epv = elementary_PID & 0x1FFF; + epv |= (const1_value0 << 13) & 0xE000; + stream->write_2bytes(epv); + + int16_t eilv = ES_info_length & 0x0FFF; + eilv |= (const1_value1 << 12) & 0xF000; + stream->write_2bytes(eilv); + + if (ES_info_length > 0) { + if (!stream->require(ES_info_length)) { + ret = ERROR_STREAM_CASTER_TS_PMT; + srs_error("ts: mux PMT es info data failed. ret=%d", ret); + return ret; + } + stream->write_bytes(ES_info, ES_info_length); + } + + return ret; +} + +SrsTsPayloadPMT::SrsTsPayloadPMT(SrsTsPacket* p) : SrsTsPayloadPSI(p) +{ + const1_value0 = 3; + const1_value1 = 7; + const1_value2 = 0x0f; + program_info_length = 0; + program_info_desc = NULL; +} + +SrsTsPayloadPMT::~SrsTsPayloadPMT() +{ + srs_freep(program_info_desc); + + std::vector::iterator it; + for (it = infos.begin(); it != infos.end(); ++it) { + SrsTsPayloadPMTESInfo* info = *it; + srs_freep(info); + } + infos.clear(); +} + +int SrsTsPayloadPMT::psi_decode(SrsStream* stream) +{ + int ret = ERROR_SUCCESS; + + // atleast 9B for PMT specified + if (!stream->require(9)) { + ret = ERROR_STREAM_CASTER_TS_PMT; + srs_error("ts: demux PMT failed. ret=%d", ret); + return ret; + } + + // 2B + program_number = stream->read_2bytes(); + + // 1B + int8_t cniv = stream->read_1bytes(); + + const1_value0 = (cniv >> 6) & 0x03; + version_number = (cniv >> 1) & 0x1F; + current_next_indicator = cniv & 0x01; + + // 1B + section_number = stream->read_1bytes(); + + // 1B + last_section_number = stream->read_1bytes(); + + // 2B + int16_t ppv = stream->read_2bytes(); + const1_value1 = (ppv >> 13) & 0x07; + PCR_PID = ppv & 0x1FFF; + + // 2B + int16_t pilv = stream->read_2bytes(); + const1_value2 = (pilv >> 12) & 0x0F; + program_info_length = pilv & 0xFFF; + + if (program_info_length > 0) { + if (!stream->require(program_info_length)) { + ret = ERROR_STREAM_CASTER_TS_PMT; + srs_error("ts: demux PMT program info failed. ret=%d", ret); + return ret; + } + + srs_freep(program_info_desc); + program_info_desc = new char[program_info_length]; + stream->read_bytes(program_info_desc, program_info_length); + } + + // [section_length] - 4(CRC) - 9B - [program_info_length] + int ES_EOF_pos = stream->pos() + section_length - 4 - 9 - program_info_length; + while (stream->pos() < ES_EOF_pos) { + SrsTsPayloadPMTESInfo* info = new SrsTsPayloadPMTESInfo(); + infos.push_back(info); + + if ((ret = info->decode(stream)) != ERROR_SUCCESS) { + return ret; + } + + // update the apply pid table + switch (info->stream_type) { + case SrsTsStreamVideoH264: + case SrsTsStreamVideoMpeg4: + packet->context->set(info->elementary_PID, SrsTsPidApplyVideo, info->stream_type); + break; + case SrsTsStreamAudioAAC: + case SrsTsStreamAudioAC3: + case SrsTsStreamAudioDTS: + case SrsTsStreamAudioMp3: + packet->context->set(info->elementary_PID, SrsTsPidApplyAudio, info->stream_type); + break; + default: + srs_warn("ts: drop pid=%#x, stream=%#x", info->elementary_PID, info->stream_type); + break; + } + } + + // update the apply pid table. + packet->context->set(packet->pid, SrsTsPidApplyPMT); + + return ret; +} + +int SrsTsPayloadPMT::psi_size() +{ + int sz = 9; + sz += program_info_length; + for (int i = 0; i < (int)infos.size(); i ++) { + SrsTsPayloadPMTESInfo* info = infos.at(i); + sz += info->size(); + } + return sz; +} + +int SrsTsPayloadPMT::psi_encode(SrsStream* stream) +{ + int ret = ERROR_SUCCESS; + + // atleast 9B for PMT specified + if (!stream->require(9)) { + ret = ERROR_STREAM_CASTER_TS_PMT; + srs_error("ts: mux PMT failed. ret=%d", ret); + return ret; + } + + // 2B + stream->write_2bytes(program_number); + + // 1B + int8_t cniv = current_next_indicator & 0x01; + cniv |= (const1_value0 << 6) & 0xC0; + cniv |= (version_number << 1) & 0xFE; + stream->write_1bytes(cniv); + + // 1B + stream->write_1bytes(section_number); + + // 1B + stream->write_1bytes(last_section_number); + + // 2B + int16_t ppv = PCR_PID & 0x1FFF; + ppv |= (const1_value1 << 13) & 0xE000; + stream->write_2bytes(ppv); + + // 2B + int16_t pilv = program_info_length & 0xFFF; + pilv |= (const1_value2 << 12) & 0xF000; + stream->write_2bytes(pilv); + + if (program_info_length > 0) { + if (!stream->require(program_info_length)) { + ret = ERROR_STREAM_CASTER_TS_PMT; + srs_error("ts: mux PMT program info failed. ret=%d", ret); + return ret; + } + + stream->write_bytes(program_info_desc, program_info_length); + } + + for (int i = 0; i < (int)infos.size(); i ++) { + SrsTsPayloadPMTESInfo* info = infos.at(i); + if ((ret = info->encode(stream)) != ERROR_SUCCESS) { + return ret; + } + + // update the apply pid table + switch (info->stream_type) { + case SrsTsStreamVideoH264: + case SrsTsStreamVideoMpeg4: + packet->context->set(info->elementary_PID, SrsTsPidApplyVideo, info->stream_type); + break; + case SrsTsStreamAudioAAC: + case SrsTsStreamAudioAC3: + case SrsTsStreamAudioDTS: + case SrsTsStreamAudioMp3: + packet->context->set(info->elementary_PID, SrsTsPidApplyAudio, info->stream_type); + break; + default: + srs_warn("ts: drop pid=%#x, stream=%#x", info->elementary_PID, info->stream_type); + break; + } + } + + // update the apply pid table. + packet->context->set(packet->pid, SrsTsPidApplyPMT); + + return ret; +} + +SrsTSMuxer::SrsTSMuxer(SrsFileWriter* w, SrsTsContext* c, SrsCodecAudio ac, SrsCodecVideo vc) +{ + writer = w; + context = c; + + acodec = ac; + vcodec = vc; +} + +SrsTSMuxer::~SrsTSMuxer() +{ + close(); +} + +int SrsTSMuxer::open(string _path) +{ + int ret = ERROR_SUCCESS; + + path = _path; + + close(); + + // reset the context for a new ts start. + context->reset(); + + if ((ret = writer->open(path)) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +int SrsTSMuxer::update_acodec(SrsCodecAudio ac) +{ + acodec = ac; + return ERROR_SUCCESS; +} + +int SrsTSMuxer::write_audio(SrsTsMessage* audio) +{ + int ret = ERROR_SUCCESS; + + srs_info("hls: write audio pts=%"PRId64", dts=%"PRId64", size=%d", + audio->pts, audio->dts, audio->PES_packet_length); + + if ((ret = context->encode(writer, audio, vcodec, acodec)) != ERROR_SUCCESS) { + srs_error("hls encode audio failed. ret=%d", ret); + return ret; + } + srs_info("hls encode audio ok"); + + return ret; +} + +int SrsTSMuxer::write_video(SrsTsMessage* video) +{ + int ret = ERROR_SUCCESS; + + srs_info("hls: write video pts=%"PRId64", dts=%"PRId64", size=%d", + video->pts, video->dts, video->PES_packet_length); + + if ((ret = context->encode(writer, video, vcodec, acodec)) != ERROR_SUCCESS) { + srs_error("hls encode video failed. ret=%d", ret); + return ret; + } + srs_info("hls encode video ok"); + + return ret; +} + +void SrsTSMuxer::close() +{ + writer->close(); +} + +SrsTsCache::SrsTsCache() +{ + audio = NULL; + video = NULL; +} + +SrsTsCache::~SrsTsCache() +{ + srs_freep(audio); + srs_freep(video); +} + +int SrsTsCache::cache_audio(SrsAvcAacCodec* codec, int64_t dts, SrsCodecSample* sample) +{ + int ret = ERROR_SUCCESS; + + // create the ts audio message. + if (!audio) { + audio = new SrsTsMessage(); + audio->write_pcr = false; + audio->start_pts = dts; + } + + audio->dts = dts; + audio->pts = audio->dts; + audio->sid = SrsTsPESStreamIdAudioCommon; + + // must be aac or mp3 + SrsCodecAudio acodec = (SrsCodecAudio)codec->audio_codec_id; + srs_assert(acodec == SrsCodecAudioAAC || acodec == SrsCodecAudioMP3); + + // write video to cache. + if (codec->audio_codec_id == SrsCodecAudioAAC) { + if ((ret = do_cache_aac(codec, sample)) != ERROR_SUCCESS) { + return ret; + } + } else { + if ((ret = do_cache_mp3(codec, sample)) != ERROR_SUCCESS) { + return ret; + } + } + + return ret; +} + +int SrsTsCache::cache_video(SrsAvcAacCodec* codec, int64_t dts, SrsCodecSample* sample) +{ + int ret = ERROR_SUCCESS; + + // create the ts video message. + if (!video) { + video = new SrsTsMessage(); + video->write_pcr = sample->frame_type == SrsCodecVideoAVCFrameKeyFrame; + video->start_pts = dts; + } + + video->dts = dts; + video->pts = video->dts + sample->cts * 90; + video->sid = SrsTsPESStreamIdVideoCommon; + + // write video to cache. + if ((ret = do_cache_avc(codec, sample)) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +int SrsTsCache::do_cache_mp3(SrsAvcAacCodec* codec, SrsCodecSample* sample) +{ + int ret = ERROR_SUCCESS; + + // for mp3, directly write to cache. + // TODO: FIXME: implements the ts jitter. + for (int i = 0; i < sample->nb_sample_units; i++) { + SrsCodecSampleUnit* sample_unit = &sample->sample_units[i]; + audio->payload->append(sample_unit->bytes, sample_unit->size); + } + + return ret; +} + +int SrsTsCache::do_cache_aac(SrsAvcAacCodec* codec, SrsCodecSample* sample) +{ + int ret = ERROR_SUCCESS; + + for (int i = 0; i < sample->nb_sample_units; i++) { + SrsCodecSampleUnit* sample_unit = &sample->sample_units[i]; + int32_t size = sample_unit->size; + + if (!sample_unit->bytes || size <= 0 || size > 0x1fff) { + ret = ERROR_HLS_AAC_FRAME_LENGTH; + srs_error("invalid aac frame length=%d, ret=%d", size, ret); + return ret; + } + + // the frame length is the AAC raw data plus the adts header size. + int32_t frame_length = size + 7; + + // AAC-ADTS + // 6.2 Audio Data Transport Stream, ADTS + // in aac-iso-13818-7.pdf, page 26. + // fixed 7bytes header + u_int8_t adts_header[7] = {0xff, 0xf9, 0x00, 0x00, 0x00, 0x0f, 0xfc}; + /* + // adts_fixed_header + // 2B, 16bits + int16_t syncword; //12bits, '1111 1111 1111' + int8_t ID; //1bit, '1' + int8_t layer; //2bits, '00' + int8_t protection_absent; //1bit, can be '1' + // 12bits + int8_t profile; //2bit, 7.1 Profiles, page 40 + TSAacSampleFrequency sampling_frequency_index; //4bits, Table 35, page 46 + int8_t private_bit; //1bit, can be '0' + int8_t channel_configuration; //3bits, Table 8 + int8_t original_or_copy; //1bit, can be '0' + int8_t home; //1bit, can be '0' + + // adts_variable_header + // 28bits + int8_t copyright_identification_bit; //1bit, can be '0' + int8_t copyright_identification_start; //1bit, can be '0' + int16_t frame_length; //13bits + int16_t adts_buffer_fullness; //11bits, 7FF signals that the bitstream is a variable rate bitstream. + int8_t number_of_raw_data_blocks_in_frame; //2bits, 0 indicating 1 raw_data_block() + */ + // profile, 2bits + SrsAacProfile aac_profile = srs_codec_aac_rtmp2ts(codec->aac_object); + adts_header[2] = (aac_profile << 6) & 0xc0; + // sampling_frequency_index 4bits + adts_header[2] |= (codec->aac_sample_rate << 2) & 0x3c; + // channel_configuration 3bits + adts_header[2] |= (codec->aac_channels >> 2) & 0x01; + adts_header[3] = (codec->aac_channels << 6) & 0xc0; + // frame_length 13bits + adts_header[3] |= (frame_length >> 11) & 0x03; + adts_header[4] = (frame_length >> 3) & 0xff; + adts_header[5] = ((frame_length << 5) & 0xe0); + // adts_buffer_fullness; //11bits + adts_header[5] |= 0x1f; + + // copy to audio buffer + audio->payload->append((const char*)adts_header, sizeof(adts_header)); + audio->payload->append(sample_unit->bytes, sample_unit->size); + } + + return ret; +} + +int SrsTsCache::do_cache_avc(SrsAvcAacCodec* codec, SrsCodecSample* sample) +{ + int ret = ERROR_SUCCESS; + + // mux the samples in annexb format, + // H.264-AVC-ISO_IEC_14496-10-2012.pdf, page 324. + /** + * 00 00 00 01 // header + * xxxxxxx // data bytes + * 00 00 01 // continue header + * xxxxxxx // data bytes. + * + * nal_unit_type specifies the type of RBSP data structure contained in the NAL unit as specified in Table 7-1. + * Table 7-1 – NAL unit type codes, syntax element categories, and NAL unit type classes + * H.264-AVC-ISO_IEC_14496-10-2012.pdf, page 83. + * 1, Coded slice of a non-IDR picture slice_layer_without_partitioning_rbsp( ) + * 2, Coded slice data partition A slice_data_partition_a_layer_rbsp( ) + * 3, Coded slice data partition B slice_data_partition_b_layer_rbsp( ) + * 4, Coded slice data partition C slice_data_partition_c_layer_rbsp( ) + * 5, Coded slice of an IDR picture slice_layer_without_partitioning_rbsp( ) + * 6, Supplemental enhancement information (SEI) sei_rbsp( ) + * 7, Sequence parameter set seq_parameter_set_rbsp( ) + * 8, Picture parameter set pic_parameter_set_rbsp( ) + * 9, Access unit delimiter access_unit_delimiter_rbsp( ) + * 10, End of sequence end_of_seq_rbsp( ) + * 11, End of stream end_of_stream_rbsp( ) + * 12, Filler data filler_data_rbsp( ) + * 13, Sequence parameter set extension seq_parameter_set_extension_rbsp( ) + * 14, Prefix NAL unit prefix_nal_unit_rbsp( ) + * 15, Subset sequence parameter set subset_seq_parameter_set_rbsp( ) + * 19, Coded slice of an auxiliary coded picture without partitioning slice_layer_without_partitioning_rbsp( ) + * 20, Coded slice extension slice_layer_extension_rbsp( ) + * the first ts message of apple sample: + * annexb 4B header, 2B aud(nal_unit_type:6)(0x09 0xf0) + * annexb 4B header, 19B sps(nal_unit_type:7) + * annexb 3B header, 4B pps(nal_unit_type:8) + * annexb 3B header, 12B nalu(nal_unit_type:6) + * annexb 3B header, 21B nalu(nal_unit_type:6) + * annexb 3B header, 2762B nalu(nal_unit_type:5) + * annexb 3B header, 3535B nalu(nal_unit_type:5) + * the second ts message of apple ts sample: + * annexb 4B header, 2B aud(nal_unit_type:6)(0x09 0xf0) + * annexb 3B header, 21B nalu(nal_unit_type:6) + * annexb 3B header, 379B nalu(nal_unit_type:1) + * annexb 3B header, 406B nalu(nal_unit_type:1) + */ + static u_int8_t fresh_nalu_header[] = { 0x00, 0x00, 0x00, 0x01 }; + static u_int8_t cont_nalu_header[] = { 0x00, 0x00, 0x01 }; + + // the aud(access unit delimiter) before each frame. + // 7.3.2.4 Access unit delimiter RBSP syntax + // H.264-AVC-ISO_IEC_14496-10-2012.pdf, page 66. + // + // primary_pic_type u(3), the first 3bits, primary_pic_type indicates that the slice_type values + // for all slices of the primary coded picture are members of the set listed in Table 7-5 for + // the given value of primary_pic_type. + // 0, slice_type 2, 7 + // 1, slice_type 0, 2, 5, 7 + // 2, slice_type 0, 1, 2, 5, 6, 7 + // 3, slice_type 4, 9 + // 4, slice_type 3, 4, 8, 9 + // 5, slice_type 2, 4, 7, 9 + // 6, slice_type 0, 2, 3, 4, 5, 7, 8, 9 + // 7, slice_type 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + // 7.4.2.4 Access unit delimiter RBSP semantics + // H.264-AVC-ISO_IEC_14496-10-2012.pdf, page 102. + // + // slice_type specifies the coding type of the slice according to Table 7-6. + // 0, P (P slice) + // 1, B (B slice) + // 2, I (I slice) + // 3, SP (SP slice) + // 4, SI (SI slice) + // 5, P (P slice) + // 6, B (B slice) + // 7, I (I slice) + // 8, SP (SP slice) + // 9, SI (SI slice) + // H.264-AVC-ISO_IEC_14496-10-2012.pdf, page 105. + static u_int8_t aud_nalu_7[] = { 0x09, 0xf0}; + + // always append a aud nalu for each frame. + video->payload->append((const char*)fresh_nalu_header, 4); + video->payload->append((const char*)aud_nalu_7, 2); + + // when ts message(samples) contains IDR, insert sps+pps. + if (sample->has_idr) { + // fresh nalu header before sps. + if (codec->sequenceParameterSetLength > 0) { + // AnnexB prefix, for sps always 4 bytes header + video->payload->append((const char*)fresh_nalu_header, 4); + // sps + video->payload->append(codec->sequenceParameterSetNALUnit, codec->sequenceParameterSetLength); + } + // cont nalu header before pps. + if (codec->pictureParameterSetLength > 0) { + // AnnexB prefix, for pps always 3 bytes header + video->payload->append((const char*)cont_nalu_header, 3); + // pps + video->payload->append(codec->pictureParameterSetNALUnit, codec->pictureParameterSetLength); + } + } + + // all sample use cont nalu header, except the sps-pps before IDR frame. + for (int i = 0; i < sample->nb_sample_units; i++) { + SrsCodecSampleUnit* sample_unit = &sample->sample_units[i]; + int32_t size = sample_unit->size; + + if (!sample_unit->bytes || size <= 0) { + ret = ERROR_HLS_AVC_SAMPLE_SIZE; + srs_error("invalid avc sample length=%d, ret=%d", size, ret); + return ret; + } + + // 5bits, 7.3.1 NAL unit syntax, + // H.264-AVC-ISO_IEC_14496-10-2012.pdf, page 83. + SrsAvcNaluType nal_unit_type = (SrsAvcNaluType)(sample_unit->bytes[0] & 0x1f); + + // ignore SPS/PPS/AUD + switch (nal_unit_type) { + case SrsAvcNaluTypeSPS: + case SrsAvcNaluTypePPS: + case SrsAvcNaluTypeAccessUnitDelimiter: + continue; + default: + break; + } + + // insert cont nalu header before frame. + video->payload->append((const char*)cont_nalu_header, 3); + + // sample data + video->payload->append(sample_unit->bytes, sample_unit->size); + } + + return ret; +} + +SrsTsEncoder::SrsTsEncoder() +{ + _fs = NULL; + codec = new SrsAvcAacCodec(); + sample = new SrsCodecSample(); + cache = new SrsTsCache(); + context = new SrsTsContext(); + muxer = NULL; +} + +SrsTsEncoder::~SrsTsEncoder() +{ + srs_freep(codec); + srs_freep(sample); + srs_freep(cache); + srs_freep(muxer); + srs_freep(context); +} + +int SrsTsEncoder::initialize(SrsFileWriter* fs) +{ + int ret = ERROR_SUCCESS; + + srs_assert(fs); + + if (!fs->is_open()) { + ret = ERROR_KERNEL_FLV_STREAM_CLOSED; + srs_warn("stream is not open for encoder. ret=%d", ret); + return ret; + } + + _fs = fs; + + srs_freep(muxer); + muxer = new SrsTSMuxer(fs, context, SrsCodecAudioAAC, SrsCodecVideoAVC); + + if ((ret = muxer->open("")) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +int SrsTsEncoder::write_audio(int64_t timestamp, char* data, int size) +{ + int ret = ERROR_SUCCESS; + + sample->clear(); + if ((ret = codec->audio_aac_demux(data, size, sample)) != ERROR_SUCCESS) { + if (ret != ERROR_HLS_TRY_MP3) { + srs_error("http: ts aac demux audio failed. ret=%d", ret); + return ret; + } + if ((ret = codec->audio_mp3_demux(data, size, sample)) != ERROR_SUCCESS) { + srs_error("http: ts mp3 demux audio failed. ret=%d", ret); + return ret; + } + } + SrsCodecAudio acodec = (SrsCodecAudio)codec->audio_codec_id; + + // ts support audio codec: aac/mp3 + if (acodec != SrsCodecAudioAAC && acodec != SrsCodecAudioMP3) { + return ret; + } + + // when codec changed, write new header. + if ((ret = muxer->update_acodec(acodec)) != ERROR_SUCCESS) { + srs_error("http: ts audio write header failed. ret=%d", ret); + return ret; + } + + // for aac: ignore sequence header + if (acodec == SrsCodecAudioAAC && sample->aac_packet_type == SrsCodecAudioTypeSequenceHeader) { + return ret; + } + + // the dts calc from rtmp/flv header. + // @remark for http ts stream, the timestamp is always monotonically increase, + // for the packet is filtered by consumer. + int64_t dts = timestamp * 90; + + // write audio to cache. + if ((ret = cache->cache_audio(codec, dts, sample)) != ERROR_SUCCESS) { + return ret; + } + + // flush if buffer exceed max size. + if (cache->audio->payload->length() > SRS_AUTO_HLS_AUDIO_CACHE_SIZE) { + return flush_video(); + } + + // TODO: config it. + // in ms, audio delay to flush the audios. + int64_t audio_delay = SRS_CONF_DEFAULT_AAC_DELAY; + // flush if audio delay exceed + if (dts - cache->audio->start_pts > audio_delay * 90) { + return flush_audio(); + } + + return ret; +} + +int SrsTsEncoder::write_video(int64_t timestamp, char* data, int size) +{ + int ret = ERROR_SUCCESS; + + sample->clear(); + if ((ret = codec->video_avc_demux(data, size, sample)) != ERROR_SUCCESS) { + srs_error("http: ts codec demux video failed. ret=%d", ret); + return ret; + } + + // ignore info frame, + // @see https://github.com/simple-rtmp-server/srs/issues/288#issuecomment-69863909 + if (sample->frame_type == SrsCodecVideoAVCFrameVideoInfoFrame) { + return ret; + } + + if (codec->video_codec_id != SrsCodecVideoAVC) { + return ret; + } + + // ignore sequence header + if (sample->frame_type == SrsCodecVideoAVCFrameKeyFrame + && sample->avc_packet_type == SrsCodecVideoAVCTypeSequenceHeader) { + return ret; + } + + int64_t dts = timestamp * 90; + + // write video to cache. + if ((ret = cache->cache_video(codec, dts, sample)) != ERROR_SUCCESS) { + return ret; + } + + return flush_video(); +} + +int SrsTsEncoder::flush_audio() +{ + int ret = ERROR_SUCCESS; + + if ((ret = muxer->write_audio(cache->audio)) != ERROR_SUCCESS) { + return ret; + } + + // write success, clear and free the ts message. + srs_freep(cache->audio); + + return ret; +} + +int SrsTsEncoder::flush_video() +{ + int ret = ERROR_SUCCESS; + + if ((ret = muxer->write_video(cache->video)) != ERROR_SUCCESS) { + return ret; + } + + // write success, clear and free the ts message. + srs_freep(cache->video); + + return ret; +} + + diff --git a/trunk/src/kernel/srs_kernel_ts.hpp b/trunk/src/kernel/srs_kernel_ts.hpp new file mode 100644 index 0000000000..4f4bda647d --- /dev/null +++ b/trunk/src/kernel/srs_kernel_ts.hpp @@ -0,0 +1,1656 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef SRS_KERNEL_TS_HPP +#define SRS_KERNEL_TS_HPP + +/* +#include +*/ +#include + +#include +#include +#include + +#include + +class SrsStream; +class SrsTsCache; +class SrsTSMuxer; +class SrsFileWriter; +class SrsFileReader; +class SrsAvcAacCodec; +class SrsCodecSample; +class SrsSimpleBuffer; +class SrsTsAdaptationField; +class SrsTsPayload; +class SrsTsMessage; +class SrsTsPacket; +class SrsTsContext; + +// Transport Stream packets are 188 bytes in length. +#define SRS_TS_PACKET_SIZE 188 + +/** +* the pid of ts packet, +* Table 2-3 - PID table, hls-mpeg-ts-iso13818-1.pdf, page 37 +* NOTE - The transport packets with PID values 0x0000, 0x0001, and 0x0010-0x1FFE are allowed to carry a PCR. +*/ +enum SrsTsPid +{ + // Program Association Table(see Table 2-25). + SrsTsPidPAT = 0x00, + // Conditional Access Table (see Table 2-27). + SrsTsPidCAT = 0x01, + // Transport Stream Description Table + SrsTsPidTSDT = 0x02, + // Reserved + SrsTsPidReservedStart = 0x03, + SrsTsPidReservedEnd = 0x0f, + // May be assigned as network_PID, Program_map_PID, elementary_PID, or for other purposes + SrsTsPidAppStart = 0x10, + SrsTsPidAppEnd = 0x1ffe, + // null packets (see Table 2-3) + SrsTsPidNULL = 0x01FFF, +}; + +/** +* the transport_scrambling_control of ts packet, +* Table 2-4 - Scrambling control values, hls-mpeg-ts-iso13818-1.pdf, page 38 +*/ +enum SrsTsScrambled +{ + // Not scrambled + SrsTsScrambledDisabled = 0x00, + // User-defined + SrsTsScrambledUserDefined1 = 0x01, + // User-defined + SrsTsScrambledUserDefined2 = 0x02, + // User-defined + SrsTsScrambledUserDefined3 = 0x03, +}; + +/** +* the adaption_field_control of ts packet, +* Table 2-5 - Adaptation field control values, hls-mpeg-ts-iso13818-1.pdf, page 38 +*/ +enum SrsTsAdaptationFieldType +{ + // Reserved for future use by ISO/IEC + SrsTsAdaptationFieldTypeReserved = 0x00, + // No adaptation_field, payload only + SrsTsAdaptationFieldTypePayloadOnly = 0x01, + // Adaptation_field only, no payload + SrsTsAdaptationFieldTypeAdaptionOnly = 0x02, + // Adaptation_field followed by payload + SrsTsAdaptationFieldTypeBoth = 0x03, +}; + +/** +* the actually parsed ts pid, +* @see SrsTsPid, some pid, for example, PMT/Video/Audio is specified by PAT or other tables. +*/ +enum SrsTsPidApply +{ + SrsTsPidApplyReserved = 0, // TSPidTypeReserved, nothing parsed, used reserved. + + SrsTsPidApplyPAT, // Program associtate table + SrsTsPidApplyPMT, // Program map table. + + SrsTsPidApplyVideo, // for video + SrsTsPidApplyAudio, // vor audio +}; + +/** +* Table 2-29 - Stream type assignments +*/ +enum SrsTsStream +{ + // ITU-T | ISO/IEC Reserved + SrsTsStreamReserved = 0x00, + // ISO/IEC 11172 Video + // ITU-T Rec. H.262 | ISO/IEC 13818-2 Video or ISO/IEC 11172-2 constrained parameter video stream + // ISO/IEC 11172 Audio + // ISO/IEC 13818-3 Audio + SrsTsStreamAudioMp3 = 0x04, + // ITU-T Rec. H.222.0 | ISO/IEC 13818-1 private_sections + // ITU-T Rec. H.222.0 | ISO/IEC 13818-1 PES packets containing private data + // ISO/IEC 13522 MHEG + // ITU-T Rec. H.222.0 | ISO/IEC 13818-1 Annex A DSM-CC + // ITU-T Rec. H.222.1 + // ISO/IEC 13818-6 type A + // ISO/IEC 13818-6 type B + // ISO/IEC 13818-6 type C + // ISO/IEC 13818-6 type D + // ITU-T Rec. H.222.0 | ISO/IEC 13818-1 auxiliary + // ISO/IEC 13818-7 Audio with ADTS transport syntax + SrsTsStreamAudioAAC = 0x0f, + // ISO/IEC 14496-2 Visual + SrsTsStreamVideoMpeg4 = 0x10, + // ISO/IEC 14496-3 Audio with the LATM transport syntax as defined in ISO/IEC 14496-3 / AMD 1 + SrsTsStreamAudioMpeg4 = 0x11, + // ISO/IEC 14496-1 SL-packetized stream or FlexMux stream carried in PES packets + // ISO/IEC 14496-1 SL-packetized stream or FlexMux stream carried in ISO/IEC14496_sections. + // ISO/IEC 13818-6 Synchronized Download Protocol + // ITU-T Rec. H.222.0 | ISO/IEC 13818-1 Reserved + // 0x15-0x7F + SrsTsStreamVideoH264 = 0x1b, + // User Private + // 0x80-0xFF + SrsTsStreamAudioAC3 = 0x81, + SrsTsStreamAudioDTS = 0x8a, +}; +std::string srs_ts_stream2string(SrsTsStream stream); + +/** +* the ts channel. +*/ +struct SrsTsChannel +{ + int pid; + SrsTsPidApply apply; + SrsTsStream stream; + SrsTsMessage* msg; + SrsTsContext* context; + // for encoder. + u_int8_t continuity_counter; + + SrsTsChannel(); + virtual ~SrsTsChannel(); +}; + +/** +* the stream_id of PES payload of ts packet. +* Table 2-18 – Stream_id assignments, hls-mpeg-ts-iso13818-1.pdf, page 52. +*/ +enum SrsTsPESStreamId +{ + // program_stream_map + SrsTsPESStreamIdProgramStreamMap = 0xbc, // 0b10111100 + // private_stream_1 + SrsTsPESStreamIdPrivateStream1 = 0xbd, // 0b10111101 + // padding_stream + SrsTsPESStreamIdPaddingStream = 0xbe, // 0b10111110 + // private_stream_2 + SrsTsPESStreamIdPrivateStream2 = 0xbf, // 0b10111111 + + // 110x xxxx + // ISO/IEC 13818-3 or ISO/IEC 11172-3 or ISO/IEC 13818-7 or ISO/IEC + // 14496-3 audio stream number x xxxx + // ((sid >> 5) & 0x07) == SrsTsPESStreamIdAudio + // @remark, use SrsTsPESStreamIdAudioCommon as actually audio, SrsTsPESStreamIdAudio to check whether audio. + SrsTsPESStreamIdAudioChecker = 0x06, // 0b110 + SrsTsPESStreamIdAudioCommon = 0xc0, + + // 1110 xxxx + // ITU-T Rec. H.262 | ISO/IEC 13818-2 or ISO/IEC 11172-2 or ISO/IEC + // 14496-2 video stream number xxxx + // ((stream_id >> 4) & 0x0f) == SrsTsPESStreamIdVideo + // @remark, use SrsTsPESStreamIdVideoCommon as actually video, SrsTsPESStreamIdVideo to check whether video. + SrsTsPESStreamIdVideoChecker = 0x0e, // 0b1110 + SrsTsPESStreamIdVideoCommon = 0xe0, + + // ECM_stream + SrsTsPESStreamIdEcmStream = 0xf0, // 0b11110000 + // EMM_stream + SrsTsPESStreamIdEmmStream = 0xf1, // 0b11110001 + // DSMCC_stream + SrsTsPESStreamIdDsmccStream = 0xf2, // 0b11110010 + // 13522_stream + SrsTsPESStreamId13522Stream = 0xf3, // 0b11110011 + // H_222_1_type_A + SrsTsPESStreamIdH2221TypeA = 0xf4, // 0b11110100 + // H_222_1_type_B + SrsTsPESStreamIdH2221TypeB = 0xf5, // 0b11110101 + // H_222_1_type_C + SrsTsPESStreamIdH2221TypeC = 0xf6, // 0b11110110 + // H_222_1_type_D + SrsTsPESStreamIdH2221TypeD = 0xf7, // 0b11110111 + // H_222_1_type_E + SrsTsPESStreamIdH2221TypeE = 0xf8, // 0b11111000 + // ancillary_stream + SrsTsPESStreamIdAncillaryStream = 0xf9, // 0b11111001 + // SL_packetized_stream + SrsTsPESStreamIdSlPacketizedStream = 0xfa, // 0b11111010 + // FlexMux_stream + SrsTsPESStreamIdFlexMuxStream = 0xfb, // 0b11111011 + // reserved data stream + // 1111 1100 … 1111 1110 + // program_stream_directory + SrsTsPESStreamIdProgramStreamDirectory = 0xff, // 0b11111111 +}; + +/** +* the media audio/video message parsed from PES packet. +*/ +class SrsTsMessage +{ +public: + // decoder only, + // the ts messgae does not use them, + // for user to get the channel and packet. + SrsTsChannel* channel; + SrsTsPacket* packet; +public: + // the audio cache buffer start pts, to flush audio if full. + // @remark the pts is not the adjust one, it's the orignal pts. + int64_t start_pts; + // whether this message with pcr info, + // generally, the video IDR(I frame, the keyframe of h.264) carray the pcr info. + bool write_pcr; + // whether got discontinuity ts, for example, sequence header changed. + bool is_discontinuity; +public: + // the timestamp in 90khz + int64_t dts; + int64_t pts; + // the id of pes stream to indicates the payload codec. + // @remark use is_audio() and is_video() to check it, and stream_number() to finger it out. + SrsTsPESStreamId sid; + // the size of payload, 0 indicates the length() of payload. + u_int16_t PES_packet_length; + // the chunk id. + u_int8_t continuity_counter; + // the payload bytes. + SrsSimpleBuffer* payload; +public: + SrsTsMessage(SrsTsChannel* c = NULL, SrsTsPacket* p = NULL); + virtual ~SrsTsMessage(); +// decoder +public: + /** + * dumps all bytes in stream to ts message. + */ + virtual int dump(SrsStream* stream, int* pnb_bytes); + /** + * whether ts message is completed to reap. + * @param payload_unit_start_indicator whether new ts message start. + * PES_packet_length is 0, the payload_unit_start_indicator=1 to reap ts message. + * PES_packet_length > 0, the payload.length() == PES_packet_length to reap ts message. + * @remark when PES_packet_length>0, the payload_unit_start_indicator should never be 1 when not completed. + * @remark when fresh, the payload_unit_start_indicator should be 1. + */ + virtual bool completed(int8_t payload_unit_start_indicator); + /** + * whether the message is fresh. + */ + virtual bool fresh(); +public: + /** + * whether the sid indicates the elementary stream audio. + */ + virtual bool is_audio(); + /** + * whether the sid indicates the elementary stream video. + */ + virtual bool is_video(); + /** + * when audio or video, get the stream number which specifies the format of stream. + * @return the stream number for audio/video; otherwise, -1. + */ + virtual int stream_number(); +public: + /** + * detach the ts message, + * for user maybe need to parse the message by queue. + * @remark we always use the payload of original message. + */ + virtual SrsTsMessage* detach(); +}; + +/** +* the ts message handler. +*/ +class ISrsTsHandler +{ +public: + ISrsTsHandler(); + virtual ~ISrsTsHandler(); +public: + /** + * when ts context got message, use handler to process it. + * @param msg the ts msg, user should never free it. + * @return an int error code. + */ + virtual int on_ts_message(SrsTsMessage* msg) = 0; +}; + +/** +* the context of ts, to decode the ts stream. +*/ +class SrsTsContext +{ +// codec +private: + std::map pids; + bool pure_audio; +// encoder +private: + // when any codec changed, write the PAT/PMT. + SrsCodecVideo vcodec; + SrsCodecAudio acodec; +public: + SrsTsContext(); + virtual ~SrsTsContext(); +public: + /** + * whether the hls stream is pure audio stream. + */ + virtual bool is_pure_audio(); + /** + * when PMT table parsed, we know some info about stream. + */ + virtual void on_pmt_parsed(); + /** + * reset the context for a new ts segment start. + */ + virtual void reset(); +// codec +public: + /** + * get the pid apply, the parsed pid. + * @return the apply channel; NULL for invalid. + */ + virtual SrsTsChannel* get(int pid); + /** + * set the pid apply, the parsed pid. + */ + virtual void set(int pid, SrsTsPidApply apply_pid, SrsTsStream stream = SrsTsStreamReserved); +// decode methods +public: + /** + * the stream contains only one ts packet. + * @param handler the ts message handler to process the msg. + * @remark we will consume all bytes in stream. + */ + virtual int decode(SrsStream* stream, ISrsTsHandler* handler); +// encode methods +public: + /** + * write the PES packet, the video/audio stream. + * @param msg the video/audio msg to write to ts. + * @param vc the video codec, write the PAT/PMT table when changed. + * @param ac the audio codec, write the PAT/PMT table when changed. + */ + virtual int encode(SrsFileWriter* writer, SrsTsMessage* msg, SrsCodecVideo vc, SrsCodecAudio ac); +private: + virtual int encode_pat_pmt(SrsFileWriter* writer, int16_t vpid, SrsTsStream vs, int16_t apid, SrsTsStream as); + virtual int encode_pes(SrsFileWriter* writer, SrsTsMessage* msg, int16_t pid, SrsTsStream sid, bool pure_audio); +}; + +/** +* the packet in ts stream, +* 2.4.3.2 Transport Stream packet layer, hls-mpeg-ts-iso13818-1.pdf, page 36 +* Transport Stream packets shall be 188 bytes long. +*/ +class SrsTsPacket +{ +public: + // 1B + /** + * The sync_byte is a fixed 8-bit field whose value is '0100 0111' (0x47). Sync_byte emulation in the choice of + * values for other regularly occurring fields, such as PID, should be avoided. + */ + int8_t sync_byte; //8bits + + // 2B + /** + * The transport_error_indicator is a 1-bit flag. When set to '1' it indicates that at least + * 1 uncorrectable bit error exists in the associated Transport Stream packet. This bit may be set to '1' by entities external to + * the transport layer. When set to '1' this bit shall not be reset to '0' unless the bit value(s) in error have been corrected. + */ + int8_t transport_error_indicator; //1bit + /** + * The payload_unit_start_indicator is a 1-bit flag which has normative meaning for + * Transport Stream packets that carry PES packets (refer to 2.4.3.6) or PSI data (refer to 2.4.4). + * + * When the payload of the Transport Stream packet contains PES packet data, the payload_unit_start_indicator has the + * following significance: a '1' indicates that the payload of this Transport Stream packet will commence(start) with the first byte + * of a PES packet and a '0' indicates no PES packet shall start in this Transport Stream packet. If the + * payload_unit_start_indicator is set to '1', then one and only one PES packet starts in this Transport Stream packet. This + * also applies to private streams of stream_type 6 (refer to Table 2-29). + * + * When the payload of the Transport Stream packet contains PSI data, the payload_unit_start_indicator has the following + * significance: if the Transport Stream packet carries the first byte of a PSI section, the payload_unit_start_indicator value + * shall be '1', indicating that the first byte of the payload of this Transport Stream packet carries the pointer_field. If the + * Transport Stream packet does not carry the first byte of a PSI section, the payload_unit_start_indicator value shall be '0', + * indicating that there is no pointer_field in the payload. Refer to 2.4.4.1 and 2.4.4.2. This also applies to private streams of + * stream_type 5 (refer to Table 2-29). + * + * For null packets the payload_unit_start_indicator shall be set to '0'. + * + * The meaning of this bit for Transport Stream packets carrying only private data is not defined in this Specification. + */ + int8_t payload_unit_start_indicator; //1bit + /** + * The transport_priority is a 1-bit indicator. When set to '1' it indicates that the associated packet is + * of greater priority than other packets having the same PID which do not have the bit set to '1'. The transport mechanism + * can use this to prioritize its data within an elementary stream. Depending on the application the transport_priority field + * may be coded regardless of the PID or within one PID only. This field may be changed by channel specific encoders or + * decoders. + */ + int8_t transport_priority; //1bit + /** + * The PID is a 13-bit field, indicating the type of the data stored in the packet payload. PID value 0x0000 is + * reserved for the Program Association Table (see Table 2-25). PID value 0x0001 is reserved for the Conditional Access + * Table (see Table 2-27). PID values 0x0002 - 0x000F are reserved. PID value 0x1FFF is reserved for null packets (see + * Table 2-3). + */ + SrsTsPid pid; //13bits + + // 1B + /** + * This 2-bit field indicates the scrambling mode of the Transport Stream packet payload. + * The Transport Stream packet header, and the adaptation field when present, shall not be scrambled. In the case of a null + * packet the value of the transport_scrambling_control field shall be set to '00' (see Table 2-4). + */ + SrsTsScrambled transport_scrambling_control; //2bits + /** + * This 2-bit field indicates whether this Transport Stream packet header is followed by an + * adaptation field and/or payload (see Table 2-5). + * + * ITU-T Rec. H.222.0 | ISO/IEC 13818-1 decoders shall discard Transport Stream packets with the + * adaptation_field_control field set to a value of '00'. In the case of a null packet the value of the adaptation_field_control + * shall be set to '01'. + */ + SrsTsAdaptationFieldType adaption_field_control; //2bits + /** + * The continuity_counter is a 4-bit field incrementing with each Transport Stream packet with the + * same PID. The continuity_counter wraps around to 0 after its maximum value. The continuity_counter shall not be + * incremented when the adaptation_field_control of the packet equals '00'(reseverd) or '10'(adaptation field only). + * + * In Transport Streams, duplicate packets may be sent as two, and only two, consecutive Transport Stream packets of the + * same PID. The duplicate packets shall have the same continuity_counter value as the original packet and the + * adaptation_field_control field shall be equal to '01'(payload only) or '11'(both). In duplicate packets each byte of the original packet shall be + * duplicated, with the exception that in the program clock reference fields, if present, a valid value shall be encoded. + * + * The continuity_counter in a particular Transport Stream packet is continuous when it differs by a positive value of one + * from the continuity_counter value in the previous Transport Stream packet of the same PID, or when either of the nonincrementing + * conditions (adaptation_field_control set to '00' or '10', or duplicate packets as described above) are met. + * The continuity counter may be discontinuous when the discontinuity_indicator is set to '1' (refer to 2.4.3.4). In the case of + * a null packet the value of the continuity_counter is undefined. + */ + u_int8_t continuity_counter; //4bits +private: + SrsTsAdaptationField* adaptation_field; + SrsTsPayload* payload; +public: + SrsTsContext* context; +public: + SrsTsPacket(SrsTsContext* c); + virtual ~SrsTsPacket(); +public: + virtual int decode(SrsStream* stream, SrsTsMessage** ppmsg); +public: + virtual int size(); + virtual int encode(SrsStream* stream); + virtual void padding(int nb_stuffings); +public: + static SrsTsPacket* create_pat(SrsTsContext* context, + int16_t pmt_number, int16_t pmt_pid + ); + static SrsTsPacket* create_pmt(SrsTsContext* context, + int16_t pmt_number, int16_t pmt_pid, int16_t vpid, SrsTsStream vs, + int16_t apid, SrsTsStream as + ); + static SrsTsPacket* create_pes_first(SrsTsContext* context, + int16_t pid, SrsTsPESStreamId sid, u_int8_t continuity_counter, bool discontinuity, + int64_t pcr, int64_t dts, int64_t pts, int size + ); + static SrsTsPacket* create_pes_continue(SrsTsContext* context, + int16_t pid, SrsTsPESStreamId sid, u_int8_t continuity_counter + ); +}; + +/** +* the adaption field of ts packet. +* 2.4.3.5 Semantic definition of fields in adaptation field, hls-mpeg-ts-iso13818-1.pdf, page 39 +* Table 2-6 - Transport Stream adaptation field, hls-mpeg-ts-iso13818-1.pdf, page 40 +*/ +class SrsTsAdaptationField +{ +public: + // 1B + /** + * The adaptation_field_length is an 8-bit field specifying the number of bytes in the + * adaptation_field immediately following the adaptation_field_length. The value 0 is for inserting a single stuffing byte in + * a Transport Stream packet. When the adaptation_field_control value is '11', the value of the adaptation_field_length shall + * be in the range 0 to 182. When the adaptation_field_control value is '10', the value of the adaptation_field_length shall + * be 183. For Transport Stream packets carrying PES packets, stuffing is needed when there is insufficient PES packet data + * to completely fill the Transport Stream packet payload bytes. Stuffing is accomplished by defining an adaptation field + * longer than the sum of the lengths of the data elements in it, so that the payload bytes remaining after the adaptation field + * exactly accommodates the available PES packet data. The extra space in the adaptation field is filled with stuffing bytes. + * + * This is the only method of stuffing allowed for Transport Stream packets carrying PES packets. For Transport Stream + * packets carrying PSI, an alternative stuffing method is described in 2.4.4. + */ + u_int8_t adaption_field_length; //8bits + // 1B + /** + * This is a 1-bit field which when set to '1' indicates that the discontinuity state is true for the + * current Transport Stream packet. When the discontinuity_indicator is set to '0' or is not present, the discontinuity state is + * false. The discontinuity indicator is used to indicate two types of discontinuities, system time-base discontinuities and + * continuity_counter discontinuities. + * + * A system time-base discontinuity is indicated by the use of the discontinuity_indicator in Transport Stream packets of a + * PID designated as a PCR_PID (refer to 2.4.4.9). When the discontinuity state is true for a Transport Stream packet of a + * PID designated as a PCR_PID, the next PCR in a Transport Stream packet with that same PID represents a sample of a + * new system time clock for the associated program. The system time-base discontinuity point is defined to be the instant + * in time when the first byte of a packet containing a PCR of a new system time-base arrives at the input of the T-STD. + * The discontinuity_indicator shall be set to '1' in the packet in which the system time-base discontinuity occurs. The + * discontinuity_indicator bit may also be set to '1' in Transport Stream packets of the same PCR_PID prior to the packet + * which contains the new system time-base PCR. In this case, once the discontinuity_indicator has been set to '1', it shall + * continue to be set to '1' in all Transport Stream packets of the same PCR_PID up to and including the Transport Stream + * packet which contains the first PCR of the new system time-base. After the occurrence of a system time-base + * discontinuity, no fewer than two PCRs for the new system time-base shall be received before another system time-base + * discontinuity can occur. Further, except when trick mode status is true, data from no more than two system time-bases + * shall be present in the set of T-STD buffers for one program at any time. + * + * Prior to the occurrence of a system time-base discontinuity, the first byte of a Transport Stream packet which contains a + * PTS or DTS which refers to the new system time-base shall not arrive at the input of the T-STD. After the occurrence of + * a system time-base discontinuity, the first byte of a Transport Stream packet which contains a PTS or DTS which refers + * to the previous system time-base shall not arrive at the input of the T-STD. + * + * A continuity_counter discontinuity is indicated by the use of the discontinuity_indicator in any Transport Stream packet. + * When the discontinuity state is true in any Transport Stream packet of a PID not designated as a PCR_PID, the + * continuity_counter in that packet may be discontinuous with respect to the previous Transport Stream packet of the same + * PID. When the discontinuity state is true in a Transport Stream packet of a PID that is designated as a PCR_PID, the + * continuity_counter may only be discontinuous in the packet in which a system time-base discontinuity occurs. A + * continuity counter discontinuity point occurs when the discontinuity state is true in a Transport Stream packet and the + * continuity_counter in the same packet is discontinuous with respect to the previous Transport Stream packet of the same + * PID. A continuity counter discontinuity point shall occur at most one time from the initiation of the discontinuity state + * until the conclusion of the discontinuity state. Furthermore, for all PIDs that are not designated as PCR_PIDs, when the + * discontinuity_indicator is set to '1' in a packet of a specific PID, the discontinuity_indicator may be set to '1' in the next + * Transport Stream packet of that same PID, but shall not be set to '1' in three consecutive Transport Stream packet of that + * same PID. + * + * For the purpose of this clause, an elementary stream access point is defined as follows: + * Video - The first byte of a video sequence header. + * Audio - The first byte of an audio frame. + * + * After a continuity counter discontinuity in a Transport packet which is designated as containing elementary stream data, + * the first byte of elementary stream data in a Transport Stream packet of the same PID shall be the first byte of an + * elementary stream access point or in the case of video, the first byte of an elementary stream access point or a + * sequence_end_code followed by an access point. Each Transport Stream packet which contains elementary stream data + * with a PID not designated as a PCR_PID, and in which a continuity counter discontinuity point occurs, and in which a + * PTS or DTS occurs, shall arrive at the input of the T-STD after the system time-base discontinuity for the associated + * program occurs. In the case where the discontinuity state is true, if two consecutive Transport Stream packets of the same + * PID occur which have the same continuity_counter value and have adaptation_field_control values set to '01' or '11', the + * second packet may be discarded. A Transport Stream shall not be constructed in such a way that discarding such a packet + * will cause the loss of PES packet payload data or PSI data. + * + * After the occurrence of a discontinuity_indicator set to '1' in a Transport Stream packet which contains PSI information, + * a single discontinuity in the version_number of PSI sections may occur. At the occurrence of such a discontinuity, a + * version of the TS_program_map_sections of the appropriate program shall be sent with section_length = = 13 and the + * current_next_indicator = = 1, such that there are no program_descriptors and no elementary streams described. This shall + * then be followed by a version of the TS_program_map_section for each affected program with the version_number + * incremented by one and the current_next_indicator = = 1, containing a complete program definition. This indicates a + * version change in PSI data. + */ + int8_t discontinuity_indicator; //1bit + /** + * The random_access_indicator is a 1-bit field that indicates that the current Transport + * Stream packet, and possibly subsequent Transport Stream packets with the same PID, contain some information to aid + * random access at this point. Specifically, when the bit is set to '1', the next PES packet to start in the payload of Transport + * Stream packets with the current PID shall contain the first byte of a video sequence header if the PES stream type (refer + * to Table 2-29) is 1 or 2, or shall contain the first byte of an audio frame if the PES stream type is 3 or 4. In addition, in + * the case of video, a presentation timestamp shall be present in the PES packet containing the first picture following the + * sequence header. In the case of audio, the presentation timestamp shall be present in the PES packet containing the first + * byte of the audio frame. In the PCR_PID the random_access_indicator may only be set to '1' in Transport Stream packet + * containing the PCR fields. + */ + int8_t random_access_indicator; //1bit + /** + * The elementary_stream_priority_indicator is a 1-bit field. It indicates, among + * packets with the same PID, the priority of the elementary stream data carried within the payload of this Transport Stream + * packet. A '1' indicates that the payload has a higher priority than the payloads of other Transport Stream packets. In the + * case of video, this field may be set to '1' only if the payload contains one or more bytes from an intra-coded slice. A + * value of '0' indicates that the payload has the same priority as all other packets which do not have this bit set to '1'. + */ + int8_t elementary_stream_priority_indicator; //1bit + /** + * The PCR_flag is a 1-bit flag. A value of '1' indicates that the adaptation_field contains a PCR field coded in + * two parts. A value of '0' indicates that the adaptation field does not contain any PCR field. + */ + int8_t PCR_flag; //1bit + /** + * The OPCR_flag is a 1-bit flag. A value of '1' indicates that the adaptation_field contains an OPCR field + * coded in two parts. A value of '0' indicates that the adaptation field does not contain any OPCR field. + */ + int8_t OPCR_flag; //1bit + /** + * The splicing_point_flag is a 1-bit flag. When set to '1', it indicates that a splice_countdown field + * shall be present in the associated adaptation field, specifying the occurrence of a splicing point. A value of '0' indicates + * that a splice_countdown field is not present in the adaptation field. + */ + int8_t splicing_point_flag; //1bit + /** + * The transport_private_data_flag is a 1-bit flag. A value of '1' indicates that the + * adaptation field contains one or more private_data bytes. A value of '0' indicates the adaptation field does not contain any + * private_data bytes. + */ + int8_t transport_private_data_flag; //1bit + /** + * The adaptation_field_extension_flag is a 1-bit field which when set to '1' indicates + * the presence of an adaptation field extension. A value of '0' indicates that an adaptation field extension is not present in + * the adaptation field. + */ + int8_t adaptation_field_extension_flag; //1bit + + // if PCR_flag, 6B + /** + * The program_clock_reference (PCR) is a + * 42-bit field coded in two parts. The first part, program_clock_reference_base, is a 33-bit field whose value is given by + * PCR_base(i), as given in equation 2-2. The second part, program_clock_reference_extension, is a 9-bit field whose value + * is given by PCR_ext(i), as given in equation 2-3. The PCR indicates the intended time of arrival of the byte containing + * the last bit of the program_clock_reference_base at the input of the system target decoder. + */ + int64_t program_clock_reference_base; //33bits + /** + * 6bits reserved, must be '1' + */ + int8_t const1_value0; // 6bits + int16_t program_clock_reference_extension; //9bits + + // if OPCR_flag, 6B + /** + * The optional original + * program reference (OPCR) is a 42-bit field coded in two parts. These two parts, the base and the extension, are coded + * identically to the two corresponding parts of the PCR field. The presence of the OPCR is indicated by the OPCR_flag. + * The OPCR field shall be coded only in Transport Stream packets in which the PCR field is present. OPCRs are permitted + * in both single program and multiple program Transport Streams. + * + * OPCR assists in the reconstruction of a single program Transport Stream from another Transport Stream. When + * reconstructing the original single program Transport Stream, the OPCR may be copied to the PCR field. The resulting + * PCR value is valid only if the original single program Transport Stream is reconstructed exactly in its entirety. This + * would include at least any PSI and private data packets which were present in the original Transport Stream and would + * possibly require other private arrangements. It also means that the OPCR must be an identical copy of its associated PCR + * in the original single program Transport Stream. + */ + int64_t original_program_clock_reference_base; //33bits + /** + * 6bits reserved, must be '1' + */ + int8_t const1_value2; // 6bits + int16_t original_program_clock_reference_extension; //9bits + + // if splicing_point_flag, 1B + /** + * The splice_countdown is an 8-bit field, representing a value which may be positive or negative. A + * positive value specifies the remaining number of Transport Stream packets, of the same PID, following the associated + * Transport Stream packet until a splicing point is reached. Duplicate Transport Stream packets and Transport Stream + * packets which only contain adaptation fields are excluded. The splicing point is located immediately after the last byte of + * the Transport Stream packet in which the associated splice_countdown field reaches zero. In the Transport Stream packet + * where the splice_countdown reaches zero, the last data byte of the Transport Stream packet payload shall be the last byte + * of a coded audio frame or a coded picture. In the case of video, the corresponding access unit may or may not be + * terminated by a sequence_end_code. Transport Stream packets with the same PID, which follow, may contain data from + * a different elementary stream of the same type. + * + * The payload of the next Transport Stream packet of the same PID (duplicate packets and packets without payload being + * excluded) shall commence with the first byte of a PES packet.In the case of audio, the PES packet payload shall + * commence with an access point. In the case of video, the PES packet payload shall commence with an access point, or + * with a sequence_end_code, followed by an access point. Thus, the previous coded audio frame or coded picture aligns + * with the packet boundary, or is padded to make this so. Subsequent to the splicing point, the countdown field may also + * be present. When the splice_countdown is a negative number whose value is minus n(-n), it indicates that the associated + * Transport Stream packet is the n-th packet following the splicing point (duplicate packets and packets without payload + * being excluded). + * + * For the purposes of this subclause, an access point is defined as follows: + * Video - The first byte of a video_sequence_header. + * Audio - The first byte of an audio frame. + */ + int8_t splice_countdown; //8bits + + // if transport_private_data_flag, 1+p[0] B + /** + * The transport_private_data_length is an 8-bit field specifying the number of + * private_data bytes immediately following the transport private_data_length field. The number of private_data bytes shall + * not be such that private data extends beyond the adaptation field. + */ + u_int8_t transport_private_data_length; //8bits + char* transport_private_data; //[transport_private_data_length]bytes + + // if adaptation_field_extension_flag, 2+x B + /** + * The adaptation_field_extension_length is an 8-bit field. It indicates the number of + * bytes of the extended adaptation field data immediately following this field, including reserved bytes if present. + */ + u_int8_t adaptation_field_extension_length; //8bits + /** + * This is a 1-bit field which when set to '1' indicates the presence of the ltw_offset + * field. + */ + int8_t ltw_flag; //1bit + /** + * This is a 1-bit field which when set to '1' indicates the presence of the piecewise_rate field. + */ + int8_t piecewise_rate_flag; //1bit + /** + * This is a 1-bit flag which when set to '1' indicates that the splice_type and DTS_next_AU fields + * are present. A value of '0' indicates that neither splice_type nor DTS_next_AU fields are present. This field shall not be + * set to '1' in Transport Stream packets in which the splicing_point_flag is not set to '1'. Once it is set to '1' in a Transport + * Stream packet in which the splice_countdown is positive, it shall be set to '1' in all the subsequent Transport Stream + * packets of the same PID that have the splicing_point_flag set to '1', until the packet in which the splice_countdown + * reaches zero (including this packet). When this flag is set, if the elementary stream carried in this PID is an audio stream, + * the splice_type field shall be set to '0000'. If the elementary stream carried in this PID is a video stream, it shall fulfil the + * constraints indicated by the splice_type value. + */ + int8_t seamless_splice_flag; //1bit + /** + * reserved 5bits, must be '1' + */ + int8_t const1_value1; //5bits + // if ltw_flag, 2B + /** + * (legal time window_valid_flag) - This is a 1-bit field which when set to '1' indicates that the value of the + * ltw_offset shall be valid. A value of '0' indicates that the value in the ltw_offset field is undefined. + */ + int8_t ltw_valid_flag; //1bit + /** + * (legal time window offset) - This is a 15-bit field, the value of which is defined only if the ltw_valid flag has + * a value of '1'. When defined, the legal time window offset is in units of (300/fs) seconds, where fs is the system clock + * frequency of the program that this PID belongs to, and fulfils: + * offset = t1(i) - t(i) + * ltw_offset = offset//1 + * where i is the index of the first byte of this Transport Stream packet, offset is the value encoded in this field, t(i) is the + * arrival time of byte i in the T-STD, and t1(i) is the upper bound in time of a time interval called the Legal Time Window + * which is associated with this Transport Stream packet. + */ + int16_t ltw_offset; //15bits + // if piecewise_rate_flag, 3B + //2bits reserved + /** + * The meaning of this 22-bit field is only defined when both the ltw_flag and the ltw_valid_flag are set + * to '1'. When defined, it is a positive integer specifying a hypothetical bitrate R which is used to define the end times of + * the Legal Time Windows of Transport Stream packets of the same PID that follow this packet but do not include the + * legal_time_window_offset field. + */ + int32_t piecewise_rate; //22bits + // if seamless_splice_flag, 5B + /** + * This is a 4-bit field. From the first occurrence of this field onwards, it shall have the same value in all the + * subsequent Transport Stream packets of the same PID in which it is present, until the packet in which the + * splice_countdown reaches zero (including this packet). If the elementary stream carried in that PID is an audio stream, + * this field shall have the value '0000'. If the elementary stream carried in that PID is a video stream, this field indicates the + * conditions that shall be respected by this elementary stream for splicing purposes. These conditions are defined as a + * function of profile, level and splice_type in Table 2-7 through Table 2-16. + */ + int8_t splice_type; //4bits + /** + * (decoding time stamp next access unit) - This is a 33-bit field, coded in three parts. In the case of + * continuous and periodic decoding through this splicing point it indicates the decoding time of the first access unit + * following the splicing point. This decoding time is expressed in the time base which is valid in the Transport Stream + * packet in which the splice_countdown reaches zero. From the first occurrence of this field onwards, it shall have the + * same value in all the subsequent Transport Stream packets of the same PID in which it is present, until the packet in + * which the splice_countdown reaches zero (including this packet). + */ + int8_t DTS_next_AU0; //3bits + int8_t marker_bit0; //1bit + int16_t DTS_next_AU1; //15bits + int8_t marker_bit1; //1bit + int16_t DTS_next_AU2; //15bits + int8_t marker_bit2; //1bit + // left bytes. + /** + * This is a fixed 8-bit value equal to '1111 1111' that can be inserted by the encoder. It is discarded by the + * decoder. + */ + int nb_af_ext_reserved; + + // left bytes. + /** + * This is a fixed 8-bit value equal to '1111 1111' that can be inserted by the encoder. It is discarded by the + * decoder. + */ + int nb_af_reserved; +private: + SrsTsPacket* packet; +public: + SrsTsAdaptationField(SrsTsPacket* pkt); + virtual ~SrsTsAdaptationField(); +public: + virtual int decode(SrsStream* stream); +public: + virtual int size(); + virtual int encode(SrsStream* stream); +}; + +/** +* 2.4.4.4 Table_id assignments, hls-mpeg-ts-iso13818-1.pdf, page 62 +* The table_id field identifies the contents of a Transport Stream PSI section as shown in Table 2-26. +*/ +enum SrsTsPsiId +{ + // program_association_section + SrsTsPsiIdPas = 0x00, + // conditional_access_section (CA_section) + SrsTsPsiIdCas = 0x01, + // TS_program_map_section + SrsTsPsiIdPms = 0x02, + // TS_description_section + SrsTsPsiIdDs = 0x03, + // ISO_IEC_14496_scene_description_section + SrsTsPsiIdSds = 0x04, + // ISO_IEC_14496_object_descriptor_section + SrsTsPsiIdOds = 0x05, + // ITU-T Rec. H.222.0 | ISO/IEC 13818-1 reserved + SrsTsPsiIdIso138181Start = 0x06, + SrsTsPsiIdIso138181End = 0x37, + // Defined in ISO/IEC 13818-6 + SrsTsPsiIdIso138186Start = 0x38, + SrsTsPsiIdIso138186End = 0x3F, + // User private + SrsTsPsiIdUserStart = 0x40, + SrsTsPsiIdUserEnd = 0xFE, + // forbidden + SrsTsPsiIdForbidden = 0xFF, +}; + +/** +* the payload of ts packet, can be PES or PSI payload. +*/ +class SrsTsPayload +{ +protected: + SrsTsPacket* packet; +public: + SrsTsPayload(SrsTsPacket* p); + virtual ~SrsTsPayload(); +public: + virtual int decode(SrsStream* stream, SrsTsMessage** ppmsg) = 0; +public: + virtual int size() = 0; + virtual int encode(SrsStream* stream) = 0; +}; + +/** +* the PES payload of ts packet. +* 2.4.3.6 PES packet, hls-mpeg-ts-iso13818-1.pdf, page 49 +*/ +class SrsTsPayloadPES : public SrsTsPayload +{ +public: + // 3B + /** + * The packet_start_code_prefix is a 24-bit code. Together with the stream_id that follows it + * constitutes a packet start code that identifies the beginning of a packet. The packet_start_code_prefix is the bit string + * '0000 0000 0000 0000 0000 0001' (0x000001). + */ + int32_t packet_start_code_prefix; //24bits + // 1B + /** + * In Program Streams, the stream_id specifies the type and number of the elementary stream as defined by the + * stream_id Table 2-18. In Transport Streams, the stream_id may be set to any valid value which correctly describes the + * elementary stream type as defined in Table 2-18. In Transport Streams, the elementary stream type is specified in the + * Program Specific Information as specified in 2.4.4. + */ + // @see SrsTsPESStreamId, value can be SrsTsPESStreamIdAudioCommon or SrsTsPESStreamIdVideoCommon. + u_int8_t stream_id; //8bits + // 2B + /** + * A 16-bit field specifying the number of bytes in the PES packet following the last byte of the + * field. A value of 0 indicates that the PES packet length is neither specified nor bounded and is allowed only in + * PES packets whose payload consists of bytes from a video elementary stream contained in Transport Stream packets. + */ + u_int16_t PES_packet_length; //16bits + + // 1B + /** + * 2bits const '10' + */ + int8_t const2bits; //2bits + /** + * The 2-bit PES_scrambling_control field indicates the scrambling mode of the PES packet + * payload. When scrambling is performed at the PES level, the PES packet header, including the optional fields when + * present, shall not be scrambled (see Table 2-19). + */ + int8_t PES_scrambling_control; //2bits + /** + * This is a 1-bit field indicating the priority of the payload in this PES packet. A '1' indicates a higher + * priority of the payload of the PES packet payload than a PES packet payload with this field set to '0'. A multiplexor can + * use the PES_priority bit to prioritize its data within an elementary stream. This field shall not be changed by the transport + * mechanism. + */ + int8_t PES_priority; //1bit + /** + * This is a 1-bit flag. When set to a value of '1' it indicates that the PES packet header is + * immediately followed by the video start code or audio syncword indicated in the data_stream_alignment_descriptor + * in 2.6.10 if this descriptor is present. If set to a value of '1' and the descriptor is not present, alignment as indicated in + * alignment_type '01' in Table 2-47 and Table 2-48 is required. When set to a value of '0' it is not defined whether any such + * alignment occurs or not. + */ + int8_t data_alignment_indicator; //1bit + /** + * This is a 1-bit field. When set to '1' it indicates that the material of the associated PES packet payload is + * protected by copyright. When set to '0' it is not defined whether the material is protected by copyright. A copyright + * descriptor described in 2.6.24 is associated with the elementary stream which contains this PES packet and the copyright + * flag is set to '1' if the descriptor applies to the material contained in this PES packet + */ + int8_t copyright; //1bit + /** + * This is a 1-bit field. When set to '1' the contents of the associated PES packet payload is an original. + * When set to '0' it indicates that the contents of the associated PES packet payload is a copy. + */ + int8_t original_or_copy; //1bit + + // 1B + /** + * This is a 2-bit field. When the PTS_DTS_flags field is set to '10', the PTS fields shall be present in + * the PES packet header. When the PTS_DTS_flags field is set to '11', both the PTS fields and DTS fields shall be present + * in the PES packet header. When the PTS_DTS_flags field is set to '00' no PTS or DTS fields shall be present in the PES + * packet header. The value '01' is forbidden. + */ + int8_t PTS_DTS_flags; //2bits + /** + * A 1-bit flag, which when set to '1' indicates that ESCR base and extension fields are present in the PES + * packet header. When set to '0' it indicates that no ESCR fields are present. + */ + int8_t ESCR_flag; //1bit + /** + * A 1-bit flag, which when set to '1' indicates that the ES_rate field is present in the PES packet header. + * When set to '0' it indicates that no ES_rate field is present. + */ + int8_t ES_rate_flag; //1bit + /** + * A 1-bit flag, which when set to '1' it indicates the presence of an 8-bit trick mode field. When + * set to '0' it indicates that this field is not present. + */ + int8_t DSM_trick_mode_flag; //1bit + /** + * A 1-bit flag, which when set to '1' indicates the presence of the additional_copy_info field. + * When set to '0' it indicates that this field is not present. + */ + int8_t additional_copy_info_flag; //1bit + /** + * A 1-bit flag, which when set to '1' indicates that a CRC field is present in the PES packet. When set to + * '0' it indicates that this field is not present. + */ + int8_t PES_CRC_flag; //1bit + /** + * A 1-bit flag, which when set to '1' indicates that an extension field exists in this PES packet + * header. When set to '0' it indicates that this field is not present. + */ + int8_t PES_extension_flag; //1bit + + // 1B + /** + * An 8-bit field specifying the total number of bytes occupied by the optional fields and any + * stuffing bytes contained in this PES packet header. The presence of optional fields is indicated in the byte that precedes + * the PES_header_data_length field. + */ + u_int8_t PES_header_data_length; //8bits + + // 5B + /** + * Presentation times shall be related to decoding times as follows: The PTS is a 33-bit + * number coded in three separate fields. It indicates the time of presentation, tp n (k), in the system target decoder of a + * presentation unit k of elementary stream n. The value of PTS is specified in units of the period of the system clock + * frequency divided by 300 (yielding 90 kHz). The presentation time is derived from the PTS according to equation 2-11 + * below. Refer to 2.7.4 for constraints on the frequency of coding presentation timestamps. + */ + // ===========1B + // 4bits const + // 3bits PTS [32..30] + // 1bit const '1' + // ===========2B + // 15bits PTS [29..15] + // 1bit const '1' + // ===========2B + // 15bits PTS [14..0] + // 1bit const '1' + int64_t pts; // 33bits + + // 5B + /** + * The DTS is a 33-bit number coded in three separate fields. It indicates the decoding time, + * td n (j), in the system target decoder of an access unit j of elementary stream n. The value of DTS is specified in units of + * the period of the system clock frequency divided by 300 (yielding 90 kHz). + */ + // ===========1B + // 4bits const + // 3bits DTS [32..30] + // 1bit const '1' + // ===========2B + // 15bits DTS [29..15] + // 1bit const '1' + // ===========2B + // 15bits DTS [14..0] + // 1bit const '1' + int64_t dts; // 33bits + + // 6B + /** + * The elementary stream clock reference is a 42-bit field coded in two parts. The first + * part, ESCR_base, is a 33-bit field whose value is given by ESCR_base(i), as given in equation 2-14. The second part, + * ESCR_ext, is a 9-bit field whose value is given by ESCR_ext(i), as given in equation 2-15. The ESCR field indicates the + * intended time of arrival of the byte containing the last bit of the ESCR_base at the input of the PES-STD for PES streams + * (refer to 2.5.2.4). + */ + // 2bits reserved + // 3bits ESCR_base[32..30] + // 1bit const '1' + // 15bits ESCR_base[29..15] + // 1bit const '1' + // 15bits ESCR_base[14..0] + // 1bit const '1' + // 9bits ESCR_extension + // 1bit const '1' + int64_t ESCR_base; //33bits + int16_t ESCR_extension; //9bits + + // 3B + /** + * The ES_rate field is a 22-bit unsigned integer specifying the rate at which the + * system target decoder receives bytes of the PES packet in the case of a PES stream. The ES_rate is valid in the PES + * packet in which it is included and in subsequent PES packets of the same PES stream until a new ES_rate field is + * encountered. The value of the ES_rate is measured in units of 50 bytes/second. The value 0 is forbidden. The value of the + * ES_rate is used to define the time of arrival of bytes at the input of a P-STD for PES streams defined in 2.5.2.4. The + * value encoded in the ES_rate field may vary from PES_packet to PES_packet. + */ + // 1bit const '1' + // 22bits ES_rate + // 1bit const '1' + int32_t ES_rate; //22bits + + // 1B + /** + * A 3-bit field that indicates which trick mode is applied to the associated video stream. In cases of + * other types of elementary streams, the meanings of this field and those defined by the following five bits are undefined. + * For the definition of trick_mode status, refer to the trick mode section of 2.4.2.3. + */ + int8_t trick_mode_control; //3bits + int8_t trick_mode_value; //5bits + + // 1B + // 1bit const '1' + /** + * This 7-bit field contains private data relating to copyright information. + */ + int8_t additional_copy_info; //7bits + + // 2B + /** + * The previous_PES_packet_CRC is a 16-bit field that contains the CRC value that yields + * a zero output of the 16 registers in the decoder similar to the one defined in Annex A, + */ + int16_t previous_PES_packet_CRC; //16bits + + // 1B + /** + * A 1-bit flag which when set to '1' indicates that the PES packet header contains private data. + * When set to a value of '0' it indicates that private data is not present in the PES header. + */ + int8_t PES_private_data_flag; //1bit + /** + * A 1-bit flag which when set to '1' indicates that an ISO/IEC 11172-1 pack header or a + * Program Stream pack header is stored in this PES packet header. If this field is in a PES packet that is contained in a + * Program Stream, then this field shall be set to '0'. In a Transport Stream, when set to the value '0' it indicates that no pack + * header is present in the PES header. + */ + int8_t pack_header_field_flag; //1bit + /** + * A 1-bit flag which when set to '1' indicates that the + * program_packet_sequence_counter, MPEG1_MPEG2_identifier, and original_stuff_length fields are present in this + * PES packet. When set to a value of '0' it indicates that these fields are not present in the PES header. + */ + int8_t program_packet_sequence_counter_flag; //1bit + /** + * A 1-bit flag which when set to '1' indicates that the P-STD_buffer_scale and P-STD_buffer_size + * are present in the PES packet header. When set to a value of '0' it indicates that these fields are not present in the + * PES header. + */ + int8_t P_STD_buffer_flag; //1bit + /** + * reverved value, must be '1' + */ + int8_t const1_value0; //3bits + /** + * A 1-bit field which when set to '1' indicates the presence of the PES_extension_field_length + * field and associated fields. When set to a value of '0' this indicates that the PES_extension_field_length field and any + * associated fields are not present. + */ + int8_t PES_extension_flag_2; //1bit + + // 16B + /** + * This is a 16-byte field which contains private data. This data, combined with the fields before and + * after, shall not emulate the packet_start_code_prefix (0x000001). + */ + char* PES_private_data; //128bits + + // (1+x)B + /** + * This is an 8-bit field which indicates the length, in bytes, of the pack_header_field(). + */ + u_int8_t pack_field_length; //8bits + char* pack_field; //[pack_field_length] bytes + + // 2B + // 1bit const '1' + /** + * The program_packet_sequence_counter field is a 7-bit field. It is an optional + * counter that increments with each successive PES packet from a Program Stream or from an ISO/IEC 11172-1 Stream or + * the PES packets associated with a single program definition in a Transport Stream, providing functionality similar to a + * continuity counter (refer to 2.4.3.2). This allows an application to retrieve the original PES packet sequence of a Program + * Stream or the original packet sequence of the original ISO/IEC 11172-1 stream. The counter will wrap around to 0 after + * its maximum value. Repetition of PES packets shall not occur. Consequently, no two consecutive PES packets in the + * program multiplex shall have identical program_packet_sequence_counter values. + */ + int8_t program_packet_sequence_counter; //7bits + // 1bit const '1' + /** + * A 1-bit flag which when set to '1' indicates that this PES packet carries information from + * an ISO/IEC 11172-1 stream. When set to '0' it indicates that this PES packet carries information from a Program Stream. + */ + int8_t MPEG1_MPEG2_identifier; //1bit + /** + * This 6-bit field specifies the number of stuffing bytes used in the original ITU-T + * Rec. H.222.0 | ISO/IEC 13818-1 PES packet header or in the original ISO/IEC 11172-1 packet header. + */ + int8_t original_stuff_length; //6bits + + // 2B + // 2bits const '01' + /** + * The P-STD_buffer_scale is a 1-bit field, the meaning of which is only defined if this PES packet + * is contained in a Program Stream. It indicates the scaling factor used to interpret the subsequent P-STD_buffer_size field. + * If the preceding stream_id indicates an audio stream, P-STD_buffer_scale shall have the value '0'. If the preceding + * stream_id indicates a video stream, P-STD_buffer_scale shall have the value '1'. For all other stream types, the value + * may be either '1' or '0'. + */ + int8_t P_STD_buffer_scale; //1bit + /** + * The P-STD_buffer_size is a 13-bit unsigned integer, the meaning of which is only defined if this + * PES packet is contained in a Program Stream. It defines the size of the input buffer, BS n , in the P-STD. If + * P-STD_buffer_scale has the value '0', then the P-STD_buffer_size measures the buffer size in units of 128 bytes. If + * P-STD_buffer_scale has the value '1', then the P-STD_buffer_size measures the buffer size in units of 1024 bytes. + */ + int16_t P_STD_buffer_size; //13bits + + // (1+x)B + // 1bit const '1' + /** + * This is a 7-bit field which specifies the length, in bytes, of the data following this field in + * the PES extension field up to and including any reserved bytes. + */ + u_int8_t PES_extension_field_length; //7bits + char* PES_extension_field; //[PES_extension_field_length] bytes + + // NB + /** + * This is a fixed 8-bit value equal to '1111 1111' that can be inserted by the encoder, for example to meet + * the requirements of the channel. It is discarded by the decoder. No more than 32 stuffing bytes shall be present in one + * PES packet header. + */ + int nb_stuffings; + + // NB + /** + * PES_packet_data_bytes shall be contiguous bytes of data from the elementary stream + * indicated by the packet’s stream_id or PID. When the elementary stream data conforms to ITU-T + * Rec. H.262 | ISO/IEC 13818-2 or ISO/IEC 13818-3, the PES_packet_data_bytes shall be byte aligned to the bytes of this + * Recommendation | International Standard. The byte-order of the elementary stream shall be preserved. The number of + * PES_packet_data_bytes, N, is specified by the PES_packet_length field. N shall be equal to the value indicated in the + * PES_packet_length minus the number of bytes between the last byte of the PES_packet_length field and the first + * PES_packet_data_byte. + * + * In the case of a private_stream_1, private_stream_2, ECM_stream, or EMM_stream, the contents of the + * PES_packet_data_byte field are user definable and will not be specified by ITU-T | ISO/IEC in the future. + */ + int nb_bytes; + + // NB + /** + * This is a fixed 8-bit value equal to '1111 1111'. It is discarded by the decoder. + */ + int nb_paddings; +public: + SrsTsPayloadPES(SrsTsPacket* p); + virtual ~SrsTsPayloadPES(); +public: + virtual int decode(SrsStream* stream, SrsTsMessage** ppmsg); +public: + virtual int size(); + virtual int encode(SrsStream* stream); +private: + virtual int decode_33bits_dts_pts(SrsStream* stream, int64_t* pv); + virtual int encode_33bits_dts_pts(SrsStream* stream, u_int8_t fb, int64_t v); +}; + +/** +* the PSI payload of ts packet. +* 2.4.4 Program specific information, hls-mpeg-ts-iso13818-1.pdf, page 59 +*/ +class SrsTsPayloadPSI : public SrsTsPayload +{ +public: + // 1B + /** + * This is an 8-bit field whose value shall be the number of bytes, immediately following the pointer_field + * until the first byte of the first section that is present in the payload of the Transport Stream packet (so a value of 0x00 in + * the pointer_field indicates that the section starts immediately after the pointer_field). When at least one section begins in + * a given Transport Stream packet, then the payload_unit_start_indicator (refer to 2.4.3.2) shall be set to 1 and the first + * byte of the payload of that Transport Stream packet shall contain the pointer. When no section begins in a given + * Transport Stream packet, then the payload_unit_start_indicator shall be set to 0 and no pointer shall be sent in the + * payload of that packet. + */ + int8_t pointer_field; +public: + // 1B + /** + * This is an 8-bit field, which shall be set to 0x00 as shown in Table 2-26. + */ + SrsTsPsiId table_id; //8bits + + // 2B + /** + * The section_syntax_indicator is a 1-bit field which shall be set to '1'. + */ + int8_t section_syntax_indicator; //1bit + /** + * const value, must be '0' + */ + int8_t const0_value; //1bit + /** + * reverved value, must be '1' + */ + int8_t const1_value; //2bits + /** + * This is a 12-bit field, the first two bits of which shall be '00'. The remaining 10 bits specify the number + * of bytes of the section, starting immediately following the section_length field, and including the CRC. The value in this + * field shall not exceed 1021 (0x3FD). + */ + u_int16_t section_length; //12bits +public: + // the specified psi info, for example, PAT fields. +public: + // 4B + /** + * This is a 32-bit field that contains the CRC value that gives a zero output of the registers in the decoder + * defined in Annex A after processing the entire section. + * @remark crc32(bytes without pointer field, before crc32 field) + */ + int32_t CRC_32; //32bits +public: + SrsTsPayloadPSI(SrsTsPacket* p); + virtual ~SrsTsPayloadPSI(); +public: + virtual int decode(SrsStream* stream, SrsTsMessage** ppmsg); +public: + virtual int size(); + virtual int encode(SrsStream* stream); +protected: + virtual int psi_size() = 0; + virtual int psi_encode(SrsStream* stream) = 0; + virtual int psi_decode(SrsStream* stream) = 0; +}; + +/** +* the program of PAT of PSI ts packet. +*/ +class SrsTsPayloadPATProgram +{ +public: + // 4B + /** + * Program_number is a 16-bit field. It specifies the program to which the program_map_PID is + * applicable. When set to 0x0000, then the following PID reference shall be the network PID. For all other cases the value + * of this field is user defined. This field shall not take any single value more than once within one version of the Program + * Association Table. + */ + int16_t number; // 16bits + /** + * reverved value, must be '1' + */ + int8_t const1_value; //3bits + /** + * program_map_PID/network_PID 13bits + * network_PID - The network_PID is a 13-bit field, which is used only in conjunction with the value of the + * program_number set to 0x0000, specifies the PID of the Transport Stream packets which shall contain the Network + * Information Table. The value of the network_PID field is defined by the user, but shall only take values as specified in + * Table 2-3. The presence of the network_PID is optional. + */ + int16_t pid; //13bits +public: + SrsTsPayloadPATProgram(int16_t n = 0, int16_t p = 0); + virtual ~SrsTsPayloadPATProgram(); +public: + virtual int decode(SrsStream* stream); +public: + virtual int size(); + virtual int encode(SrsStream* stream); +}; + +/** +* the PAT payload of PSI ts packet. +* 2.4.4.3 Program association Table, hls-mpeg-ts-iso13818-1.pdf, page 61 +* The Program Association Table provides the correspondence between a program_number and the PID value of the +* Transport Stream packets which carry the program definition. The program_number is the numeric label associated with +* a program. +*/ +class SrsTsPayloadPAT : public SrsTsPayloadPSI +{ +public: + // 2B + /** + * This is a 16-bit field which serves as a label to identify this Transport Stream from any other + * multiplex within a network. Its value is defined by the user. + */ + u_int16_t transport_stream_id; //16bits + + // 1B + /** + * reverved value, must be '1' + */ + int8_t const3_value; //2bits + /** + * This 5-bit field is the version number of the whole Program Association Table. The version number + * shall be incremented by 1 modulo 32 whenever the definition of the Program Association Table changes. When the + * current_next_indicator is set to '1', then the version_number shall be that of the currently applicable Program Association + * Table. When the current_next_indicator is set to '0', then the version_number shall be that of the next applicable Program + * Association Table. + */ + int8_t version_number; //5bits + /** + * A 1-bit indicator, which when set to '1' indicates that the Program Association Table sent is + * currently applicable. When the bit is set to '0', it indicates that the table sent is not yet applicable and shall be the next + * table to become valid. + */ + int8_t current_next_indicator; //1bit + + // 1B + /** + * This 8-bit field gives the number of this section. The section_number of the first section in the + * Program Association Table shall be 0x00. It shall be incremented by 1 with each additional section in the Program + * Association Table. + */ + u_int8_t section_number; //8bits + + // 1B + /** + * This 8-bit field specifies the number of the last section (that is, the section with the highest + * section_number) of the complete Program Association Table. + */ + u_int8_t last_section_number; //8bits + + // multiple 4B program data. + std::vector programs; +public: + SrsTsPayloadPAT(SrsTsPacket* p); + virtual ~SrsTsPayloadPAT(); +protected: + virtual int psi_decode(SrsStream* stream); +protected: + virtual int psi_size(); + virtual int psi_encode(SrsStream* stream); +}; + +/** +* the esinfo for PMT program. +*/ +class SrsTsPayloadPMTESInfo +{ +public: + // 1B + /** + * This is an 8-bit field specifying the type of program element carried within the packets with the PID + * whose value is specified by the elementary_PID. The values of stream_type are specified in Table 2-29. + */ + SrsTsStream stream_type; //8bits + + // 2B + /** + * reverved value, must be '1' + */ + int8_t const1_value0; //3bits + /** + * This is a 13-bit field specifying the PID of the Transport Stream packets which carry the associated + * program element. + */ + int16_t elementary_PID; //13bits + + // (2+x)B + /** + * reverved value, must be '1' + */ + int8_t const1_value1; //4bits + /** + * This is a 12-bit field, the first two bits of which shall be '00'. The remaining 10 bits specify the number + * of bytes of the descriptors of the associated program element immediately following the ES_info_length field. + */ + int16_t ES_info_length; //12bits + char* ES_info; //[ES_info_length] bytes. +public: + SrsTsPayloadPMTESInfo(SrsTsStream st = SrsTsStreamReserved, int16_t epid = 0); + virtual ~SrsTsPayloadPMTESInfo(); +public: + virtual int decode(SrsStream* stream); +public: + virtual int size(); + virtual int encode(SrsStream* stream); +}; + +/** +* the PMT payload of PSI ts packet. +* 2.4.4.8 Program Map Table, hls-mpeg-ts-iso13818-1.pdf, page 64 +* The Program Map Table provides the mappings between program numbers and the program elements that comprise +* them. A single instance of such a mapping is referred to as a "program definition". The program map table is the +* complete collection of all program definitions for a Transport Stream. This table shall be transmitted in packets, the PID +* values of which are selected by the encoder. More than one PID value may be used, if desired. The table is contained in +* one or more sections with the following syntax. It may be segmented to occupy multiple sections. In each section, the +* section number field shall be set to zero. Sections are identified by the program_number field. +*/ +class SrsTsPayloadPMT : public SrsTsPayloadPSI +{ +public: + // 2B + /** + * program_number is a 16-bit field. It specifies the program to which the program_map_PID is + * applicable. One program definition shall be carried within only one TS_program_map_section. This implies that a + * program definition is never longer than 1016 (0x3F8). See Informative Annex C for ways to deal with the cases when + * that length is not sufficient. The program_number may be used as a designation for a broadcast channel, for example. By + * describing the different program elements belonging to a program, data from different sources (e.g. sequential events) + * can be concatenated together to form a continuous set of streams using a program_number. For examples of applications + * refer to Annex C. + */ + u_int16_t program_number; //16bits + + // 1B + /** + * reverved value, must be '1' + */ + int8_t const1_value0; //2bits + /** + * This 5-bit field is the version number of the TS_program_map_section. The version number shall be + * incremented by 1 modulo 32 when a change in the information carried within the section occurs. Version number refers + * to the definition of a single program, and therefore to a single section. When the current_next_indicator is set to '1', then + * the version_number shall be that of the currently applicable TS_program_map_section. When the current_next_indicator + * is set to '0', then the version_number shall be that of the next applicable TS_program_map_section. + */ + int8_t version_number; //5bits + /** + * A 1-bit field, which when set to '1' indicates that the TS_program_map_section sent is + * currently applicable. When the bit is set to '0', it indicates that the TS_program_map_section sent is not yet applicable + * and shall be the next TS_program_map_section to become valid. + */ + int8_t current_next_indicator; //1bit + + // 1B + /** + * The value of this 8-bit field shall be 0x00. + */ + u_int8_t section_number; //8bits + + // 1B + /** + * The value of this 8-bit field shall be 0x00. + */ + u_int8_t last_section_number; //8bits + + // 2B + /** + * reverved value, must be '1' + */ + int8_t const1_value1; //3bits + /** + * This is a 13-bit field indicating the PID of the Transport Stream packets which shall contain the PCR fields + * valid for the program specified by program_number. If no PCR is associated with a program definition for private + * streams, then this field shall take the value of 0x1FFF. Refer to the semantic definition of PCR in 2.4.3.5 and Table 2-3 + * for restrictions on the choice of PCR_PID value. + */ + int16_t PCR_PID; //13bits + + // 2B + int8_t const1_value2; //4bits + /** + * This is a 12-bit field, the first two bits of which shall be '00'. The remaining 10 bits specify the + * number of bytes of the descriptors immediately following the program_info_length field. + */ + u_int16_t program_info_length; //12bits + char* program_info_desc; //[program_info_length]bytes + + // array of TSPMTESInfo. + std::vector infos; +public: + SrsTsPayloadPMT(SrsTsPacket* p); + virtual ~SrsTsPayloadPMT(); +protected: + virtual int psi_decode(SrsStream* stream); +protected: + virtual int psi_size(); + virtual int psi_encode(SrsStream* stream); +}; + +/** +* write data from frame(header info) and buffer(data) to ts file. +* it's a simple object wrapper for utility from nginx-rtmp: SrsMpegtsWriter +*/ +class SrsTSMuxer +{ +private: + SrsCodecVideo vcodec; + SrsCodecAudio acodec; +private: + SrsTsContext* context; + SrsFileWriter* writer; + std::string path; +public: + SrsTSMuxer(SrsFileWriter* w, SrsTsContext* c, SrsCodecAudio ac, SrsCodecVideo vc); + virtual ~SrsTSMuxer(); +public: + /** + * open the writer, donot write the PSI of ts. + */ + virtual int open(std::string _path); + /** + * when open ts, we donot write the header(PSI), + * for user may need to update the acodec to mp3 or others, + * so we use delay write PSI, when write audio or video. + * @remark for audio aac codec, for example, SRS1, it's ok to write PSI when open ts. + * @see https://github.com/simple-rtmp-server/srs/issues/301 + */ + virtual int update_acodec(SrsCodecAudio ac); + /** + * write an audio frame to ts, + */ + virtual int write_audio(SrsTsMessage* audio); + /** + * write a video frame to ts, + */ + virtual int write_video(SrsTsMessage* video); + /** + * close the writer. + */ + virtual void close(); +}; + +/** +* ts stream cache, +* use to cache ts stream. +* +* about the flv tbn problem: +* flv tbn is 1/1000, ts tbn is 1/90000, +* when timestamp convert to flv tbn, it will loose precise, +* so we must gather audio frame together, and recalc the timestamp @see SrsTsAacJitter, +* we use a aac jitter to correct the audio pts. +*/ +class SrsTsCache +{ +public: + // current ts message. + SrsTsMessage* audio; + SrsTsMessage* video; +public: + SrsTsCache(); + virtual ~SrsTsCache(); +public: + /** + * write audio to cache + */ + virtual int cache_audio(SrsAvcAacCodec* codec, int64_t dts, SrsCodecSample* sample); + /** + * write video to muxer. + */ + virtual int cache_video(SrsAvcAacCodec* codec, int64_t dts, SrsCodecSample* sample); +private: + virtual int do_cache_mp3(SrsAvcAacCodec* codec, SrsCodecSample* sample); + virtual int do_cache_aac(SrsAvcAacCodec* codec, SrsCodecSample* sample); + virtual int do_cache_avc(SrsAvcAacCodec* codec, SrsCodecSample* sample); +}; + +/** +* encode data to ts file. +*/ +class SrsTsEncoder +{ +private: + SrsFileWriter* _fs; +private: + SrsAvcAacCodec* codec; + SrsCodecSample* sample; + SrsTsCache* cache; + SrsTSMuxer* muxer; + SrsTsContext* context; +public: + SrsTsEncoder(); + virtual ~SrsTsEncoder(); +public: + /** + * initialize the underlayer file stream. + */ + virtual int initialize(SrsFileWriter* fs); +public: + /** + * write audio/video packet. + * @remark assert data is not NULL. + */ + virtual int write_audio(int64_t timestamp, char* data, int size); + virtual int write_video(int64_t timestamp, char* data, int size); +private: + virtual int flush_audio(); + virtual int flush_video(); +}; + +#endif + diff --git a/trunk/src/kernel/srs_kernel_utility.cpp b/trunk/src/kernel/srs_kernel_utility.cpp index 1bbfbf516f..c47a81ca2c 100644 --- a/trunk/src/kernel/srs_kernel_utility.cpp +++ b/trunk/src/kernel/srs_kernel_utility.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -23,21 +23,76 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include +// for srs-librtmp, @see https://github.com/simple-rtmp-server/srs/issues/213 +#ifndef _WIN32 #include -#include #include #include #include +#endif + +#include +#include +#include using namespace std; #include +#include +#include // this value must: // equals to (SRS_SYS_CYCLE_INTERVAL*SRS_SYS_TIME_RESOLUTION_MS_TIMES)*1000 // @see SRS_SYS_TIME_RESOLUTION_MS_TIMES #define SYS_TIME_RESOLUTION_US 300*1000 +int srs_avc_nalu_read_uev(SrsBitStream* stream, int32_t& v) +{ + int ret = ERROR_SUCCESS; + + if (stream->empty()) { + return ERROR_AVC_NALU_UEV; + } + + // ue(v) in 9.1 Parsing process for Exp-Golomb codes + // H.264-AVC-ISO_IEC_14496-10-2012.pdf, page 227. + // Syntax elements coded as ue(v), me(v), or se(v) are Exp-Golomb-coded. + // leadingZeroBits = -1; + // for( b = 0; !b; leadingZeroBits++ ) + // b = read_bits( 1 ) + // The variable codeNum is then assigned as follows: + // codeNum = (2<empty(); leadingZeroBits++) { + b = stream->read_bit(); + } + + if (leadingZeroBits >= 31) { + return ERROR_AVC_NALU_UEV; + } + + v = (1 << leadingZeroBits) - 1; + for (int i = 0; i < leadingZeroBits; i++) { + int32_t b = stream->read_bit(); + v += b << (leadingZeroBits - 1); + } + + return ret; +} + +int srs_avc_nalu_read_bit(SrsBitStream* stream, int8_t& v) +{ + int ret = ERROR_SUCCESS; + + if (stream->empty()) { + return ERROR_AVC_NALU_UEV; + } + + v = stream->read_bit(); + + return ret; +} + static int64_t _srs_system_time_us_cache = 0; static int64_t _srs_system_time_startup_time = 0; @@ -57,16 +112,16 @@ int64_t srs_get_system_startup_time_ms() return _srs_system_time_startup_time / 1000; } -void srs_update_system_time_ms() +int64_t srs_update_system_time_ms() { timeval now; if (gettimeofday(&now, NULL) < 0) { srs_warn("gettimeofday failed, ignore"); - return; + return -1; } - // @see: https://github.com/winlinvip/simple-rtmp-server/issues/35 + // @see: https://github.com/simple-rtmp-server/srs/issues/35 // we must convert the tv_sec/tv_usec to int64_t. int64_t now_us = ((int64_t)now.tv_sec) * 1000 * 1000 + (int64_t)now.tv_usec; @@ -79,7 +134,7 @@ void srs_update_system_time_ms() if (_srs_system_time_us_cache <= 0) { _srs_system_time_us_cache = now_us; _srs_system_time_startup_time = now_us; - return; + return _srs_system_time_us_cache / 1000; } // use relative time. @@ -88,13 +143,15 @@ void srs_update_system_time_ms() if (diff < 0 || diff > 1000 * SYS_TIME_RESOLUTION_US) { srs_warn("system time jump, history=%"PRId64"us, now=%"PRId64"us, diff=%"PRId64"us", _srs_system_time_us_cache, now_us, diff); - // @see: https://github.com/winlinvip/simple-rtmp-server/issues/109 + // @see: https://github.com/simple-rtmp-server/srs/issues/109 _srs_system_time_startup_time += diff; } _srs_system_time_us_cache = now_us; srs_info("system time updated, startup=%"PRId64"us, now=%"PRId64"us", _srs_system_time_startup_time, _srs_system_time_us_cache); + + return _srs_system_time_us_cache / 1000; } string srs_dns_resolve(string host) @@ -216,3 +273,489 @@ bool srs_string_ends_with(string str, string flag) return str.rfind(flag) == str.length() - flag.length(); } +bool srs_string_starts_with(string str, string flag) +{ + return str.find(flag) == 0; +} + +bool srs_string_contains(string str, string flag) +{ + return str.find(flag) != string::npos; +} + +int srs_do_create_dir_recursively(string dir) +{ + int ret = ERROR_SUCCESS; + + // stat current dir, if exists, return error. + if (srs_path_exists(dir)) { + return ERROR_SYSTEM_DIR_EXISTS; + } + + // create parent first. + size_t pos; + if ((pos = dir.rfind("/")) != std::string::npos) { + std::string parent = dir.substr(0, pos); + ret = srs_do_create_dir_recursively(parent); + // return for error. + if (ret != ERROR_SUCCESS && ret != ERROR_SYSTEM_DIR_EXISTS) { + return ret; + } + // parent exists, set to ok. + ret = ERROR_SUCCESS; + } + + // create curren dir. + mode_t mode = S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IXOTH; + if (::mkdir(dir.c_str(), mode) < 0) { + if (errno == EEXIST) { + return ERROR_SYSTEM_DIR_EXISTS; + } + + ret = ERROR_SYSTEM_CREATE_DIR; + srs_error("create dir %s failed. ret=%d", dir.c_str(), ret); + return ret; + } + srs_info("create dir %s success.", dir.c_str()); + + return ret; +} + +int srs_create_dir_recursively(string dir) +{ + int ret = ERROR_SUCCESS; + + ret = srs_do_create_dir_recursively(dir); + + if (ret == ERROR_SYSTEM_DIR_EXISTS) { + return ERROR_SUCCESS; + } + + return ret; +} + +bool srs_path_exists(std::string path) +{ + struct stat st; + + // stat current dir, if exists, return error. + if (stat(path.c_str(), &st) == 0) { + return true; + } + + return false; +} + +string srs_path_dirname(string path) +{ + std::string dirname = path; + size_t pos = string::npos; + + if ((pos = dirname.rfind("/")) != string::npos) { + if (pos == 0) { + return "/"; + } + dirname = dirname.substr(0, pos); + } + + return dirname; +} + +string srs_path_basename(string path) +{ + std::string dirname = path; + size_t pos = string::npos; + + if ((pos = dirname.rfind("/")) != string::npos) { + // the basename("/") is "/" + if (dirname.length() == 1) { + return dirname; + } + dirname = dirname.substr(pos + 1); + } + + return dirname; +} + +bool srs_avc_startswith_annexb(SrsStream* stream, int* pnb_start_code) +{ + char* bytes = stream->data() + stream->pos(); + char* p = bytes; + + for (;;) { + if (!stream->require(p - bytes + 3)) { + return false; + } + + // not match + if (p[0] != (char)0x00 || p[1] != (char)0x00) { + return false; + } + + // match N[00] 00 00 01, where N>=0 + if (p[2] == (char)0x01) { + if (pnb_start_code) { + *pnb_start_code = (int)(p - bytes) + 3; + } + return true; + } + + p++; + } + + return false; +} + +bool srs_aac_startswith_adts(SrsStream* stream) +{ + char* bytes = stream->data() + stream->pos(); + char* p = bytes; + + if (!stream->require((int)(p - bytes) + 2)) { + return false; + } + + // matched 12bits 0xFFF, + // @remark, we must cast the 0xff to char to compare. + if (p[0] != (char)0xff || (char)(p[1] & 0xf0) != (char)0xf0) { + return false; + } + + return true; +} + +/* + * MPEG2 transport stream (aka DVB) mux + * Copyright (c) 2003 Fabrice Bellard. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +static const u_int32_t crc_table[256] = { + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, + 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, + 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7, + 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, + 0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef, + 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, + 0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, + 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, + 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, + 0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, + 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, + 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, + 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, + 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050, + 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, + 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, + 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1, + 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, + 0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9, + 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, + 0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, + 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, + 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, + 0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, + 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, + 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, + 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, + 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676, + 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, + 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, + 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 +}; + +// @see http://www.stmc.edu.hk/~vincent/ffmpeg_0.4.9-pre1/libavformat/mpegtsenc.c +unsigned int mpegts_crc32(const u_int8_t *data, int len) +{ + register int i; + unsigned int crc = 0xffffffff; + + for (i=0; i> 24) ^ *data++) & 0xff]; + + return crc; +} + +u_int32_t srs_crc32(const void* buf, int size) +{ + return mpegts_crc32((const u_int8_t*)buf, size); +} + +/* + * Copyright (c) 2006 Ryan Martell. (rdm4@martellventures.com) + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef UINT_MAX +#define UINT_MAX 0xffffffff +#endif + +#ifndef AV_RB32 +# define AV_RB32(x) \ + (((uint32_t)((const u_int8_t*)(x))[0] << 24) | \ + (((const u_int8_t*)(x))[1] << 16) | \ + (((const u_int8_t*)(x))[2] << 8) | \ + ((const u_int8_t*)(x))[3]) +#endif + +#ifndef AV_WL32 +# define AV_WL32(p, darg) do { \ + unsigned d = (darg); \ + ((u_int8_t*)(p))[0] = (d); \ + ((u_int8_t*)(p))[1] = (d)>>8; \ + ((u_int8_t*)(p))[2] = (d)>>16; \ + ((u_int8_t*)(p))[3] = (d)>>24; \ + } while(0) +#endif + +# define AV_WN(s, p, v) AV_WL##s(p, v) + +# if defined(AV_WN32) && !defined(AV_WL32) +# define AV_WL32(p, v) AV_WN32(p, v) +# elif !defined(AV_WN32) && defined(AV_WL32) +# define AV_WN32(p, v) AV_WL32(p, v) +# endif + +#ifndef AV_WN32 +# define AV_WN32(p, v) AV_WN(32, p, v) +#endif + +#define AV_BSWAP16C(x) (((x) << 8 & 0xff00) | ((x) >> 8 & 0x00ff)) +#define AV_BSWAP32C(x) (AV_BSWAP16C(x) << 16 | AV_BSWAP16C((x) >> 16)) + +#ifndef av_bswap32 +static const u_int32_t av_bswap32(u_int32_t x) +{ + return AV_BSWAP32C(x); +} +#endif + +#define av_be2ne32(x) av_bswap32(x) + +/** + * @file + * @brief Base64 encode/decode + * @author Ryan Martell (with lots of Michael) + */ + +/* ---------------- private code */ +static const u_int8_t map2[256] = +{ + 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, + + 0x3e, 0xff, 0xff, 0xff, 0x3f, 0x34, 0x35, 0x36, + 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0xff, + 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0x00, 0x01, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1a, 0x1b, + 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, + 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, + 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, + + 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +}; + +#define BASE64_DEC_STEP(i) do { \ + bits = map2[in[i]]; \ + if (bits & 0x80) \ + goto out ## i; \ + v = i ? (v << 6) + bits : bits; \ +} while(0) + +int srs_av_base64_decode(u_int8_t* out, const char* in_str, int out_size) +{ + u_int8_t *dst = out; + u_int8_t *end = out + out_size; + // no sign extension + const u_int8_t *in = (const u_int8_t*)in_str; + unsigned bits = 0xff; + unsigned v; + + while (end - dst > 3) { + BASE64_DEC_STEP(0); + BASE64_DEC_STEP(1); + BASE64_DEC_STEP(2); + BASE64_DEC_STEP(3); + // Using AV_WB32 directly confuses compiler + v = av_be2ne32(v << 8); + AV_WN32(dst, v); + dst += 3; + in += 4; + } + if (end - dst) { + BASE64_DEC_STEP(0); + BASE64_DEC_STEP(1); + BASE64_DEC_STEP(2); + BASE64_DEC_STEP(3); + *dst++ = v >> 16; + if (end - dst) + *dst++ = v >> 8; + if (end - dst) + *dst++ = v; + in += 4; + } + while (1) { + BASE64_DEC_STEP(0); + in++; + BASE64_DEC_STEP(0); + in++; + BASE64_DEC_STEP(0); + in++; + BASE64_DEC_STEP(0); + in++; + } + +out3: + *dst++ = v >> 10; + v <<= 2; +out2: + *dst++ = v >> 4; +out1: +out0: + return bits & 1 ? -1 : dst - out; +} + +/***************************************************************************** +* b64_encode: Stolen from VLC's http.c. +* Simplified by Michael. +* Fixed edge cases and made it work from data (vs. strings) by Ryan. +*****************************************************************************/ + +char* srs_av_base64_encode(char* out, int out_size, const u_int8_t* in, int in_size) +{ + static const char b64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + char *ret, *dst; + unsigned i_bits = 0; + int i_shift = 0; + int bytes_remaining = in_size; + + if (in_size >= (int)(UINT_MAX / 4) || + out_size < SRS_AV_BASE64_SIZE(in_size)) + return NULL; + ret = dst = out; + while (bytes_remaining > 3) { + i_bits = AV_RB32(in); + in += 3; bytes_remaining -= 3; + *dst++ = b64[ i_bits>>26 ]; + *dst++ = b64[(i_bits>>20) & 0x3F]; + *dst++ = b64[(i_bits>>14) & 0x3F]; + *dst++ = b64[(i_bits>>8 ) & 0x3F]; + } + i_bits = 0; + while (bytes_remaining) { + i_bits = (i_bits << 8) + *in++; + bytes_remaining--; + i_shift += 8; + } + while (i_shift > 0) { + *dst++ = b64[(i_bits << 6 >> i_shift) & 0x3f]; + i_shift -= 6; + } + while ((dst - ret) & 3) + *dst++ = '='; + *dst = '\0'; + + return ret; +} + +#define SPACE_CHARS " \t\r\n" + +int av_toupper(int c) +{ + if (c >= 'a' && c <= 'z') { + c ^= 0x20; + } + return c; +} + +int ff_hex_to_data(u_int8_t* data, const char* p) +{ + int c, len, v; + + len = 0; + v = 1; + for (;;) { + p += strspn(p, SPACE_CHARS); + if (*p == '\0') + break; + c = av_toupper((unsigned char) *p++); + if (c >= '0' && c <= '9') + c = c - '0'; + else if (c >= 'A' && c <= 'F') + c = c - 'A' + 10; + else + break; + v = (v << 4) | c; + if (v & 0x100) { + if (data) + data[len] = v; + len++; + v = 1; + } + } + return len; +} + diff --git a/trunk/src/kernel/srs_kernel_utility.hpp b/trunk/src/kernel/srs_kernel_utility.hpp index 706a7e6811..678cd6d94e 100644 --- a/trunk/src/kernel/srs_kernel_utility.hpp +++ b/trunk/src/kernel/srs_kernel_utility.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -32,15 +32,22 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include +class SrsStream; +class SrsBitStream; + // compare #define srs_min(a, b) (((a) < (b))? (a) : (b)) #define srs_max(a, b) (((a) < (b))? (b) : (a)) +// read nalu uev. +extern int srs_avc_nalu_read_uev(SrsBitStream* stream, int32_t& v); +extern int srs_avc_nalu_read_bit(SrsBitStream* stream, int8_t& v); + // get current system time in ms, use cache to avoid performance problem extern int64_t srs_get_system_time_ms(); extern int64_t srs_get_system_startup_time_ms(); // the deamon st-thread will update it. -extern void srs_update_system_time_ms(); +extern int64_t srs_update_system_time_ms(); // dns resolve utility, return the resolved ip address. extern std::string srs_dns_resolve(std::string host); @@ -58,6 +65,78 @@ extern std::string srs_string_trim_start(std::string str, std::string trim_chars extern std::string srs_string_remove(std::string str, std::string remove_chars); // whether string end with extern bool srs_string_ends_with(std::string str, std::string flag); +// whether string starts with +extern bool srs_string_starts_with(std::string str, std::string flag); +// whether string contains with +extern bool srs_string_contains(std::string str, std::string flag); + +// create dir recursively +extern int srs_create_dir_recursively(std::string dir); + +// whether path exists. +extern bool srs_path_exists(std::string path); +// get the dirname of path +extern std::string srs_path_dirname(std::string path); +// get the basename of path +extern std::string srs_path_basename(std::string path); + +/** +* whether stream starts with the avc NALU in "AnnexB" +* from H.264-AVC-ISO_IEC_14496-10.pdf, page 211. +* start code must be "N[00] 00 00 01" where N>=0 +* @param pnb_start_code output the size of start code, must >=3. +* NULL to ignore. +*/ +extern bool srs_avc_startswith_annexb(SrsStream* stream, int* pnb_start_code = NULL); + +/** +* whether stream starts with the aac ADTS +* from aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 75, 1.A.2.2 ADTS. +* start code must be '1111 1111 1111'B, that is 0xFFF +*/ +extern bool srs_aac_startswith_adts(SrsStream* stream); + +/** +* cacl the crc32 of bytes in buf. +*/ +extern u_int32_t srs_crc32(const void* buf, int size); + +/** +* Decode a base64-encoded string. +* +* @param out buffer for decoded data +* @param in null-terminated input string +* @param out_size size in bytes of the out buffer, must be at +* least 3/4 of the length of in +* @return number of bytes written, or a negative value in case of +* invalid input +*/ +extern int srs_av_base64_decode(u_int8_t* out, const char* in, int out_size); + +/** +* Encode data to base64 and null-terminate. +* +* @param out buffer for encoded data +* @param out_size size in bytes of the out buffer (including the +* null terminator), must be at least AV_BASE64_SIZE(in_size) +* @param in input buffer containing the data to encode +* @param in_size size in bytes of the in buffer +* @return out or NULL in case of error +*/ +extern char* srs_av_base64_encode(char* out, int out_size, const u_int8_t* in, int in_size); + +/** + * Calculate the output size needed to base64-encode x bytes to a + * null-terminated string. + */ +#define SRS_AV_BASE64_SIZE(x) (((x)+2) / 3 * 4 + 1) + +/** +* convert hex string to data. +* for example, p=config='139056E5A0' +* output hex to data={0x13, 0x90, 0x56, 0xe5, 0xa0} +*/ +extern int ff_hex_to_data(u_int8_t* data, const char* p); #endif diff --git a/trunk/src/libs/srs_lib_bandwidth.cpp b/trunk/src/libs/srs_lib_bandwidth.cpp index 88c32c37dd..b743aaf64d 100644 --- a/trunk/src/libs/srs_lib_bandwidth.cpp +++ b/trunk/src/libs/srs_lib_bandwidth.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -23,17 +23,20 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include +// for srs-librtmp, @see https://github.com/simple-rtmp-server/srs/issues/213 +#ifndef _WIN32 #include +#endif #include using namespace std; #include -#include -#include +#include +#include #include #include -#include +#include /** * recv bandwidth helper. @@ -64,12 +67,12 @@ int _srs_expect_bandwidth_packet(SrsRtmpClient* rtmp, _CheckPacketType pfn) int ret = ERROR_SUCCESS; while (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; SrsBandwidthPacket* pkt = NULL; if ((ret = rtmp->expect_message(&msg, &pkt)) != ERROR_SUCCESS) { return ret; } - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); SrsAutoFree(SrsBandwidthPacket, pkt); srs_info("get final message success."); @@ -85,12 +88,12 @@ int _srs_expect_bandwidth_packet2(SrsRtmpClient* rtmp, _CheckPacketType pfn, Srs int ret = ERROR_SUCCESS; while (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; SrsBandwidthPacket* pkt = NULL; if ((ret = rtmp->expect_message(&msg, &pkt)) != ERROR_SUCCESS) { return ret; } - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); srs_info("get final message success."); if (pfn(pkt)) { @@ -293,11 +296,6 @@ int SrsBandwidthClient::publish_checking(int duration_ms, int play_kbps) return ret; } - // send play data to client - int size = 1024; // TODO: FIXME: magic number - char random_data[size]; - memset(random_data, 'A', size); - int data_count = 1; srs_update_system_time_ms(); int64_t starttime = srs_get_system_time_ms(); @@ -321,13 +319,13 @@ int SrsBandwidthClient::publish_checking(int duration_ms, int play_kbps) // use the play kbps to control the publish srs_update_system_time_ms(); - int elaps = srs_get_system_time_ms() - starttime; + int elaps = (int)(srs_get_system_time_ms() - starttime); if (elaps > 0) { - int current_kbps = _rtmp->get_send_bytes() * 8 / elaps; + int current_kbps = (int)(_rtmp->get_send_bytes() * 8 / elaps); while (current_kbps > play_kbps) { srs_update_system_time_ms(); - elaps = srs_get_system_time_ms() - starttime; - current_kbps = _rtmp->get_send_bytes() * 8 / elaps; + elaps = (int)(srs_get_system_time_ms() - starttime); + current_kbps = (int)(_rtmp->get_send_bytes() * 8 / elaps); usleep(100 * 1000); // TODO: FIXME: magic number. } } diff --git a/trunk/src/libs/srs_lib_bandwidth.hpp b/trunk/src/libs/srs_lib_bandwidth.hpp index f7a459b23d..3dd6c951ad 100644 --- a/trunk/src/libs/srs_lib_bandwidth.hpp +++ b/trunk/src/libs/srs_lib_bandwidth.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/trunk/src/libs/srs_lib_simple_socket.cpp b/trunk/src/libs/srs_lib_simple_socket.cpp index cc84767787..b50bc2155f 100644 --- a/trunk/src/libs/srs_lib_simple_socket.cpp +++ b/trunk/src/libs/srs_lib_simple_socket.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -25,13 +25,45 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include -#include +// for srs-librtmp, @see https://github.com/simple-rtmp-server/srs/issues/213 +#ifndef _WIN32 + #define SOCKET_ETIME ETIME + #define SOCKET_ECONNRESET ECONNRESET + + #define SOCKET_ERRNO() errno + #define SOCKET_RESET(fd) fd = -1; (void)0 + #define SOCKET_CLOSE(fd) \ + if (fd > 0) {\ + ::close(fd); \ + fd = -1; \ + } \ + (void)0 + #define SOCKET_VALID(x) (x > 0) + #define SOCKET_SETUP() (void)0 + #define SOCKET_CLEANUP() (void)0 +#else + #define SOCKET_ETIME WSAETIMEDOUT + #define SOCKET_ECONNRESET WSAECONNRESET + #define SOCKET_ERRNO() WSAGetLastError() + #define SOCKET_RESET(x) x=INVALID_SOCKET + #define SOCKET_CLOSE(x) if(x!=INVALID_SOCKET){::closesocket(x);x=INVALID_SOCKET;} + #define SOCKET_VALID(x) (x!=INVALID_SOCKET) + #define SOCKET_BUFF(x) ((char*)x) + #define SOCKET_SETUP() socket_setup() + #define SOCKET_CLEANUP() socket_cleanup() +#endif + +// for srs-librtmp, @see https://github.com/simple-rtmp-server/srs/issues/213 +#ifndef _WIN32 + #include + #include + #include + #include + #include +#endif + #include -#include -#include -#include #include -#include #include @@ -39,188 +71,310 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define ST_UTIME_NO_TIMEOUT -1 #endif +// when io not hijacked, use simple socket, the block sync stream. +#ifndef SRS_HIJACK_IO + struct SrsBlockSyncSocket + { + SOCKET fd; + int64_t recv_timeout; + int64_t send_timeout; + int64_t recv_bytes; + int64_t send_bytes; + + SrsBlockSyncSocket() { + send_timeout = recv_timeout = ST_UTIME_NO_TIMEOUT; + recv_bytes = send_bytes = 0; + + SOCKET_RESET(fd); + SOCKET_SETUP(); + } + + virtual ~SrsBlockSyncSocket() { + SOCKET_CLOSE(fd); + SOCKET_CLEANUP(); + } + }; + srs_hijack_io_t srs_hijack_io_create() + { + SrsBlockSyncSocket* skt = new SrsBlockSyncSocket(); + return skt; + } + void srs_hijack_io_destroy(srs_hijack_io_t ctx) + { + SrsBlockSyncSocket* skt = (SrsBlockSyncSocket*)ctx; + srs_freep(skt); + } + int srs_hijack_io_create_socket(srs_hijack_io_t ctx) + { + SrsBlockSyncSocket* skt = (SrsBlockSyncSocket*)ctx; + + skt->fd = ::socket(AF_INET, SOCK_STREAM, 0); + if (!SOCKET_VALID(skt->fd)) { + return ERROR_SOCKET_CREATE; + } + + return ERROR_SUCCESS; + } + int srs_hijack_io_connect(srs_hijack_io_t ctx, const char* server_ip, int port) + { + SrsBlockSyncSocket* skt = (SrsBlockSyncSocket*)ctx; + + sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = inet_addr(server_ip); + + if(::connect(skt->fd, (const struct sockaddr*)&addr, sizeof(sockaddr_in)) < 0){ + return ERROR_SOCKET_CONNECT; + } + + return ERROR_SUCCESS; + } + int srs_hijack_io_read(srs_hijack_io_t ctx, void* buf, size_t size, ssize_t* nread) + { + SrsBlockSyncSocket* skt = (SrsBlockSyncSocket*)ctx; + + int ret = ERROR_SUCCESS; + + ssize_t nb_read = ::recv(skt->fd, (char*)buf, size, 0); + + if (nread) { + *nread = nb_read; + } + + // On success a non-negative integer indicating the number of bytes actually read is returned + // (a value of 0 means the network connection is closed or end of file is reached). + if (nb_read <= 0) { + if (nb_read < 0 && SOCKET_ERRNO() == SOCKET_ETIME) { + return ERROR_SOCKET_TIMEOUT; + } + + if (nb_read == 0) { + errno = SOCKET_ECONNRESET; + } + + return ERROR_SOCKET_READ; + } + + skt->recv_bytes += nb_read; + + return ret; + } + void srs_hijack_io_set_recv_timeout(srs_hijack_io_t ctx, int64_t timeout_us) + { + SrsBlockSyncSocket* skt = (SrsBlockSyncSocket*)ctx; + skt->recv_timeout = timeout_us; + } + int64_t srs_hijack_io_get_recv_timeout(srs_hijack_io_t ctx) + { + SrsBlockSyncSocket* skt = (SrsBlockSyncSocket*)ctx; + return skt->recv_timeout; + } + int64_t srs_hijack_io_get_recv_bytes(srs_hijack_io_t ctx) + { + SrsBlockSyncSocket* skt = (SrsBlockSyncSocket*)ctx; + return skt->recv_bytes; + } + void srs_hijack_io_set_send_timeout(srs_hijack_io_t ctx, int64_t timeout_us) + { + SrsBlockSyncSocket* skt = (SrsBlockSyncSocket*)ctx; + skt->send_timeout = timeout_us; + } + int64_t srs_hijack_io_get_send_timeout(srs_hijack_io_t ctx) + { + SrsBlockSyncSocket* skt = (SrsBlockSyncSocket*)ctx; + return skt->send_timeout; + } + int64_t srs_hijack_io_get_send_bytes(srs_hijack_io_t ctx) + { + SrsBlockSyncSocket* skt = (SrsBlockSyncSocket*)ctx; + return skt->send_bytes; + } + int srs_hijack_io_writev(srs_hijack_io_t ctx, const iovec *iov, int iov_size, ssize_t* nwrite) + { + SrsBlockSyncSocket* skt = (SrsBlockSyncSocket*)ctx; + + int ret = ERROR_SUCCESS; + + ssize_t nb_write = ::writev(skt->fd, iov, iov_size); + + if (nwrite) { + *nwrite = nb_write; + } + + // On success, the readv() function returns the number of bytes read; + // the writev() function returns the number of bytes written. On error, -1 is + // returned, and errno is set appropriately. + if (nb_write <= 0) { + // @see https://github.com/simple-rtmp-server/srs/issues/200 + if (nb_write < 0 && SOCKET_ERRNO() == SOCKET_ETIME) { + return ERROR_SOCKET_TIMEOUT; + } + + return ERROR_SOCKET_WRITE; + } + + skt->send_bytes += nb_write; + + return ret; + } + bool srs_hijack_io_is_never_timeout(srs_hijack_io_t ctx, int64_t timeout_us) + { + return timeout_us == (int64_t)ST_UTIME_NO_TIMEOUT; + } + int srs_hijack_io_read_fully(srs_hijack_io_t ctx, void* buf, size_t size, ssize_t* nread) + { + SrsBlockSyncSocket* skt = (SrsBlockSyncSocket*)ctx; + + int ret = ERROR_SUCCESS; + + size_t left = size; + ssize_t nb_read = 0; + + while (left > 0) { + char* this_buf = (char*)buf + nb_read; + ssize_t this_nread; + + if ((ret = srs_hijack_io_read(ctx, this_buf, left, &this_nread)) != ERROR_SUCCESS) { + return ret; + } + + nb_read += this_nread; + left -= (size_t)this_nread; + } + + if (nread) { + *nread = nb_read; + } + skt->recv_bytes += nb_read; + + return ret; + } + int srs_hijack_io_write(srs_hijack_io_t ctx, void* buf, size_t size, ssize_t* nwrite) + { + SrsBlockSyncSocket* skt = (SrsBlockSyncSocket*)ctx; + + int ret = ERROR_SUCCESS; + + ssize_t nb_write = ::send(skt->fd, (char*)buf, size, 0); + + if (nwrite) { + *nwrite = nb_write; + } + + if (nb_write <= 0) { + // @see https://github.com/simple-rtmp-server/srs/issues/200 + if (nb_write < 0 && SOCKET_ERRNO() == SOCKET_ETIME) { + return ERROR_SOCKET_TIMEOUT; + } + + return ERROR_SOCKET_WRITE; + } + + skt->send_bytes += nb_write; + + return ret; + } +#endif + SimpleSocketStream::SimpleSocketStream() { - fd = -1; - send_timeout = recv_timeout = ST_UTIME_NO_TIMEOUT; - recv_bytes = send_bytes = 0; + io = srs_hijack_io_create(); } SimpleSocketStream::~SimpleSocketStream() { - if (fd != -1) { - ::close(fd); - fd = -1; + if (io) { + srs_hijack_io_destroy(io); + io = NULL; } } -int SimpleSocketStream::create_socket() +srs_hijack_io_t SimpleSocketStream::hijack_io() { - if((fd = ::socket(AF_INET, SOCK_STREAM, 0)) < 0){ - return ERROR_SOCKET_CREATE; - } + return io; +} - return ERROR_SUCCESS; +int SimpleSocketStream::create_socket() +{ + srs_assert(io); + return srs_hijack_io_create_socket(io); } int SimpleSocketStream::connect(const char* server_ip, int port) { - sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - addr.sin_addr.s_addr = inet_addr(server_ip); - - if(::connect(fd, (const struct sockaddr*)&addr, sizeof(sockaddr_in)) < 0){ - return ERROR_SOCKET_CONNECT; - } - - return ERROR_SUCCESS; + srs_assert(io); + return srs_hijack_io_connect(io, server_ip, port); } // ISrsBufferReader int SimpleSocketStream::read(void* buf, size_t size, ssize_t* nread) { - int ret = ERROR_SUCCESS; - - ssize_t nb_read = ::recv(fd, buf, size, 0); - - if (nread) { - *nread = nb_read; - } - - // On success a non-negative integer indicating the number of bytes actually read is returned - // (a value of 0 means the network connection is closed or end of file is reached). - if (nb_read <= 0) { - if (nb_read < 0 && errno == ETIME) { - return ERROR_SOCKET_TIMEOUT; - } - - if (nb_read == 0) { - errno = ECONNRESET; - } - - return ERROR_SOCKET_READ; - } - - recv_bytes += nb_read; - - return ret; + srs_assert(io); + return srs_hijack_io_read(io, buf, size, nread); } // ISrsProtocolReader void SimpleSocketStream::set_recv_timeout(int64_t timeout_us) { - recv_timeout = timeout_us; + srs_assert(io); + srs_hijack_io_set_recv_timeout(io, timeout_us); } int64_t SimpleSocketStream::get_recv_timeout() { - return recv_timeout; + srs_assert(io); + return srs_hijack_io_get_recv_timeout(io); } int64_t SimpleSocketStream::get_recv_bytes() { - return recv_bytes; + srs_assert(io); + return srs_hijack_io_get_recv_bytes(io); } // ISrsProtocolWriter void SimpleSocketStream::set_send_timeout(int64_t timeout_us) { - send_timeout = timeout_us; + srs_assert(io); + srs_hijack_io_set_send_timeout(io, timeout_us); } int64_t SimpleSocketStream::get_send_timeout() { - return send_timeout; + srs_assert(io); + return srs_hijack_io_get_send_timeout(io); } int64_t SimpleSocketStream::get_send_bytes() { - return send_bytes; + srs_assert(io); + return srs_hijack_io_get_send_bytes(io); } int SimpleSocketStream::writev(const iovec *iov, int iov_size, ssize_t* nwrite) { - int ret = ERROR_SUCCESS; - - ssize_t nb_write = ::writev(fd, iov, iov_size); - - if (nwrite) { - *nwrite = nb_write; - } - - // On success, the readv() function returns the number of bytes read; - // the writev() function returns the number of bytes written. On error, -1 is - // returned, and errno is set appropriately. - if (nb_write <= 0) { - // @see https://github.com/winlinvip/simple-rtmp-server/issues/200 - if (nb_write < 0 && errno == ETIME) { - return ERROR_SOCKET_TIMEOUT; - } - - return ERROR_SOCKET_WRITE; - } - - send_bytes += nb_write; - - return ret; + srs_assert(io); + return srs_hijack_io_writev(io, iov, iov_size, nwrite); } // ISrsProtocolReaderWriter bool SimpleSocketStream::is_never_timeout(int64_t timeout_us) { - return timeout_us == (int64_t)ST_UTIME_NO_TIMEOUT; + srs_assert(io); + return srs_hijack_io_is_never_timeout(io, timeout_us); } int SimpleSocketStream::read_fully(void* buf, size_t size, ssize_t* nread) { - int ret = ERROR_SUCCESS; - - size_t left = size; - ssize_t nb_read = 0; - - while (left > 0) { - char* this_buf = (char*)buf + nb_read; - ssize_t this_nread; - - if ((ret = this->read(this_buf, left, &this_nread)) != ERROR_SUCCESS) { - return ret; - } - - nb_read += this_nread; - left -= this_nread; - } - - if (nread) { - *nread = nb_read; - } - recv_bytes += nb_read; - - return ret; + srs_assert(io); + return srs_hijack_io_read_fully(io, buf, size, nread); } int SimpleSocketStream::write(void* buf, size_t size, ssize_t* nwrite) { - int ret = ERROR_SUCCESS; - - ssize_t nb_write = ::send(fd, (void*)buf, size, 0); - - if (nwrite) { - *nwrite = nb_write; - } - - if (nb_write <= 0) { - // @see https://github.com/winlinvip/simple-rtmp-server/issues/200 - if (nb_write < 0 && errno == ETIME) { - return ERROR_SOCKET_TIMEOUT; - } - - return ERROR_SOCKET_WRITE; - } - - send_bytes += nb_write; - - return ret; + srs_assert(io); + return srs_hijack_io_write(io, buf, size, nwrite); } diff --git a/trunk/src/libs/srs_lib_simple_socket.hpp b/trunk/src/libs/srs_lib_simple_socket.hpp index 2aa32bd391..c401c7c9f2 100644 --- a/trunk/src/libs/srs_lib_simple_socket.hpp +++ b/trunk/src/libs/srs_lib_simple_socket.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -30,8 +30,14 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include -#include - +#include +#include + +// for srs-librtmp, @see https://github.com/simple-rtmp-server/srs/issues/213 +#ifndef _WIN32 + #define SOCKET int +#endif + /** * simple socket stream, * use tcp socket, sync block mode, for client like srs-librtmp. @@ -39,15 +45,12 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. class SimpleSocketStream : public ISrsProtocolReaderWriter { private: - int64_t recv_timeout; - int64_t send_timeout; - int64_t recv_bytes; - int64_t send_bytes; - int fd; + srs_hijack_io_t io; public: SimpleSocketStream(); virtual ~SimpleSocketStream(); public: + virtual srs_hijack_io_t hijack_io(); virtual int create_socket(); virtual int connect(const char* server, int port); // ISrsBufferReader diff --git a/trunk/src/libs/srs_librtmp.cpp b/trunk/src/libs/srs_librtmp.cpp index 33aaa10a43..4b3bb4215d 100644 --- a/trunk/src/libs/srs_librtmp.cpp +++ b/trunk/src/libs/srs_librtmp.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -25,31 +25,33 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include +// for srs-librtmp, @see https://github.com/simple-rtmp-server/srs/issues/213 +#ifndef _WIN32 +#include +#endif + #include #include using namespace std; #include -#include +#include #include -#include -#include +#include #include -#include +#include #include #include -#include +#include #include #include #include #include +#include -// if user want to define log, define the folowing macro. -#ifndef SRS_RTMP_USER_DEFINED_LOG - // kernel module. - ISrsLog* _srs_log = new ISrsLog(); - ISrsThreadContext* _srs_context = new ISrsThreadContext(); -#endif +// kernel module. +ISrsLog* _srs_log = new ISrsLog(); +ISrsThreadContext* _srs_context = new ISrsThreadContext(); /** * export runtime context. @@ -65,22 +67,398 @@ struct Context std::string app; std::string stream; std::string param; + + // extra request object for connect to server, NULL to ignore. + SrsRequest* req; + + // the message received cache, + // for example, when got aggregate message, + // the context will parse to videos/audios, + // and return one by one. + std::vector msgs; SrsRtmpClient* rtmp; SimpleSocketStream* skt; int stream_id; + // the remux raw codec. + SrsRawH264Stream avc_raw; + SrsRawAacStream aac_raw; + + // for h264 raw stream, + // @see: https://github.com/simple-rtmp-server/srs/issues/66#issuecomment-62240521 + SrsStream h264_raw_stream; + // about SPS, @see: 7.3.2.1.1, H.264-AVC-ISO_IEC_14496-10-2012.pdf, page 62 + std::string h264_sps; + std::string h264_pps; + // whether the sps and pps sent, + // @see https://github.com/simple-rtmp-server/srs/issues/203 + bool h264_sps_pps_sent; + // only send the ssp and pps when both changed. + // @see https://github.com/simple-rtmp-server/srs/issues/204 + bool h264_sps_changed; + bool h264_pps_changed; + // for aac raw stream, + // @see: https://github.com/simple-rtmp-server/srs/issues/212#issuecomment-64146250 + SrsStream aac_raw_stream; + // the aac sequence header. + std::string aac_specific_config; + Context() { rtmp = NULL; skt = NULL; + req = NULL; stream_id = 0; + h264_sps_pps_sent = false; + h264_sps_changed = false; + h264_pps_changed = false; } virtual ~Context() { + srs_freep(req); srs_freep(rtmp); srs_freep(skt); + + std::vector::iterator it; + for (it = msgs.begin(); it != msgs.end(); ++it) { + SrsCommonMessage* msg = *it; + srs_freep(msg); + } + msgs.clear(); } }; +// for srs-librtmp, @see https://github.com/simple-rtmp-server/srs/issues/213 +#ifdef _WIN32 + int gettimeofday(struct timeval* tv, struct timezone* tz) + { + time_t clock; + struct tm tm; + SYSTEMTIME win_time; + + GetLocalTime(&win_time); + + tm.tm_year = win_time.wYear - 1900; + tm.tm_mon = win_time.wMonth - 1; + tm.tm_mday = win_time.wDay; + tm.tm_hour = win_time.wHour; + tm.tm_min = win_time.wMinute; + tm.tm_sec = win_time.wSecond; + tm.tm_isdst = -1; + + clock = mktime(&tm); + + tv->tv_sec = (long)clock; + tv->tv_usec = win_time.wMilliseconds * 1000; + + return 0; + } + + int socket_setup() + { + WORD wVersionRequested; + WSADATA wsaData; + int err; + + /* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */ + wVersionRequested = MAKEWORD(2, 2); + + err = WSAStartup(wVersionRequested, &wsaData); + if (err != 0) { + /* Tell the user that we could not find a usable */ + /* Winsock DLL. */ + //printf("WSAStartup failed with error: %d\n", err); + return -1; + } + return 0; + } + + int socket_cleanup() + { + WSACleanup(); + return 0; + } + + pid_t getpid(void) + { + return (pid_t)GetCurrentProcessId(); + } + + int usleep(useconds_t usec) + { + Sleep((DWORD)(usec / 1000)); + return 0; + } + + ssize_t writev(int fd, const struct iovec *iov, int iovcnt) + { + ssize_t nwrite = 0; + for (int i = 0; i < iovcnt; i++) { + const struct iovec* current = iov + i; + + int nsent = ::send(fd, (char*)current->iov_base, current->iov_len, 0); + if (nsent < 0) { + return nsent; + } + + nwrite += nsent; + if (nsent == 0) { + return nwrite; + } + } + return nwrite; + } + + //////////////////////// strlcpy.c (modified) ////////////////////////// + + /* $OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 millert Exp $ */ + + /*- + * Copyright (c) 1998 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + //#include // **** + //#include // **** + // __FBSDID("$FreeBSD: stable/9/sys/libkern/strlcpy.c 243811 2012-12-03 18:08:44Z delphij $"); // **** + + // #include // **** + // #include // **** + + /* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ + + //#define __restrict // **** + + std::size_t strlcpy(char * __restrict dst, const char * __restrict src, size_t siz) + { + char *d = dst; + const char *s = src; + size_t n = siz; + + /* Copy as many bytes as will fit */ + if (n != 0) { + while (--n != 0) { + if ((*d++ = *s++) == '\0') + break; + } + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + + return(s - src - 1); /* count does not include NUL */ + } + + // http://www.cplusplus.com/forum/general/141779///////////////////////// inet_ntop.c (modified) ////////////////////////// + /* + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1996-1999 by Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + // #if defined(LIBC_SCCS) && !defined(lint) // **** + //static const char rcsid[] = "$Id: inet_ntop.c,v 1.3.18.2 2005/11/03 23:02:22 marka Exp $"; + // #endif /* LIBC_SCCS and not lint */ // **** + // #include // **** + // __FBSDID("$FreeBSD: stable/9/sys/libkern/inet_ntop.c 213103 2010-09-24 15:01:45Z attilio $"); // **** + + //#define _WIN32_WINNT _WIN32_WINNT_WIN8 // **** + //#include // **** + #pragma comment(lib, "Ws2_32.lib") // **** + //#include // **** + + // #include // **** + // #include // **** + // #include // **** + + // #include // **** + + /*% + * WARNING: Don't even consider trying to compile this on a system where + * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX. + */ + + static char *inet_ntop4(const u_char *src, char *dst, socklen_t size); + static char *inet_ntop6(const u_char *src, char *dst, socklen_t size); + + /* char * + * inet_ntop(af, src, dst, size) + * convert a network format address to presentation format. + * return: + * pointer to presentation format address (`dst'), or NULL (see errno). + * author: + * Paul Vixie, 1996. + */ + const char* inet_ntop(int af, const void *src, char *dst, socklen_t size) + { + switch (af) { + case AF_INET: + return (inet_ntop4( (unsigned char*)src, (char*)dst, size)); // **** + #ifdef AF_INET6 + #error "IPv6 not supported" + //case AF_INET6: + // return (char*)(inet_ntop6( (unsigned char*)src, (char*)dst, size)); // **** + #endif + default: + // return (NULL); // **** + return 0 ; // **** + } + /* NOTREACHED */ + } + + /* const char * + * inet_ntop4(src, dst, size) + * format an IPv4 address + * return: + * `dst' (as a const) + * notes: + * (1) uses no statics + * (2) takes a u_char* not an in_addr as input + * author: + * Paul Vixie, 1996. + */ + static char * inet_ntop4(const u_char *src, char *dst, socklen_t size) + { + static const char fmt[128] = "%u.%u.%u.%u"; + char tmp[sizeof "255.255.255.255"]; + int l; + + l = snprintf(tmp, sizeof(tmp), fmt, src[0], src[1], src[2], src[3]); // **** + if (l <= 0 || (socklen_t) l >= size) { + return (NULL); + } + strlcpy(dst, tmp, size); + return (dst); + } + + /* const char * + * inet_ntop6(src, dst, size) + * convert IPv6 binary address into presentation (printable) format + * author: + * Paul Vixie, 1996. + */ + static char * inet_ntop6(const u_char *src, char *dst, socklen_t size) + { + /* + * Note that int32_t and int16_t need only be "at least" large enough + * to contain a value of the specified size. On some systems, like + * Crays, there is no such thing as an integer variable with 16 bits. + * Keep this in mind if you think this function should have been coded + * to use pointer overlays. All the world's not a VAX. + */ + char tmp[sizeof "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255"], *tp; + struct { int base, len; } best, cur; + #define NS_IN6ADDRSZ 16 + #define NS_INT16SZ 2 + u_int words[NS_IN6ADDRSZ / NS_INT16SZ]; + int i; + + /* + * Preprocess: + * Copy the input (bytewise) array into a wordwise array. + * Find the longest run of 0x00's in src[] for :: shorthanding. + */ + memset(words, '\0', sizeof words); + for (i = 0; i < NS_IN6ADDRSZ; i++) + words[i / 2] |= (src[i] << ((1 - (i % 2)) << 3)); + best.base = -1; + best.len = 0; + cur.base = -1; + cur.len = 0; + for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) { + if (words[i] == 0) { + if (cur.base == -1) + cur.base = i, cur.len = 1; + else + cur.len++; + } else { + if (cur.base != -1) { + if (best.base == -1 || cur.len > best.len) + best = cur; + cur.base = -1; + } + } + } + if (cur.base != -1) { + if (best.base == -1 || cur.len > best.len) + best = cur; + } + if (best.base != -1 && best.len < 2) + best.base = -1; + + /* + * Format the result. + */ + tp = tmp; + for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) { + /* Are we inside the best run of 0x00's? */ + if (best.base != -1 && i >= best.base && + i < (best.base + best.len)) { + if (i == best.base) + *tp++ = ':'; + continue; + } + /* Are we following an initial run of 0x00s or any real hex? */ + if (i != 0) + *tp++ = ':'; + /* Is this address an encapsulated IPv4? */ + if (i == 6 && best.base == 0 && (best.len == 6 || + (best.len == 7 && words[7] != 0x0001) || + (best.len == 5 && words[5] == 0xffff))) { + if (!inet_ntop4(src+12, tp, sizeof tmp - (tp - tmp))) + return (NULL); + tp += strlen(tp); + break; + } + tp += std::sprintf(tp, "%x", words[i]); // **** + } + /* Was it a trailing run of 0x00's? */ + if (best.base != -1 && (best.base + best.len) == + (NS_IN6ADDRSZ / NS_INT16SZ)) + *tp++ = ':'; + *tp++ = '\0'; + + /* + * Check for overflow, copy, and we're done. + */ + if ((socklen_t)(tp - tmp) > size) { + return (NULL); + } + strcpy(dst, tmp); + return (dst); + } +#endif + int srs_librtmp_context_parse_uri(Context* context) { int ret = ERROR_SUCCESS; @@ -143,6 +521,21 @@ int srs_librtmp_context_connect(Context* context) extern "C"{ #endif +int srs_version_major() +{ + return VERSION_MAJOR; +} + +int srs_version_minor() +{ + return VERSION_MINOR; +} + +int srs_version_revision() +{ + return VERSION_REVISION; +} + srs_rtmp_t srs_rtmp_create(const char* url) { Context* context = new Context(); @@ -164,32 +557,35 @@ srs_rtmp_t srs_rtmp_create2(const char* url) void srs_rtmp_destroy(srs_rtmp_t rtmp) { - srs_assert(rtmp != NULL); + if (!rtmp) { + return; + } + Context* context = (Context*)rtmp; srs_freep(context); } -int srs_simple_handshake(srs_rtmp_t rtmp) +int srs_rtmp_handshake(srs_rtmp_t rtmp) { int ret = ERROR_SUCCESS; - if ((ret = __srs_dns_resolve(rtmp)) != ERROR_SUCCESS) { + if ((ret = srs_rtmp_dns_resolve(rtmp)) != ERROR_SUCCESS) { return ret; } - if ((ret = __srs_connect_server(rtmp)) != ERROR_SUCCESS) { + if ((ret = srs_rtmp_connect_server(rtmp)) != ERROR_SUCCESS) { return ret; } - if ((ret = __srs_do_simple_handshake(rtmp)) != ERROR_SUCCESS) { + if ((ret = srs_rtmp_do_simple_handshake(rtmp)) != ERROR_SUCCESS) { return ret; } return ret; } -int __srs_dns_resolve(srs_rtmp_t rtmp) +int srs_rtmp_dns_resolve(srs_rtmp_t rtmp) { int ret = ERROR_SUCCESS; @@ -208,7 +604,7 @@ int __srs_dns_resolve(srs_rtmp_t rtmp) return ret; } -int __srs_connect_server(srs_rtmp_t rtmp) +int srs_rtmp_connect_server(srs_rtmp_t rtmp) { int ret = ERROR_SUCCESS; @@ -222,7 +618,59 @@ int __srs_connect_server(srs_rtmp_t rtmp) return ret; } -int __srs_do_simple_handshake(srs_rtmp_t rtmp) +int srs_rtmp_do_complex_handshake(srs_rtmp_t rtmp) +{ +#ifndef SRS_AUTO_SSL + // complex handshake requires ssl + return ERROR_RTMP_HS_SSL_REQUIRE; +#endif + + int ret = ERROR_SUCCESS; + + srs_assert(rtmp != NULL); + Context* context = (Context*)rtmp; + + srs_assert(context->skt != NULL); + + // simple handshake + srs_freep(context->rtmp); + context->rtmp = new SrsRtmpClient(context->skt); + + if ((ret = context->rtmp->complex_handshake()) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +int srs_rtmp_set_connect_args(srs_rtmp_t rtmp, + const char* tcUrl, const char* swfUrl, const char* pageUrl, srs_amf0_t args +) { + int ret = ERROR_SUCCESS; + + srs_assert(rtmp != NULL); + Context* context = (Context*)rtmp; + + srs_freep(context->req); + context->req = new SrsRequest(); + + if (args) { + context->req->args = (SrsAmf0Object*)args; + } + if (tcUrl) { + context->req->tcUrl = tcUrl; + } + if (swfUrl) { + context->req->swfUrl = swfUrl; + } + if (pageUrl) { + context->req->pageUrl = pageUrl; + } + + return ret; +} + +int srs_rtmp_do_simple_handshake(srs_rtmp_t rtmp) { int ret = ERROR_SUCCESS; @@ -242,7 +690,7 @@ int __srs_do_simple_handshake(srs_rtmp_t rtmp) return ret; } -int srs_connect_app(srs_rtmp_t rtmp) +int srs_rtmp_connect_app(srs_rtmp_t rtmp) { int ret = ERROR_SUCCESS; @@ -255,7 +703,7 @@ int srs_connect_app(srs_rtmp_t rtmp) ); if ((ret = context->rtmp->connect_app( - context->app, tcUrl, NULL, true)) != ERROR_SUCCESS) + context->app, tcUrl, context->req, true)) != ERROR_SUCCESS) { return ret; } @@ -263,7 +711,7 @@ int srs_connect_app(srs_rtmp_t rtmp) return ret; } -int srs_connect_app2(srs_rtmp_t rtmp, +int srs_rtmp_connect_app2(srs_rtmp_t rtmp, char srs_server_ip[128],char srs_server[128], char srs_primary[128], char srs_authors[128], char srs_version[32], int* srs_id, int* srs_pid @@ -302,7 +750,7 @@ int srs_connect_app2(srs_rtmp_t rtmp, return ret; } -int srs_play_stream(srs_rtmp_t rtmp) +int srs_rtmp_play_stream(srs_rtmp_t rtmp) { int ret = ERROR_SUCCESS; @@ -319,7 +767,7 @@ int srs_play_stream(srs_rtmp_t rtmp) return ret; } -int srs_publish_stream(srs_rtmp_t rtmp) +int srs_rtmp_publish_stream(srs_rtmp_t rtmp) { int ret = ERROR_SUCCESS; @@ -333,24 +781,7 @@ int srs_publish_stream(srs_rtmp_t rtmp) return ret; } -const char* srs_type2string(int type) -{ - static const char* audio = "Audio"; - static const char* video = "Video"; - static const char* data = "Data"; - static const char* unknown = "Unknown"; - - switch (type) { - case SRS_RTMP_TYPE_AUDIO: return audio; - case SRS_RTMP_TYPE_VIDEO: return video; - case SRS_RTMP_TYPE_SCRIPT: return data; - default: return unknown; - } - - return unknown; -} - -int srs_bandwidth_check(srs_rtmp_t rtmp, +int srs_rtmp_bandwidth_check(srs_rtmp_t rtmp, int64_t* start_time, int64_t* end_time, int* play_kbps, int* publish_kbps, int* play_bytes, int* publish_bytes, @@ -386,173 +817,649 @@ int srs_bandwidth_check(srs_rtmp_t rtmp, return ret; } -int srs_read_packet(srs_rtmp_t rtmp, int* type, u_int32_t* timestamp, char** data, int* size) + +int srs_rtmp_on_aggregate(Context* context, SrsCommonMessage* msg) { - *type = 0; - *timestamp = 0; - *data = NULL; - *size = 0; - int ret = ERROR_SUCCESS; - srs_assert(rtmp != NULL); - Context* context = (Context*)rtmp; + SrsStream aggregate_stream; + SrsStream* stream = &aggregate_stream; + if ((ret = stream->initialize(msg->payload, msg->size)) != ERROR_SUCCESS) { + return ret; + } - for (;;) { - SrsMessage* msg = NULL; - if ((ret = context->rtmp->recv_message(&msg)) != ERROR_SUCCESS) { + // the aggregate message always use abs time. + int delta = -1; + + while (!stream->empty()) { + if (!stream->require(1)) { + ret = ERROR_RTMP_AGGREGATE; + srs_error("invalid aggregate message type. ret=%d", ret); return ret; } - if (!msg) { - continue; + int8_t type = stream->read_1bytes(); + + if (!stream->require(3)) { + ret = ERROR_RTMP_AGGREGATE; + srs_error("invalid aggregate message size. ret=%d", ret); + return ret; } + int32_t data_size = stream->read_3bytes(); - SrsAutoFree(SrsMessage, msg); + if (data_size < 0) { + ret = ERROR_RTMP_AGGREGATE; + srs_error("invalid aggregate message size(negative). ret=%d", ret); + return ret; + } - if (msg->header.is_audio()) { - *type = SRS_RTMP_TYPE_AUDIO; - *timestamp = (u_int32_t)msg->header.timestamp; - *data = (char*)msg->payload; - *size = (int)msg->size; - // detach bytes from packet. - msg->payload = NULL; - } else if (msg->header.is_video()) { - *type = SRS_RTMP_TYPE_VIDEO; - *timestamp = (u_int32_t)msg->header.timestamp; - *data = (char*)msg->payload; - *size = (int)msg->size; - // detach bytes from packet. - msg->payload = NULL; - } else if (msg->header.is_amf0_data() || msg->header.is_amf3_data()) { - *type = SRS_RTMP_TYPE_SCRIPT; - *data = (char*)msg->payload; - *size = (int)msg->size; - // detach bytes from packet. - msg->payload = NULL; - } else { - // ignore and continue - continue; + if (!stream->require(3)) { + ret = ERROR_RTMP_AGGREGATE; + srs_error("invalid aggregate message time. ret=%d", ret); + return ret; } + int32_t timestamp = stream->read_3bytes(); - // got expected message. - break; + if (!stream->require(1)) { + ret = ERROR_RTMP_AGGREGATE; + srs_error("invalid aggregate message time(high). ret=%d", ret); + return ret; + } + int32_t time_h = stream->read_1bytes(); + + timestamp |= time_h<<24; + timestamp &= 0x7FFFFFFF; + + // adjust abs timestamp in aggregate msg. + if (delta < 0) { + delta = (int)msg->header.timestamp - (int)timestamp; + } + timestamp += delta; + + if (!stream->require(3)) { + ret = ERROR_RTMP_AGGREGATE; + srs_error("invalid aggregate message stream_id. ret=%d", ret); + return ret; + } + int32_t stream_id = stream->read_3bytes(); + + if (data_size > 0 && !stream->require(data_size)) { + ret = ERROR_RTMP_AGGREGATE; + srs_error("invalid aggregate message data. ret=%d", ret); + return ret; + } + + // to common message. + SrsCommonMessage o; + + o.header.message_type = type; + o.header.payload_length = data_size; + o.header.timestamp_delta = timestamp; + o.header.timestamp = timestamp; + o.header.stream_id = stream_id; + o.header.perfer_cid = msg->header.perfer_cid; + + if (data_size > 0) { + o.size = data_size; + o.payload = new char[o.size]; + stream->read_bytes(o.payload, o.size); + } + + if (!stream->require(4)) { + ret = ERROR_RTMP_AGGREGATE; + srs_error("invalid aggregate message previous tag size. ret=%d", ret); + return ret; + } + stream->read_4bytes(); + + // process parsed message + SrsCommonMessage* parsed_msg = new SrsCommonMessage(); + parsed_msg->header = o.header; + parsed_msg->payload = o.payload; + parsed_msg->size = o.size; + o.payload = NULL; + context->msgs.push_back(parsed_msg); + } + + return ret; +} + +int srs_rtmp_go_packet(Context* context, SrsCommonMessage* msg, + char* type, u_int32_t* timestamp, char** data, int* size, + bool* got_msg +) { + int ret = ERROR_SUCCESS; + + // generally we got a message. + *got_msg = true; + + if (msg->header.is_audio()) { + *type = SRS_RTMP_TYPE_AUDIO; + *timestamp = (u_int32_t)msg->header.timestamp; + *data = (char*)msg->payload; + *size = (int)msg->size; + // detach bytes from packet. + msg->payload = NULL; + } else if (msg->header.is_video()) { + *type = SRS_RTMP_TYPE_VIDEO; + *timestamp = (u_int32_t)msg->header.timestamp; + *data = (char*)msg->payload; + *size = (int)msg->size; + // detach bytes from packet. + msg->payload = NULL; + } else if (msg->header.is_amf0_data() || msg->header.is_amf3_data()) { + *type = SRS_RTMP_TYPE_SCRIPT; + *data = (char*)msg->payload; + *size = (int)msg->size; + // detach bytes from packet. + msg->payload = NULL; + } else if (msg->header.is_aggregate()) { + if ((ret = srs_rtmp_on_aggregate(context, msg)) != ERROR_SUCCESS) { + return ret; + } + *got_msg = false; + } else { + *type = msg->header.message_type; + *data = (char*)msg->payload; + *size = (int)msg->size; + // detach bytes from packet. + msg->payload = NULL; } return ret; } -int srs_write_packet(srs_rtmp_t rtmp, int type, u_int32_t timestamp, char* data, int size) +int srs_rtmp_read_packet(srs_rtmp_t rtmp, char* type, u_int32_t* timestamp, char** data, int* size) { + *type = 0; + *timestamp = 0; + *data = NULL; + *size = 0; + int ret = ERROR_SUCCESS; srs_assert(rtmp != NULL); Context* context = (Context*)rtmp; - SrsSharedPtrMessage* msg = NULL; - - if (type == SRS_RTMP_TYPE_AUDIO) { - SrsMessageHeader header; - header.initialize_audio(size, timestamp, context->stream_id); + for (;;) { + SrsCommonMessage* msg = NULL; - msg = new SrsSharedPtrMessage(); - if ((ret = msg->create(&header, data, size)) != ERROR_SUCCESS) { - srs_freep(data); - return ret; + // read from cache first. + if (!context->msgs.empty()) { + std::vector::iterator it = context->msgs.begin(); + msg = *it; + context->msgs.erase(it); } - } else if (type == SRS_RTMP_TYPE_VIDEO) { - SrsMessageHeader header; - header.initialize_video(size, timestamp, context->stream_id); - msg = new SrsSharedPtrMessage(); - if ((ret = msg->create(&header, data, size)) != ERROR_SUCCESS) { - srs_freep(data); + // read from protocol sdk. + if (!msg && (ret = context->rtmp->recv_message(&msg)) != ERROR_SUCCESS) { return ret; } - } else if (type == SRS_RTMP_TYPE_SCRIPT) { - SrsMessageHeader header; - header.initialize_amf0_script(size, context->stream_id); - msg = new SrsSharedPtrMessage(); - if ((ret = msg->create(&header, data, size)) != ERROR_SUCCESS) { - srs_freep(data); - return ret; + // no msg, try again. + if (!msg) { + continue; } - } - - if (msg) { - // send out encoded msg. - if ((ret = context->rtmp->send_and_free_message(msg, context->stream_id)) != ERROR_SUCCESS) { + + SrsAutoFree(SrsCommonMessage, msg); + + // process the got packet, if nothing, try again. + bool got_msg; + if ((ret = srs_rtmp_go_packet(context, msg, type, timestamp, data, size, &got_msg)) != ERROR_SUCCESS) { return ret; } - } else { - // directly free data if not sent out. - srs_freep(data); + + // got expected message. + if (got_msg) { + break; + } } return ret; } -int srs_version_major() -{ - return VERSION_MAJOR; -} - -int srs_version_minor() -{ - return VERSION_MINOR; -} - -int srs_version_revision() -{ - return VERSION_REVISION; -} - -int64_t srs_get_time_ms() -{ - srs_update_system_time_ms(); - return srs_get_system_time_ms(); -} - -int64_t srs_get_nsend_bytes(srs_rtmp_t rtmp) +int srs_rtmp_write_packet(srs_rtmp_t rtmp, char type, u_int32_t timestamp, char* data, int size) { + int ret = ERROR_SUCCESS; + srs_assert(rtmp != NULL); Context* context = (Context*)rtmp; - return context->rtmp->get_send_bytes(); -} + + SrsSharedPtrMessage* msg = NULL; -int64_t srs_get_nrecv_bytes(srs_rtmp_t rtmp) -{ - srs_assert(rtmp != NULL); - Context* context = (Context*)rtmp; - return context->rtmp->get_recv_bytes(); -} + if ((ret = srs_rtmp_create_msg(type, timestamp, data, size, context->stream_id, &msg)) != ERROR_SUCCESS) { + return ret; + } -struct FlvContext -{ - SrsFileReader reader; - SrsFileWriter writer; - SrsFlvEncoder enc; - SrsFlvDecoder dec; -}; + srs_assert(msg); -srs_flv_t srs_flv_open_read(const char* file) + // send out encoded msg. + if ((ret = context->rtmp->send_and_free_message(msg, context->stream_id)) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +srs_bool srs_rtmp_is_onMetaData(char type, char* data, int size) { int ret = ERROR_SUCCESS; - FlvContext* flv = new FlvContext(); + if (type != SRS_RTMP_TYPE_SCRIPT) { + return false; + } - if ((ret = flv->reader.open(file)) != ERROR_SUCCESS) { - srs_freep(flv); - return NULL; + SrsStream stream; + if ((ret = stream.initialize(data, size)) != ERROR_SUCCESS) { + return false; } - if ((ret = flv->dec.initialize(&flv->reader)) != ERROR_SUCCESS) { - srs_freep(flv); - return NULL; + std::string name; + if ((ret = srs_amf0_read_string(&stream, name)) != ERROR_SUCCESS) { + return false; } - return flv; -} + if (name == SRS_CONSTS_RTMP_ON_METADATA) { + return true; + } + + if (name == SRS_CONSTS_RTMP_SET_DATAFRAME) { + return true; + } + + return false; +} + +/** +* directly write a audio frame. +*/ +int srs_write_audio_raw_frame(Context* context, + char* frame, int frame_size, SrsRawAacStreamCodec* codec, u_int32_t timestamp +) { + int ret = ERROR_SUCCESS; + + char* data = NULL; + int size = 0; + if ((ret = context->aac_raw.mux_aac2flv(frame, frame_size, codec, timestamp, &data, &size)) != ERROR_SUCCESS) { + return ret; + } + + return srs_rtmp_write_packet(context, SRS_RTMP_TYPE_AUDIO, timestamp, data, size); +} + +/** +* write aac frame in adts. +*/ +int srs_write_aac_adts_frame(Context* context, + SrsRawAacStreamCodec* codec, char* frame, int frame_size, u_int32_t timestamp +) { + int ret = ERROR_SUCCESS; + + // send out aac sequence header if not sent. + if (context->aac_specific_config.empty()) { + std::string sh; + if ((ret = context->aac_raw.mux_sequence_header(codec, sh)) != ERROR_SUCCESS) { + return ret; + } + context->aac_specific_config = sh; + + codec->aac_packet_type = 0; + + if ((ret = srs_write_audio_raw_frame(context, (char*)sh.data(), (int)sh.length(), codec, timestamp)) != ERROR_SUCCESS) { + return ret; + } + } + + codec->aac_packet_type = 1; + return srs_write_audio_raw_frame(context, frame, frame_size, codec, timestamp); +} + +/** +* write aac frames in adts. +*/ +int srs_write_aac_adts_frames(Context* context, + char sound_format, char sound_rate, char sound_size, char sound_type, + char* frames, int frames_size, u_int32_t timestamp +) { + int ret = ERROR_SUCCESS; + + SrsStream* stream = &context->aac_raw_stream; + if ((ret = stream->initialize(frames, frames_size)) != ERROR_SUCCESS) { + return ret; + } + + while (!stream->empty()) { + char* frame = NULL; + int frame_size = 0; + SrsRawAacStreamCodec codec; + if ((ret = context->aac_raw.adts_demux(stream, &frame, &frame_size, codec)) != ERROR_SUCCESS) { + return ret; + } + + // override by user specified. + codec.sound_format = sound_format; + codec.sound_rate = sound_rate; + codec.sound_size = sound_size; + codec.sound_type = sound_type; + + if ((ret = srs_write_aac_adts_frame(context, &codec, frame, frame_size, timestamp)) != ERROR_SUCCESS) { + return ret; + } + } + + return ret; +} + +/** +* write audio raw frame to SRS. +*/ +int srs_audio_write_raw_frame(srs_rtmp_t rtmp, + char sound_format, char sound_rate, char sound_size, char sound_type, + char* frame, int frame_size, u_int32_t timestamp +) { + int ret = ERROR_SUCCESS; + + Context* context = (Context*)rtmp; + srs_assert(context); + + if (sound_format == SrsCodecAudioAAC) { + // for aac, the frame must be ADTS format. + if (!srs_aac_is_adts(frame, frame_size)) { + return ERROR_AAC_REQUIRED_ADTS; + } + + // for aac, demux the ADTS to RTMP format. + return srs_write_aac_adts_frames(context, + sound_format, sound_rate, sound_size, sound_type, + frame, frame_size, timestamp); + } else { + // use codec info for aac. + SrsRawAacStreamCodec codec; + codec.sound_format = sound_format; + codec.sound_rate = sound_rate; + codec.sound_size = sound_size; + codec.sound_type = sound_type; + codec.aac_packet_type = 0; + + // for other data, directly write frame. + return srs_write_audio_raw_frame(context, frame, frame_size, &codec, timestamp); + } + + + return ret; +} + +/** +* whether aac raw data is in adts format, +* which bytes sequence matches '1111 1111 1111'B, that is 0xFFF. +*/ +srs_bool srs_aac_is_adts(char* aac_raw_data, int ac_raw_size) +{ + SrsStream stream; + if (stream.initialize(aac_raw_data, ac_raw_size) != ERROR_SUCCESS) { + return false; + } + + return srs_aac_startswith_adts(&stream); +} + +/** +* parse the adts header to get the frame size. +*/ +int srs_aac_adts_frame_size(char* aac_raw_data, int ac_raw_size) +{ + int size = -1; + + if (!srs_aac_is_adts(aac_raw_data, ac_raw_size)) { + return size; + } + + // adts always 7bytes. + if (ac_raw_size <= 7) { + return size; + } + + // last 2bits + int16_t ch3 = aac_raw_data[3]; + // whole 8bits + int16_t ch4 = aac_raw_data[4]; + // first 3bits + int16_t ch5 = aac_raw_data[5]; + + size = ((ch3 << 11) & 0x1800) | ((ch4 << 3) & 0x07f8) | ((ch5 >> 5) & 0x0007); + + return size; +} + +/** +* write h264 IPB-frame. +*/ +int srs_write_h264_ipb_frame(Context* context, + char* frame, int frame_size, u_int32_t dts, u_int32_t pts +) { + int ret = ERROR_SUCCESS; + + // when sps or pps not sent, ignore the packet. + // @see https://github.com/simple-rtmp-server/srs/issues/203 + if (!context->h264_sps_pps_sent) { + return ERROR_H264_DROP_BEFORE_SPS_PPS; + } + + // 5bits, 7.3.1 NAL unit syntax, + // H.264-AVC-ISO_IEC_14496-10.pdf, page 44. + // 7: SPS, 8: PPS, 5: I Frame, 1: P Frame + SrsAvcNaluType nal_unit_type = (SrsAvcNaluType)(frame[0] & 0x1f); + + // for IDR frame, the frame is keyframe. + SrsCodecVideoAVCFrame frame_type = SrsCodecVideoAVCFrameInterFrame; + if (nal_unit_type == SrsAvcNaluTypeIDR) { + frame_type = SrsCodecVideoAVCFrameKeyFrame; + } + + std::string ibp; + if ((ret = context->avc_raw.mux_ipb_frame(frame, frame_size, ibp)) != ERROR_SUCCESS) { + return ret; + } + + int8_t avc_packet_type = SrsCodecVideoAVCTypeNALU; + char* flv = NULL; + int nb_flv = 0; + if ((ret = context->avc_raw.mux_avc2flv(ibp, frame_type, avc_packet_type, dts, pts, &flv, &nb_flv)) != ERROR_SUCCESS) { + return ret; + } + + // the timestamp in rtmp message header is dts. + u_int32_t timestamp = dts; + return srs_rtmp_write_packet(context, SRS_RTMP_TYPE_VIDEO, timestamp, flv, nb_flv); +} + +/** +* write the h264 sps/pps in context over RTMP. +*/ +int srs_write_h264_sps_pps(Context* context, u_int32_t dts, u_int32_t pts) +{ + int ret = ERROR_SUCCESS; + + // only send when both sps and pps changed. + if (!context->h264_sps_changed || !context->h264_pps_changed) { + return ret; + } + + // h264 raw to h264 packet. + std::string sh; + if ((ret = context->avc_raw.mux_sequence_header(context->h264_sps, context->h264_pps, dts, pts, sh)) != ERROR_SUCCESS) { + return ret; + } + + // h264 packet to flv packet. + int8_t frame_type = SrsCodecVideoAVCFrameKeyFrame; + int8_t avc_packet_type = SrsCodecVideoAVCTypeSequenceHeader; + char* flv = NULL; + int nb_flv = 0; + if ((ret = context->avc_raw.mux_avc2flv(sh, frame_type, avc_packet_type, dts, pts, &flv, &nb_flv)) != ERROR_SUCCESS) { + return ret; + } + + // reset sps and pps. + context->h264_sps_changed = false; + context->h264_pps_changed = false; + context->h264_sps_pps_sent = true; + + // the timestamp in rtmp message header is dts. + u_int32_t timestamp = dts; + return srs_rtmp_write_packet(context, SRS_RTMP_TYPE_VIDEO, timestamp, flv, nb_flv); +} + +/** +* write h264 raw frame, maybe sps/pps/IPB-frame. +*/ +int srs_write_h264_raw_frame(Context* context, + char* frame, int frame_size, u_int32_t dts, u_int32_t pts +) { + int ret = ERROR_SUCCESS; + + // for sps + if (context->avc_raw.is_sps(frame, frame_size)) { + std::string sps; + if ((ret = context->avc_raw.sps_demux(frame, frame_size, sps)) != ERROR_SUCCESS) { + return ret; + } + + if (context->h264_sps == sps) { + return ERROR_H264_DUPLICATED_SPS; + } + context->h264_sps_changed = true; + context->h264_sps = sps; + + return srs_write_h264_sps_pps(context, dts, pts); + } + + // for pps + if (context->avc_raw.is_pps(frame, frame_size)) { + std::string pps; + if ((ret = context->avc_raw.pps_demux(frame, frame_size, pps)) != ERROR_SUCCESS) { + return ret; + } + + if (context->h264_pps == pps) { + return ERROR_H264_DUPLICATED_PPS; + } + context->h264_pps_changed = true; + context->h264_pps = pps; + + return srs_write_h264_sps_pps(context, dts, pts); + } + + // ibp frame. + return srs_write_h264_ipb_frame(context, frame, frame_size, dts, pts); +} + +/** +* write h264 multiple frames, in annexb format. +*/ +int srs_h264_write_raw_frames(srs_rtmp_t rtmp, + char* frames, int frames_size, u_int32_t dts, u_int32_t pts +) { + int ret = ERROR_SUCCESS; + + srs_assert(frames != NULL); + srs_assert(frames_size > 0); + + srs_assert(rtmp != NULL); + Context* context = (Context*)rtmp; + + if ((ret = context->h264_raw_stream.initialize(frames, frames_size)) != ERROR_SUCCESS) { + return ret; + } + + // use the last error + // @see https://github.com/simple-rtmp-server/srs/issues/203 + // @see https://github.com/simple-rtmp-server/srs/issues/204 + int error_code_return = ret; + + // send each frame. + while (!context->h264_raw_stream.empty()) { + char* frame = NULL; + int frame_size = 0; + if ((ret = context->avc_raw.annexb_demux(&context->h264_raw_stream, &frame, &frame_size)) != ERROR_SUCCESS) { + return ret; + } + + // ignore invalid frame, + // atleast 1bytes for SPS to decode the type + if (frame_size <= 0) { + continue; + } + + // it may be return error, but we must process all packets. + if ((ret = srs_write_h264_raw_frame(context, frame, frame_size, dts, pts)) != ERROR_SUCCESS) { + error_code_return = ret; + + // ignore known error, process all packets. + if (srs_h264_is_dvbsp_error(ret) + || srs_h264_is_duplicated_sps_error(ret) + || srs_h264_is_duplicated_pps_error(ret) + ) { + continue; + } + + return ret; + } + } + + return error_code_return; +} + +srs_bool srs_h264_is_dvbsp_error(int error_code) +{ + return error_code == ERROR_H264_DROP_BEFORE_SPS_PPS; +} + +srs_bool srs_h264_is_duplicated_sps_error(int error_code) +{ + return error_code == ERROR_H264_DUPLICATED_SPS; +} + +srs_bool srs_h264_is_duplicated_pps_error(int error_code) +{ + return error_code == ERROR_H264_DUPLICATED_PPS; +} + +srs_bool srs_h264_startswith_annexb(char* h264_raw_data, int h264_raw_size, int* pnb_start_code) +{ + SrsStream stream; + if (stream.initialize(h264_raw_data, h264_raw_size) != ERROR_SUCCESS) { + return false; + } + + return srs_avc_startswith_annexb(&stream, pnb_start_code); +} + +struct FlvContext +{ + SrsFileReader reader; + SrsFileWriter writer; + SrsFlvEncoder enc; + SrsFlvDecoder dec; +}; + +srs_flv_t srs_flv_open_read(const char* file) +{ + int ret = ERROR_SUCCESS; + + FlvContext* flv = new FlvContext(); + + if ((ret = flv->reader.open(file)) != ERROR_SUCCESS) { + srs_freep(flv); + return NULL; + } + + if ((ret = flv->dec.initialize(&flv->reader)) != ERROR_SUCCESS) { + srs_freep(flv); + return NULL; + } + + return flv; +} srs_flv_t srs_flv_open_write(const char* file) { @@ -672,7 +1579,7 @@ int srs_flv_write_tag(srs_flv_t flv, char type, int32_t time, char* data, int si } else if (type == SRS_RTMP_TYPE_VIDEO) { return context->enc.write_video(time, data, size); } else { - return context->enc.write_metadata(data, size); + return context->enc.write_metadata(type, data, size); } return ret; @@ -695,17 +1602,17 @@ void srs_flv_lseek(srs_flv_t flv, int64_t offset) context->reader.lseek(offset); } -flv_bool srs_flv_is_eof(int error_code) +srs_bool srs_flv_is_eof(int error_code) { return error_code == ERROR_SYSTEM_FILE_EOF; } -flv_bool srs_flv_is_sequence_header(char* data, int32_t size) +srs_bool srs_flv_is_sequence_header(char* data, int32_t size) { return SrsFlvCodec::video_is_sequence_header(data, (int)size); } -flv_bool srs_flv_is_keyframe(char* data, int32_t size) +srs_bool srs_flv_is_keyframe(char* data, int32_t size) { return SrsFlvCodec::video_is_keyframe(data, (int)size); } @@ -732,13 +1639,20 @@ srs_amf0_t srs_amf0_parse(char* data, int size, int* nparsed) return amf0; } - *nparsed = stream.pos(); + if (nparsed) { + *nparsed = stream.pos(); + } amf0 = (srs_amf0_t)any; return amf0; } -srs_amf0_t srs_amf0_create_number(amf0_number value) +srs_amf0_t srs_amf0_create_string(const char* value) +{ + return SrsAmf0Any::str(value); +} + +srs_amf0_t srs_amf0_create_number(srs_amf0_number value) { return SrsAmf0Any::number(value); } @@ -758,15 +1672,26 @@ srs_amf0_t srs_amf0_create_object() return SrsAmf0Any::object(); } -void srs_amf0_free(srs_amf0_t amf0) +srs_amf0_t srs_amf0_ecma_array_to_object(srs_amf0_t ecma_arr) { - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - srs_freep(any); + srs_assert(srs_amf0_is_ecma_array(ecma_arr)); + + SrsAmf0EcmaArray* arr = (SrsAmf0EcmaArray*)ecma_arr; + SrsAmf0Object* obj = SrsAmf0Any::object(); + + for (int i = 0; i < arr->count(); i++) { + std::string key = arr->key_at(i); + SrsAmf0Any* value = arr->value_at(i); + obj->set(key, value->copy()); + } + + return obj; } -void srs_amf0_free_bytes(char* data) +void srs_amf0_free(srs_amf0_t amf0) { - srs_freep(data); + SrsAmf0Any* any = (SrsAmf0Any*)amf0; + srs_freep(any); } int srs_amf0_size(srs_amf0_t amf0) @@ -793,43 +1718,43 @@ int srs_amf0_serialize(srs_amf0_t amf0, char* data, int size) return ret; } -amf0_bool srs_amf0_is_string(srs_amf0_t amf0) +srs_bool srs_amf0_is_string(srs_amf0_t amf0) { SrsAmf0Any* any = (SrsAmf0Any*)amf0; return any->is_string(); } -amf0_bool srs_amf0_is_boolean(srs_amf0_t amf0) +srs_bool srs_amf0_is_boolean(srs_amf0_t amf0) { SrsAmf0Any* any = (SrsAmf0Any*)amf0; return any->is_boolean(); } -amf0_bool srs_amf0_is_number(srs_amf0_t amf0) +srs_bool srs_amf0_is_number(srs_amf0_t amf0) { SrsAmf0Any* any = (SrsAmf0Any*)amf0; return any->is_number(); } -amf0_bool srs_amf0_is_null(srs_amf0_t amf0) +srs_bool srs_amf0_is_null(srs_amf0_t amf0) { SrsAmf0Any* any = (SrsAmf0Any*)amf0; return any->is_null(); } -amf0_bool srs_amf0_is_object(srs_amf0_t amf0) +srs_bool srs_amf0_is_object(srs_amf0_t amf0) { SrsAmf0Any* any = (SrsAmf0Any*)amf0; return any->is_object(); } -amf0_bool srs_amf0_is_ecma_array(srs_amf0_t amf0) +srs_bool srs_amf0_is_ecma_array(srs_amf0_t amf0) { SrsAmf0Any* any = (SrsAmf0Any*)amf0; return any->is_ecma_array(); } -amf0_bool srs_amf0_is_strict_array(srs_amf0_t amf0) +srs_bool srs_amf0_is_strict_array(srs_amf0_t amf0) { SrsAmf0Any* any = (SrsAmf0Any*)amf0; return any->is_strict_array(); @@ -841,19 +1766,19 @@ const char* srs_amf0_to_string(srs_amf0_t amf0) return any->to_str_raw(); } -amf0_bool srs_amf0_to_boolean(srs_amf0_t amf0) +srs_bool srs_amf0_to_boolean(srs_amf0_t amf0) { SrsAmf0Any* any = (SrsAmf0Any*)amf0; return any->to_boolean(); } -amf0_number srs_amf0_to_number(srs_amf0_t amf0) +srs_amf0_number srs_amf0_to_number(srs_amf0_t amf0) { SrsAmf0Any* any = (SrsAmf0Any*)amf0; return any->to_number(); } -void srs_amf0_set_number(srs_amf0_t amf0, amf0_number value) +void srs_amf0_set_number(srs_amf0_t amf0, srs_amf0_number value) { SrsAmf0Any* any = (SrsAmf0Any*)amf0; any->set_number(value); @@ -988,7 +1913,216 @@ void srs_amf0_strict_array_append(srs_amf0_t amf0, srs_amf0_t value) obj->append(any); } -char* srs_amf0_human_print(srs_amf0_t amf0, char** pdata, int* psize) +int64_t srs_utils_time_ms() +{ + srs_update_system_time_ms(); + return srs_get_system_time_ms(); +} + +int64_t srs_utils_send_bytes(srs_rtmp_t rtmp) +{ + srs_assert(rtmp != NULL); + Context* context = (Context*)rtmp; + return context->rtmp->get_send_bytes(); +} + +int64_t srs_utils_recv_bytes(srs_rtmp_t rtmp) +{ + srs_assert(rtmp != NULL); + Context* context = (Context*)rtmp; + return context->rtmp->get_recv_bytes(); +} + +int srs_utils_parse_timestamp( + u_int32_t time, char type, char* data, int size, + u_int32_t* ppts +) { + int ret = ERROR_SUCCESS; + + if (type != SRS_RTMP_TYPE_VIDEO) { + *ppts = time; + return ret; + } + + if (!SrsFlvCodec::video_is_h264(data, size)) { + return ERROR_FLV_INVALID_VIDEO_TAG; + } + + if (SrsFlvCodec::video_is_sequence_header(data, size)) { + *ppts = time; + return ret; + } + + // 1bytes, frame type and codec id. + // 1bytes, avc packet type. + // 3bytes, cts, composition time, + // pts = dts + cts, or + // cts = pts - dts. + if (size < 5) { + return ERROR_FLV_INVALID_VIDEO_TAG; + } + + u_int32_t cts = 0; + char* p = data + 2; + char* pp = (char*)&cts; + pp[2] = *p++; + pp[1] = *p++; + pp[0] = *p++; + + *ppts = time + cts; + + return ret; +} + +srs_bool srs_utils_flv_tag_is_ok(char type) +{ + return type == SRS_RTMP_TYPE_AUDIO || type == SRS_RTMP_TYPE_VIDEO || type == SRS_RTMP_TYPE_SCRIPT; +} + +srs_bool srs_utils_flv_tag_is_audio(char type) +{ + return type == SRS_RTMP_TYPE_AUDIO; +} + +srs_bool srs_utils_flv_tag_is_video(char type) +{ + return type == SRS_RTMP_TYPE_VIDEO; +} + +srs_bool srs_utils_flv_tag_is_av(char type) +{ + return type == SRS_RTMP_TYPE_AUDIO || type == SRS_RTMP_TYPE_VIDEO; +} + +char srs_utils_flv_video_codec_id(char* data, int size) +{ + if (size < 1) { + return 0; + } + + char codec_id = data[0]; + codec_id = codec_id & 0x0F; + + return codec_id; +} + +char srs_utils_flv_video_avc_packet_type(char* data, int size) +{ + if (size < 2) { + return -1; + } + + if (!SrsFlvCodec::video_is_h264(data, size)) { + return -1; + } + + u_int8_t avc_packet_type = data[1]; + + if (avc_packet_type > 2) { + return -1; + } + + return avc_packet_type; +} + +char srs_utils_flv_video_frame_type(char* data, int size) +{ + if (size < 1) { + return -1; + } + + if (!SrsFlvCodec::video_is_h264(data, size)) { + return -1; + } + + u_int8_t frame_type = data[0]; + frame_type = (frame_type >> 4) & 0x0f; + if (frame_type < 1 || frame_type > 5) { + return -1; + } + + return frame_type; +} + +char srs_utils_flv_audio_sound_format(char* data, int size) +{ + if (size < 1) { + return -1; + } + + u_int8_t sound_format = data[0]; + sound_format = (sound_format >> 4) & 0x0f; + if (sound_format > 15 || sound_format == 12 || sound_format == 13) { + return -1; + } + + return sound_format; +} + +char srs_utils_flv_audio_sound_rate(char* data, int size) +{ + if (size < 1) { + return -1; + } + + u_int8_t sound_rate = data[0]; + sound_rate = (sound_rate >> 2) & 0x03; + if (sound_rate > 3) { + return -1; + } + + return sound_rate; +} + +char srs_utils_flv_audio_sound_size(char* data, int size) +{ + if (size < 1) { + return -1; + } + + u_int8_t sound_size = data[0]; + sound_size = (sound_size >> 1) & 0x01; + if (sound_size > 1) { + return -1; + } + + return sound_size; +} + +char srs_utils_flv_audio_sound_type(char* data, int size) +{ + if (size < 1) { + return -1; + } + + u_int8_t sound_type = data[0]; + sound_type = sound_type & 0x01; + if (sound_type > 1) { + return -1; + } + + return sound_type; +} + +char srs_utils_flv_audio_aac_packet_type(char* data, int size) +{ + if (size < 2) { + return -1; + } + + if (srs_utils_flv_audio_sound_format(data, size) != 10) { + return -1; + } + + u_int8_t aac_packet_type = data[1]; + if (aac_packet_type > 1) { + return -1; + } + + return aac_packet_type; +} + +char* srs_human_amf0_print(srs_amf0_t amf0, char** pdata, int* psize) { if (!amf0) { return NULL; @@ -999,6 +2133,287 @@ char* srs_amf0_human_print(srs_amf0_t amf0, char** pdata, int* psize) return any->human_print(pdata, psize); } +const char* srs_human_flv_tag_type2string(char type) +{ + static const char* audio = "Audio"; + static const char* video = "Video"; + static const char* data = "Data"; + static const char* unknown = "Unknown"; + + switch (type) { + case SRS_RTMP_TYPE_AUDIO: return audio; + case SRS_RTMP_TYPE_VIDEO: return video; + case SRS_RTMP_TYPE_SCRIPT: return data; + default: return unknown; + } + + return unknown; +} + +const char* srs_human_flv_video_codec_id2string(char codec_id) +{ + static const char* h263 = "H.263"; + static const char* screen = "Screen"; + static const char* vp6 = "VP6"; + static const char* vp6_alpha = "VP6Alpha"; + static const char* screen2 = "Screen2"; + static const char* h264 = "H.264"; + static const char* unknown = "Unknown"; + + switch (codec_id) { + case 2: return h263; + case 3: return screen; + case 4: return vp6; + case 5: return vp6_alpha; + case 6: return screen2; + case 7: return h264; + default: return unknown; + } + + return unknown; +} + +const char* srs_human_flv_video_avc_packet_type2string(char avc_packet_type) +{ + static const char* sps_pps = "SH"; + static const char* nalu = "Nalu"; + static const char* sps_pps_end = "SpsPpsEnd"; + static const char* unknown = "Unknown"; + + switch (avc_packet_type) { + case 0: return sps_pps; + case 1: return nalu; + case 2: return sps_pps_end; + default: return unknown; + } + + return unknown; +} + +const char* srs_human_flv_video_frame_type2string(char frame_type) +{ + static const char* keyframe = "I"; + static const char* interframe = "P/B"; + static const char* disposable_interframe = "DI"; + static const char* generated_keyframe = "GI"; + static const char* video_infoframe = "VI"; + static const char* unknown = "Unknown"; + + switch (frame_type) { + case 1: return keyframe; + case 2: return interframe; + case 3: return disposable_interframe; + case 4: return generated_keyframe; + case 5: return video_infoframe; + default: return unknown; + } + + return unknown; +} + +const char* srs_human_flv_audio_sound_format2string(char sound_format) +{ + static const char* linear_pcm = "LinearPCM"; + static const char* ad_pcm = "ADPCM"; + static const char* mp3 = "MP3"; + static const char* linear_pcm_le = "LinearPCMLe"; + static const char* nellymoser_16khz = "NellymoserKHz16"; + static const char* nellymoser_8khz = "NellymoserKHz8"; + static const char* nellymoser = "Nellymoser"; + static const char* g711_a_pcm = "G711APCM"; + static const char* g711_mu_pcm = "G711MuPCM"; + static const char* reserved = "Reserved"; + static const char* aac = "AAC"; + static const char* speex = "Speex"; + static const char* mp3_8khz = "MP3KHz8"; + static const char* device_specific = "DeviceSpecific"; + static const char* unknown = "Unknown"; + + switch (sound_format) { + case 0: return linear_pcm; + case 1: return ad_pcm; + case 2: return mp3; + case 3: return linear_pcm_le; + case 4: return nellymoser_16khz; + case 5: return nellymoser_8khz; + case 6: return nellymoser; + case 7: return g711_a_pcm; + case 8: return g711_mu_pcm; + case 9: return reserved; + case 10: return aac; + case 11: return speex; + case 14: return mp3_8khz; + case 15: return device_specific; + default: return unknown; + } + + return unknown; +} + +const char* srs_human_flv_audio_sound_rate2string(char sound_rate) +{ + static const char* khz_5_5 = "5.5KHz"; + static const char* khz_11 = "11KHz"; + static const char* khz_22 = "22KHz"; + static const char* khz_44 = "44KHz"; + static const char* unknown = "Unknown"; + + switch (sound_rate) { + case 0: return khz_5_5; + case 1: return khz_11; + case 2: return khz_22; + case 3: return khz_44; + default: return unknown; + } + + return unknown; +} + +const char* srs_human_flv_audio_sound_size2string(char sound_size) +{ + static const char* bit_8 = "8bit"; + static const char* bit_16 = "16bit"; + static const char* unknown = "Unknown"; + + switch (sound_size) { + case 0: return bit_8; + case 1: return bit_16; + default: return unknown; + } + + return unknown; +} + +const char* srs_human_flv_audio_sound_type2string(char sound_type) +{ + static const char* mono = "Mono"; + static const char* stereo = "Stereo"; + static const char* unknown = "Unknown"; + + switch (sound_type) { + case 0: return mono; + case 1: return stereo; + default: return unknown; + } + + return unknown; +} + +const char* srs_human_flv_audio_aac_packet_type2string(char aac_packet_type) +{ + static const char* sps_pps = "SH"; + static const char* raw = "Raw"; + static const char* unknown = "Unknown"; + + switch (aac_packet_type) { + case 0: return sps_pps; + case 1: return raw; + default: return unknown; + } + + return unknown; +} + +int srs_human_print_rtmp_packet(char type, u_int32_t timestamp, char* data, int size) +{ + int ret = ERROR_SUCCESS; + + u_int32_t pts; + if (srs_utils_parse_timestamp(timestamp, type, data, size, &pts) != 0) { + srs_human_trace("Rtmp packet type=%s, dts=%d, size=%d, DecodeError", + srs_human_flv_tag_type2string(type), timestamp, size + ); + return ret; + } + + if (type == SRS_RTMP_TYPE_VIDEO) { + srs_human_trace("Video packet type=%s, dts=%d, pts=%d, size=%d, %s(%s,%s)", + srs_human_flv_tag_type2string(type), timestamp, pts, size, + srs_human_flv_video_codec_id2string(srs_utils_flv_video_codec_id(data, size)), + srs_human_flv_video_avc_packet_type2string(srs_utils_flv_video_avc_packet_type(data, size)), + srs_human_flv_video_frame_type2string(srs_utils_flv_video_frame_type(data, size)) + ); + } else if (type == SRS_RTMP_TYPE_AUDIO) { + srs_human_trace("Audio packet type=%s, dts=%d, pts=%d, size=%d, %s(%s,%s,%s,%s)", + srs_human_flv_tag_type2string(type), timestamp, pts, size, + srs_human_flv_audio_sound_format2string(srs_utils_flv_audio_sound_format(data, size)), + srs_human_flv_audio_sound_rate2string(srs_utils_flv_audio_sound_rate(data, size)), + srs_human_flv_audio_sound_size2string(srs_utils_flv_audio_sound_size(data, size)), + srs_human_flv_audio_sound_type2string(srs_utils_flv_audio_sound_type(data, size)), + srs_human_flv_audio_aac_packet_type2string(srs_utils_flv_audio_aac_packet_type(data, size)) + ); + } else if (type == SRS_RTMP_TYPE_SCRIPT) { + srs_human_verbose("Data packet type=%s, time=%d, size=%d", + srs_human_flv_tag_type2string(type), timestamp, size); + int nparsed = 0; + while (nparsed < size) { + int nb_parsed_this = 0; + srs_amf0_t amf0 = srs_amf0_parse(data + nparsed, size - nparsed, &nb_parsed_this); + if (amf0 == NULL) { + break; + } + + nparsed += nb_parsed_this; + + char* amf0_str = NULL; + srs_human_raw("%s", srs_human_amf0_print(amf0, &amf0_str, NULL)); + srs_freep(amf0_str); + } + } else { + srs_human_trace("Rtmp packet type=%#x, dts=%d, pts=%d, size=%d", + type, timestamp, pts, size); + } + + return ret; +} + +const char* srs_human_format_time() +{ + struct timeval tv; + static char buf[23]; + + memset(buf, 0, sizeof(buf)); + + // clock time + if (gettimeofday(&tv, NULL) == -1) { + return buf; + } + + // to calendar time + struct tm* tm; + if ((tm = localtime((const time_t*)&tv.tv_sec)) == NULL) { + return buf; + } + + snprintf(buf, sizeof(buf), + "%d-%02d-%02d %02d:%02d:%02d.%03d", + 1900 + tm->tm_year, 1 + tm->tm_mon, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec, + (int)(tv.tv_usec / 1000)); + + // for srs-librtmp, @see https://github.com/simple-rtmp-server/srs/issues/213 + buf[sizeof(buf) - 1] = 0; + + return buf; +} + + +#ifdef SRS_HIJACK_IO +srs_hijack_io_t srs_hijack_io_get(srs_rtmp_t rtmp) +{ + if (!rtmp) { + return NULL; + } + + Context* context = (Context*)rtmp; + if (!context->skt) { + return NULL; + } + + return context->skt->hijack_io(); +} +#endif + #ifdef __cplusplus } #endif diff --git a/trunk/src/libs/srs_librtmp.hpp b/trunk/src/libs/srs_librtmp.hpp index 59bef95744..7fb10f2f05 100644 --- a/trunk/src/libs/srs_librtmp.hpp +++ b/trunk/src/libs/srs_librtmp.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -28,8 +28,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include */ -#include - /** * srs-librtmp is a librtmp like library, * used to play/publish rtmp stream from/to rtmp server. @@ -38,68 +36,135 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * thread-safe: no */ +/************************************************************* +************************************************************** +* Windows SRS-LIBRTMP pre-declare +************************************************************** +*************************************************************/ +// for srs-librtmp, @see https://github.com/simple-rtmp-server/srs/issues/213 +#ifdef _WIN32 + // include windows first. + #include + // the type used by this header for windows. + typedef unsigned long long u_int64_t; + typedef long long int64_t; + typedef unsigned int u_int32_t; + typedef int int32_t; + typedef unsigned char u_int8_t; + typedef char int8_t; + typedef unsigned short u_int16_t; + typedef short int16_t; + typedef int64_t ssize_t; + struct iovec { + void *iov_base; /* Starting address */ + size_t iov_len; /* Number of bytes to transfer */ + }; +#endif + +#include + #ifdef __cplusplus extern "C"{ #endif +// typedefs +typedef int srs_bool; + +/************************************************************* +************************************************************** +* srs-librtmp version +************************************************************** +*************************************************************/ +extern int srs_version_major(); +extern int srs_version_minor(); +extern int srs_version_revision(); + +/************************************************************* +************************************************************** +* RTMP protocol context +************************************************************** +*************************************************************/ // the RTMP handler. typedef void* srs_rtmp_t; +typedef void* srs_amf0_t; /** * create/destroy a rtmp protocol stack. * @url rtmp url, for example: * rtmp://localhost/live/livestream +* * @return a rtmp handler, or NULL if error occured. */ -srs_rtmp_t srs_rtmp_create(const char* url); +extern srs_rtmp_t srs_rtmp_create(const char* url); /** * create rtmp with url, used for connection specified application. * @param url the tcUrl, for exmple: * rtmp://localhost/live * @remark this is used to create application connection-oriented, * for example, the bandwidth client used this, no stream specified. +* +* @return a rtmp handler, or NULL if error occured. */ -srs_rtmp_t srs_rtmp_create2(const char* url); +extern srs_rtmp_t srs_rtmp_create2(const char* url); /** * close and destroy the rtmp stack. -* @remark, user should use the rtmp again. +* @remark, user should never use the rtmp again. */ -void srs_rtmp_destroy(srs_rtmp_t rtmp); +extern void srs_rtmp_destroy(srs_rtmp_t rtmp); +/************************************************************* +************************************************************** +* RTMP protocol stack +************************************************************** +*************************************************************/ /** * connect and handshake with server * category: publish/play * previous: rtmp-create * next: connect-app -* @return 0, success; otherwise, failed. +* +* @return 0, success; otherswise, failed. */ /** * simple handshake specifies in rtmp 1.0, * not depends on ssl. */ /** -* srs_simple_handshake equals to invoke: -* __srs_dns_resolve() -* __srs_connect_server() -* __srs_do_simple_handshake() +* srs_rtmp_handshake equals to invoke: +* srs_rtmp_dns_resolve() +* srs_rtmp_connect_server() +* srs_rtmp_do_simple_handshake() * user can use these functions if needed. */ -int srs_simple_handshake(srs_rtmp_t rtmp); +extern int srs_rtmp_handshake(srs_rtmp_t rtmp); // parse uri, create socket, resolve host -int __srs_dns_resolve(srs_rtmp_t rtmp); +extern int srs_rtmp_dns_resolve(srs_rtmp_t rtmp); // connect socket to server -int __srs_connect_server(srs_rtmp_t rtmp); +extern int srs_rtmp_connect_server(srs_rtmp_t rtmp); // do simple handshake over socket. -int __srs_do_simple_handshake(srs_rtmp_t rtmp); +extern int srs_rtmp_do_simple_handshake(srs_rtmp_t rtmp); +// do complex handshake over socket. +extern int srs_rtmp_do_complex_handshake(srs_rtmp_t rtmp); + +/** +* set the args of connect packet for rtmp. +* @param args, the extra amf0 object args. +* @remark, all params can be NULL to ignore. +* @remark, user should never free the args for we directly use it. +*/ +extern int srs_rtmp_set_connect_args(srs_rtmp_t rtmp, + const char* tcUrl, const char* swfUrl, const char* pageUrl, srs_amf0_t args +); /** * connect to rtmp vhost/app * category: publish/play * previous: handshake * next: publish or play -* @return 0, success; otherwise, failed. +* +* @return 0, success; otherswise, failed. */ -int srs_connect_app(srs_rtmp_t rtmp); +extern int srs_rtmp_connect_app(srs_rtmp_t rtmp); /** * connect to server, get the debug srs info. @@ -112,8 +177,10 @@ int srs_connect_app(srs_rtmp_t rtmp); * @param srs_version, 32bytes, server version. * @param srs_id, int, debug info, client id in server log. * @param srs_pid, int, debug info, server pid in log. +* +* @return 0, success; otherswise, failed. */ -int srs_connect_app2(srs_rtmp_t rtmp, +extern int srs_rtmp_connect_app2(srs_rtmp_t rtmp, char srs_server_ip[128], char srs_server[128], char srs_primary[128], char srs_authors[128], char srs_version[32], int* srs_id, int* srs_pid @@ -126,7 +193,7 @@ int srs_connect_app2(srs_rtmp_t rtmp, * next: destroy * @return 0, success; otherwise, failed. */ -int srs_play_stream(srs_rtmp_t rtmp); +extern int srs_rtmp_play_stream(srs_rtmp_t rtmp); /** * publish a live stream. @@ -135,7 +202,7 @@ int srs_play_stream(srs_rtmp_t rtmp); * next: destroy * @return 0, success; otherwise, failed. */ -int srs_publish_stream(srs_rtmp_t rtmp); +extern int srs_rtmp_publish_stream(srs_rtmp_t rtmp); /** * do bandwidth check with srs server. @@ -149,8 +216,10 @@ int srs_publish_stream(srs_rtmp_t rtmp); * @param publish_bytes, output the publish/upload bytes. * @param play_duration, output the play/download test duration, in ms. * @param publish_duration, output the publish/upload test duration, in ms. +* +* @return 0, success; otherswise, failed. */ -int srs_bandwidth_check(srs_rtmp_t rtmp, +extern int srs_rtmp_bandwidth_check(srs_rtmp_t rtmp, int64_t* start_time, int64_t* end_time, int* play_kbps, int* publish_kbps, int* play_bytes, int* publish_bytes, @@ -167,21 +236,12 @@ int srs_bandwidth_check(srs_rtmp_t rtmp, // 18 = script data #define SRS_RTMP_TYPE_SCRIPT 18 /** -* convert the flv tag type to string. -* SRS_RTMP_TYPE_AUDIO to "Audio" -* SRS_RTMP_TYPE_VIDEO to "Video" -* SRS_RTMP_TYPE_SCRIPT to "Data" -* otherwise, "Unknown" -* @remark user never free the return char*, -* it's static shared const string. -*/ -const char* srs_type2string(int type); -/** * read a audio/video/script-data packet from rtmp stream. * @param type, output the packet type, macros: * SRS_RTMP_TYPE_AUDIO, FlvTagAudio * SRS_RTMP_TYPE_VIDEO, FlvTagVideo * SRS_RTMP_TYPE_SCRIPT, FlvTagScript +* otherswise, invalid type. * @param timestamp, in ms, overflow in 50days * @param data, the packet data, according to type: * FlvTagAudio, @see "E.4.2.1 AUDIODATA" @@ -192,114 +252,808 @@ const char* srs_type2string(int type); * * @remark: for read, user must free the data. * @remark: for write, user should never free the data, even if error. +* @example /trunk/research/librtmp/srs_play.c +* @example /trunk/research/librtmp/srs_publish.c +* +* @return 0, success; otherswise, failed. */ -int srs_read_packet(srs_rtmp_t rtmp, int* type, u_int32_t* timestamp, char** data, int* size); -int srs_write_packet(srs_rtmp_t rtmp, int type, u_int32_t timestamp, char* data, int size); +extern int srs_rtmp_read_packet(srs_rtmp_t rtmp, + char* type, u_int32_t* timestamp, char** data, int* size +); +extern int srs_rtmp_write_packet(srs_rtmp_t rtmp, + char type, u_int32_t timestamp, char* data, int size +); /** -* get protocol stack version +* whether type is script data and the data is onMetaData. */ -int srs_version_major(); -int srs_version_minor(); -int srs_version_revision(); +extern srs_bool srs_rtmp_is_onMetaData(char type, char* data, int size); +/************************************************************* +************************************************************** +* audio raw codec +************************************************************** +*************************************************************/ /** -* utilities +* write an audio raw frame to srs. +* not similar to h.264 video, the audio never aggregated, always +* encoded one frame by one, so this api is used to write a frame. +* +* @param sound_format Format of SoundData. The following values are defined: +* 0 = Linear PCM, platform endian +* 1 = ADPCM +* 2 = MP3 +* 3 = Linear PCM, little endian +* 4 = Nellymoser 16 kHz mono +* 5 = Nellymoser 8 kHz mono +* 6 = Nellymoser +* 7 = G.711 A-law logarithmic PCM +* 8 = G.711 mu-law logarithmic PCM +* 9 = reserved +* 10 = AAC +* 11 = Speex +* 14 = MP3 8 kHz +* 15 = Device-specific sound +* Formats 7, 8, 14, and 15 are reserved. +* AAC is supported in Flash Player 9,0,115,0 and higher. +* Speex is supported in Flash Player 10 and higher. +* @param sound_rate Sampling rate. The following values are defined: +* 0 = 5.5 kHz +* 1 = 11 kHz +* 2 = 22 kHz +* 3 = 44 kHz +* @param sound_size Size of each audio sample. This parameter only pertains to +* uncompressed formats. Compressed formats always decode +* to 16 bits internally. +* 0 = 8-bit samples +* 1 = 16-bit samples +* @param sound_type Mono or stereo sound +* 0 = Mono sound +* 1 = Stereo sound +* @param timestamp The timestamp of audio. +* +* @example /trunk/research/librtmp/srs_aac_raw_publish.c +* @example /trunk/research/librtmp/srs_audio_raw_publish.c +* +* @remark for aac, the frame must be in ADTS format. +* @see aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 75, 1.A.2.2 ADTS +* @remark for aac, only support profile 1-4, AAC main/LC/SSR/LTP, +* @see aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 23, 1.5.1.1 Audio object type +* +* @see https://github.com/simple-rtmp-server/srs/issues/212 +* @see E.4.2.1 AUDIODATA of video_file_format_spec_v10_1.pdf +* +* @return 0, success; otherswise, failed. */ -int64_t srs_get_time_ms(); -int64_t srs_get_nsend_bytes(srs_rtmp_t rtmp); -int64_t srs_get_nrecv_bytes(srs_rtmp_t rtmp); +extern int srs_audio_write_raw_frame(srs_rtmp_t rtmp, + char sound_format, char sound_rate, char sound_size, char sound_type, + char* frame, int frame_size, u_int32_t timestamp +); /** -* flv codec +* whether aac raw data is in adts format, +* which bytes sequence matches '1111 1111 1111'B, that is 0xFFF. +* @param aac_raw_data the input aac raw data, a encoded aac frame data. +* @param ac_raw_size the size of aac raw data. +* +* @reamrk used to check whether current frame is in adts format. +* @see aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 75, 1.A.2.2 ADTS +* @example /trunk/research/librtmp/srs_aac_raw_publish.c +* +* @return 0 false; otherwise, true. +*/ +extern srs_bool srs_aac_is_adts(char* aac_raw_data, int ac_raw_size); + +/** +* parse the adts header to get the frame size, +* which bytes sequence matches '1111 1111 1111'B, that is 0xFFF. +* @param aac_raw_data the input aac raw data, a encoded aac frame data. +* @param ac_raw_size the size of aac raw data. +* +* @return failed when <=0 failed; otherwise, ok. +*/ +extern int srs_aac_adts_frame_size(char* aac_raw_data, int ac_raw_size); + +/************************************************************* +************************************************************** +* h264 raw codec +************************************************************** +*************************************************************/ +/** +* write h.264 raw frame over RTMP to rtmp server. +* @param frames the input h264 raw data, encoded h.264 I/P/B frames data. +* frames can be one or more than one frame, +* each frame prefixed h.264 annexb header, by N[00] 00 00 01, where N>=0, +* for instance, frame = header(00 00 00 01) + payload(67 42 80 29 95 A0 14 01 6E 40) +* about annexb, @see H.264-AVC-ISO_IEC_14496-10.pdf, page 211. +* @param frames_size the size of h264 raw data. +* assert frames_size > 0, at least has 1 bytes header. +* @param dts the dts of h.264 raw data. +* @param pts the pts of h.264 raw data. +* +* @remark, user should free the frames. +* @remark, the tbn of dts/pts is 1/1000 for RTMP, that is, in ms. +* @remark, cts = pts - dts +* @remark, use srs_h264_startswith_annexb to check whether frame is annexb format. +* @example /trunk/research/librtmp/srs_h264_raw_publish.c +* @see https://github.com/simple-rtmp-server/srs/issues/66 +* +* @return 0, success; otherswise, failed. +* for dvbsp error, @see srs_h264_is_dvbsp_error(). +* for duplictated sps error, @see srs_h264_is_duplicated_sps_error(). +* for duplictated pps error, @see srs_h264_is_duplicated_pps_error(). +*/ +/** +For the example file: + http://winlinvip.github.io/srs.release/3rdparty/720p.h264.raw +The data sequence is: + // SPS + 000000016742802995A014016E40 + // PPS + 0000000168CE3880 + // IFrame + 0000000165B8041014C038008B0D0D3A071..... + // PFrame + 0000000141E02041F8CDDC562BBDEFAD2F..... +User can send the SPS+PPS, then each frame: + // SPS+PPS + srs_h264_write_raw_frames('000000016742802995A014016E400000000168CE3880', size, dts, pts) + // IFrame + srs_h264_write_raw_frames('0000000165B8041014C038008B0D0D3A071......', size, dts, pts) + // PFrame + srs_h264_write_raw_frames('0000000141E02041F8CDDC562BBDEFAD2F......', size, dts, pts) +User also can send one by one: + // SPS + srs_h264_write_raw_frames('000000016742802995A014016E4', size, dts, pts) + // PPS + srs_h264_write_raw_frames('00000000168CE3880', size, dts, pts) + // IFrame + srs_h264_write_raw_frames('0000000165B8041014C038008B0D0D3A071......', size, dts, pts) + // PFrame + srs_h264_write_raw_frames('0000000141E02041F8CDDC562BBDEFAD2F......', size, dts, pts) +*/ +extern int srs_h264_write_raw_frames(srs_rtmp_t rtmp, + char* frames, int frames_size, u_int32_t dts, u_int32_t pts +); +/** +* whether error_code is dvbsp(drop video before sps/pps/sequence-header) error. +* +* @see https://github.com/simple-rtmp-server/srs/issues/203 +* @example /trunk/research/librtmp/srs_h264_raw_publish.c +* @remark why drop video? +* some encoder, for example, ipcamera, will send sps/pps before each IFrame, +* so, when error and reconnect the rtmp, the first video is not sps/pps(sequence header), +* this will cause SRS server to disable HLS. +*/ +extern srs_bool srs_h264_is_dvbsp_error(int error_code); +/** +* whether error_code is duplicated sps error. +* +* @see https://github.com/simple-rtmp-server/srs/issues/204 +* @example /trunk/research/librtmp/srs_h264_raw_publish.c +*/ +extern srs_bool srs_h264_is_duplicated_sps_error(int error_code); +/** +* whether error_code is duplicated pps error. +* +* @see https://github.com/simple-rtmp-server/srs/issues/204 +* @example /trunk/research/librtmp/srs_h264_raw_publish.c */ +extern srs_bool srs_h264_is_duplicated_pps_error(int error_code); +/** +* whether h264 raw data starts with the annexb, +* which bytes sequence matches N[00] 00 00 01, where N>=0. +* @param h264_raw_data the input h264 raw data, a encoded h.264 I/P/B frame data. +* @paam h264_raw_size the size of h264 raw data. +* @param pnb_start_code output the size of start code, must >=3. +* NULL to ignore. +* +* @reamrk used to check whether current frame is in annexb format. +* @example /trunk/research/librtmp/srs_h264_raw_publish.c +* +* @return 0 false; otherwise, true. +*/ +extern srs_bool srs_h264_startswith_annexb( + char* h264_raw_data, int h264_raw_size, + int* pnb_start_code +); + +/************************************************************* +************************************************************** +* flv codec +* @example /trunk/research/librtmp/srs_flv_injecter.c +* @example /trunk/research/librtmp/srs_flv_parser.c +* @example /trunk/research/librtmp/srs_ingest_flv.c +* @example /trunk/research/librtmp/srs_ingest_rtmp.c +************************************************************** +*************************************************************/ typedef void* srs_flv_t; -typedef int flv_bool; /* open flv file for both read/write. */ -srs_flv_t srs_flv_open_read(const char* file); -srs_flv_t srs_flv_open_write(const char* file); -void srs_flv_close(srs_flv_t flv); -/* read the flv header. 9bytes header. drop the 4bytes zero previous tag size */ -int srs_flv_read_header(srs_flv_t flv, char header[9]); -/* read the flv tag header, 1bytes tag, 3bytes data_size, 4bytes time, 3bytes stream id. */ -int srs_flv_read_tag_header(srs_flv_t flv, char* ptype, int32_t* pdata_size, u_int32_t* ptime); -/* read the tag data. drop the 4bytes previous tag size */ -int srs_flv_read_tag_data(srs_flv_t flv, char* data, int32_t size); -/* write flv header to file, auto write the 4bytes zero previous tag size. */ -int srs_flv_write_header(srs_flv_t flv, char header[9]); +extern srs_flv_t srs_flv_open_read(const char* file); +extern srs_flv_t srs_flv_open_write(const char* file); +extern void srs_flv_close(srs_flv_t flv); +/** +* read the flv header. 9bytes header. +* @param header, @see E.2 The FLV header, flv_v10_1.pdf in SRS doc. +* 3bytes, signature, "FLV", +* 1bytes, version, 0x01, +* 1bytes, flags, UB[5] 0, UB[1] audio present, UB[1] 0, UB[1] video present. +* 4bytes, dataoffset, 0x09, The length of this header in bytes +* +* @return 0, success; otherswise, failed. +* @remark, drop the 4bytes zero previous tag size. +*/ +extern int srs_flv_read_header(srs_flv_t flv, char header[9]); +/** +* read the flv tag header, 1bytes tag, 3bytes data_size, +* 4bytes time, 3bytes stream id. +* @param ptype, output the type of tag, macros: +* SRS_RTMP_TYPE_AUDIO, FlvTagAudio +* SRS_RTMP_TYPE_VIDEO, FlvTagVideo +* SRS_RTMP_TYPE_SCRIPT, FlvTagScript +* @param pdata_size, output the size of tag data. +* @param ptime, output the time of tag, the dts in ms. +* +* @return 0, success; otherswise, failed. +* @remark, user must ensure the next is a tag, srs never check it. +*/ +extern int srs_flv_read_tag_header(srs_flv_t flv, + char* ptype, int32_t* pdata_size, u_int32_t* ptime +); +/** +* read the tag data. drop the 4bytes previous tag size +* @param data, the data to read, user alloc and free it. +* @param size, the size of data to read, get by srs_flv_read_tag_header(). +* @remark, srs will ignore and drop the 4bytes previous tag size. +*/ +extern int srs_flv_read_tag_data(srs_flv_t flv, char* data, int32_t size); +/** +* write the flv header. 9bytes header. +* @param header, @see E.2 The FLV header, flv_v10_1.pdf in SRS doc. +* 3bytes, signature, "FLV", +* 1bytes, version, 0x01, +* 1bytes, flags, UB[5] 0, UB[1] audio present, UB[1] 0, UB[1] video present. +* 4bytes, dataoffset, 0x09, The length of this header in bytes +* +* @return 0, success; otherswise, failed. +* @remark, auto write the 4bytes zero previous tag size. +*/ +extern int srs_flv_write_header(srs_flv_t flv, char header[9]); +/** +* write the flv tag to file. +* +* @return 0, success; otherswise, failed. +* @remark, auto write the 4bytes zero previous tag size. +*/ /* write flv tag to file, auto write the 4bytes previous tag size */ -int srs_flv_write_tag(srs_flv_t flv, char type, int32_t time, char* data, int size); -/* get the tag size, for flv injecter to adjust offset, size=tag_header+data+previous_tag */ -int srs_flv_size_tag(int data_size); +extern int srs_flv_write_tag(srs_flv_t flv, + char type, int32_t time, char* data, int size +); +/** +* get the tag size, for flv injecter to adjust offset, +* size = tag_header(11B) + data_size + previous_tag(4B) +* @return the size of tag. +*/ +extern int srs_flv_size_tag(int data_size); /* file stream */ /* file stream tellg to get offset */ -int64_t srs_flv_tellg(srs_flv_t flv); +extern int64_t srs_flv_tellg(srs_flv_t flv); /* seek file stream, offset is form the start of file */ -void srs_flv_lseek(srs_flv_t flv, int64_t offset); +extern void srs_flv_lseek(srs_flv_t flv, int64_t offset); /* error code */ /* whether the error code indicates EOF */ -flv_bool srs_flv_is_eof(int error_code); +extern srs_bool srs_flv_is_eof(int error_code); /* media codec */ -/* whether the video body is sequence header */ -flv_bool srs_flv_is_sequence_header(char* data, int32_t size); -/* whether the video body is keyframe */ -flv_bool srs_flv_is_keyframe(char* data, int32_t size); - /** -* amf0 codec +* whether the video body is sequence header +* @param data, the data of tag, read by srs_flv_read_tag_data(). +* @param size, the size of tag, read by srs_flv_read_tag_data(). +*/ +extern srs_bool srs_flv_is_sequence_header(char* data, int32_t size); +/** +* whether the video body is keyframe +* @param data, the data of tag, read by srs_flv_read_tag_data(). +* @param size, the size of tag, read by srs_flv_read_tag_data(). */ +extern srs_bool srs_flv_is_keyframe(char* data, int32_t size); + +/************************************************************* +************************************************************** +* amf0 codec +* @example /trunk/research/librtmp/srs_ingest_flv.c +* @example /trunk/research/librtmp/srs_ingest_rtmp.c +************************************************************** +*************************************************************/ /* the output handler. */ -typedef void* srs_amf0_t; -typedef int amf0_bool; -typedef double amf0_number; -srs_amf0_t srs_amf0_parse(char* data, int size, int* nparsed); -srs_amf0_t srs_amf0_create_number(amf0_number value); -srs_amf0_t srs_amf0_create_ecma_array(); -srs_amf0_t srs_amf0_create_strict_array(); -srs_amf0_t srs_amf0_create_object(); -void srs_amf0_free(srs_amf0_t amf0); -void srs_amf0_free_bytes(char* data); +typedef double srs_amf0_number; +/** +* parse amf0 from data. +* @param nparsed, the parsed size, NULL to ignore. +* @return the parsed amf0 object. NULL for error. +* @remark user must free the parsed or created object by srs_amf0_free. +*/ +extern srs_amf0_t srs_amf0_parse(char* data, int size, int* nparsed); +extern srs_amf0_t srs_amf0_create_string(const char* value); +extern srs_amf0_t srs_amf0_create_number(srs_amf0_number value); +extern srs_amf0_t srs_amf0_create_ecma_array(); +extern srs_amf0_t srs_amf0_create_strict_array(); +extern srs_amf0_t srs_amf0_create_object(); +extern srs_amf0_t srs_amf0_ecma_array_to_object(srs_amf0_t ecma_arr); +extern void srs_amf0_free(srs_amf0_t amf0); /* size and to bytes */ -int srs_amf0_size(srs_amf0_t amf0); -int srs_amf0_serialize(srs_amf0_t amf0, char* data, int size); +extern int srs_amf0_size(srs_amf0_t amf0); +extern int srs_amf0_serialize(srs_amf0_t amf0, char* data, int size); /* type detecter */ -amf0_bool srs_amf0_is_string(srs_amf0_t amf0); -amf0_bool srs_amf0_is_boolean(srs_amf0_t amf0); -amf0_bool srs_amf0_is_number(srs_amf0_t amf0); -amf0_bool srs_amf0_is_null(srs_amf0_t amf0); -amf0_bool srs_amf0_is_object(srs_amf0_t amf0); -amf0_bool srs_amf0_is_ecma_array(srs_amf0_t amf0); -amf0_bool srs_amf0_is_strict_array(srs_amf0_t amf0); +extern srs_bool srs_amf0_is_string(srs_amf0_t amf0); +extern srs_bool srs_amf0_is_boolean(srs_amf0_t amf0); +extern srs_bool srs_amf0_is_number(srs_amf0_t amf0); +extern srs_bool srs_amf0_is_null(srs_amf0_t amf0); +extern srs_bool srs_amf0_is_object(srs_amf0_t amf0); +extern srs_bool srs_amf0_is_ecma_array(srs_amf0_t amf0); +extern srs_bool srs_amf0_is_strict_array(srs_amf0_t amf0); /* value converter */ -const char* srs_amf0_to_string(srs_amf0_t amf0); -amf0_bool srs_amf0_to_boolean(srs_amf0_t amf0); -amf0_number srs_amf0_to_number(srs_amf0_t amf0); +extern const char* srs_amf0_to_string(srs_amf0_t amf0); +extern srs_bool srs_amf0_to_boolean(srs_amf0_t amf0); +extern srs_amf0_number srs_amf0_to_number(srs_amf0_t amf0); /* value setter */ -void srs_amf0_set_number(srs_amf0_t amf0, amf0_number value); +extern void srs_amf0_set_number(srs_amf0_t amf0, srs_amf0_number value); /* object value converter */ -int srs_amf0_object_property_count(srs_amf0_t amf0); -const char* srs_amf0_object_property_name_at(srs_amf0_t amf0, int index); -srs_amf0_t srs_amf0_object_property_value_at(srs_amf0_t amf0, int index); -srs_amf0_t srs_amf0_object_property(srs_amf0_t amf0, const char* name); -void srs_amf0_object_property_set(srs_amf0_t amf0, const char* name, srs_amf0_t value); -void srs_amf0_object_clear(srs_amf0_t amf0); +extern int srs_amf0_object_property_count(srs_amf0_t amf0); +extern const char* srs_amf0_object_property_name_at(srs_amf0_t amf0, int index); +extern srs_amf0_t srs_amf0_object_property_value_at(srs_amf0_t amf0, int index); +extern srs_amf0_t srs_amf0_object_property(srs_amf0_t amf0, const char* name); +extern void srs_amf0_object_property_set(srs_amf0_t amf0, const char* name, srs_amf0_t value); +extern void srs_amf0_object_clear(srs_amf0_t amf0); /* ecma array value converter */ -int srs_amf0_ecma_array_property_count(srs_amf0_t amf0); -const char* srs_amf0_ecma_array_property_name_at(srs_amf0_t amf0, int index); -srs_amf0_t srs_amf0_ecma_array_property_value_at(srs_amf0_t amf0, int index); -srs_amf0_t srs_amf0_ecma_array_property(srs_amf0_t amf0, const char* name); -void srs_amf0_ecma_array_property_set(srs_amf0_t amf0, const char* name, srs_amf0_t value); +extern int srs_amf0_ecma_array_property_count(srs_amf0_t amf0); +extern const char* srs_amf0_ecma_array_property_name_at(srs_amf0_t amf0, int index); +extern srs_amf0_t srs_amf0_ecma_array_property_value_at(srs_amf0_t amf0, int index); +extern srs_amf0_t srs_amf0_ecma_array_property(srs_amf0_t amf0, const char* name); +extern void srs_amf0_ecma_array_property_set(srs_amf0_t amf0, const char* name, srs_amf0_t value); /* strict array value converter */ -int srs_amf0_strict_array_property_count(srs_amf0_t amf0); -srs_amf0_t srs_amf0_strict_array_property_at(srs_amf0_t amf0, int index); -void srs_amf0_strict_array_append(srs_amf0_t amf0, srs_amf0_t value); +extern int srs_amf0_strict_array_property_count(srs_amf0_t amf0); +extern srs_amf0_t srs_amf0_strict_array_property_at(srs_amf0_t amf0, int index); +extern void srs_amf0_strict_array_append(srs_amf0_t amf0, srs_amf0_t value); + +/************************************************************* +************************************************************** +* utilities +************************************************************** +*************************************************************/ +/** +* get the current system time in ms. +* use gettimeofday() to get system time. +*/ +extern int64_t srs_utils_time_ms(); + +/** +* get the send bytes. +*/ +extern int64_t srs_utils_send_bytes(srs_rtmp_t rtmp); + +/** +* get the recv bytes. +*/ +extern int64_t srs_utils_recv_bytes(srs_rtmp_t rtmp); + +/** +* parse the dts and pts by time in header and data in tag, +* or to parse the RTMP packet by srs_rtmp_read_packet(). +* +* @param time, the timestamp of tag, read by srs_flv_read_tag_header(). +* @param type, the type of tag, read by srs_flv_read_tag_header(). +* @param data, the data of tag, read by srs_flv_read_tag_data(). +* @param size, the size of tag, read by srs_flv_read_tag_header(). +* @param ppts, output the pts in ms, +* +* @return 0, success; otherswise, failed. +* @remark, the dts always equals to @param time. +* @remark, the pts=dts for audio or data. +* @remark, video only support h.264. +*/ +extern int srs_utils_parse_timestamp( + u_int32_t time, char type, char* data, int size, + u_int32_t* ppts +); + +/** + * whether the flv tag specified by param type is ok. + * @return true when tag is video/audio/script-data; otherwise, false. + */ +extern srs_bool srs_utils_flv_tag_is_ok(char type); +extern srs_bool srs_utils_flv_tag_is_audio(char type); +extern srs_bool srs_utils_flv_tag_is_video(char type); +extern srs_bool srs_utils_flv_tag_is_av(char type); + +/** +* get the CodecID of video tag. +* Codec Identifier. The following values are defined: +* 2 = Sorenson H.263 +* 3 = Screen video +* 4 = On2 VP6 +* 5 = On2 VP6 with alpha channel +* 6 = Screen video version 2 +* 7 = AVC +* @return the code id. 0 for error. +*/ +extern char srs_utils_flv_video_codec_id(char* data, int size); + +/** +* get the AVCPacketType of video tag. +* The following values are defined: +* 0 = AVC sequence header +* 1 = AVC NALU +* 2 = AVC end of sequence (lower level NALU sequence ender is +* not required or supported) +* @return the avc packet type. -1(0xff) for error. +*/ +extern char srs_utils_flv_video_avc_packet_type(char* data, int size); + +/** +* get the FrameType of video tag. +* Type of video frame. The following values are defined: +* 1 = key frame (for AVC, a seekable frame) +* 2 = inter frame (for AVC, a non-seekable frame) +* 3 = disposable inter frame (H.263 only) +* 4 = generated key frame (reserved for server use only) +* 5 = video info/command frame +* @return the frame type. 0 for error. +*/ +extern char srs_utils_flv_video_frame_type(char* data, int size); + +/** +* get the SoundFormat of audio tag. +* Format of SoundData. The following values are defined: +* 0 = Linear PCM, platform endian +* 1 = ADPCM +* 2 = MP3 +* 3 = Linear PCM, little endian +* 4 = Nellymoser 16 kHz mono +* 5 = Nellymoser 8 kHz mono +* 6 = Nellymoser +* 7 = G.711 A-law logarithmic PCM +* 8 = G.711 mu-law logarithmic PCM +* 9 = reserved +* 10 = AAC +* 11 = Speex +* 14 = MP3 8 kHz +* 15 = Device-specific sound +* Formats 7, 8, 14, and 15 are reserved. +* AAC is supported in Flash Player 9,0,115,0 and higher. +* Speex is supported in Flash Player 10 and higher. +* @return the sound format. -1(0xff) for error. +*/ +extern char srs_utils_flv_audio_sound_format(char* data, int size); + +/** +* get the SoundRate of audio tag. +* Sampling rate. The following values are defined: +* 0 = 5.5 kHz +* 1 = 11 kHz +* 2 = 22 kHz +* 3 = 44 kHz +* @return the sound rate. -1(0xff) for error. +*/ +extern char srs_utils_flv_audio_sound_rate(char* data, int size); + +/** +* get the SoundSize of audio tag. +* Size of each audio sample. This parameter only pertains to +* uncompressed formats. Compressed formats always decode +* to 16 bits internally. +* 0 = 8-bit samples +* 1 = 16-bit samples +* @return the sound size. -1(0xff) for error. +*/ +extern char srs_utils_flv_audio_sound_size(char* data, int size); + +/** +* get the SoundType of audio tag. +* Mono or stereo sound +* 0 = Mono sound +* 1 = Stereo sound +* @return the sound type. -1(0xff) for error. +*/ +extern char srs_utils_flv_audio_sound_type(char* data, int size); + +/** +* get the AACPacketType of audio tag. +* The following values are defined: +* 0 = AAC sequence header +* 1 = AAC raw +* @return the aac packet type. -1(0xff) for error. +*/ +extern char srs_utils_flv_audio_aac_packet_type(char* data, int size); + +/************************************************************* +************************************************************** +* human readable print. +************************************************************** +*************************************************************/ /** * human readable print * @param pdata, output the heap data, NULL to ignore. -* user must use srs_amf0_free_bytes to free it. +* user must use srs_amf0_free_bytes to free it. * @return return the *pdata for print. NULL to ignore. */ -char* srs_amf0_human_print(srs_amf0_t amf0, char** pdata, int* psize); +extern char* srs_human_amf0_print(srs_amf0_t amf0, char** pdata, int* psize); +/** +* convert the flv tag type to string. +* SRS_RTMP_TYPE_AUDIO to "Audio" +* SRS_RTMP_TYPE_VIDEO to "Video" +* SRS_RTMP_TYPE_SCRIPT to "Data" +* otherwise, "Unknown" +* @remark user never free the return char*, +* it's static shared const string. +*/ +extern const char* srs_human_flv_tag_type2string(char type); + +/** +* get the codec id string. +* H.263 = Sorenson H.263 +* Screen = Screen video +* VP6 = On2 VP6 +* VP6Alpha = On2 VP6 with alpha channel +* Screen2 = Screen video version 2 +* H.264 = AVC +* otherwise, "Unknown" +* @remark user never free the return char*, +* it's static shared const string. +*/ +extern const char* srs_human_flv_video_codec_id2string(char codec_id); + +/** +* get the avc packet type string. +* SH = AVC sequence header +* Nalu = AVC NALU +* SpsPpsEnd = AVC end of sequence +* otherwise, "Unknown" +* @remark user never free the return char*, +* it's static shared const string. +*/ +extern const char* srs_human_flv_video_avc_packet_type2string(char avc_packet_type); + +/** +* get the frame type string. +* I = key frame (for AVC, a seekable frame) +* P/B = inter frame (for AVC, a non-seekable frame) +* DI = disposable inter frame (H.263 only) +* GI = generated key frame (reserved for server use only) +* VI = video info/command frame +* otherwise, "Unknown" +* @remark user never free the return char*, +* it's static shared const string. +*/ +extern const char* srs_human_flv_video_frame_type2string(char frame_type); + +/** +* get the SoundFormat string. +* Format of SoundData. The following values are defined: +* LinearPCM = Linear PCM, platform endian +* ADPCM = ADPCM +* MP3 = MP3 +* LinearPCMLe = Linear PCM, little endian +* NellymoserKHz16 = Nellymoser 16 kHz mono +* NellymoserKHz8 = Nellymoser 8 kHz mono +* Nellymoser = Nellymoser +* G711APCM = G.711 A-law logarithmic PCM +* G711MuPCM = G.711 mu-law logarithmic PCM +* Reserved = reserved +* AAC = AAC +* Speex = Speex +* MP3KHz8 = MP3 8 kHz +* DeviceSpecific = Device-specific sound +* otherwise, "Unknown" +* @remark user never free the return char*, +* it's static shared const string. +*/ +extern const char* srs_human_flv_audio_sound_format2string(char sound_format); + +/** +* get the SoundRate of audio tag. +* Sampling rate. The following values are defined: +* 5.5KHz = 5.5 kHz +* 11KHz = 11 kHz +* 22KHz = 22 kHz +* 44KHz = 44 kHz +* otherwise, "Unknown" +* @remark user never free the return char*, +* it's static shared const string. +*/ +extern const char* srs_human_flv_audio_sound_rate2string(char sound_rate); + +/** +* get the SoundSize of audio tag. +* Size of each audio sample. This parameter only pertains to +* uncompressed formats. Compressed formats always decode +* to 16 bits internally. +* 8bit = 8-bit samples +* 16bit = 16-bit samples +* otherwise, "Unknown" +* @remark user never free the return char*, +* it's static shared const string. +*/ +extern const char* srs_human_flv_audio_sound_size2string(char sound_size); + +/** +* get the SoundType of audio tag. +* Mono or stereo sound +* Mono = Mono sound +* Stereo = Stereo sound +* otherwise, "Unknown" +* @remark user never free the return char*, +* it's static shared const string. +*/ +extern const char* srs_human_flv_audio_sound_type2string(char sound_type); + +/** +* get the AACPacketType of audio tag. +* The following values are defined: +* SH = AAC sequence header +* Raw = AAC raw +* otherwise, "Unknown" +* @remark user never free the return char*, +* it's static shared const string. +*/ +extern const char* srs_human_flv_audio_aac_packet_type2string(char aac_packet_type); + +/** +* print the rtmp packet, use srs_human_trace/srs_human_verbose for packet, +* and use srs_human_raw for script data body. +* @return an error code for parse the timetstamp to dts and pts. +*/ +extern int srs_human_print_rtmp_packet(char type, u_int32_t timestamp, char* data, int size); + +// log to console, for use srs-librtmp application. +extern const char* srs_human_format_time(); + +// when disabled log, donot compile it. +#ifdef SRS_DISABLE_LOG + #define srs_human_trace(msg, ...) (void)0 + #define srs_human_verbose(msg, ...) (void)0 + #define srs_human_raw(msg, ...) (void)0 +#else + #define srs_human_trace(msg, ...) printf("[%s] ", srs_human_format_time());printf(msg, ##__VA_ARGS__);printf("\n") + #define srs_human_verbose(msg, ...) printf("[%s] ", srs_human_format_time());printf(msg, ##__VA_ARGS__);printf("\n") + #define srs_human_raw(msg, ...) printf(msg, ##__VA_ARGS__) +#endif + +/************************************************************* +************************************************************** +* IO hijack, use your specified io functions. +************************************************************** +*************************************************************/ +// the void* will convert to your handler for io hijack. +typedef void* srs_hijack_io_t; +#ifdef SRS_HIJACK_IO + #ifndef _WIN32 + // for iovec. + #include + #endif + /** + * get the hijack io object in rtmp protocol sdk. + * @remark, user should never provides this method, srs-librtmp provides it. + */ + extern srs_hijack_io_t srs_hijack_io_get(srs_rtmp_t rtmp); +#endif +// define the following macro and functions in your module to hijack the io. +// the example @see https://github.com/winlinvip/st-load +// which use librtmp but use its own io(use st also). +#ifdef SRS_HIJACK_IO + /** + * create hijack. + * @return NULL for error; otherwise, ok. + */ + extern srs_hijack_io_t srs_hijack_io_create(); + /** + * destroy the context, user must close the socket. + */ + extern void srs_hijack_io_destroy(srs_hijack_io_t ctx); + /** + * create socket, not connect yet. + * @return 0, success; otherswise, failed. + */ + extern int srs_hijack_io_create_socket(srs_hijack_io_t ctx); + /** + * connect socket at server_ip:port. + * @return 0, success; otherswise, failed. + */ + extern int srs_hijack_io_connect(srs_hijack_io_t ctx, const char* server_ip, int port); + /** + * read from socket. + * @return 0, success; otherswise, failed. + */ + extern int srs_hijack_io_read(srs_hijack_io_t ctx, void* buf, size_t size, ssize_t* nread); + /** + * set the socket recv timeout. + * @return 0, success; otherswise, failed. + */ + extern void srs_hijack_io_set_recv_timeout(srs_hijack_io_t ctx, int64_t timeout_us); + /** + * get the socket recv timeout. + * @return 0, success; otherswise, failed. + */ + extern int64_t srs_hijack_io_get_recv_timeout(srs_hijack_io_t ctx); + /** + * get the socket recv bytes. + * @return 0, success; otherswise, failed. + */ + extern int64_t srs_hijack_io_get_recv_bytes(srs_hijack_io_t ctx); + /** + * set the socket send timeout. + * @return 0, success; otherswise, failed. + */ + extern void srs_hijack_io_set_send_timeout(srs_hijack_io_t ctx, int64_t timeout_us); + /** + * get the socket send timeout. + * @return 0, success; otherswise, failed. + */ + extern int64_t srs_hijack_io_get_send_timeout(srs_hijack_io_t ctx); + /** + * get the socket send bytes. + * @return 0, success; otherswise, failed. + */ + extern int64_t srs_hijack_io_get_send_bytes(srs_hijack_io_t ctx); + /** + * writev of socket. + * @return 0, success; otherswise, failed. + */ + extern int srs_hijack_io_writev(srs_hijack_io_t ctx, const iovec *iov, int iov_size, ssize_t* nwrite); + /** + * whether the timeout is never timeout. + * @return 0, success; otherswise, failed. + */ + extern bool srs_hijack_io_is_never_timeout(srs_hijack_io_t ctx, int64_t timeout_us); + /** + * read fully, fill the buf exactly size bytes. + * @return 0, success; otherswise, failed. + */ + extern int srs_hijack_io_read_fully(srs_hijack_io_t ctx, void* buf, size_t size, ssize_t* nread); + /** + * write bytes to socket. + * @return 0, success; otherswise, failed. + */ + extern int srs_hijack_io_write(srs_hijack_io_t ctx, void* buf, size_t size, ssize_t* nwrite); +#endif + +/************************************************************* +************************************************************** +* Windows SRS-LIBRTMP solution +************************************************************** +*************************************************************/ +// for srs-librtmp, @see https://github.com/simple-rtmp-server/srs/issues/213 +#ifdef _WIN32 + #define _CRT_SECURE_NO_WARNINGS + #include + int gettimeofday(struct timeval* tv, struct timezone* tz); + #define PRId64 "lld" + + typedef int socklen_t; + const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); + typedef int mode_t; + #define S_IRUSR 0 + #define S_IWUSR 0 + #define S_IRGRP 0 + #define S_IWGRP 0 + #define S_IROTH 0 + + #include + #include + #define open _open + #define close _close + #define lseek _lseek + #define write _write + #define read _read + + typedef int pid_t; + pid_t getpid(void); + #define snprintf _snprintf + ssize_t writev(int fd, const struct iovec *iov, int iovcnt); + typedef int64_t useconds_t; + int usleep(useconds_t usec); + int socket_setup(); + int socket_cleanup(); +#endif #ifdef __cplusplus } diff --git a/trunk/src/main/srs_main_ingest_hls.cpp b/trunk/src/main/srs_main_ingest_hls.cpp new file mode 100644 index 0000000000..4de2740ca8 --- /dev/null +++ b/trunk/src/main/srs_main_ingest_hls.cpp @@ -0,0 +1,1404 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include + +#include +#include +#include +#include +using namespace std; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// pre-declare +int proxy_hls2rtmp(std::string hls, std::string rtmp); + +// for the main objects(server, config, log, context), +// never subscribe handler in constructor, +// instead, subscribe handler in initialize method. +// kernel module. +ISrsLog* _srs_log = new SrsFastLog(); +ISrsThreadContext* _srs_context = new ISrsThreadContext(); +// app module. +SrsConfig* _srs_config = NULL; +SrsServer* _srs_server = NULL; + +/** +* main entrance. +*/ +int main(int argc, char** argv) +{ + // TODO: support both little and big endian. + srs_assert(srs_is_little_endian()); + + // directly failed when compile limited. +#if !defined(SRS_AUTO_HTTP_PARSER) + srs_error("depends on http-parser."); + exit(-1); +#endif + +#if defined(SRS_AUTO_GPERF_MP) || defined(SRS_AUTO_GPERF_MP) \ +|| defined(SRS_AUTO_GPERF_MC) || defined(SRS_AUTO_GPERF_MP) + srs_error("donot support gmc/gmp/gcp/gprof"); + exit(-1); +#endif + + srs_trace("srs_ingest_hls base on %s, to ingest hls live to srs", RTMP_SIG_SRS_SERVER); + + // parse user options. + std::string in_hls_url, out_rtmp_url; + for (int opt = 0; opt < argc; opt++) { + srs_trace("argv[%d]=%s", opt, argv[opt]); + } + + // fill the options for mac + for (int opt = 0; opt < argc - 1; opt++) { + // ignore all options except -i and -y. + char* p = argv[opt]; + + // only accept -x + if (p[0] != '-' || p[1] == 0 || p[2] != 0) { + continue; + } + + // parse according the option name. + switch (p[1]) { + case 'i': in_hls_url = argv[opt + 1]; break; + case 'y': out_rtmp_url = argv[opt + 1]; break; + default: break; + } + } + + if (in_hls_url.empty() || out_rtmp_url.empty()) { + printf("ingest hls live stream and publish to RTMP server\n" + "Usage: %s <-i in_hls_url> <-y out_rtmp_url>\n" + " in_hls_url input hls url, ingest from this m3u8.\n" + " out_rtmp_url output rtmp url, publish to this url.\n" + "For example:\n" + " %s -i http://127.0.0.1:8080/live/livestream.m3u8 -y rtmp://127.0.0.1/live/ingest_hls\n" + " %s -i http://ossrs.net/live/livestream.m3u8 -y rtmp://127.0.0.1/live/ingest_hls\n", + argv[0], argv[0], argv[0]); + exit(-1); + } + + srs_trace("input: %s", in_hls_url.c_str()); + srs_trace("output: %s", out_rtmp_url.c_str()); + + return proxy_hls2rtmp(in_hls_url, out_rtmp_url); +} + +class ISrsAacHandler +{ +public: + /** + * handle the aac frame, which in ADTS format(starts with FFFx). + * @param duration the duration in seconds of frames. +*/ +virtual int on_aac_frame(char* frame, int frame_size, double duration) = 0; +}; + +// the context to ingest hls stream. +class SrsIngestSrsInput +{ +private: + struct SrsTsPiece { + double duration; + std::string url; + std::string body; + + // should skip this ts? + bool skip; + // already sent to rtmp server? + bool sent; + // whether ts piece is dirty, remove if not update. + bool dirty; + + SrsTsPiece() { + skip = false; + sent = false; + dirty = false; + } + + int fetch(std::string m3u8); + }; +private: + SrsHttpUri* in_hls; + std::vector pieces; + int64_t next_connect_time; +private: + SrsStream* stream; + SrsTsContext* context; +public: + SrsIngestSrsInput(SrsHttpUri* hls) { + in_hls = hls; + next_connect_time = 0; + + stream = new SrsStream(); + context = new SrsTsContext(); + } + virtual ~SrsIngestSrsInput() { + srs_freep(stream); + srs_freep(context); + + std::vector::iterator it; + for (it = pieces.begin(); it != pieces.end(); ++it) { + SrsTsPiece* tp = *it; + srs_freep(tp); + } + pieces.clear(); + } + /** + * parse the input hls live m3u8 index. + */ + virtual int connect(); + /** + * parse the ts and use hanler to process the message. + */ + virtual int parse(ISrsTsHandler* ts, ISrsAacHandler* aac); +private: + /** + * parse the ts pieces body. + */ + virtual int parseAac(ISrsAacHandler* handler, char* body, int nb_body, double duration); + virtual int parseTs(ISrsTsHandler* handler, char* body, int nb_body); + /** + * parse the m3u8 specified by url. + */ + virtual int parseM3u8(SrsHttpUri* url, double& td, double& duration); + /** + * find the ts piece by its url. + */ + virtual SrsTsPiece* find_ts(string url); + /** + * set all ts to dirty. + */ + virtual void dirty_all_ts(); + /** + * fetch all ts body. + */ + virtual int fetch_all_ts(bool fresh_m3u8); + /** + * remove all ts which is dirty. + */ + virtual void remove_dirty(); +}; + +int SrsIngestSrsInput::connect() +{ + int ret = ERROR_SUCCESS; + + int64_t now = srs_update_system_time_ms(); + if (now < next_connect_time) { + srs_trace("input hls wait for %dms", next_connect_time - now); + st_usleep((next_connect_time - now) * 1000); + } + + // set all ts to dirty. + dirty_all_ts(); + + bool fresh_m3u8 = pieces.empty(); + double td = 0.0; + double duration = 0.0; + if ((ret = parseM3u8(in_hls, td, duration)) != ERROR_SUCCESS) { + return ret; + } + + // fetch all ts. + if ((ret = fetch_all_ts(fresh_m3u8)) != ERROR_SUCCESS) { + srs_error("fetch all ts failed. ret=%d", ret); + return ret; + } + + // remove all dirty ts. + remove_dirty(); + + srs_trace("fetch m3u8 ok, td=%.2f, duration=%.2f, pieces=%d", td, duration, pieces.size()); + + return ret; +} + +int SrsIngestSrsInput::parse(ISrsTsHandler* ts, ISrsAacHandler* aac) +{ + int ret = ERROR_SUCCESS; + + for (int i = 0; i < (int)pieces.size(); i++) { + SrsTsPiece* tp = pieces.at(i); + + // sent only once. + if (tp->sent) { + continue; + } + tp->sent = true; + + if (tp->body.empty()) { + continue; + } + + srs_trace("proxy the ts to rtmp, ts=%s, duration=%.2f", tp->url.c_str(), tp->duration); + + if (srs_string_ends_with(tp->url, ".ts")) { + if ((ret = parseTs(ts, (char*)tp->body.data(), (int)tp->body.length())) != ERROR_SUCCESS) { + return ret; + } + } else if (srs_string_ends_with(tp->url, ".aac")) { + if ((ret = parseAac(aac, (char*)tp->body.data(), (int)tp->body.length(), tp->duration)) != ERROR_SUCCESS) { + return ret; + } + } else { + srs_warn("ignore unkown piece %s", tp->url.c_str()); + } + } + + return ret; +} + +int SrsIngestSrsInput::parseTs(ISrsTsHandler* handler, char* body, int nb_body) +{ + int ret = ERROR_SUCCESS; + + // use stream to parse ts packet. + int nb_packet = (int)nb_body / SRS_TS_PACKET_SIZE; + for (int i = 0; i < nb_packet; i++) { + char* p = (char*)body + (i * SRS_TS_PACKET_SIZE); + if ((ret = stream->initialize(p, SRS_TS_PACKET_SIZE)) != ERROR_SUCCESS) { + return ret; + } + + // process each ts packet + if ((ret = context->decode(stream, handler)) != ERROR_SUCCESS) { + srs_error("mpegts: ignore parse ts packet failed. ret=%d", ret); + return ret; + } + srs_info("mpegts: parse ts packet completed"); + } + srs_info("mpegts: parse udp packet completed"); + + return ret; +} + +int SrsIngestSrsInput::parseAac(ISrsAacHandler* handler, char* body, int nb_body, double duration) +{ + int ret = ERROR_SUCCESS; + + if ((ret = stream->initialize(body, nb_body)) != ERROR_SUCCESS) { + return ret; + } + + // atleast 2bytes. + if (!stream->require(3)) { + ret = ERROR_AAC_BYTES_INVALID; + srs_error("invalid aac, atleast 3bytes. ret=%d", ret); + return ret; + } + + u_int8_t id0 = (u_int8_t)body[0]; + u_int8_t id1 = (u_int8_t)body[1]; + u_int8_t id2 = (u_int8_t)body[2]; + + // skip ID3. + if (id0 == 0x49 && id1 == 0x44 && id2 == 0x33) { + /*char id3[] = { + (char)0x49, (char)0x44, (char)0x33, // ID3 + (char)0x03, (char)0x00, // version + (char)0x00, // flags + (char)0x00, (char)0x00, (char)0x00, (char)0x0a, // size + + (char)0x00, (char)0x00, (char)0x00, (char)0x00, // FrameID + (char)0x00, (char)0x00, (char)0x00, (char)0x00, // FrameSize + (char)0x00, (char)0x00 // Flags + };*/ + // atleast 10 bytes. + if (!stream->require(10)) { + ret = ERROR_AAC_BYTES_INVALID; + srs_error("invalid aac ID3, atleast 10bytes. ret=%d", ret); + return ret; + } + + // ignore ID3 + version + flag. + stream->skip(6); + // read the size of ID3. + u_int32_t nb_id3 = stream->read_4bytes(); + + // read body of ID3 + if (!stream->require(nb_id3)) { + ret = ERROR_AAC_BYTES_INVALID; + srs_error("invalid aac ID3 body, required %dbytes. ret=%d", nb_id3, ret); + return ret; + } + stream->skip(nb_id3); + } + + char* frame = body + stream->pos(); + int frame_size = nb_body - stream->pos(); + return handler->on_aac_frame(frame, frame_size, duration); +} + +int SrsIngestSrsInput::parseM3u8(SrsHttpUri* url, double& td, double& duration) +{ + int ret = ERROR_SUCCESS; + + SrsHttpClient client; + srs_trace("parse input hls %s", url->get_url()); + + if ((ret = client.initialize(url->get_host(), url->get_port())) != ERROR_SUCCESS) { + srs_error("connect to server failed. ret=%d", ret); + return ret; + } + + SrsHttpMessage* msg = NULL; + if ((ret = client.get(url->get_path(), "", &msg)) != ERROR_SUCCESS) { + srs_error("HTTP GET %s failed. ret=%d", url->get_url(), ret); + return ret; + } + + srs_assert(msg); + SrsAutoFree(SrsHttpMessage, msg); + + std::string body; + if ((ret = msg->body_read_all(body)) != ERROR_SUCCESS) { + srs_error("read m3u8 failed. ret=%d", ret); + return ret; + } + + if (body.empty()) { + srs_warn("ignore empty m3u8"); + return ret; + } + + std::string ptl; + while (!body.empty()) { + size_t pos = string::npos; + + std::string line; + if ((pos = body.find("\n")) != string::npos) { + line = body.substr(0, pos); + body = body.substr(pos + 1); + } else { + line = body; + body = ""; + } + + line = srs_string_replace(line, "\r", ""); + line = srs_string_replace(line, " ", ""); + + // #EXT-X-VERSION:3 + // the version must be 3.0 + if (srs_string_starts_with(line, "#EXT-X-VERSION:")) { + if (!srs_string_ends_with(line, ":3")) { + srs_warn("m3u8 3.0 required, actual is %s", line.c_str()); + } + continue; + } + + // #EXT-X-PLAYLIST-TYPE:VOD + // the playlist type, vod or nothing. + if (srs_string_starts_with(line, "#EXT-X-PLAYLIST-TYPE:")) { + ptl = line; + continue; + } + + // #EXT-X-TARGETDURATION:12 + // the target duration is required. + if (srs_string_starts_with(line, "#EXT-X-TARGETDURATION:")) { + td = ::atof(line.substr(string("#EXT-X-TARGETDURATION:").length()).c_str()); + } + + // #EXT-X-ENDLIST + // parse completed. + if (line == "#EXT-X-ENDLIST") { + break; + } + + // #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=73207,CODECS="mp4a.40.2" + if (srs_string_starts_with(line, "#EXT-X-STREAM-INF:")) { + if ((pos = body.find("\n")) == string::npos) { + srs_warn("m3u8 entry unexpected eof, inf=%s", line.c_str()); + break; + } + + std::string m3u8_url = body.substr(0, pos); + body = body.substr(pos + 1); + + if (!srs_string_starts_with(m3u8_url, "http://")) { + m3u8_url = srs_path_dirname(url->get_url()) + "/" + m3u8_url; + } + srs_trace("parse sub m3u8, url=%s", m3u8_url.c_str()); + + if ((ret = url->initialize(m3u8_url)) != ERROR_SUCCESS) { + return ret; + } + + return parseM3u8(url, td, duration); + } + + // #EXTINF:11.401, + // livestream-5.ts + // parse each ts entry, expect current line is inf. + if (!srs_string_starts_with(line, "#EXTINF:")) { + continue; + } + + // expect next line is url. + std::string ts_url; + if ((pos = body.find("\n")) != string::npos) { + ts_url = body.substr(0, pos); + body = body.substr(pos + 1); + } else { + srs_warn("ts entry unexpected eof, inf=%s", line.c_str()); + break; + } + + // parse the ts duration. + line = line.substr(string("#EXTINF:").length()); + if ((pos = line.find(",")) != string::npos) { + line = line.substr(0, pos); + } + + double ts_duration = ::atof(line.c_str()); + duration += ts_duration; + + SrsTsPiece* tp = find_ts(ts_url); + if (!tp) { + tp = new SrsTsPiece(); + tp->url = ts_url; + tp->duration = ts_duration; + pieces.push_back(tp); + } else { + tp->dirty = false; + } + } + + return ret; +} + +SrsIngestSrsInput::SrsTsPiece* SrsIngestSrsInput::find_ts(string url) +{ + std::vector::iterator it; + for (it = pieces.begin(); it != pieces.end(); ++it) { + SrsTsPiece* tp = *it; + if (tp->url == url) { + return tp; + } + } + return NULL; +} + +void SrsIngestSrsInput::dirty_all_ts() +{ + std::vector::iterator it; + for (it = pieces.begin(); it != pieces.end(); ++it) { + SrsTsPiece* tp = *it; + tp->dirty = true; + } +} + +int SrsIngestSrsInput::fetch_all_ts(bool fresh_m3u8) +{ + int ret = ERROR_SUCCESS; + + for (int i = 0; i < (int)pieces.size(); i++) { + SrsTsPiece* tp = pieces.at(i); + + // when skipped, ignore. + if (tp->skip) { + continue; + } + + // for the fresh m3u8, skip except the last one. + if (fresh_m3u8 && i != (int)pieces.size() - 1) { + tp->skip = true; + continue; + } + + if ((ret = tp->fetch(in_hls->get_url())) != ERROR_SUCCESS) { + srs_error("fetch ts %s for error. ret=%d", tp->url.c_str(), ret); + tp->skip = true; + return ret; + } + + // only wait for a duration of last piece. + if (i == (int)pieces.size() - 1) { + next_connect_time = srs_update_system_time_ms() + (int)tp->duration * 1000; + } + } + + return ret; +} + + +void SrsIngestSrsInput::remove_dirty() +{ + std::vector::iterator it; + for (it = pieces.begin(); it != pieces.end();) { + SrsTsPiece* tp = *it; + + if (tp->dirty) { + srs_trace("erase dirty ts, url=%s, duration=%.2f", tp->url.c_str(), tp->duration); + srs_freep(tp); + it = pieces.erase(it); + } else { + ++it; + } + } +} + +int SrsIngestSrsInput::SrsTsPiece::fetch(string m3u8) +{ + int ret = ERROR_SUCCESS; + + if (skip || sent || !body.empty()) { + return ret; + } + + SrsHttpClient client; + + std::string ts_url = url; + if (!srs_string_starts_with(ts_url, "http://")) { + ts_url = srs_path_dirname(m3u8) + "/" + url; + } + + SrsHttpUri uri; + if ((ret = uri.initialize(ts_url)) != ERROR_SUCCESS) { + return ret; + } + + // initialize the fresh http client. + if ((ret = client.initialize(uri.get_host(), uri.get_port()) != ERROR_SUCCESS)) { + return ret; + } + + SrsHttpMessage* msg = NULL; + if ((ret = client.get(uri.get_path(), "", &msg)) != ERROR_SUCCESS) { + srs_error("HTTP GET %s failed. ret=%d", uri.get_url(), ret); + return ret; + } + + srs_assert(msg); + SrsAutoFree(SrsHttpMessage, msg); + + if ((ret = msg->body_read_all(body)) != ERROR_SUCCESS) { + srs_error("read ts failed. ret=%d", ret); + return ret; + } + + srs_trace("fetch ts ok, duration=%.2f, url=%s, body=%dB", duration, url.c_str(), body.length()); + + return ret; +} + +// the context to output to rtmp server +class SrsIngestSrsOutput : virtual public ISrsTsHandler, virtual public ISrsAacHandler +{ +private: + SrsHttpUri* out_rtmp; +private: + bool disconnected; + std::multimap queue; + int64_t raw_aac_dts; +private: + SrsRequest* req; + st_netfd_t stfd; + SrsStSocket* io; + SrsRtmpClient* client; + int stream_id; +private: + SrsRawH264Stream* avc; + std::string h264_sps; + bool h264_sps_changed; + std::string h264_pps; + bool h264_pps_changed; + bool h264_sps_pps_sent; +private: + SrsRawAacStream* aac; + std::string aac_specific_config; +public: + SrsIngestSrsOutput(SrsHttpUri* rtmp) { + out_rtmp = rtmp; + disconnected = false; + raw_aac_dts = srs_update_system_time_ms(); + + req = NULL; + io = NULL; + client = NULL; + stfd = NULL; + stream_id = 0; + + avc = new SrsRawH264Stream(); + aac = new SrsRawAacStream(); + h264_sps_changed = false; + h264_pps_changed = false; + h264_sps_pps_sent = false; + } + virtual ~SrsIngestSrsOutput() { + close(); + + srs_freep(avc); + srs_freep(aac); + + std::multimap::iterator it; + for (it = queue.begin(); it != queue.end(); ++it) { + SrsTsMessage* msg = it->second; + srs_freep(msg); + } + queue.clear(); + } +// interface ISrsTsHandler +public: + virtual int on_ts_message(SrsTsMessage* msg); +// interface IAacHandler +public: + virtual int on_aac_frame(char* frame, int frame_size, double duration); +private: + virtual int do_on_aac_frame(SrsStream* avs, double duration); + virtual int parse_message_queue(); + virtual int on_ts_video(SrsTsMessage* msg, SrsStream* avs); + virtual int write_h264_sps_pps(u_int32_t dts, u_int32_t pts); + virtual int write_h264_ipb_frame(std::string ibps, SrsCodecVideoAVCFrame frame_type, u_int32_t dts, u_int32_t pts); + virtual int on_ts_audio(SrsTsMessage* msg, SrsStream* avs); + virtual int write_audio_raw_frame(char* frame, int frame_size, SrsRawAacStreamCodec* codec, u_int32_t dts); +private: + virtual int rtmp_write_packet(char type, u_int32_t timestamp, char* data, int size); +public: + /** + * connect to output rtmp server. + */ + virtual int connect(); + /** + * flush the message queue when all ts parsed. + */ + virtual int flush_message_queue(); +private: + virtual int connect_app(std::string ep_server, std::string ep_port); + // close the connected io and rtmp to ready to be re-connect. + virtual void close(); +}; + +int SrsIngestSrsOutput::on_ts_message(SrsTsMessage* msg) +{ + int ret = ERROR_SUCCESS; + + // about the bytes of msg, specified by elementary stream which indicates by PES_packet_data_byte and stream_id + // for example, when SrsTsStream of SrsTsChannel indicates stream_type is SrsTsStreamVideoMpeg4 and SrsTsStreamAudioMpeg4, + // the elementary stream can be mux in "2.11 Carriage of ISO/IEC 14496 data" in hls-mpeg-ts-iso13818-1.pdf, page 103 + // @remark, the most popular stream_id is 0xe0 for h.264 over mpegts, which indicates the stream_id is video and + // stream_number is 0, where I guess the elementary is specified in annexb format(H.264-AVC-ISO_IEC_14496-10.pdf, page 211). + // because when audio stream_number is 0, the elementary is ADTS(aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 75, 1.A.2.2 ADTS). + + // about the bytes of PES_packet_data_byte, defined in hls-mpeg-ts-iso13818-1.pdf, page 58 + // PES_packet_data_byte ¨C PES_packet_data_bytes shall be contiguous bytes of data from the elementary stream + // indicated by the packet¡¯s stream_id or PID. When the elementary stream data conforms to ITU-T + // Rec. H.262 | ISO/IEC 13818-2 or ISO/IEC 13818-3, the PES_packet_data_bytes shall be byte aligned to the bytes of this + // Recommendation | International Standard. The byte-order of the elementary stream shall be preserved. The number of + // PES_packet_data_bytes, N, is specified by the PES_packet_length field. N shall be equal to the value indicated in the + // PES_packet_length minus the number of bytes between the last byte of the PES_packet_length field and the first + // PES_packet_data_byte. + // + // In the case of a private_stream_1, private_stream_2, ECM_stream, or EMM_stream, the contents of the + // PES_packet_data_byte field are user definable and will not be specified by ITU-T | ISO/IEC in the future. + + // about the bytes of stream_id, define in hls-mpeg-ts-iso13818-1.pdf, page 49 + // stream_id ¨C In Program Streams, the stream_id specifies the type and number of the elementary stream as defined by the + // stream_id Table 2-18. In Transport Streams, the stream_id may be set to any valid value which correctly describes the + // elementary stream type as defined in Table 2-18. In Transport Streams, the elementary stream type is specified in the + // Program Specific Information as specified in 2.4.4. + + // about the stream_id table, define in Table 2-18 ¨C Stream_id assignments, hls-mpeg-ts-iso13818-1.pdf, page 52. + // + // 110x xxxx + // ISO/IEC 13818-3 or ISO/IEC 11172-3 or ISO/IEC 13818-7 or ISO/IEC + // 14496-3 audio stream number x xxxx + // ((sid >> 5) & 0x07) == SrsTsPESStreamIdAudio + // + // 1110 xxxx + // ITU-T Rec. H.262 | ISO/IEC 13818-2 or ISO/IEC 11172-2 or ISO/IEC + // 14496-2 video stream number xxxx + // ((stream_id >> 4) & 0x0f) == SrsTsPESStreamIdVideo + + srs_info("<- "SRS_CONSTS_LOG_STREAM_CASTER" mpegts: got %s stream=%s, dts=%"PRId64", pts=%"PRId64", size=%d, us=%d, cc=%d, sid=%#x(%s-%d)", + (msg->channel->apply == SrsTsPidApplyVideo)? "Video":"Audio", srs_ts_stream2string(msg->channel->stream).c_str(), + msg->dts, msg->pts, msg->payload->length(), msg->packet->payload_unit_start_indicator, msg->continuity_counter, msg->sid, + msg->is_audio()? "A":msg->is_video()? "V":"N", msg->stream_number()); + + // when not audio/video, or not adts/annexb format, donot support. + if (msg->stream_number() != 0) { + ret = ERROR_STREAM_CASTER_TS_ES; + srs_error("mpegts: unsupported stream format, sid=%#x(%s-%d). ret=%d", + msg->sid, msg->is_audio()? "A":msg->is_video()? "V":"N", msg->stream_number(), ret); + return ret; + } + + // check supported codec + if (msg->channel->stream != SrsTsStreamVideoH264 && msg->channel->stream != SrsTsStreamAudioAAC) { + ret = ERROR_STREAM_CASTER_TS_CODEC; + srs_error("mpegts: unsupported stream codec=%d. ret=%d", msg->channel->stream, ret); + return ret; + } + + // we must use queue to cache the msg, then parse it if possible. + queue.insert(std::make_pair(msg->dts, msg->detach())); + if ((ret = parse_message_queue()) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +int SrsIngestSrsOutput::on_aac_frame(char* frame, int frame_size, double duration) +{ + int ret = ERROR_SUCCESS; + + srs_trace("handle aac frames, size=%dB, duration=%.2f, dts=%"PRId64, frame_size, duration, raw_aac_dts); + + SrsStream stream; + if ((ret = stream.initialize(frame, frame_size)) != ERROR_SUCCESS) { + return ret; + } + + return do_on_aac_frame(&stream, duration); +} + +int SrsIngestSrsOutput::do_on_aac_frame(SrsStream* avs, double duration) +{ + int ret = ERROR_SUCCESS; + + u_int32_t duration_ms = (u_int32_t)(duration * 1000); + + // ts tbn to flv tbn. + u_int32_t dts = (u_int32_t)raw_aac_dts; + raw_aac_dts += duration_ms; + + // got the next msg to calc the delta duration for each audio. + u_int32_t max_dts = dts + duration_ms; + + // send each frame. + while (!avs->empty()) { + char* frame = NULL; + int frame_size = 0; + SrsRawAacStreamCodec codec; + if ((ret = aac->adts_demux(avs, &frame, &frame_size, codec)) != ERROR_SUCCESS) { + return ret; + } + + // ignore invalid frame, + // * atleast 1bytes for aac to decode the data. + if (frame_size <= 0) { + continue; + } + srs_info("mpegts: demux aac frame size=%d, dts=%d", frame_size, dts); + + // generate sh. + if (aac_specific_config.empty()) { + std::string sh; + if ((ret = aac->mux_sequence_header(&codec, sh)) != ERROR_SUCCESS) { + return ret; + } + aac_specific_config = sh; + + codec.aac_packet_type = 0; + + if ((ret = write_audio_raw_frame((char*)sh.data(), (int)sh.length(), &codec, dts)) != ERROR_SUCCESS) { + return ret; + } + } + + // audio raw data. + codec.aac_packet_type = 1; + if ((ret = write_audio_raw_frame(frame, frame_size, &codec, dts)) != ERROR_SUCCESS) { + return ret; + } + + // calc the delta of dts, when previous frame output. + u_int32_t delta = duration_ms / (avs->size() / frame_size); + dts = (u_int32_t)(srs_min(max_dts, dts + delta)); + } + + return ret; +} + +int SrsIngestSrsOutput::parse_message_queue() +{ + int ret = ERROR_SUCCESS; + + if (queue.empty()) { + return ret; + } + + SrsTsMessage* first_ts_msg = queue.begin()->second; + SrsTsContext* context = first_ts_msg->channel->context; + bool cpa = context->is_pure_audio(); + + int nb_videos = 0; + if (!cpa) { + std::multimap::iterator it; + for (it = queue.begin(); it != queue.end(); ++it) { + SrsTsMessage* msg = it->second; + + // publish audio or video. + if (msg->channel->stream == SrsTsStreamVideoH264) { + nb_videos++; + } + } + + // always wait 2+ videos, to left one video in the queue. + // TODO: FIXME: support pure audio hls. + if (nb_videos <= 1) { + return ret; + } + } + + // parse messages util the last video. + while ((cpa && queue.size() > 1) || nb_videos > 1) { + srs_assert(!queue.empty()); + std::multimap::iterator it = queue.begin(); + + SrsTsMessage* msg = it->second; + if (msg->channel->stream == SrsTsStreamVideoH264) { + nb_videos--; + } + queue.erase(it); + + // parse the stream. + SrsStream avs; + if ((ret = avs.initialize(msg->payload->bytes(), msg->payload->length())) != ERROR_SUCCESS) { + srs_error("mpegts: initialize av stream failed. ret=%d", ret); + return ret; + } + + // publish audio or video. + if (msg->channel->stream == SrsTsStreamVideoH264) { + if ((ret = on_ts_video(msg, &avs)) != ERROR_SUCCESS) { + return ret; + } + } + if (msg->channel->stream == SrsTsStreamAudioAAC) { + if ((ret = on_ts_audio(msg, &avs)) != ERROR_SUCCESS) { + return ret; + } + } + } + + return ret; +} + +int SrsIngestSrsOutput::flush_message_queue() +{ + int ret = ERROR_SUCCESS; + + // parse messages util the last video. + while (!queue.empty()) { + std::multimap::iterator it = queue.begin(); + + SrsTsMessage* msg = it->second; + queue.erase(it); + + // parse the stream. + SrsStream avs; + if ((ret = avs.initialize(msg->payload->bytes(), msg->payload->length())) != ERROR_SUCCESS) { + srs_error("mpegts: initialize av stream failed. ret=%d", ret); + return ret; + } + + // publish audio or video. + if (msg->channel->stream == SrsTsStreamVideoH264) { + if ((ret = on_ts_video(msg, &avs)) != ERROR_SUCCESS) { + return ret; + } + } + if (msg->channel->stream == SrsTsStreamAudioAAC) { + if ((ret = on_ts_audio(msg, &avs)) != ERROR_SUCCESS) { + return ret; + } + } + } + + return ret; +} + +int SrsIngestSrsOutput::on_ts_video(SrsTsMessage* msg, SrsStream* avs) +{ + int ret = ERROR_SUCCESS; + + // ts tbn to flv tbn. + u_int32_t dts = (u_int32_t)(msg->dts / 90); + u_int32_t pts = (u_int32_t)(msg->dts / 90); + + std::string ibps; + SrsCodecVideoAVCFrame frame_type = SrsCodecVideoAVCFrameInterFrame; + + // send each frame. + while (!avs->empty()) { + char* frame = NULL; + int frame_size = 0; + if ((ret = avc->annexb_demux(avs, &frame, &frame_size)) != ERROR_SUCCESS) { + return ret; + } + + // 5bits, 7.3.1 NAL unit syntax, + // H.264-AVC-ISO_IEC_14496-10.pdf, page 44. + // 7: SPS, 8: PPS, 5: I Frame, 1: P Frame + SrsAvcNaluType nal_unit_type = (SrsAvcNaluType)(frame[0] & 0x1f); + + // for IDR frame, the frame is keyframe. + if (nal_unit_type == SrsAvcNaluTypeIDR) { + frame_type = SrsCodecVideoAVCFrameKeyFrame; + } + + // ignore the nalu type aud(9) + if (nal_unit_type == SrsAvcNaluTypeAccessUnitDelimiter) { + continue; + } + + // for sps + if (avc->is_sps(frame, frame_size)) { + std::string sps; + if ((ret = avc->sps_demux(frame, frame_size, sps)) != ERROR_SUCCESS) { + return ret; + } + + if (h264_sps == sps) { + continue; + } + h264_sps_changed = true; + h264_sps = sps; + continue; + } + + // for pps + if (avc->is_pps(frame, frame_size)) { + std::string pps; + if ((ret = avc->pps_demux(frame, frame_size, pps)) != ERROR_SUCCESS) { + return ret; + } + + if (h264_pps == pps) { + continue; + } + h264_pps_changed = true; + h264_pps = pps; + continue; + } + + // ibp frame. + std::string ibp; + if ((ret = avc->mux_ipb_frame(frame, frame_size, ibp)) != ERROR_SUCCESS) { + return ret; + } + ibps.append(ibp); + } + + if ((ret = write_h264_sps_pps(dts, pts)) != ERROR_SUCCESS) { + return ret; + } + + if ((ret = write_h264_ipb_frame(ibps, frame_type, dts, pts)) != ERROR_SUCCESS) { + // drop the ts message. + if (ret == ERROR_H264_DROP_BEFORE_SPS_PPS) { + return ERROR_SUCCESS; + } + return ret; + } + + return ret; +} + +int SrsIngestSrsOutput::write_h264_sps_pps(u_int32_t dts, u_int32_t pts) +{ + int ret = ERROR_SUCCESS; + + // when sps or pps changed, update the sequence header, + // for the pps maybe not changed while sps changed. + // so, we must check when each video ts message frame parsed. + if (h264_sps_pps_sent && !h264_sps_changed && !h264_pps_changed) { + return ret; + } + + // when not got sps/pps, wait. + if (h264_pps.empty() || h264_sps.empty()) { + return ret; + } + + // h264 raw to h264 packet. + std::string sh; + if ((ret = avc->mux_sequence_header(h264_sps, h264_pps, dts, pts, sh)) != ERROR_SUCCESS) { + return ret; + } + + // h264 packet to flv packet. + int8_t frame_type = SrsCodecVideoAVCFrameKeyFrame; + int8_t avc_packet_type = SrsCodecVideoAVCTypeSequenceHeader; + char* flv = NULL; + int nb_flv = 0; + if ((ret = avc->mux_avc2flv(sh, frame_type, avc_packet_type, dts, pts, &flv, &nb_flv)) != ERROR_SUCCESS) { + return ret; + } + + // the timestamp in rtmp message header is dts. + u_int32_t timestamp = dts; + if ((ret = rtmp_write_packet(SrsCodecFlvTagVideo, timestamp, flv, nb_flv)) != ERROR_SUCCESS) { + return ret; + } + + // reset sps and pps. + h264_sps_changed = false; + h264_pps_changed = false; + h264_sps_pps_sent = true; + srs_trace("hls: h264 sps/pps sent, sps=%dB, pps=%dB", h264_sps.length(), h264_pps.length()); + + return ret; +} + +int SrsIngestSrsOutput::write_h264_ipb_frame(string ibps, SrsCodecVideoAVCFrame frame_type, u_int32_t dts, u_int32_t pts) +{ + int ret = ERROR_SUCCESS; + + // when sps or pps not sent, ignore the packet. + // @see https://github.com/simple-rtmp-server/srs/issues/203 + if (!h264_sps_pps_sent) { + return ERROR_H264_DROP_BEFORE_SPS_PPS; + } + + int8_t avc_packet_type = SrsCodecVideoAVCTypeNALU; + char* flv = NULL; + int nb_flv = 0; + if ((ret = avc->mux_avc2flv(ibps, frame_type, avc_packet_type, dts, pts, &flv, &nb_flv)) != ERROR_SUCCESS) { + return ret; + } + + // the timestamp in rtmp message header is dts. + u_int32_t timestamp = dts; + return rtmp_write_packet(SrsCodecFlvTagVideo, timestamp, flv, nb_flv); +} + +int SrsIngestSrsOutput::on_ts_audio(SrsTsMessage* msg, SrsStream* avs) +{ + int ret = ERROR_SUCCESS; + + // ts tbn to flv tbn. + u_int32_t dts = (u_int32_t)(msg->dts / 90); + + // got the next msg to calc the delta duration for each audio. + u_int32_t duration = 0; + if (!queue.empty()) { + SrsTsMessage* nm = queue.begin()->second; + duration = (u_int32_t)(srs_max(0, nm->dts - msg->dts) / 90); + } + u_int32_t max_dts = dts + duration; + + // send each frame. + while (!avs->empty()) { + char* frame = NULL; + int frame_size = 0; + SrsRawAacStreamCodec codec; + if ((ret = aac->adts_demux(avs, &frame, &frame_size, codec)) != ERROR_SUCCESS) { + return ret; + } + + // ignore invalid frame, + // * atleast 1bytes for aac to decode the data. + if (frame_size <= 0) { + continue; + } + srs_info("mpegts: demux aac frame size=%d, dts=%d", frame_size, dts); + + // generate sh. + if (aac_specific_config.empty()) { + std::string sh; + if ((ret = aac->mux_sequence_header(&codec, sh)) != ERROR_SUCCESS) { + return ret; + } + aac_specific_config = sh; + + codec.aac_packet_type = 0; + + if ((ret = write_audio_raw_frame((char*)sh.data(), (int)sh.length(), &codec, dts)) != ERROR_SUCCESS) { + return ret; + } + } + + // audio raw data. + codec.aac_packet_type = 1; + if ((ret = write_audio_raw_frame(frame, frame_size, &codec, dts)) != ERROR_SUCCESS) { + return ret; + } + + // calc the delta of dts, when previous frame output. + u_int32_t delta = duration / (msg->payload->length() / frame_size); + dts = (u_int32_t)(srs_min(max_dts, dts + delta)); + } + + return ret; +} + +int SrsIngestSrsOutput::write_audio_raw_frame(char* frame, int frame_size, SrsRawAacStreamCodec* codec, u_int32_t dts) +{ + int ret = ERROR_SUCCESS; + + char* data = NULL; + int size = 0; + if ((ret = aac->mux_aac2flv(frame, frame_size, codec, dts, &data, &size)) != ERROR_SUCCESS) { + return ret; + } + + return rtmp_write_packet(SrsCodecFlvTagAudio, dts, data, size); +} + +int SrsIngestSrsOutput::rtmp_write_packet(char type, u_int32_t timestamp, char* data, int size) +{ + int ret = ERROR_SUCCESS; + + SrsSharedPtrMessage* msg = NULL; + + if ((ret = srs_rtmp_create_msg(type, timestamp, data, size, stream_id, &msg)) != ERROR_SUCCESS) { + srs_error("mpegts: create shared ptr msg failed. ret=%d", ret); + return ret; + } + srs_assert(msg); + + srs_info("RTMP type=%d, dts=%d, size=%d", type, timestamp, size); + + // send out encoded msg. + if ((ret = client->send_and_free_message(msg, stream_id)) != ERROR_SUCCESS) { + srs_error("send RTMP type=%d, dts=%d, size=%d failed. ret=%d", type, timestamp, size, ret); + return ret; + } + + return ret; +} + +int SrsIngestSrsOutput::connect() +{ + int ret = ERROR_SUCCESS; + + // when ok, ignore. + // TODO: FIXME: should reconnect when disconnected. + if (io || client) { + return ret; + } + + srs_trace("connect output=%s", out_rtmp->get_url()); + + // parse uri + if (!req) { + req = new SrsRequest(); + + string uri = req->tcUrl = out_rtmp->get_url(); + + // tcUrl, stream + if (srs_string_contains(uri, "/")) { + req->stream = srs_path_basename(uri); + req->tcUrl = uri = srs_path_dirname(uri); + } + + srs_discovery_tc_url(req->tcUrl, + req->schema, req->host, req->vhost, req->app, req->port, + req->param); + } + + // connect host. + if ((ret = srs_socket_connect(req->host, ::atoi(req->port.c_str()), ST_UTIME_NO_TIMEOUT, &stfd)) != ERROR_SUCCESS) { + srs_error("mpegts: connect server %s:%s failed. ret=%d", req->host.c_str(), req->port.c_str(), ret); + return ret; + } + io = new SrsStSocket(stfd); + client = new SrsRtmpClient(io); + + client->set_recv_timeout(SRS_CONSTS_RTMP_RECV_TIMEOUT_US); + client->set_send_timeout(SRS_CONSTS_RTMP_SEND_TIMEOUT_US); + + // connect to vhost/app + if ((ret = client->handshake()) != ERROR_SUCCESS) { + srs_error("mpegts: handshake with server failed. ret=%d", ret); + return ret; + } + if ((ret = connect_app(req->host, req->port)) != ERROR_SUCCESS) { + srs_error("mpegts: connect with server failed. ret=%d", ret); + return ret; + } + if ((ret = client->create_stream(stream_id)) != ERROR_SUCCESS) { + srs_error("mpegts: connect with server failed, stream_id=%d. ret=%d", stream_id, ret); + return ret; + } + + // publish. + if ((ret = client->publish(req->stream, stream_id)) != ERROR_SUCCESS) { + srs_error("mpegts: publish failed, stream=%s, stream_id=%d. ret=%d", + req->stream.c_str(), stream_id, ret); + return ret; + } + + return ret; +} + +// TODO: FIXME: refine the connect_app. +int SrsIngestSrsOutput::connect_app(string ep_server, string ep_port) +{ + int ret = ERROR_SUCCESS; + + // args of request takes the srs info. + if (req->args == NULL) { + req->args = SrsAmf0Any::object(); + } + + // notify server the edge identity, + // @see https://github.com/simple-rtmp-server/srs/issues/147 + SrsAmf0Object* data = req->args; + data->set("srs_sig", SrsAmf0Any::str(RTMP_SIG_SRS_KEY)); + data->set("srs_server", SrsAmf0Any::str(RTMP_SIG_SRS_KEY" "RTMP_SIG_SRS_VERSION" ("RTMP_SIG_SRS_URL_SHORT")")); + data->set("srs_license", SrsAmf0Any::str(RTMP_SIG_SRS_LICENSE)); + data->set("srs_role", SrsAmf0Any::str(RTMP_SIG_SRS_ROLE)); + data->set("srs_url", SrsAmf0Any::str(RTMP_SIG_SRS_URL)); + data->set("srs_version", SrsAmf0Any::str(RTMP_SIG_SRS_VERSION)); + data->set("srs_site", SrsAmf0Any::str(RTMP_SIG_SRS_WEB)); + data->set("srs_email", SrsAmf0Any::str(RTMP_SIG_SRS_EMAIL)); + data->set("srs_copyright", SrsAmf0Any::str(RTMP_SIG_SRS_COPYRIGHT)); + data->set("srs_primary", SrsAmf0Any::str(RTMP_SIG_SRS_PRIMARY)); + data->set("srs_authors", SrsAmf0Any::str(RTMP_SIG_SRS_AUTHROS)); + // for edge to directly get the id of client. + data->set("srs_pid", SrsAmf0Any::number(getpid())); + data->set("srs_id", SrsAmf0Any::number(_srs_context->get_id())); + + // local ip of edge + std::vector ips = srs_get_local_ipv4_ips(); + assert(0 < (int)ips.size()); + std::string local_ip = ips[0]; + data->set("srs_server_ip", SrsAmf0Any::str(local_ip.c_str())); + + // generate the tcUrl + std::string param = ""; + std::string tc_url = srs_generate_tc_url(ep_server, req->vhost, req->app, ep_port, param); + + // upnode server identity will show in the connect_app of client. + // @see https://github.com/simple-rtmp-server/srs/issues/160 + // the debug_srs_upnode is config in vhost and default to true. + bool debug_srs_upnode = true; + if ((ret = client->connect_app(req->app, tc_url, req, debug_srs_upnode)) != ERROR_SUCCESS) { + srs_error("mpegts: connect with server failed, tcUrl=%s, dsu=%d. ret=%d", + tc_url.c_str(), debug_srs_upnode, ret); + return ret; + } + + return ret; +} + +void SrsIngestSrsOutput::close() +{ + srs_trace("close output=%s", out_rtmp->get_url()); + h264_sps_pps_sent = false; + + srs_freep(client); + srs_freep(io); + srs_freep(req); + srs_close_stfd(stfd); +} + +// the context for ingest hls stream. +class SrsIngestSrsContext +{ +private: + SrsIngestSrsInput* ic; + SrsIngestSrsOutput* oc; +public: + SrsIngestSrsContext(SrsHttpUri* hls, SrsHttpUri* rtmp) { + ic = new SrsIngestSrsInput(hls); + oc = new SrsIngestSrsOutput(rtmp); + } + virtual ~SrsIngestSrsContext() { + srs_freep(ic); + srs_freep(oc); + } + virtual int proxy() { + int ret = ERROR_SUCCESS; + + if ((ret = ic->connect()) != ERROR_SUCCESS) { + srs_error("connect oc failed. ret=%d", ret); + return ret; + } + + if ((ret = oc->connect()) != ERROR_SUCCESS) { + srs_error("connect ic failed. ret=%d", ret); + return ret; + } + + if ((ret = ic->parse(oc, oc)) != ERROR_SUCCESS) { + srs_error("proxy ts to rtmp failed. ret=%d", ret); + return ret; + } + + if ((ret = oc->flush_message_queue()) != ERROR_SUCCESS) { + srs_error("flush oc message failed. ret=%d", ret); + return ret; + } + + return ret; + } +}; + +int proxy_hls2rtmp(string hls, string rtmp) +{ + int ret = ERROR_SUCCESS; + + // init st. + if ((ret = srs_init_st()) != ERROR_SUCCESS) { + srs_error("init st failed. ret=%d", ret); + return ret; + } + + SrsHttpUri hls_uri, rtmp_uri; + if ((ret = hls_uri.initialize(hls)) != ERROR_SUCCESS) { + srs_error("hls uri invalid. ret=%d", ret); + return ret; + } + if ((ret = rtmp_uri.initialize(rtmp)) != ERROR_SUCCESS) { + srs_error("rtmp uri invalid. ret=%d", ret); + return ret; + } + + SrsIngestSrsContext context(&hls_uri, &rtmp_uri); + for (;;) { + if ((ret = context.proxy()) != ERROR_SUCCESS) { + srs_error("proxy hls to rtmp failed. ret=%d", ret); + return ret; + } + } + + return ret; +} + diff --git a/trunk/src/main/srs_main_server.cpp b/trunk/src/main/srs_main_server.cpp index e16689636a..f948adb71e 100644 --- a/trunk/src/main/srs_main_server.cpp +++ b/trunk/src/main/srs_main_server.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -39,6 +39,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include #include +#include // pre-declare int run(); @@ -60,82 +61,164 @@ SrsServer* _srs_server = new SrsServer(); void show_macro_features() { #ifdef SRS_AUTO_SSL - srs_trace("rtmp handshake: on"); + srs_trace("check feature rtmp handshake: on"); #else - srs_warn("rtmp handshake: off"); + srs_warn("check feature rtmp handshake: off"); #endif #ifdef SRS_AUTO_HLS - srs_trace("hls: on"); + srs_trace("check feature hls: on"); #else - srs_warn("hls: off"); + srs_warn("check feature hls: off"); +#endif + +#ifdef SRS_AUTO_HDS + srs_trace("check feature hds: on"); +#else + srs_warn("check feature hds: off"); #endif #ifdef SRS_AUTO_HTTP_CALLBACK - srs_trace("http callback: on"); + srs_trace("check feature http callback: on"); #else - srs_warn("http callback: off"); + srs_warn("check feature http callback: off"); #endif #ifdef SRS_AUTO_HTTP_API - srs_trace("http api: on"); + srs_trace("check feature http api: on"); #else - srs_warn("http api: off"); + srs_warn("check feature http api: off"); #endif #ifdef SRS_AUTO_HTTP_SERVER - srs_trace("http server: on"); + srs_trace("check feature http server: on"); #else - srs_warn("http server: off"); + srs_warn("check feature http server: off"); #endif #ifdef SRS_AUTO_HTTP_PARSER - srs_trace("http parser: on"); + srs_trace("check feature http parser: on"); #else - srs_warn("http parser: off"); + srs_warn("check feature http parser: off"); #endif #ifdef SRS_AUTO_DVR - srs_trace("dvr: on"); + srs_trace("check feature dvr: on"); #else - srs_warn("dvr: off"); + srs_warn("check feature dvr: off"); #endif #ifdef SRS_AUTO_TRANSCODE - srs_trace("transcode: on"); + srs_trace("check feature transcode: on"); #else - srs_warn("transcode: off"); + srs_warn("check feature transcode: off"); #endif #ifdef SRS_AUTO_INGEST - srs_trace("ingest: on"); + srs_trace("check feature ingest: on"); #else - srs_warn("ingest: off"); + srs_warn("check feature ingest: off"); #endif #ifdef SRS_AUTO_STAT - srs_trace("system stat: on"); + srs_trace("check feature system stat: on"); #else - srs_warn("system stat: off"); + srs_warn("check feature system stat: off"); #endif #ifdef SRS_AUTO_NGINX - srs_trace("compile nginx: on"); + srs_trace("check feature compile nginx: on"); #else - srs_warn("compile nginx: off"); + srs_warn("check feature compile nginx: off"); #endif #ifdef SRS_AUTO_FFMPEG_TOOL - srs_trace("compile ffmpeg: on"); + srs_trace("check feature compile ffmpeg: on"); +#else + srs_warn("check feature compile ffmpeg: off"); +#endif + +#ifdef SRS_AUTO_STREAM_CASTER + srs_trace("stream caster: on"); +#else + srs_warn("stream caster: off"); +#endif + +#ifdef SRS_PERF_MERGED_READ + srs_trace("MR(merged-read): on, @see %s", RTMP_SIG_SRS_ISSUES(241)); +#else + srs_warn("MR(merged-read): off, @see %s", RTMP_SIG_SRS_ISSUES(241)); +#endif + + srs_trace("MR(merged-read) default %d sleep %d", SRS_PERF_MR_ENABLED, SRS_PERF_MR_SLEEP); + srs_trace("MW(merged-write) default sleep %d", SRS_PERF_MW_SLEEP); + srs_trace("read chunk stream cache cid [0, %d)", SRS_PERF_CHUNK_STREAM_CACHE); + srs_trace("default gop cache %d, play queue %ds", SRS_PERF_GOP_CACHE, SRS_PERF_PLAY_QUEUE); + +#ifndef SRS_PERF_COMPLEX_SEND + srs_warn("complex send algorithm disabled."); +#else + srs_trace("complex send algorithm enabled."); +#endif + +#ifdef SRS_PERF_TCP_NODELAY + srs_warn("TCP_NODELAY enabled, hurts performance."); #else - srs_warn("compile ffmpeg: off"); + srs_trace("TCP_NODELAY disabled."); #endif + +#ifdef SRS_PERF_SO_SNDBUF_SIZE + srs_warn("socket send buffer size %d", SRS_PERF_SO_SNDBUF_SIZE); +#else + srs_trace("auto guess socket send buffer by merged write"); +#endif + + int possible_mr_latency = 0; +#ifdef SRS_PERF_MERGED_READ + possible_mr_latency = SRS_PERF_MR_SLEEP; +#endif + srs_trace("system default latency in ms: mw(0-%d) + mr(0-%d) + play-queue(0-%d)", + SRS_PERF_MW_SLEEP, possible_mr_latency, SRS_PERF_PLAY_QUEUE*1000); } void check_macro_features() { -#if VERSION_MAJOR > 1 - srs_warn("SRS %s is develop branch, please use %s instead", RTMP_SIG_SRS_VERSION, RTMP_SIG_SRS_RELEASE); + // important preset. +#ifdef SRS_OSX + srs_trace("SRS for OSX"); +#endif +#ifdef SRS_PI + srs_trace("SRS for pi"); +#endif +#ifdef SRS_CUBIE + srs_trace("SRS for cubieboard"); +#endif +#ifdef SRS_ARM_UBUNTU12 + srs_trace("SRS for arm(build on ubuntu)"); +#endif +#ifdef SRS_MIPS_UBUNTU12 + srs_trace("SRS for mips(build on ubuntu)"); +#endif + + // for special features. +#ifndef SRS_PERF_MERGED_READ + srs_warn("MR(merged-read) is disabled, hurts read performance. @see %s", RTMP_SIG_SRS_ISSUES(241)); +#endif + + srs_trace("writev limits write %d iovs a time", sysconf(_SC_IOV_MAX)); + +#if VERSION_MAJOR > VERSION_STABLE + #warning "current branch is not stable, please use stable branch instead." + srs_warn("SRS %s is not stable, please use stable branch %s instead", RTMP_SIG_SRS_VERSION, VERSION_STABLE_BRANCH); +#endif + +#if defined(SRS_AUTO_STREAM_CASTER) + #warning "stream caster is experiment feature." + srs_warn("stream caster is experiment feature."); +#endif + +#if defined(SRS_PERF_SO_SNDBUF_SIZE) && !defined(SRS_PERF_MW_SO_SNDBUF) + #error "SRS_PERF_SO_SNDBUF_SIZE depends on SRS_PERF_MW_SO_SNDBUF" #endif } @@ -197,21 +280,15 @@ int main(int argc, char** argv) srs_trace("conf: %s, limit: %d", _srs_config->config().c_str(), _srs_config->get_max_connections()); // features - show_macro_features(); check_macro_features(); - - // for special features. -#ifdef SRS_AUTO_HTTP_SERVER - srs_warn("http server is dev feature, " - "@see https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_HTTPServer#feature"); -#endif + show_macro_features(); /** * we do nothing in the constructor of server, * and use initialize to create members, set hooks for instance the reload handler, * all initialize will done in this stage. */ - if ((ret = _srs_server->initialize()) != ERROR_SUCCESS) { + if ((ret = _srs_server->initialize(NULL)) != ERROR_SUCCESS) { return ret; } @@ -287,6 +364,10 @@ int run_master() return ret; } + if ((ret = _srs_server->http_handle()) != ERROR_SUCCESS) { + return ret; + } + if ((ret = _srs_server->ingest()) != ERROR_SUCCESS) { return ret; } diff --git a/trunk/src/protocol/srs_raw_avc.cpp b/trunk/src/protocol/srs_raw_avc.cpp new file mode 100644 index 0000000000..0d834c8b8b --- /dev/null +++ b/trunk/src/protocol/srs_raw_avc.cpp @@ -0,0 +1,559 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include + +#include +using namespace std; + +#include +#include +#include +#include +#include +#include + +SrsRawH264Stream::SrsRawH264Stream() +{ +} + +SrsRawH264Stream::~SrsRawH264Stream() +{ +} + +int SrsRawH264Stream::annexb_demux(SrsStream* stream, char** pframe, int* pnb_frame) +{ + int ret = ERROR_SUCCESS; + + *pframe = NULL; + *pnb_frame = 0; + + while (!stream->empty()) { + // each frame must prefixed by annexb format. + // about annexb, @see H.264-AVC-ISO_IEC_14496-10.pdf, page 211. + int pnb_start_code = 0; + if (!srs_avc_startswith_annexb(stream, &pnb_start_code)) { + return ERROR_H264_API_NO_PREFIXED; + } + int start = stream->pos() + pnb_start_code; + + // find the last frame prefixed by annexb format. + stream->skip(pnb_start_code); + while (!stream->empty()) { + if (srs_avc_startswith_annexb(stream, NULL)) { + break; + } + stream->skip(1); + } + + // demux the frame. + *pnb_frame = stream->pos() - start; + *pframe = stream->data() + start; + break; + } + + return ret; +} + +bool SrsRawH264Stream::is_sps(char* frame, int nb_frame) +{ + srs_assert(nb_frame > 0); + + // 5bits, 7.3.1 NAL unit syntax, + // H.264-AVC-ISO_IEC_14496-10.pdf, page 44. + // 7: SPS, 8: PPS, 5: I Frame, 1: P Frame + u_int8_t nal_unit_type = (char)frame[0] & 0x1f; + + return nal_unit_type == 7; +} + +bool SrsRawH264Stream::is_pps(char* frame, int nb_frame) +{ + srs_assert(nb_frame > 0); + + // 5bits, 7.3.1 NAL unit syntax, + // H.264-AVC-ISO_IEC_14496-10.pdf, page 44. + // 7: SPS, 8: PPS, 5: I Frame, 1: P Frame + u_int8_t nal_unit_type = (char)frame[0] & 0x1f; + + return nal_unit_type == 8; +} + +int SrsRawH264Stream::sps_demux(char* frame, int nb_frame, string& sps) +{ + int ret = ERROR_SUCCESS; + + // atleast 1bytes for SPS to decode the type, profile, constrain and level. + if (nb_frame < 4) { + return ret; + } + + sps = ""; + if (nb_frame > 0) { + sps.append(frame, nb_frame); + } + + // should never be empty. + if (sps.empty()) { + return ERROR_STREAM_CASTER_AVC_SPS; + } + + return ret; +} + +int SrsRawH264Stream::pps_demux(char* frame, int nb_frame, string& pps) +{ + int ret = ERROR_SUCCESS; + + pps = ""; + if (nb_frame > 0) { + pps.append(frame, nb_frame); + } + + // should never be empty. + if (pps.empty()) { + return ERROR_STREAM_CASTER_AVC_PPS; + } + + return ret; +} + +int SrsRawH264Stream::mux_sequence_header(string sps, string pps, u_int32_t dts, u_int32_t pts, string& sh) +{ + int ret = ERROR_SUCCESS; + + // 5bytes sps/pps header: + // configurationVersion, AVCProfileIndication, profile_compatibility, + // AVCLevelIndication, lengthSizeMinusOne + // 3bytes size of sps: + // numOfSequenceParameterSets, sequenceParameterSetLength(2B) + // Nbytes of sps. + // sequenceParameterSetNALUnit + // 3bytes size of pps: + // numOfPictureParameterSets, pictureParameterSetLength + // Nbytes of pps: + // pictureParameterSetNALUnit + int nb_packet = 5 + + 3 + (int)sps.length() + + 3 + (int)pps.length(); + char* packet = new char[nb_packet]; + SrsAutoFree(char, packet); + + // use stream to generate the h264 packet. + SrsStream stream; + if ((ret = stream.initialize(packet, nb_packet)) != ERROR_SUCCESS) { + return ret; + } + + // decode the SPS: + // @see: 7.3.2.1.1, H.264-AVC-ISO_IEC_14496-10-2012.pdf, page 62 + if (true) { + srs_assert((int)sps.length() >= 4); + char* frame = (char*)sps.data(); + + // @see: Annex A Profiles and levels, H.264-AVC-ISO_IEC_14496-10.pdf, page 205 + // Baseline profile profile_idc is 66(0x42). + // Main profile profile_idc is 77(0x4d). + // Extended profile profile_idc is 88(0x58). + u_int8_t profile_idc = frame[1]; + //u_int8_t constraint_set = frame[2]; + u_int8_t level_idc = frame[3]; + + // generate the sps/pps header + // 5.3.4.2.1 Syntax, H.264-AVC-ISO_IEC_14496-15.pdf, page 16 + // configurationVersion + stream.write_1bytes(0x01); + // AVCProfileIndication + stream.write_1bytes(profile_idc); + // profile_compatibility + stream.write_1bytes(0x00); + // AVCLevelIndication + stream.write_1bytes(level_idc); + // lengthSizeMinusOne, or NAL_unit_length, always use 4bytes size, + // so we always set it to 0x03. + stream.write_1bytes(0x03); + } + + // sps + if (true) { + // 5.3.4.2.1 Syntax, H.264-AVC-ISO_IEC_14496-15.pdf, page 16 + // numOfSequenceParameterSets, always 1 + stream.write_1bytes(0x01); + // sequenceParameterSetLength + stream.write_2bytes(sps.length()); + // sequenceParameterSetNALUnit + stream.write_string(sps); + } + + // pps + if (true) { + // 5.3.4.2.1 Syntax, H.264-AVC-ISO_IEC_14496-15.pdf, page 16 + // numOfPictureParameterSets, always 1 + stream.write_1bytes(0x01); + // pictureParameterSetLength + stream.write_2bytes(pps.length()); + // pictureParameterSetNALUnit + stream.write_string(pps); + } + + // TODO: FIXME: for more profile. + // 5.3.4.2.1 Syntax, H.264-AVC-ISO_IEC_14496-15.pdf, page 16 + // profile_idc == 100 || profile_idc == 110 || profile_idc == 122 || profile_idc == 144 + + sh = ""; + sh.append(packet, nb_packet); + + return ret; +} + +int SrsRawH264Stream::mux_ipb_frame(char* frame, int nb_frame, string& ibp) +{ + int ret = ERROR_SUCCESS; + + // 4bytes size of nalu: + // NALUnitLength + // Nbytes of nalu. + // NALUnit + int nb_packet = 4 + nb_frame; + char* packet = new char[nb_packet]; + SrsAutoFree(char, packet); + + // use stream to generate the h264 packet. + SrsStream stream; + if ((ret = stream.initialize(packet, nb_packet)) != ERROR_SUCCESS) { + return ret; + } + + // 5.3.4.2.1 Syntax, H.264-AVC-ISO_IEC_14496-15.pdf, page 16 + // lengthSizeMinusOne, or NAL_unit_length, always use 4bytes size + u_int32_t NAL_unit_length = nb_frame; + + // mux the avc NALU in "ISO Base Media File Format" + // from H.264-AVC-ISO_IEC_14496-15.pdf, page 20 + // NALUnitLength + stream.write_4bytes(NAL_unit_length); + // NALUnit + stream.write_bytes(frame, nb_frame); + + ibp = ""; + ibp.append(packet, nb_packet); + + return ret; +} + +int SrsRawH264Stream::mux_avc2flv(string video, int8_t frame_type, int8_t avc_packet_type, u_int32_t dts, u_int32_t pts, char** flv, int* nb_flv) +{ + int ret = ERROR_SUCCESS; + + // for h264 in RTMP video payload, there is 5bytes header: + // 1bytes, FrameType | CodecID + // 1bytes, AVCPacketType + // 3bytes, CompositionTime, the cts. + // @see: E.4.3 Video Tags, video_file_format_spec_v10_1.pdf, page 78 + int size = (int)video.length() + 5; + char* data = new char[size]; + char* p = data; + + // @see: E.4.3 Video Tags, video_file_format_spec_v10_1.pdf, page 78 + // Frame Type, Type of video frame. + // CodecID, Codec Identifier. + // set the rtmp header + *p++ = (frame_type << 4) | SrsCodecVideoAVC; + + // AVCPacketType + *p++ = avc_packet_type; + + // CompositionTime + // pts = dts + cts, or + // cts = pts - dts. + // where cts is the header in rtmp video packet payload header. + u_int32_t cts = pts - dts; + char* pp = (char*)&cts; + *p++ = pp[2]; + *p++ = pp[1]; + *p++ = pp[0]; + + // h.264 raw data. + memcpy(p, video.data(), video.length()); + + *flv = data; + *nb_flv = size; + + return ret; +} + +SrsRawAacStream::SrsRawAacStream() +{ +} + +SrsRawAacStream::~SrsRawAacStream() +{ +} + +int SrsRawAacStream::adts_demux(SrsStream* stream, char** pframe, int* pnb_frame, SrsRawAacStreamCodec& codec) +{ + int ret = ERROR_SUCCESS; + + while (!stream->empty()) { + int adts_header_start = stream->pos(); + + // decode the ADTS. + // @see aac-iso-13818-7.pdf, page 26 + // 6.2 Audio Data Transport Stream, ADTS + // @see https://github.com/simple-rtmp-server/srs/issues/212#issuecomment-64145885 + // byte_alignment() + + // adts_fixed_header: + // 12bits syncword, + // 16bits left. + // adts_variable_header: + // 28bits + // 12+16+28=56bits + // adts_error_check: + // 16bits if protection_absent + // 56+16=72bits + // if protection_absent: + // require(7bytes)=56bits + // else + // require(9bytes)=72bits + if (!stream->require(7)) { + return ERROR_AAC_ADTS_HEADER; + } + + // for aac, the frame must be ADTS format. + if (!srs_aac_startswith_adts(stream)) { + return ERROR_AAC_REQUIRED_ADTS; + } + + // syncword 12 bslbf + stream->read_1bytes(); + // 4bits left. + // adts_fixed_header(), 1.A.2.2.1 Fixed Header of ADTS + // ID 1 bslbf + // layer 2 uimsbf + // protection_absent 1 bslbf + int8_t pav = (stream->read_1bytes() & 0x0f); + int8_t id = (pav >> 3) & 0x01; + /*int8_t layer = (pav >> 1) & 0x03;*/ + int8_t protection_absent = pav & 0x01; + + /** + * ID: MPEG identifier, set to ‘1’ if the audio data in the ADTS stream are MPEG-2 AAC (See ISO/IEC 13818-7) + * and set to ‘0’ if the audio data are MPEG-4. See also ISO/IEC 11172-3, subclause 2.4.2.3. + */ + if (id != 0x01) { + srs_info("adts: id must be 1(aac), actual 0(mp4a). ret=%d", ret); + + // well, some system always use 0, but actually is aac format. + // for example, houjian vod ts always set the aac id to 0, actually 1. + // we just ignore it, and alwyas use 1(aac) to demux. + id = 0x01; + } + + int16_t sfiv = stream->read_2bytes(); + // profile 2 uimsbf + // sampling_frequency_index 4 uimsbf + // private_bit 1 bslbf + // channel_configuration 3 uimsbf + // original/copy 1 bslbf + // home 1 bslbf + int8_t profile = (sfiv >> 14) & 0x03; + int8_t sampling_frequency_index = (sfiv >> 10) & 0x0f; + /*int8_t private_bit = (sfiv >> 9) & 0x01;*/ + int8_t channel_configuration = (sfiv >> 6) & 0x07; + /*int8_t original = (sfiv >> 5) & 0x01;*/ + /*int8_t home = (sfiv >> 4) & 0x01;*/ + //int8_t Emphasis; @remark, Emphasis is removed, @see https://github.com/simple-rtmp-server/srs/issues/212#issuecomment-64154736 + // 4bits left. + // adts_variable_header(), 1.A.2.2.2 Variable Header of ADTS + // copyright_identification_bit 1 bslbf + // copyright_identification_start 1 bslbf + /*int8_t fh_copyright_identification_bit = (fh1 >> 3) & 0x01;*/ + /*int8_t fh_copyright_identification_start = (fh1 >> 2) & 0x01;*/ + // frame_length 13 bslbf: Length of the frame including headers and error_check in bytes. + // use the left 2bits as the 13 and 12 bit, + // the frame_length is 13bits, so we move 13-2=11. + int16_t frame_length = (sfiv << 11) & 0x1800; + + int32_t abfv = stream->read_3bytes(); + // frame_length 13 bslbf: consume the first 13-2=11bits + // the fh2 is 24bits, so we move right 24-11=13. + frame_length |= (abfv >> 13) & 0x07ff; + // adts_buffer_fullness 11 bslbf + /*int16_t fh_adts_buffer_fullness = (abfv >> 2) & 0x7ff;*/ + // number_of_raw_data_blocks_in_frame 2 uimsbf + /*int16_t number_of_raw_data_blocks_in_frame = abfv & 0x03;*/ + // adts_error_check(), 1.A.2.2.3 Error detection + if (!protection_absent) { + if (!stream->require(2)) { + return ERROR_AAC_ADTS_HEADER; + } + // crc_check 16 Rpchof + /*int16_t crc_check = */stream->read_2bytes(); + } + + // TODO: check the sampling_frequency_index + // TODO: check the channel_configuration + + // raw_data_blocks + int adts_header_size = stream->pos() - adts_header_start; + int raw_data_size = frame_length - adts_header_size; + if (!stream->require(raw_data_size)) { + return ERROR_AAC_ADTS_HEADER; + } + + // the codec info. + codec.protection_absent = protection_absent; + codec.aac_object = srs_codec_aac_ts2rtmp((SrsAacProfile)profile); + codec.sampling_frequency_index = sampling_frequency_index; + codec.channel_configuration = channel_configuration; + codec.frame_length = frame_length; + + // @see srs_audio_write_raw_frame(). + // TODO: FIXME: maybe need to resample audio. + codec.sound_format = 10; // AAC + if (sampling_frequency_index <= 0x0c && sampling_frequency_index > 0x0a) { + codec.sound_rate = SrsCodecAudioSampleRate5512; + } else if (sampling_frequency_index <= 0x0a && sampling_frequency_index > 0x07) { + codec.sound_rate = SrsCodecAudioSampleRate11025; + } else if (sampling_frequency_index <= 0x07 && sampling_frequency_index > 0x04) { + codec.sound_rate = SrsCodecAudioSampleRate22050; + } else if (sampling_frequency_index <= 0x04) { + codec.sound_rate = SrsCodecAudioSampleRate44100; + } else { + codec.sound_rate = SrsCodecAudioSampleRate44100; + srs_warn("adts invalid sample rate for flv, rate=%#x", sampling_frequency_index); + } + codec.sound_type = srs_max(0, srs_min(1, channel_configuration - 1)); + // TODO: FIXME: finger it out the sound size by adts. + codec.sound_size = 1; // 0(8bits) or 1(16bits). + + // frame data. + *pframe = stream->data() + stream->pos(); + *pnb_frame = raw_data_size; + stream->skip(raw_data_size); + + break; + } + + return ret; +} + +int SrsRawAacStream::mux_sequence_header(SrsRawAacStreamCodec* codec, string& sh) +{ + int ret = ERROR_SUCCESS; + + // only support aac profile 1-4. + if (codec->aac_object == SrsAacObjectTypeReserved) { + return ERROR_AAC_DATA_INVALID; + } + + SrsAacObjectType audioObjectType = codec->aac_object; + char channelConfiguration = codec->channel_configuration; + char samplingFrequencyIndex = codec->sampling_frequency_index; + + // override the aac samplerate by user specified. + // @see https://github.com/simple-rtmp-server/srs/issues/212#issuecomment-64146899 + switch (codec->sound_rate) { + case SrsCodecAudioSampleRate11025: + samplingFrequencyIndex = 0x0a; break; + case SrsCodecAudioSampleRate22050: + samplingFrequencyIndex = 0x07; break; + case SrsCodecAudioSampleRate44100: + samplingFrequencyIndex = 0x04; break; + default: + break; + } + + sh = ""; + + char ch = 0; + // @see aac-mp4a-format-ISO_IEC_14496-3+2001.pdf + // AudioSpecificConfig (), page 33 + // 1.6.2.1 AudioSpecificConfig + // audioObjectType; 5 bslbf + ch = (audioObjectType << 3) & 0xf8; + // 3bits left. + + // samplingFrequencyIndex; 4 bslbf + ch |= (samplingFrequencyIndex >> 1) & 0x07; + sh += ch; + ch = (samplingFrequencyIndex << 7) & 0x80; + if (samplingFrequencyIndex == 0x0f) { + return ERROR_AAC_DATA_INVALID; + } + // 7bits left. + + // channelConfiguration; 4 bslbf + ch |= (channelConfiguration << 3) & 0x78; + // 3bits left. + + // GASpecificConfig(), page 451 + // 4.4.1 Decoder configuration (GASpecificConfig) + // frameLengthFlag; 1 bslbf + // dependsOnCoreCoder; 1 bslbf + // extensionFlag; 1 bslbf + sh += ch; + + return ret; +} + +int SrsRawAacStream::mux_aac2flv(char* frame, int nb_frame, SrsRawAacStreamCodec* codec, u_int32_t dts, char** flv, int* nb_flv) +{ + int ret = ERROR_SUCCESS; + + char sound_format = codec->sound_format; + char sound_type = codec->sound_type; + char sound_size = codec->sound_size; + char sound_rate = codec->sound_rate; + char aac_packet_type = codec->aac_packet_type; + + // for audio frame, there is 1 or 2 bytes header: + // 1bytes, SoundFormat|SoundRate|SoundSize|SoundType + // 1bytes, AACPacketType for SoundFormat == 10, 0 is sequence header. + int size = nb_frame + 1; + if (sound_format == SrsCodecAudioAAC) { + size += 1; + } + char* data = new char[size]; + char* p = data; + + u_int8_t audio_header = sound_type & 0x01; + audio_header |= (sound_size << 1) & 0x02; + audio_header |= (sound_rate << 2) & 0x0c; + audio_header |= (sound_format << 4) & 0xf0; + + *p++ = audio_header; + + if (sound_format == SrsCodecAudioAAC) { + *p++ = aac_packet_type; + } + + memcpy(p, frame, nb_frame); + + *flv = data; + *nb_flv = size; + + return ret; +} + diff --git a/trunk/src/protocol/srs_raw_avc.hpp b/trunk/src/protocol/srs_raw_avc.hpp new file mode 100644 index 0000000000..27875a67a5 --- /dev/null +++ b/trunk/src/protocol/srs_raw_avc.hpp @@ -0,0 +1,144 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef SRS_PROTOCOL_RAW_AVC_HPP +#define SRS_PROTOCOL_RAW_AVC_HPP + +/* +#include +*/ + +#include + +#include + +#include + +class SrsStream; + +/** +* the raw h.264 stream, in annexb. +*/ +class SrsRawH264Stream +{ +public: + SrsRawH264Stream(); + virtual ~SrsRawH264Stream(); +public: + /** + * demux the stream in annexb format. + * @param stream the input stream bytes. + * @param pframe the output h.264 frame in stream. user should never free it. + * @param pnb_frame the output h.264 frame size. + */ + virtual int annexb_demux(SrsStream* stream, char** pframe, int* pnb_frame); + /** + * whether the frame is sps or pps. + */ + virtual bool is_sps(char* frame, int nb_frame); + virtual bool is_pps(char* frame, int nb_frame); + /** + * demux the sps or pps to string. + * @param sps/pps output the sps/pps. + */ + virtual int sps_demux(char* frame, int nb_frame, std::string& sps); + virtual int pps_demux(char* frame, int nb_frame, std::string& pps); +public: + /** + * h264 raw data to h264 packet, without flv payload header. + * mux the sps/pps to flv sequence header packet. + * @param sh output the sequence header. + */ + virtual int mux_sequence_header(std::string sps, std::string pps, u_int32_t dts, u_int32_t pts, std::string& sh); + /** + * h264 raw data to h264 packet, without flv payload header. + * mux the ibp to flv ibp packet. + * @param ibp output the packet. + * @param frame_type output the frame type. + */ + virtual int mux_ipb_frame(char* frame, int nb_frame, std::string& ibp); + /** + * mux the avc video packet to flv video packet. + * @param frame_type, SrsCodecVideoAVCFrameKeyFrame or SrsCodecVideoAVCFrameInterFrame. + * @param avc_packet_type, SrsCodecVideoAVCTypeSequenceHeader or SrsCodecVideoAVCTypeNALU. + * @param video the h.264 raw data. + * @param flv output the muxed flv packet. + * @param nb_flv output the muxed flv size. + */ + virtual int mux_avc2flv(std::string video, int8_t frame_type, int8_t avc_packet_type, u_int32_t dts, u_int32_t pts, char** flv, int* nb_flv); +}; + +/** +* the header of adts sample. +*/ +struct SrsRawAacStreamCodec +{ + int8_t protection_absent; + SrsAacObjectType aac_object; + int8_t sampling_frequency_index; + int8_t channel_configuration; + int16_t frame_length; + + char sound_format; + char sound_rate; + char sound_size; + char sound_type; + // 0 for sh; 1 for raw data. + int8_t aac_packet_type; +}; + +/** +* the raw aac stream, in adts. +*/ +class SrsRawAacStream +{ +public: + SrsRawAacStream(); + virtual ~SrsRawAacStream(); +public: + /** + * demux the stream in adts format. + * @param stream the input stream bytes. + * @param pframe the output aac frame in stream. user should never free it. + * @param pnb_frame the output aac frame size. + * @param codec the output codec info. + */ + virtual int adts_demux(SrsStream* stream, char** pframe, int* pnb_frame, SrsRawAacStreamCodec& codec); + /** + * aac raw data to aac packet, without flv payload header. + * mux the aac specific config to flv sequence header packet. + * @param sh output the sequence header. + */ + virtual int mux_sequence_header(SrsRawAacStreamCodec* codec, std::string& sh); + /** + * mux the aac audio packet to flv audio packet. + * @param frame the aac raw data. + * @param nb_frame the count of aac frame. + * @param codec the codec info of aac. + * @param flv output the muxed flv packet. + * @param nb_flv output the muxed flv size. + */ + virtual int mux_aac2flv(char* frame, int nb_frame, SrsRawAacStreamCodec* codec, u_int32_t dts, char** flv, int* nb_flv); +}; + +#endif diff --git a/trunk/src/rtmp/srs_protocol_amf0.cpp b/trunk/src/protocol/srs_rtmp_amf0.cpp similarity index 89% rename from trunk/src/rtmp/srs_protocol_amf0.cpp rename to trunk/src/protocol/srs_rtmp_amf0.cpp index 91df826144..78db3c64cb 100644 --- a/trunk/src/rtmp/srs_protocol_amf0.cpp +++ b/trunk/src/protocol/srs_rtmp_amf0.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -21,7 +21,7 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include +#include #include #include @@ -109,6 +109,11 @@ bool SrsAmf0Any::is_strict_array() return marker == RTMP_AMF0_StrictArray; } +bool SrsAmf0Any::is_date() +{ + return marker == RTMP_AMF0_Date; +} + bool SrsAmf0Any::is_complex_object() { return is_object() || is_object_eof() || is_ecma_array() || is_strict_array(); @@ -142,6 +147,20 @@ double SrsAmf0Any::to_number() return p->value; } +int64_t SrsAmf0Any::to_date() +{ + SrsAmf0Date* p = dynamic_cast(this); + srs_assert(p != NULL); + return p->date(); +} + +int16_t SrsAmf0Any::to_date_time_zone() +{ + SrsAmf0Date* p = dynamic_cast(this); + srs_assert(p != NULL); + return p->time_zone(); +} + SrsAmf0Object* SrsAmf0Any::to_object() { SrsAmf0Object* p = dynamic_cast(this); @@ -175,13 +194,13 @@ bool SrsAmf0Any::is_object_eof() return marker == RTMP_AMF0_ObjectEnd; } -void __srs_fill_level_spaces(stringstream& ss, int level) +void srs_fill_level_spaces(stringstream& ss, int level) { for (int i = 0; i < level; i++) { ss << " "; } } -void __srs_amf0_do_print(SrsAmf0Any* any, stringstream& ss, int level) +void srs_amf0_do_print(SrsAmf0Any* any, stringstream& ss, int level) { if (any->is_boolean()) { ss << "Boolean " << (any->to_boolean()? "true":"false") << endl; @@ -189,42 +208,45 @@ void __srs_amf0_do_print(SrsAmf0Any* any, stringstream& ss, int level) ss << "Number " << std::fixed << any->to_number() << endl; } else if (any->is_string()) { ss << "String " << any->to_str() << endl; + } else if (any->is_date()) { + ss << "Date " << std::hex << any->to_date() + << "/" << std::hex << any->to_date_time_zone() << endl; } else if (any->is_null()) { ss << "Null" << endl; } else if (any->is_ecma_array()) { SrsAmf0EcmaArray* obj = any->to_ecma_array(); ss << "EcmaArray " << "(" << obj->count() << " items)" << endl; for (int i = 0; i < obj->count(); i++) { - __srs_fill_level_spaces(ss, level + 1); + srs_fill_level_spaces(ss, level + 1); ss << "Elem '" << obj->key_at(i) << "' "; if (obj->value_at(i)->is_complex_object()) { - __srs_amf0_do_print(obj->value_at(i), ss, level + 1); + srs_amf0_do_print(obj->value_at(i), ss, level + 1); } else { - __srs_amf0_do_print(obj->value_at(i), ss, 0); + srs_amf0_do_print(obj->value_at(i), ss, 0); } } } else if (any->is_strict_array()) { SrsAmf0StrictArray* obj = any->to_strict_array(); ss << "StrictArray " << "(" << obj->count() << " items)" << endl; for (int i = 0; i < obj->count(); i++) { - __srs_fill_level_spaces(ss, level + 1); + srs_fill_level_spaces(ss, level + 1); ss << "Elem "; if (obj->at(i)->is_complex_object()) { - __srs_amf0_do_print(obj->at(i), ss, level + 1); + srs_amf0_do_print(obj->at(i), ss, level + 1); } else { - __srs_amf0_do_print(obj->at(i), ss, 0); + srs_amf0_do_print(obj->at(i), ss, 0); } } } else if (any->is_object()) { SrsAmf0Object* obj = any->to_object(); ss << "Object " << "(" << obj->count() << " items)" << endl; for (int i = 0; i < obj->count(); i++) { - __srs_fill_level_spaces(ss, level + 1); + srs_fill_level_spaces(ss, level + 1); ss << "Property '" << obj->key_at(i) << "' "; if (obj->value_at(i)->is_complex_object()) { - __srs_amf0_do_print(obj->value_at(i), ss, level + 1); + srs_amf0_do_print(obj->value_at(i), ss, level + 1); } else { - __srs_amf0_do_print(obj->value_at(i), ss, 0); + srs_amf0_do_print(obj->value_at(i), ss, 0); } } } else { @@ -238,7 +260,7 @@ char* SrsAmf0Any::human_print(char** pdata, int* psize) ss.precision(1); - __srs_amf0_do_print(this, ss, 0); + srs_amf0_do_print(this, ss, 0); string str = ss.str(); if (str.empty()) { @@ -304,6 +326,11 @@ SrsAmf0StrictArray* SrsAmf0Any::strict_array() return new SrsAmf0StrictArray(); } +SrsAmf0Any* SrsAmf0Any::date(int64_t value) +{ + return new SrsAmf0Date(value); +} + int SrsAmf0Any::discovery(SrsStream* stream, SrsAmf0Any** ppvalue) { int ret = ERROR_SUCCESS; @@ -360,6 +387,10 @@ int SrsAmf0Any::discovery(SrsStream* stream, SrsAmf0Any** ppvalue) *ppvalue = SrsAmf0Any::strict_array(); return ret; } + case RTMP_AMF0_Date: { + *ppvalue = SrsAmf0Any::date(); + return ret; + } case RTMP_AMF0_Invalid: default: { ret = ERROR_RTMP_AMF0_INVALID; @@ -417,11 +448,6 @@ SrsAmf0Any* SrsUnSortedHashtable::value_at(int index) void SrsUnSortedHashtable::set(string key, SrsAmf0Any* value) { - if (!value) { - srs_warn("add a NULL propertity %s", key.c_str()); - return; - } - std::vector::iterator it; for (it = properties.begin(); it != properties.end(); ++it) { @@ -436,7 +462,9 @@ void SrsUnSortedHashtable::set(string key, SrsAmf0Any* value) } } - properties.push_back(std::make_pair(key, value)); + if (value) { + properties.push_back(std::make_pair(key, value)); + } } SrsAmf0Any* SrsUnSortedHashtable::get_property(string name) @@ -805,7 +833,7 @@ int SrsAmf0EcmaArray::read(SrsStream* stream) if (marker != RTMP_AMF0_EcmaArray) { ret = ERROR_RTMP_AMF0_DECODE; srs_error("amf0 check ecma_array marker failed. " - "marker=%#x, required=%#x, ret=%d", marker, RTMP_AMF0_Object, ret); + "marker=%#x, required=%#x, ret=%d", marker, RTMP_AMF0_EcmaArray, ret); return ret; } srs_verbose("amf0 read ecma_array marker success"); @@ -1003,7 +1031,7 @@ int SrsAmf0StrictArray::read(SrsStream* stream) if (marker != RTMP_AMF0_StrictArray) { ret = ERROR_RTMP_AMF0_DECODE; srs_error("amf0 check strict_array marker failed. " - "marker=%#x, required=%#x, ret=%d", marker, RTMP_AMF0_Object, ret); + "marker=%#x, required=%#x, ret=%d", marker, RTMP_AMF0_StrictArray, ret); return ret; } srs_verbose("amf0 read strict_array marker success"); @@ -1127,6 +1155,11 @@ int SrsAmf0Size::number() return 1 + 8; } +int SrsAmf0Size::date() +{ + return 1 + 8 + 2; +} + int SrsAmf0Size::null() { return 1; @@ -1278,6 +1311,130 @@ SrsAmf0Any* SrsAmf0Number::copy() return copy; } +SrsAmf0Date::SrsAmf0Date(int64_t value) +{ + marker = RTMP_AMF0_Date; + _date_value = value; + _time_zone = 0; +} + +SrsAmf0Date::~SrsAmf0Date() +{ +} + +int SrsAmf0Date::total_size() +{ + return SrsAmf0Size::date(); +} + +int SrsAmf0Date::read(SrsStream* stream) +{ + int ret = ERROR_SUCCESS; + + // marker + if (!stream->require(1)) { + ret = ERROR_RTMP_AMF0_DECODE; + srs_error("amf0 read date marker failed. ret=%d", ret); + return ret; + } + + char marker = stream->read_1bytes(); + if (marker != RTMP_AMF0_Date) { + ret = ERROR_RTMP_AMF0_DECODE; + srs_error("amf0 check date marker failed. " + "marker=%#x, required=%#x, ret=%d", marker, RTMP_AMF0_Date, ret); + return ret; + } + srs_verbose("amf0 read date marker success"); + + // date value + // An ActionScript Date is serialized as the number of milliseconds + // elapsed since the epoch of midnight on 1st Jan 1970 in the UTC + // time zone. + if (!stream->require(8)) { + ret = ERROR_RTMP_AMF0_DECODE; + srs_error("amf0 read date failed. ret=%d", ret); + return ret; + } + + _date_value = stream->read_8bytes(); + srs_verbose("amf0 read date success. date=%"PRId64, _date_value); + + // time zone + // While the design of this type reserves room for time zone offset + // information, it should not be filled in, nor used, as it is unconventional + // to change time zones when serializing dates on a network. It is suggested + // that the time zone be queried independently as needed. + if (!stream->require(2)) { + ret = ERROR_RTMP_AMF0_DECODE; + srs_error("amf0 read time zone failed. ret=%d", ret); + return ret; + } + + _time_zone = stream->read_2bytes(); + srs_verbose("amf0 read time zone success. zone=%d", _time_zone); + + return ret; +} +int SrsAmf0Date::write(SrsStream* stream) +{ + int ret = ERROR_SUCCESS; + + // marker + if (!stream->require(1)) { + ret = ERROR_RTMP_AMF0_ENCODE; + srs_error("amf0 write date marker failed. ret=%d", ret); + return ret; + } + + stream->write_1bytes(RTMP_AMF0_Date); + srs_verbose("amf0 write date marker success"); + + // date value + if (!stream->require(8)) { + ret = ERROR_RTMP_AMF0_ENCODE; + srs_error("amf0 write date failed. ret=%d", ret); + return ret; + } + + stream->write_8bytes(_date_value); + srs_verbose("amf0 write date success. date=%"PRId64, _date_value); + + // time zone + if (!stream->require(2)) { + ret = ERROR_RTMP_AMF0_ENCODE; + srs_error("amf0 write time zone failed. ret=%d", ret); + return ret; + } + + stream->write_2bytes(_time_zone); + srs_verbose("amf0 write time zone success. date=%d", _time_zone); + + srs_verbose("write date object success."); + + return ret; +} + +SrsAmf0Any* SrsAmf0Date::copy() +{ + SrsAmf0Date* copy = new SrsAmf0Date(0); + + copy->_date_value = _date_value; + copy->_time_zone = _time_zone; + + return copy; +} + +int64_t SrsAmf0Date::date() +{ + return _date_value; +} + +int16_t SrsAmf0Date::time_zone() +{ + return _time_zone; +} + SrsAmf0Null::SrsAmf0Null() { marker = RTMP_AMF0_Null; diff --git a/trunk/src/rtmp/srs_protocol_amf0.hpp b/trunk/src/protocol/srs_rtmp_amf0.hpp similarity index 92% rename from trunk/src/rtmp/srs_protocol_amf0.hpp rename to trunk/src/protocol/srs_rtmp_amf0.hpp index 1ecc3f4ae4..6b697c9aca 100644 --- a/trunk/src/rtmp/srs_protocol_amf0.hpp +++ b/trunk/src/protocol/srs_rtmp_amf0.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -25,7 +25,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define SRS_RTMP_PROTOCOL_AMF0_HPP /* -#include +#include */ #include @@ -43,6 +43,7 @@ namespace _srs_internal { class SrsUnSortedHashtable; class SrsAmf0ObjectEOF; + class SrsAmf0Date; } /* @@ -185,6 +186,12 @@ class SrsAmf0Any */ virtual bool is_strict_array(); /** + * whether current instance is an AMF0 date. + * @return true if instance is an AMF0 date; otherwise, false. + * @remark, if true, use to_date() to get its value. + */ + virtual bool is_date(); + /** * whether current instance is an AMF0 object, object-EOF, ecma-array or strict-array. */ virtual bool is_complex_object(); @@ -212,6 +219,12 @@ class SrsAmf0Any */ virtual double to_number(); /** + * convert instance to date, + * @remark assert is_date(), user must ensure the type then convert. + */ + virtual int64_t to_date(); + virtual int16_t to_date_time_zone(); + /** * convert instance to amf0 object, * @remark assert is_object(), user must ensure the type then convert. */ @@ -274,6 +287,10 @@ class SrsAmf0Any */ static SrsAmf0Any* number(double value = 0.0); /** + * create an AMF0 date instance + */ + static SrsAmf0Any* date(int64_t value = 0); + /** * create an AMF0 null instance */ static SrsAmf0Any* null(); @@ -532,6 +549,7 @@ class SrsAmf0Size static int utf8(std::string value); static int str(std::string value); static int number(); + static int date(); static int null(); static int undefined(); static int boolean(); @@ -673,6 +691,43 @@ namespace _srs_internal virtual SrsAmf0Any* copy(); }; + /** + * 2.13 Date Type + * time-zone = S16 ; reserved, not supported should be set to 0x0000 + * date-type = date-marker DOUBLE time-zone + * @see: https://github.com/simple-rtmp-server/srs/issues/185 + */ + class SrsAmf0Date : public SrsAmf0Any + { + private: + int64_t _date_value; + int16_t _time_zone; + private: + friend class SrsAmf0Any; + /** + * make amf0 date to private, + * use should never declare it, use SrsAmf0Any::date() to create it. + */ + SrsAmf0Date(int64_t value); + public: + virtual ~SrsAmf0Date(); + // serialize/deserialize to/from stream. + public: + virtual int total_size(); + virtual int read(SrsStream* stream); + virtual int write(SrsStream* stream); + virtual SrsAmf0Any* copy(); + public: + /** + * get the date value. + */ + virtual int64_t date(); + /** + * get the time_zone. + */ + virtual int16_t time_zone(); + }; + /** * read amf0 null from stream. * 2.7 null Type @@ -739,6 +794,10 @@ namespace _srs_internal virtual std::string key_at(int index); virtual const char* key_raw_at(int index); virtual SrsAmf0Any* value_at(int index); + /** + * set the value of hashtable. + * @param value, the value to set. NULL to delete the property. + */ virtual void set(std::string key, SrsAmf0Any* value); public: virtual SrsAmf0Any* get_property(std::string name); diff --git a/trunk/src/protocol/srs_rtmp_buffer.cpp b/trunk/src/protocol/srs_rtmp_buffer.cpp new file mode 100644 index 0000000000..86a38eddfc --- /dev/null +++ b/trunk/src/protocol/srs_rtmp_buffer.cpp @@ -0,0 +1,213 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include + +#include + +#include +#include +#include +#include + +// the default recv buffer size, 128KB. +#define SRS_DEFAULT_RECV_BUFFER_SIZE 131072 + +// limit user-space buffer to 256KB, for 3Mbps stream delivery. +// 800*2000/8=200000B(about 195KB). +// @remark it's ok for higher stream, the buffer is ok for one chunk is 256KB. +#define SRS_MAX_SOCKET_BUFFER 262144 + +// the max header size, +// @see SrsProtocol::read_message_header(). +#define SRS_RTMP_MAX_MESSAGE_HEADER 11 + +#ifdef SRS_PERF_MERGED_READ +IMergeReadHandler::IMergeReadHandler() +{ +} + +IMergeReadHandler::~IMergeReadHandler() +{ +} +#endif + +SrsFastBuffer::SrsFastBuffer() +{ +#ifdef SRS_PERF_MERGED_READ + merged_read = false; + _handler = NULL; +#endif + + nb_buffer = SRS_DEFAULT_RECV_BUFFER_SIZE; + buffer = (char*)malloc(nb_buffer); + p = end = buffer; +} + +SrsFastBuffer::~SrsFastBuffer() +{ + free(buffer); + buffer = NULL; +} + +int SrsFastBuffer::size() +{ + return end - p; +} + +char* SrsFastBuffer::bytes() +{ + return p; +} + +void SrsFastBuffer::set_buffer(int buffer_size) +{ + // never exceed the max size. + if (buffer_size > SRS_MAX_SOCKET_BUFFER) { + srs_warn("limit the user-space buffer from %d to %d", + buffer_size, SRS_MAX_SOCKET_BUFFER); + } + + // the user-space buffer size limit to a max value. + int nb_resize_buf = srs_min(buffer_size, SRS_MAX_SOCKET_BUFFER); + + // only realloc when buffer changed bigger + if (nb_resize_buf <= nb_buffer) { + return; + } + + // realloc for buffer change bigger. + int start = p - buffer; + int nb_bytes = end - p; + + buffer = (char*)realloc(buffer, nb_resize_buf); + nb_buffer = nb_resize_buf; + p = buffer + start; + end = p + nb_bytes; +} + +char SrsFastBuffer::read_1byte() +{ + srs_assert(end - p >= 1); + return *p++; +} + +char* SrsFastBuffer::read_slice(int size) +{ + srs_assert(end - p >= size); + srs_assert(p + size > buffer); + + char* ptr = p; + p += size; + + return ptr; +} + +void SrsFastBuffer::skip(int size) +{ + srs_assert(end - p >= size); + srs_assert(p + size > buffer); + p += size; +} + +int SrsFastBuffer::grow(ISrsBufferReader* reader, int required_size) +{ + int ret = ERROR_SUCCESS; + + // already got required size of bytes. + if (end - p >= required_size) { + return ret; + } + + // must be positive. + srs_assert(required_size > 0); + + // the free space of buffer, + // buffer = consumed_bytes + exists_bytes + free_space. + int nb_free_space = (int)(buffer + nb_buffer - end); + // resize the space when no left space. + if (nb_free_space < required_size) { + // the bytes already in buffer + int nb_exists_bytes = (int)(end - p); + srs_assert(nb_exists_bytes >= 0); + srs_verbose("move fast buffer %d bytes", nb_exists_bytes); + + // reset or move to get more space. + if (!nb_exists_bytes) { + // reset when buffer is empty. + p = end = buffer; + srs_verbose("all consumed, reset fast buffer"); + } else { + // move the left bytes to start of buffer. + srs_assert(nb_exists_bytes < nb_buffer); + buffer = (char*)memmove(buffer, p, nb_exists_bytes); + p = buffer; + end = p + nb_exists_bytes; + } + + // check whether enough free space in buffer. + nb_free_space = (int)(buffer + nb_buffer - end); + if (nb_free_space < required_size) { + ret = ERROR_READER_BUFFER_OVERFLOW; + srs_error("buffer overflow, required=%d, max=%d, left=%d, ret=%d", + required_size, nb_buffer, nb_free_space, ret); + return ret; + } + } + + // buffer is ok, read required size of bytes. + while (end - p < required_size) { + ssize_t nread; + if ((ret = reader->read(end, nb_free_space, &nread)) != ERROR_SUCCESS) { + return ret; + } + +#ifdef SRS_PERF_MERGED_READ + /** + * to improve read performance, merge some packets then read, + * when it on and read small bytes, we sleep to wait more data., + * that is, we merge some data to read together. + * @see https://github.com/simple-rtmp-server/srs/issues/241 + */ + if (merged_read && _handler) { + _handler->on_read(nread); + } +#endif + + // we just move the ptr to next. + srs_assert((int)nread > 0); + end += nread; + nb_free_space -= nread; + } + + return ret; +} + +#ifdef SRS_PERF_MERGED_READ +void SrsFastBuffer::set_merge_read(bool v, IMergeReadHandler* handler) +{ + merged_read = v; + _handler = handler; +} +#endif + diff --git a/trunk/src/protocol/srs_rtmp_buffer.hpp b/trunk/src/protocol/srs_rtmp_buffer.hpp new file mode 100644 index 0000000000..316ad8de41 --- /dev/null +++ b/trunk/src/protocol/srs_rtmp_buffer.hpp @@ -0,0 +1,157 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef SRS_PROTOCOL_BUFFER_HPP +#define SRS_PROTOCOL_BUFFER_HPP + +/* +#include +*/ + +#include + +#include +#include +#include + +#ifdef SRS_PERF_MERGED_READ +/** +* to improve read performance, merge some packets then read, +* when it on and read small bytes, we sleep to wait more data., +* that is, we merge some data to read together. +* @see https://github.com/simple-rtmp-server/srs/issues/241 +*/ +class IMergeReadHandler +{ +public: + IMergeReadHandler(); + virtual ~IMergeReadHandler(); +public: + /** + * when read from channel, notice the merge handler to sleep for + * some small bytes. + * @remark, it only for server-side, client srs-librtmp just ignore. + */ + virtual void on_read(ssize_t nread) = 0; +}; +#endif + +/** +* the buffer provices bytes cache for protocol. generally, +* protocol recv data from socket, put into buffer, decode to RTMP message. +* Usage: +* ISrsBufferReader* r = ......; +* SrsFastBuffer* fb = ......; +* fb->grow(r, 1024); +* char* header = fb->read_slice(100); +* char* payload = fb->read_payload(924); +*/ +// TODO: FIXME: add utest for it. +class SrsFastBuffer +{ +private: +#ifdef SRS_PERF_MERGED_READ + // the merged handler + bool merged_read; + IMergeReadHandler* _handler; +#endif + // the user-space buffer to fill by reader, + // which use fast index and reset when chunk body read ok. + // @see https://github.com/simple-rtmp-server/srs/issues/248 + // ptr to the current read position. + char* p; + // ptr to the content end. + char* end; + // ptr to the buffer. + // buffer <= p <= end <= buffer+nb_buffer + char* buffer; + // the size of buffer. + int nb_buffer; +public: + SrsFastBuffer(); + virtual ~SrsFastBuffer(); +public: + /** + * get the size of current bytes in buffer. + */ + virtual int size(); + /** + * get the current bytes in buffer. + * @remark user should use read_slice() if possible, + * the bytes() is used to test bytes, for example, to detect the bytes schema. + */ + virtual char* bytes(); + /** + * create buffer with specifeid size. + * @param buffer the size of buffer. ignore when smaller than SRS_MAX_SOCKET_BUFFER. + * @remark when MR(SRS_PERF_MERGED_READ) disabled, always set to 8K. + * @remark when buffer changed, the previous ptr maybe invalid. + * @see https://github.com/simple-rtmp-server/srs/issues/241 + */ + virtual void set_buffer(int buffer_size); +public: + /** + * read 1byte from buffer, move to next bytes. + * @remark assert buffer already grow(1). + */ + virtual char read_1byte(); + /** + * read a slice in size bytes, move to next bytes. + * user can use this char* ptr directly, and should never free it. + * @remark user can use the returned ptr util grow(size), + * for the ptr returned maybe invalid after grow(x). + */ + virtual char* read_slice(int size); + /** + * skip some bytes in buffer. + * @param size the bytes to skip. positive to next; negative to previous. + * @remark assert buffer already grow(size). + * @remark always use read_slice to consume bytes, which will reset for EOF. + * while skip never consume bytes. + */ + virtual void skip(int size); +public: + /** + * grow buffer to the required size, loop to read from skt to fill. + * @param reader, read more bytes from reader to fill the buffer to required size. + * @param required_size, loop to fill to ensure buffer size to required. + * @return an int error code, error if required_size negative. + * @remark, we actually maybe read more than required_size, maybe 4k for example. + */ + virtual int grow(ISrsBufferReader* reader, int required_size); +public: +#ifdef SRS_PERF_MERGED_READ + /** + * to improve read performance, merge some packets then read, + * when it on and read small bytes, we sleep to wait more data., + * that is, we merge some data to read together. + * @param v true to ename merged read. + * @param handler the handler when merge read is enabled. + * @see https://github.com/simple-rtmp-server/srs/issues/241 + * @remark the merged read is optional, ignore if not specifies. + */ + virtual void set_merge_read(bool v, IMergeReadHandler* handler); +#endif +}; + +#endif diff --git a/trunk/src/rtmp/srs_protocol_handshake.cpp b/trunk/src/protocol/srs_rtmp_handshake.cpp similarity index 59% rename from trunk/src/rtmp/srs_protocol_handshake.cpp rename to trunk/src/protocol/srs_rtmp_handshake.cpp index 89a5d3a326..177c0a485c 100644 --- a/trunk/src/rtmp/srs_protocol_handshake.cpp +++ b/trunk/src/protocol/srs_rtmp_handshake.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -21,16 +21,16 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include +#include #include #include #include #include -#include -#include -#include +#include +#include +#include #include #ifdef SRS_AUTO_SSL @@ -40,7 +40,7 @@ using namespace _srs_internal; // for openssl_HMACsha256 #include #include -// for __openssl_generate_key +// for openssl_generate_key #include namespace _srs_internal @@ -70,7 +70,7 @@ namespace _srs_internal 0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE }; // 62 - int __openssl_HMACsha256(HMAC_CTX* ctx, const void* data, int data_size, void* digest, unsigned int* digest_size) + int do_openssl_HMACsha256(HMAC_CTX* ctx, const void* data, int data_size, void* digest, unsigned int* digest_size) { int ret = ERROR_SUCCESS; @@ -97,14 +97,14 @@ namespace _srs_internal unsigned int digest_size = 0; - unsigned char* __key = (unsigned char*)key; - unsigned char* __digest = (unsigned char*)digest; + unsigned char* temp_key = (unsigned char*)key; + unsigned char* temp_digest = (unsigned char*)digest; if (key == NULL) { // use data to digest. // @see ./crypto/sha/sha256t.c // @see ./crypto/evp/digest.c - if (EVP_Digest(data, data_size, __digest, &digest_size, EVP_sha256(), NULL) < 0) + if (EVP_Digest(data, data_size, temp_digest, &digest_size, EVP_sha256(), NULL) < 0) { ret = ERROR_OpenSslSha256EvpDigest; return ret; @@ -116,13 +116,12 @@ namespace _srs_internal // @remark, if no key, use EVP_Digest to digest, // for instance, in python, hashlib.sha256(data).digest(). HMAC_CTX_init(&ctx); - - if (HMAC_Init_ex(&ctx, __key, key_size, EVP_sha256(), NULL) < 0) { + if (HMAC_Init_ex(&ctx, temp_key, key_size, EVP_sha256(), NULL) < 0) { ret = ERROR_OpenSslSha256Init; return ret; } - ret = __openssl_HMACsha256(&ctx, data, data_size, __digest, &digest_size); + ret = do_openssl_HMACsha256(&ctx, data, data_size, temp_digest, &digest_size); HMAC_CTX_cleanup(&ctx); if (ret != ERROR_SUCCESS) { @@ -201,12 +200,12 @@ namespace _srs_internal // maybe the key_size is 127, but dh will write all 128bytes pkey, // so, donot need to set/initialize the pkey. - // @see https://github.com/winlinvip/simple-rtmp-server/issues/165 + // @see https://github.com/simple-rtmp-server/srs/issues/165 key_size = BN_bn2bin(pdh->pub_key, (unsigned char*)pkey); srs_assert(key_size > 0); // output the size of public key. - // @see https://github.com/winlinvip/simple-rtmp-server/issues/165 + // @see https://github.com/simple-rtmp-server/srs/issues/165 srs_assert(key_size <= pkey_size); pkey_size = key_size; @@ -226,7 +225,7 @@ namespace _srs_internal // if failed, donot return, do cleanup, @see ./test/dhtest.c:168 // maybe the key_size is 127, but dh will write all 128bytes skey, // so, donot need to set/initialize the skey. - // @see https://github.com/winlinvip/simple-rtmp-server/issues/165 + // @see https://github.com/simple-rtmp-server/srs/issues/165 int32_t key_size = DH_compute_key((unsigned char*)skey, ppk, pdh); if (key_size < ppkey_size) { @@ -292,768 +291,798 @@ namespace _srs_internal return ret; } - // read/write stream using SrsStream. - void __srs_stream_write_4bytes(char* pp, int32_t value) + key_block::key_block() { - static SrsStream stream; + offset = (int32_t)rand(); + random0 = NULL; + random1 = NULL; + + int valid_offset = calc_valid_offset(); + srs_assert(valid_offset >= 0); + + random0_size = valid_offset; + if (random0_size > 0) { + random0 = new char[random0_size]; + srs_random_generate(random0, random0_size); + snprintf(random0, random0_size, "%s", RTMP_SIG_SRS_HANDSHAKE); + } - int ret = stream.initialize(pp, 4); - srs_assert(ret == ERROR_SUCCESS); + srs_random_generate(key, sizeof(key)); - stream.write_4bytes(value); + random1_size = 764 - valid_offset - 128 - 4; + if (random1_size > 0) { + random1 = new char[random1_size]; + srs_random_generate(random1, random1_size); + snprintf(random1, random1_size, "%s", RTMP_SIG_SRS_HANDSHAKE); + } } - int32_t __srs_stream_read_4bytes(char* pp) + + key_block::~key_block() { - static SrsStream stream; + srs_freep(random0); + srs_freep(random1); + } + + int key_block::parse(SrsStream* stream) + { + int ret = ERROR_SUCCESS; + + // the key must be 764 bytes. + srs_assert(stream->require(764)); + + // read the last offset first, 760-763 + stream->skip(764 - sizeof(int32_t)); + offset = stream->read_4bytes(); - int ret = stream.initialize(pp, 4); - srs_assert(ret == ERROR_SUCCESS); + // reset stream to read others. + stream->skip(-764); + + int valid_offset = calc_valid_offset(); + srs_assert(valid_offset >= 0); + + random0_size = valid_offset; + if (random0_size > 0) { + srs_freep(random0); + random0 = new char[random0_size]; + stream->read_bytes(random0, random0_size); + } - return stream.read_4bytes(); + stream->read_bytes(key, 128); + + random1_size = 764 - valid_offset - 128 - 4; + if (random1_size > 0) { + srs_freep(random1); + random1 = new char[random1_size]; + stream->read_bytes(random1, random1_size); + } + + return ret; } - // calc the offset of key, - // the key->offset cannot be used as the offset of key. - int srs_key_block_get_offset(key_block* key) + int key_block::calc_valid_offset() { int max_offset_size = 764 - 128 - 4; - int offset = 0; - u_int8_t* pp = (u_int8_t*)&key->offset; - offset += *pp++; - offset += *pp++; - offset += *pp++; - offset += *pp++; + int valid_offset = 0; + u_int8_t* pp = (u_int8_t*)&offset; + valid_offset += *pp++; + valid_offset += *pp++; + valid_offset += *pp++; + valid_offset += *pp++; - return offset % max_offset_size; + return valid_offset % max_offset_size; } - // create new key block data. - // if created, user must free it by srs_key_block_free - void srs_key_block_init(key_block* key) + digest_block::digest_block() { - key->offset = (int32_t)rand(); - key->random0 = NULL; - key->random1 = NULL; + offset = (int32_t)rand(); + random0 = NULL; + random1 = NULL; - int offset = srs_key_block_get_offset(key); - srs_assert(offset >= 0); + int valid_offset = calc_valid_offset(); + srs_assert(valid_offset >= 0); - key->random0_size = offset; - if (key->random0_size > 0) { - key->random0 = new char[key->random0_size]; - srs_random_generate(key->random0, key->random0_size); - snprintf(key->random0, key->random0_size, "%s", RTMP_SIG_SRS_HANDSHAKE); + random0_size = valid_offset; + if (random0_size > 0) { + random0 = new char[random0_size]; + srs_random_generate(random0, random0_size); + snprintf(random0, random0_size, "%s", RTMP_SIG_SRS_HANDSHAKE); } - srs_random_generate(key->key, sizeof(key->key)); + srs_random_generate(digest, sizeof(digest)); - key->random1_size = 764 - offset - 128 - 4; - if (key->random1_size > 0) { - key->random1 = new char[key->random1_size]; - srs_random_generate(key->random1, key->random1_size); - snprintf(key->random1, key->random1_size, "%s", RTMP_SIG_SRS_HANDSHAKE); + random1_size = 764 - 4 - valid_offset - 32; + if (random1_size > 0) { + random1 = new char[random1_size]; + srs_random_generate(random1, random1_size); + snprintf(random1, random1_size, "%s", RTMP_SIG_SRS_HANDSHAKE); } } - // parse key block from c1s1. - // if created, user must free it by srs_key_block_free - // @c1s1_key_bytes the key start bytes, maybe c1s1 or c1s1+764 - int srs_key_block_parse(key_block* key, char* c1s1_key_bytes) + digest_block::~digest_block() + { + srs_freep(random0); + srs_freep(random1); + } + + int digest_block::parse(SrsStream* stream) { int ret = ERROR_SUCCESS; - - char* pp = c1s1_key_bytes + 764; - pp -= sizeof(int32_t); - key->offset = __srs_stream_read_4bytes(pp); + // the digest must be 764 bytes. + srs_assert(stream->require(764)); - key->random0 = NULL; - key->random1 = NULL; + offset = stream->read_4bytes(); - int offset = srs_key_block_get_offset(key); - srs_assert(offset >= 0); + int valid_offset = calc_valid_offset(); + srs_assert(valid_offset >= 0); - pp = c1s1_key_bytes; - key->random0_size = offset; - if (key->random0_size > 0) { - key->random0 = new char[key->random0_size]; - memcpy(key->random0, pp, key->random0_size); + random0_size = valid_offset; + if (random0_size > 0) { + srs_freep(random0); + random0 = new char[random0_size]; + stream->read_bytes(random0, random0_size); } - pp += key->random0_size; - memcpy(key->key, pp, sizeof(key->key)); - pp += sizeof(key->key); + stream->read_bytes(digest, 32); - key->random1_size = 764 - offset - 128 - 4; - if (key->random1_size > 0) { - key->random1 = new char[key->random1_size]; - memcpy(key->random1, pp, key->random1_size); + random1_size = 764 - 4 - valid_offset - 32; + if (random1_size > 0) { + srs_freep(random1); + random1 = new char[random1_size]; + stream->read_bytes(random1, random1_size); } return ret; } - // free the block data create by - // srs_key_block_init or srs_key_block_parse - void srs_key_block_free(key_block* key) + int digest_block::calc_valid_offset() { - if (key->random0) { - srs_freep(key->random0); - } - if (key->random1) { - srs_freep(key->random1); - } + int max_offset_size = 764 - 32 - 4; + + int valid_offset = 0; + u_int8_t* pp = (u_int8_t*)&offset; + valid_offset += *pp++; + valid_offset += *pp++; + valid_offset += *pp++; + valid_offset += *pp++; + + return valid_offset % max_offset_size; } - // calc the offset of digest, - // the key->offset cannot be used as the offset of digest. - int srs_digest_block_get_offset(digest_block* digest) + c1s1_strategy::c1s1_strategy() { - int max_offset_size = 764 - 32 - 4; - - int offset = 0; - u_int8_t* pp = (u_int8_t*)&digest->offset; - offset += *pp++; - offset += *pp++; - offset += *pp++; - offset += *pp++; + } + + c1s1_strategy::~c1s1_strategy() + { + } + + char* c1s1_strategy::get_digest() + { + return digest.digest; + } - return offset % max_offset_size; + char* c1s1_strategy::get_key() + { + return key.key; } - // create new digest block data. - // if created, user must free it by srs_digest_block_free - void srs_digest_block_init(digest_block* digest) + int c1s1_strategy::dump(c1s1* owner, char* _c1s1, int size) { - digest->offset = (int32_t)rand(); - digest->random0 = NULL; - digest->random1 = NULL; + srs_assert(size == 1536); + return copy_to(owner, _c1s1, size, true); + } + + int c1s1_strategy::c1_create(c1s1* owner) + { + int ret = ERROR_SUCCESS; - int offset = srs_digest_block_get_offset(digest); - srs_assert(offset >= 0); + // generate digest + char* c1_digest = NULL; - digest->random0_size = offset; - if (digest->random0_size > 0) { - digest->random0 = new char[digest->random0_size]; - srs_random_generate(digest->random0, digest->random0_size); - snprintf(digest->random0, digest->random0_size, "%s", RTMP_SIG_SRS_HANDSHAKE); + if ((ret = calc_c1_digest(owner, c1_digest)) != ERROR_SUCCESS) { + srs_error("sign c1 error, failed to calc digest. ret=%d", ret); + return ret; } - srs_random_generate(digest->digest, sizeof(digest->digest)); + srs_assert(c1_digest != NULL); + SrsAutoFree(char, c1_digest); - digest->random1_size = 764 - 4 - offset - 32; - if (digest->random1_size > 0) { - digest->random1 = new char[digest->random1_size]; - srs_random_generate(digest->random1, digest->random1_size); - snprintf(digest->random1, digest->random1_size, "%s", RTMP_SIG_SRS_HANDSHAKE); - } + memcpy(digest.digest, c1_digest, 32); + + return ret; } - - // parse digest block from c1s1. - // if created, user must free it by srs_digest_block_free - // @c1s1_digest_bytes the digest start bytes, maybe c1s1 or c1s1+764 - int srs_digest_block_parse(digest_block* digest, char* c1s1_digest_bytes) + + int c1s1_strategy::c1_validate_digest(c1s1* owner, bool& is_valid) { int ret = ERROR_SUCCESS; - - char* pp = c1s1_digest_bytes; - digest->offset = __srs_stream_read_4bytes(pp); - pp += sizeof(int32_t); + char* c1_digest = NULL; - digest->random0 = NULL; - digest->random1 = NULL; + if ((ret = calc_c1_digest(owner, c1_digest)) != ERROR_SUCCESS) { + srs_error("validate c1 error, failed to calc digest. ret=%d", ret); + return ret; + } + + srs_assert(c1_digest != NULL); + SrsAutoFree(char, c1_digest); + + is_valid = srs_bytes_equals(digest.digest, c1_digest, 32); + + return ret; + } + + int c1s1_strategy::s1_create(c1s1* owner, c1s1* c1) + { + int ret = ERROR_SUCCESS; + + SrsDH dh; - int offset = srs_digest_block_get_offset(digest); - srs_assert(offset >= 0); + // ensure generate 128bytes public key. + if ((ret = dh.initialize(true)) != ERROR_SUCCESS) { + return ret; + } - digest->random0_size = offset; - if (digest->random0_size > 0) { - digest->random0 = new char[digest->random0_size]; - memcpy(digest->random0, pp, digest->random0_size); + // directly generate the public key. + // @see: https://github.com/simple-rtmp-server/srs/issues/148 + int pkey_size = 128; + if ((ret = dh.copy_shared_key(c1->get_key(), 128, key.key, pkey_size)) != ERROR_SUCCESS) { + srs_error("calc s1 key failed. ret=%d", ret); + return ret; + } + + // altough the public key is always 128bytes, but the share key maybe not. + // we just ignore the actual key size, but if need to use the key, must use the actual size. + // TODO: FIXME: use the actual key size. + //srs_assert(pkey_size == 128); + srs_verbose("calc s1 key success."); + + char* s1_digest = NULL; + if ((ret = calc_s1_digest(owner, s1_digest)) != ERROR_SUCCESS) { + srs_error("calc s1 digest failed. ret=%d", ret); + return ret; } - pp += digest->random0_size; + srs_verbose("calc s1 digest success."); + + srs_assert(s1_digest != NULL); + SrsAutoFree(char, s1_digest); + + memcpy(digest.digest, s1_digest, 32); + srs_verbose("copy s1 key success."); + + return ret; + } + + int c1s1_strategy::s1_validate_digest(c1s1* owner, bool& is_valid) + { + int ret = ERROR_SUCCESS; - memcpy(digest->digest, pp, sizeof(digest->digest)); - pp += sizeof(digest->digest); + char* s1_digest = NULL; - digest->random1_size = 764 - 4 - offset - 32; - if (digest->random1_size > 0) { - digest->random1 = new char[digest->random1_size]; - memcpy(digest->random1, pp, digest->random1_size); + if ((ret = calc_s1_digest(owner, s1_digest)) != ERROR_SUCCESS) { + srs_error("validate s1 error, failed to calc digest. ret=%d", ret); + return ret; } + srs_assert(s1_digest != NULL); + SrsAutoFree(char, s1_digest); + + is_valid = srs_bytes_equals(digest.digest, s1_digest, 32); + return ret; } - // free the block data create by - // srs_digest_block_init or srs_digest_block_parse - void srs_digest_block_free(digest_block* digest) + int c1s1_strategy::calc_c1_digest(c1s1* owner, char*& c1_digest) { - if (digest->random0) { - srs_freep(digest->random0); + int ret = ERROR_SUCCESS; + + /** + * c1s1 is splited by digest: + * c1s1-part1: n bytes (time, version, key and digest-part1). + * digest-data: 32bytes + * c1s1-part2: (1536-n-32)bytes (digest-part2) + * @return a new allocated bytes, user must free it. + */ + char* c1s1_joined_bytes = new char[1536 -32]; + SrsAutoFree(char, c1s1_joined_bytes); + if ((ret = copy_to(owner, c1s1_joined_bytes, 1536 - 32, false)) != ERROR_SUCCESS) { + return ret; } - if (digest->random1) { - srs_freep(digest->random1); + + c1_digest = new char[SRS_OpensslHashSize]; + if ((ret = openssl_HMACsha256(SrsGenuineFPKey, 30, c1s1_joined_bytes, 1536 - 32, c1_digest)) != ERROR_SUCCESS) { + srs_freep(c1_digest); + srs_error("calc digest for c1 failed. ret=%d", ret); + return ret; } + srs_verbose("digest calculated for c1"); + + return ret; } - void __srs_time_copy_to(char*& pp, int32_t time) + int c1s1_strategy::calc_s1_digest(c1s1* owner, char*& s1_digest) { - // 4bytes time - __srs_stream_write_4bytes(pp, time); - pp += 4; + int ret = ERROR_SUCCESS; + + /** + * c1s1 is splited by digest: + * c1s1-part1: n bytes (time, version, key and digest-part1). + * digest-data: 32bytes + * c1s1-part2: (1536-n-32)bytes (digest-part2) + * @return a new allocated bytes, user must free it. + */ + char* c1s1_joined_bytes = new char[1536 -32]; + SrsAutoFree(char, c1s1_joined_bytes); + if ((ret = copy_to(owner, c1s1_joined_bytes, 1536 - 32, false)) != ERROR_SUCCESS) { + return ret; + } + + s1_digest = new char[SRS_OpensslHashSize]; + if ((ret = openssl_HMACsha256(SrsGenuineFMSKey, 36, c1s1_joined_bytes, 1536 - 32, s1_digest)) != ERROR_SUCCESS) { + srs_freep(s1_digest); + srs_error("calc digest for s1 failed. ret=%d", ret); + return ret; + } + srs_verbose("digest calculated for s1"); + + return ret; } - void __srs_version_copy_to(char*& pp, int32_t version) + + void c1s1_strategy::copy_time_version(SrsStream* stream, c1s1* owner) { + srs_assert(stream->require(8)); + + // 4bytes time + stream->write_4bytes(owner->time); + // 4bytes version - __srs_stream_write_4bytes(pp, version); - pp += 4; + stream->write_4bytes(owner->version); } - void __srs_key_copy_to(char*& pp, key_block* key) + void c1s1_strategy::copy_key(SrsStream* stream) { + srs_assert(key.random0_size >= 0); + srs_assert(key.random1_size >= 0); + + int total = key.random0_size + 128 + key.random1_size + 4; + srs_assert(stream->require(total)); + // 764bytes key block - if (key->random0_size > 0) { - memcpy(pp, key->random0, key->random0_size); + if (key.random0_size > 0) { + stream->write_bytes(key.random0, key.random0_size); } - pp += key->random0_size; - memcpy(pp, key->key, sizeof(key->key)); - pp += sizeof(key->key); + stream->write_bytes(key.key, 128); - if (key->random1_size > 0) { - memcpy(pp, key->random1, key->random1_size); + if (key.random1_size > 0) { + stream->write_bytes(key.random1, key.random1_size); } - pp += key->random1_size; - __srs_stream_write_4bytes(pp, key->offset); - pp += 4; + stream->write_4bytes(key.offset); } - void __srs_digest_copy_to(char*& pp, digest_block* digest, bool with_digest) + void c1s1_strategy::copy_digest(SrsStream* stream, bool with_digest) { + srs_assert(key.random0_size >= 0); + srs_assert(key.random1_size >= 0); + + int total = 4 + digest.random0_size + digest.random1_size; + if (with_digest) { + total += 32; + } + srs_assert(stream->require(total)); + // 732bytes digest block without the 32bytes digest-data // nbytes digest block part1 - __srs_stream_write_4bytes(pp, digest->offset); - pp += 4; + stream->write_4bytes(digest.offset); // digest random padding. - if (digest->random0_size > 0) { - memcpy(pp, digest->random0, digest->random0_size); + if (digest.random0_size > 0) { + stream->write_bytes(digest.random0, digest.random0_size); } - pp += digest->random0_size; // digest if (with_digest) { - memcpy(pp, digest->digest, 32); - pp += 32; + stream->write_bytes(digest.digest, 32); } // nbytes digest block part2 - if (digest->random1_size > 0) { - memcpy(pp, digest->random1, digest->random1_size); + if (digest.random1_size > 0) { + stream->write_bytes(digest.random1, digest.random1_size); } - pp += digest->random1_size; } - /** - * copy whole c1s1 to bytes. - */ - void srs_schema0_copy_to(char* bytes, bool with_digest, - int32_t time, int32_t version, key_block* key, digest_block* digest) + c1s1_strategy_schema0::c1s1_strategy_schema0() { - char* pp = bytes; - - __srs_time_copy_to(pp, time); - __srs_version_copy_to(pp, version); - __srs_key_copy_to(pp, key); - __srs_digest_copy_to(pp, digest, with_digest); - - if (with_digest) { - srs_assert(pp - bytes == 1536); - } else { - srs_assert(pp - bytes == 1536 - 32); - } } - void srs_schema1_copy_to(char* bytes, bool with_digest, - int32_t time, int32_t version, digest_block* digest, key_block* key) - { - char* pp = bytes; - __srs_time_copy_to(pp, time); - __srs_version_copy_to(pp, version); - __srs_digest_copy_to(pp, digest, with_digest); - __srs_key_copy_to(pp, key); - - if (with_digest) { - srs_assert(pp - bytes == 1536); - } else { - srs_assert(pp - bytes == 1536 - 32); - } - } - - /** - * c1s1 is splited by digest: - * c1s1-part1: n bytes (time, version, key and digest-part1). - * digest-data: 32bytes - * c1s1-part2: (1536-n-32)bytes (digest-part2) - */ - char* srs_bytes_join_schema0(int32_t time, int32_t version, key_block* key, digest_block* digest) + c1s1_strategy_schema0::~c1s1_strategy_schema0() { - char* bytes = new char[1536 -32]; - - srs_schema0_copy_to(bytes, false, time, version, key, digest); - - return bytes; } - /** - * c1s1 is splited by digest: - * c1s1-part1: n bytes (time, version and digest-part1). - * digest-data: 32bytes - * c1s1-part2: (1536-n-32)bytes (digest-part2 and key) - */ - char* srs_bytes_join_schema1(int32_t time, int32_t version, digest_block* digest, key_block* key) + srs_schema_type c1s1_strategy_schema0::schema() { - char* bytes = new char[1536 -32]; - - srs_schema1_copy_to(bytes, false, time, version, digest, key); - - return bytes; + return srs_schema0; } - c2s2::c2s2() + int c1s1_strategy_schema0::parse(char* _c1s1, int size) { - srs_random_generate(random, 1504); + int ret = ERROR_SUCCESS; - int size = snprintf(random, 1504, "%s", RTMP_SIG_SRS_HANDSHAKE); - srs_assert(++size < 1504); - snprintf(random + 1504 - size, size, "%s", RTMP_SIG_SRS_HANDSHAKE); + srs_assert(size == 1536); - srs_random_generate(digest, 32); - } - - c2s2::~c2s2() - { - } - - void c2s2::dump(char* _c2s2) - { - memcpy(_c2s2, random, 1504); - memcpy(_c2s2 + 1504, digest, 32); - } - - void c2s2::parse(char* _c2s2) - { - memcpy(random, _c2s2, 1504); - memcpy(digest, _c2s2 + 1504, 32); - } - - int c2s2::c2_create(c1s1* s1) - { - int ret = ERROR_SUCCESS; + SrsStream stream; - char temp_key[__SRS_OpensslHashSize]; - if ((ret = openssl_HMACsha256(SrsGenuineFPKey, 62, s1->get_digest(), 32, temp_key)) != ERROR_SUCCESS) { - srs_error("create c2 temp key failed. ret=%d", ret); + if ((ret = stream.initialize(_c1s1 + 8, 764)) != ERROR_SUCCESS) { return ret; } - srs_verbose("generate c2 temp key success."); - char _digest[__SRS_OpensslHashSize]; - if ((ret = openssl_HMACsha256(temp_key, 32, random, 1504, _digest)) != ERROR_SUCCESS) { - srs_error("create c2 digest failed. ret=%d", ret); + if ((ret = key.parse(&stream)) != ERROR_SUCCESS) { + srs_error("parse the c1 key failed. ret=%d", ret); return ret; } - srs_verbose("generate c2 digest success."); - memcpy(digest, _digest, 32); + if ((ret = stream.initialize(_c1s1 + 8 + 764, 764)) != ERROR_SUCCESS) { + return ret; + } + + if ((ret = digest.parse(&stream)) != ERROR_SUCCESS) { + srs_error("parse the c1 digest failed. ret=%d", ret); + return ret; + } + + srs_verbose("parse c1 key-digest success"); return ret; } - int c2s2::c2_validate(c1s1* s1, bool& is_valid) + int c1s1_strategy_schema0::copy_to(c1s1* owner, char* bytes, int size, bool with_digest) { - is_valid = false; int ret = ERROR_SUCCESS; - char temp_key[__SRS_OpensslHashSize]; - if ((ret = openssl_HMACsha256(SrsGenuineFPKey, 62, s1->get_digest(), 32, temp_key)) != ERROR_SUCCESS) { - srs_error("create c2 temp key failed. ret=%d", ret); - return ret; + if (with_digest) { + srs_assert(size == 1536); + } else { + srs_assert(size == 1504); } - srs_verbose("generate c2 temp key success."); - char _digest[__SRS_OpensslHashSize]; - if ((ret = openssl_HMACsha256(temp_key, 32, random, 1504, _digest)) != ERROR_SUCCESS) { - srs_error("create c2 digest failed. ret=%d", ret); + SrsStream stream; + + if ((ret = stream.initialize(bytes, size)) != ERROR_SUCCESS) { return ret; } - srs_verbose("generate c2 digest success."); - is_valid = srs_bytes_equals(digest, _digest, 32); + copy_time_version(&stream, owner); + copy_key(&stream); + copy_digest(&stream, with_digest); + + srs_assert(stream.empty()); return ret; } - int c2s2::s2_create(c1s1* c1) + c1s1_strategy_schema1::c1s1_strategy_schema1() + { + } + + c1s1_strategy_schema1::~c1s1_strategy_schema1() + { + } + + srs_schema_type c1s1_strategy_schema1::schema() + { + return srs_schema1; + } + + int c1s1_strategy_schema1::parse(char* _c1s1, int size) { int ret = ERROR_SUCCESS; - char temp_key[__SRS_OpensslHashSize]; - if ((ret = openssl_HMACsha256(SrsGenuineFMSKey, 68, c1->get_digest(), 32, temp_key)) != ERROR_SUCCESS) { - srs_error("create s2 temp key failed. ret=%d", ret); + srs_assert(size == 1536); + + SrsStream stream; + + if ((ret = stream.initialize(_c1s1 + 8, 764)) != ERROR_SUCCESS) { + return ret; + } + + if ((ret = digest.parse(&stream)) != ERROR_SUCCESS) { + srs_error("parse the c1 digest failed. ret=%d", ret); return ret; } - srs_verbose("generate s2 temp key success."); - char _digest[__SRS_OpensslHashSize]; - if ((ret = openssl_HMACsha256(temp_key, 32, random, 1504, _digest)) != ERROR_SUCCESS) { - srs_error("create s2 digest failed. ret=%d", ret); + if ((ret = stream.initialize(_c1s1 + 8 + 764, 764)) != ERROR_SUCCESS) { return ret; } - srs_verbose("generate s2 digest success."); - memcpy(digest, _digest, 32); + if ((ret = key.parse(&stream)) != ERROR_SUCCESS) { + srs_error("parse the c1 key failed. ret=%d", ret); + return ret; + } + + srs_verbose("parse c1 digest-key success"); return ret; } - int c2s2::s2_validate(c1s1* c1, bool& is_valid) + int c1s1_strategy_schema1::copy_to(c1s1* owner, char* bytes, int size, bool with_digest) { - is_valid = false; int ret = ERROR_SUCCESS; - char temp_key[__SRS_OpensslHashSize]; - if ((ret = openssl_HMACsha256(SrsGenuineFMSKey, 68, c1->get_digest(), 32, temp_key)) != ERROR_SUCCESS) { - srs_error("create s2 temp key failed. ret=%d", ret); - return ret; + if (with_digest) { + srs_assert(size == 1536); + } else { + srs_assert(size == 1504); } - srs_verbose("generate s2 temp key success."); - char _digest[__SRS_OpensslHashSize]; - if ((ret = openssl_HMACsha256(temp_key, 32, random, 1504, _digest)) != ERROR_SUCCESS) { - srs_error("create s2 digest failed. ret=%d", ret); + SrsStream stream; + + if ((ret = stream.initialize(bytes, size)) != ERROR_SUCCESS) { return ret; } - srs_verbose("generate s2 digest success."); - is_valid = srs_bytes_equals(digest, _digest, 32); + copy_time_version(&stream, owner); + copy_digest(&stream, with_digest); + copy_key(&stream); + + srs_assert(stream.empty()); return ret; } - // TODO: FIXME: move to the right position. c1s1::c1s1() { - schema = srs_schema_invalid; + payload = NULL; } c1s1::~c1s1() { - destroy_blocks(); + srs_freep(payload); + } + + srs_schema_type c1s1::schema() + { + srs_assert(payload != NULL); + return payload->schema(); } char* c1s1::get_digest() { - srs_assert(schema != srs_schema_invalid); - - if (schema == srs_schema0) { - return block1.digest.digest; - } else { - return block0.digest.digest; - } + srs_assert(payload != NULL); + return payload->get_digest(); } - void c1s1::dump(char* _c1s1) + char* c1s1::get_key() { - srs_assert(schema != srs_schema_invalid); - - if (schema == srs_schema0) { - srs_schema0_copy_to(_c1s1, true, time, version, &block0.key, &block1.digest); - } else { - srs_schema1_copy_to(_c1s1, true, time, version, &block0.digest, &block1.key); - } + srs_assert(payload != NULL); + return payload->get_key(); + } + + int c1s1::dump(char* _c1s1, int size) + { + srs_assert(size == 1536); + srs_assert(payload != NULL); + return payload->dump(this, _c1s1, size); } - int c1s1::parse(char* _c1s1, srs_schema_type _schema) + int c1s1::parse(char* _c1s1, int size, srs_schema_type schema) { int ret = ERROR_SUCCESS; - if (_schema == srs_schema_invalid) { + srs_assert(size == 1536); + + if (schema != srs_schema0 && schema != srs_schema1) { ret = ERROR_RTMP_CH_SCHEMA; - srs_error("parse c1 failed. invalid schema=%d, ret=%d", _schema, ret); + srs_error("parse c1 failed. invalid schema=%d, ret=%d", schema, ret); return ret; } - destroy_blocks(); - + SrsStream stream; - time = __srs_stream_read_4bytes(_c1s1); - version = __srs_stream_read_4bytes(_c1s1 + 4); // client c1 version - - if (_schema == srs_schema0) { - if ((ret = srs_key_block_parse(&block0.key, _c1s1 + 8)) != ERROR_SUCCESS) { - srs_error("parse the c1 key failed. ret=%d", ret); - return ret; - } - if ((ret = srs_digest_block_parse(&block1.digest, _c1s1 + 8 + 764)) != ERROR_SUCCESS) { - srs_error("parse the c1 digest failed. ret=%d", ret); - return ret; - } - srs_verbose("parse c1 key-digest success"); - } else if (_schema == srs_schema1) { - if ((ret = srs_digest_block_parse(&block0.digest, _c1s1 + 8)) != ERROR_SUCCESS) { - srs_error("parse the c1 key failed. ret=%d", ret); - return ret; - } - if ((ret = srs_key_block_parse(&block1.key, _c1s1 + 8 + 764)) != ERROR_SUCCESS) { - srs_error("parse the c1 digest failed. ret=%d", ret); - return ret; - } - srs_verbose("parse c1 digest-key success"); - } else { - ret = ERROR_RTMP_CH_SCHEMA; - srs_error("parse c1 failed. invalid schema=%d, ret=%d", _schema, ret); + if ((ret = stream.initialize(_c1s1, size)) != ERROR_SUCCESS) { return ret; } - schema = _schema; + time = stream.read_4bytes(); + version = stream.read_4bytes(); // client c1 version - return ret; + srs_freep(payload); + if (schema == srs_schema0) { + payload = new c1s1_strategy_schema0(); + } else { + payload = new c1s1_strategy_schema1(); + } + + return payload->parse(_c1s1, size); } - int c1s1::c1_create(srs_schema_type _schema) + int c1s1::c1_create(srs_schema_type schema) { int ret = ERROR_SUCCESS; - if (_schema == srs_schema_invalid) { + if (schema != srs_schema0 && schema != srs_schema1) { ret = ERROR_RTMP_CH_SCHEMA; - srs_error("create c1 failed. invalid schema=%d, ret=%d", _schema, ret); + srs_error("create c1 failed. invalid schema=%d, ret=%d", schema, ret); return ret; } - destroy_blocks(); - // client c1 time and version time = ::time(NULL); version = 0x80000702; // client c1 version - + // generate signature by schema - if (_schema == srs_schema0) { - srs_key_block_init(&block0.key); - srs_digest_block_init(&block1.digest); - } else { - srs_digest_block_init(&block0.digest); - srs_key_block_init(&block1.key); - } - - schema = _schema; - - // generate digest - char* digest = NULL; - - if ((ret = calc_c1_digest(digest)) != ERROR_SUCCESS) { - srs_error("sign c1 error, failed to calc digest. ret=%d", ret); - return ret; - } - - srs_assert(digest != NULL); - SrsAutoFree(char, digest); - + srs_freep(payload); if (schema == srs_schema0) { - memcpy(block1.digest.digest, digest, 32); + payload = new c1s1_strategy_schema0(); } else { - memcpy(block0.digest.digest, digest, 32); + payload = new c1s1_strategy_schema1(); } - return ret; + return payload->c1_create(this); } int c1s1::c1_validate_digest(bool& is_valid) { is_valid = false; + srs_assert(payload); + return payload->c1_validate_digest(this, is_valid); + } + + int c1s1::s1_create(c1s1* c1) + { int ret = ERROR_SUCCESS; - char* c1_digest = NULL; - - if ((ret = calc_c1_digest(c1_digest)) != ERROR_SUCCESS) { - srs_error("validate c1 error, failed to calc digest. ret=%d", ret); + if (c1->schema() != srs_schema0 && c1->schema() != srs_schema1) { + ret = ERROR_RTMP_CH_SCHEMA; + srs_error("create s1 failed. invalid schema=%d, ret=%d", c1->schema(), ret); return ret; } - srs_assert(c1_digest != NULL); - SrsAutoFree(char, c1_digest); + time = ::time(NULL); + version = 0x01000504; // server s1 version - if (schema == srs_schema0) { - is_valid = srs_bytes_equals(block1.digest.digest, c1_digest, 32); + srs_freep(payload); + if (c1->schema() == srs_schema0) { + payload = new c1s1_strategy_schema0(); } else { - is_valid = srs_bytes_equals(block0.digest.digest, c1_digest, 32); + payload = new c1s1_strategy_schema1(); } - return ret; + return payload->s1_create(this, c1); } int c1s1::s1_validate_digest(bool& is_valid) { is_valid = false; - int ret = ERROR_SUCCESS; - - char* s1_digest = NULL; - - if ((ret = calc_s1_digest(s1_digest)) != ERROR_SUCCESS) { - srs_error("validate s1 error, failed to calc digest. ret=%d", ret); - return ret; - } - - srs_assert(s1_digest != NULL); - SrsAutoFree(char, s1_digest); + srs_assert(payload); + return payload->s1_validate_digest(this, is_valid); + } + + c2s2::c2s2() + { + srs_random_generate(random, 1504); - if (schema == srs_schema0) { - is_valid = srs_bytes_equals(block1.digest.digest, s1_digest, 32); - } else { - is_valid = srs_bytes_equals(block0.digest.digest, s1_digest, 32); - } + int size = snprintf(random, 1504, "%s", RTMP_SIG_SRS_HANDSHAKE); + srs_assert(++size < 1504); + snprintf(random + 1504 - size, size, "%s", RTMP_SIG_SRS_HANDSHAKE); - return ret; + srs_random_generate(digest, 32); } - int c1s1::s1_create(c1s1* c1) + c2s2::~c2s2() { - int ret = ERROR_SUCCESS; + } + + int c2s2::dump(char* _c2s2, int size) + { + srs_assert(size == 1536); - if (c1->schema == srs_schema_invalid) { - ret = ERROR_RTMP_CH_SCHEMA; - srs_error("create s1 failed. invalid schema=%d, ret=%d", c1->schema, ret); - return ret; - } + memcpy(_c2s2, random, 1504); + memcpy(_c2s2 + 1504, digest, 32); - destroy_blocks(); - schema = c1->schema; + return ERROR_SUCCESS; + } + + int c2s2::parse(char* _c2s2, int size) + { + srs_assert(size == 1536); - time = ::time(NULL); - version = 0x01000504; // server s1 version + memcpy(random, _c2s2, 1504); + memcpy(digest, _c2s2 + 1504, 32); - SrsDH dh; + return ERROR_SUCCESS; + } + + int c2s2::c2_create(c1s1* s1) + { + int ret = ERROR_SUCCESS; - // ensure generate 128bytes public key. - if ((ret = dh.initialize(true)) != ERROR_SUCCESS) { + char temp_key[SRS_OpensslHashSize]; + if ((ret = openssl_HMACsha256(SrsGenuineFPKey, 62, s1->get_digest(), 32, temp_key)) != ERROR_SUCCESS) { + srs_error("create c2 temp key failed. ret=%d", ret); return ret; } + srs_verbose("generate c2 temp key success."); - if (schema == srs_schema0) { - srs_key_block_init(&block0.key); - srs_digest_block_init(&block1.digest); - - // directly generate the public key. - // @see: https://github.com/winlinvip/simple-rtmp-server/issues/148 - int pkey_size = 128; - if ((ret = dh.copy_public_key(block0.key.key, pkey_size)) != ERROR_SUCCESS) { - srs_error("calc s1 key failed. ret=%d", ret); - return ret; - } - srs_assert(pkey_size == 128); - } else { - srs_digest_block_init(&block0.digest); - srs_key_block_init(&block1.key); - - // directly generate the public key. - // @see: https://github.com/winlinvip/simple-rtmp-server/issues/148 - int pkey_size = 128; - if ((ret = dh.copy_public_key(block1.key.key, pkey_size)) != ERROR_SUCCESS) { - srs_error("calc s1 key failed. ret=%d", ret); - return ret; - } - srs_assert(pkey_size == 128); - } - srs_verbose("calc s1 key success."); - - char* s1_digest = NULL; - if ((ret = calc_s1_digest(s1_digest)) != ERROR_SUCCESS) { - srs_error("calc s1 digest failed. ret=%d", ret); + char _digest[SRS_OpensslHashSize]; + if ((ret = openssl_HMACsha256(temp_key, 32, random, 1504, _digest)) != ERROR_SUCCESS) { + srs_error("create c2 digest failed. ret=%d", ret); return ret; } - srs_verbose("calc s1 digest success."); - - srs_assert(s1_digest != NULL); - SrsAutoFree(char, s1_digest); + srs_verbose("generate c2 digest success."); - if (schema == srs_schema0) { - memcpy(block1.digest.digest, s1_digest, 32); - } else { - memcpy(block0.digest.digest, s1_digest, 32); - } - srs_verbose("copy s1 key success."); + memcpy(digest, _digest, 32); return ret; } - int c1s1::calc_s1_digest(char*& digest) + int c2s2::c2_validate(c1s1* s1, bool& is_valid) { + is_valid = false; int ret = ERROR_SUCCESS; - srs_assert(schema == srs_schema0 || schema == srs_schema1); - - char* c1s1_joined_bytes = NULL; - - if (schema == srs_schema0) { - c1s1_joined_bytes = srs_bytes_join_schema0(time, version, &block0.key, &block1.digest); - } else { - c1s1_joined_bytes = srs_bytes_join_schema1(time, version, &block0.digest, &block1.key); + char temp_key[SRS_OpensslHashSize]; + if ((ret = openssl_HMACsha256(SrsGenuineFPKey, 62, s1->get_digest(), 32, temp_key)) != ERROR_SUCCESS) { + srs_error("create c2 temp key failed. ret=%d", ret); + return ret; } + srs_verbose("generate c2 temp key success."); - srs_assert(c1s1_joined_bytes != NULL); - SrsAutoFree(char, c1s1_joined_bytes); - - digest = new char[__SRS_OpensslHashSize]; - if ((ret = openssl_HMACsha256(SrsGenuineFMSKey, 36, c1s1_joined_bytes, 1536 - 32, digest)) != ERROR_SUCCESS) { - srs_error("calc digest for s1 failed. ret=%d", ret); + char _digest[SRS_OpensslHashSize]; + if ((ret = openssl_HMACsha256(temp_key, 32, random, 1504, _digest)) != ERROR_SUCCESS) { + srs_error("create c2 digest failed. ret=%d", ret); return ret; } - srs_verbose("digest calculated for s1"); + srs_verbose("generate c2 digest success."); + + is_valid = srs_bytes_equals(digest, _digest, 32); return ret; } - int c1s1::calc_c1_digest(char*& digest) + int c2s2::s2_create(c1s1* c1) { int ret = ERROR_SUCCESS; - srs_assert(schema == srs_schema0 || schema == srs_schema1); - - char* c1s1_joined_bytes = NULL; - - if (schema == srs_schema0) { - c1s1_joined_bytes = srs_bytes_join_schema0(time, version, &block0.key, &block1.digest); - } else { - c1s1_joined_bytes = srs_bytes_join_schema1(time, version, &block0.digest, &block1.key); + char temp_key[SRS_OpensslHashSize]; + if ((ret = openssl_HMACsha256(SrsGenuineFMSKey, 68, c1->get_digest(), 32, temp_key)) != ERROR_SUCCESS) { + srs_error("create s2 temp key failed. ret=%d", ret); + return ret; } + srs_verbose("generate s2 temp key success."); - srs_assert(c1s1_joined_bytes != NULL); - SrsAutoFree(char, c1s1_joined_bytes); - - digest = new char[__SRS_OpensslHashSize]; - if ((ret = openssl_HMACsha256(SrsGenuineFPKey, 30, c1s1_joined_bytes, 1536 - 32, digest)) != ERROR_SUCCESS) { - srs_error("calc digest for c1 failed. ret=%d", ret); + char _digest[SRS_OpensslHashSize]; + if ((ret = openssl_HMACsha256(temp_key, 32, random, 1504, _digest)) != ERROR_SUCCESS) { + srs_error("create s2 digest failed. ret=%d", ret); return ret; } - srs_verbose("digest calculated for c1"); + srs_verbose("generate s2 digest success."); + + memcpy(digest, _digest, 32); return ret; } - void c1s1::destroy_blocks() + int c2s2::s2_validate(c1s1* c1, bool& is_valid) { - if (schema == srs_schema_invalid) { - return; + is_valid = false; + int ret = ERROR_SUCCESS; + + char temp_key[SRS_OpensslHashSize]; + if ((ret = openssl_HMACsha256(SrsGenuineFMSKey, 68, c1->get_digest(), 32, temp_key)) != ERROR_SUCCESS) { + srs_error("create s2 temp key failed. ret=%d", ret); + return ret; } + srs_verbose("generate s2 temp key success."); - if (schema == srs_schema0) { - srs_key_block_free(&block0.key); - srs_digest_block_free(&block1.digest); - } else { - srs_digest_block_free(&block0.digest); - srs_key_block_free(&block1.key); + char _digest[SRS_OpensslHashSize]; + if ((ret = openssl_HMACsha256(temp_key, 32, random, 1504, _digest)) != ERROR_SUCCESS) { + srs_error("create s2 digest failed. ret=%d", ret); + return ret; } + srs_verbose("generate s2 digest success."); + + is_valid = srs_bytes_equals(digest, _digest, 32); + + return ret; } } @@ -1174,14 +1203,16 @@ int SrsComplexHandshake::handshake_with_client(SrsHandshakeBytes* hs_bytes, ISrs // decode c1 c1s1 c1; // try schema0. - if ((ret = c1.parse(hs_bytes->c0c1 + 1, srs_schema0)) != ERROR_SUCCESS) { + // @remark, use schema0 to make flash player happy. + if ((ret = c1.parse(hs_bytes->c0c1 + 1, 1536, srs_schema0)) != ERROR_SUCCESS) { srs_error("parse c1 schema%d error. ret=%d", srs_schema0, ret); return ret; } // try schema1 bool is_valid = false; if ((ret = c1.c1_validate_digest(is_valid)) != ERROR_SUCCESS || !is_valid) { - if ((ret = c1.parse(hs_bytes->c0c1 + 1, srs_schema1)) != ERROR_SUCCESS) { + srs_info("schema0 failed, try schema1."); + if ((ret = c1.parse(hs_bytes->c0c1 + 1, 1536, srs_schema1)) != ERROR_SUCCESS) { srs_error("parse c1 schema%d error. ret=%d", srs_schema1, ret); return ret; } @@ -1191,6 +1222,8 @@ int SrsComplexHandshake::handshake_with_client(SrsHandshakeBytes* hs_bytes, ISrs srs_info("all schema valid failed, try simple handshake. ret=%d", ret); return ret; } + } else { + srs_info("schema0 is ok."); } srs_verbose("decode c1 success."); @@ -1227,8 +1260,12 @@ int SrsComplexHandshake::handshake_with_client(SrsHandshakeBytes* hs_bytes, ISrs if ((ret = hs_bytes->create_s0s1s2()) != ERROR_SUCCESS) { return ret; } - s1.dump(hs_bytes->s0s1s2 + 1); - s2.dump(hs_bytes->s0s1s2 + 1537); + if ((ret = s1.dump(hs_bytes->s0s1s2 + 1, 1536)) != ERROR_SUCCESS) { + return ret; + } + if ((ret = s2.dump(hs_bytes->s0s1s2 + 1537, 1536)) != ERROR_SUCCESS) { + return ret; + } if ((ret = io->write(hs_bytes->s0s1s2, 3073, &nsize)) != ERROR_SUCCESS) { srs_warn("complex handshake send s0s1s2 failed. ret=%d", ret); return ret; @@ -1240,7 +1277,9 @@ int SrsComplexHandshake::handshake_with_client(SrsHandshakeBytes* hs_bytes, ISrs return ret; } c2s2 c2; - c2.parse(hs_bytes->c2); + if ((ret = c2.parse(hs_bytes->c2, 1536)) != ERROR_SUCCESS) { + return ret; + } srs_verbose("complex handshake read c2 success."); // verify c2 @@ -1276,7 +1315,9 @@ int SrsComplexHandshake::handshake_with_server(SrsHandshakeBytes* hs_bytes, ISrs if ((ret = c1.c1_create(srs_schema1)) != ERROR_SUCCESS) { return ret; } - c1.dump(hs_bytes->c0c1 + 1); + if ((ret = c1.dump(hs_bytes->c0c1 + 1, 1536)) != ERROR_SUCCESS) { + return ret; + } // verify c1 bool is_valid; @@ -1305,7 +1346,7 @@ int SrsComplexHandshake::handshake_with_server(SrsHandshakeBytes* hs_bytes, ISrs // verify s1s2 c1s1 s1; - if ((ret = s1.parse(hs_bytes->s0s1s2 + 1, c1.schema)) != ERROR_SUCCESS) { + if ((ret = s1.parse(hs_bytes->s0s1s2 + 1, 1536, c1.schema())) != ERROR_SUCCESS) { return ret; } @@ -1323,7 +1364,9 @@ int SrsComplexHandshake::handshake_with_server(SrsHandshakeBytes* hs_bytes, ISrs return ret; } - c2.dump(hs_bytes->c2); + if ((ret = c2.dump(hs_bytes->c2, 1536)) != ERROR_SUCCESS) { + return ret; + } if ((ret = io->write(hs_bytes->c2, 1536, &nsize)) != ERROR_SUCCESS) { srs_warn("complex handshake write c2 failed. ret=%d", ret); return ret; diff --git a/trunk/src/rtmp/srs_protocol_handshake.hpp b/trunk/src/protocol/srs_rtmp_handshake.hpp similarity index 53% rename from trunk/src/rtmp/srs_protocol_handshake.hpp rename to trunk/src/protocol/srs_rtmp_handshake.hpp index 7dac4f8ca9..9b6b04d7c6 100644 --- a/trunk/src/rtmp/srs_protocol_handshake.hpp +++ b/trunk/src/protocol/srs_rtmp_handshake.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -25,7 +25,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define SRS_RTMP_PROTOCOL_HANDSHKAE_HPP /* -#include +#include */ #include @@ -33,6 +33,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. class ISrsProtocolReaderWriter; class SrsComplexHandshake; class SrsHandshakeBytes; +class SrsStream; #ifdef SRS_AUTO_SSL @@ -41,6 +42,52 @@ class SrsHandshakeBytes; namespace _srs_internal { + // the digest key generate size. + #define SRS_OpensslHashSize 512 + extern u_int8_t SrsGenuineFMSKey[]; + extern u_int8_t SrsGenuineFPKey[]; + int openssl_HMACsha256(const void* key, int key_size, const void* data, int data_size, void* digest); + int openssl_generate_key(char* public_key, int32_t size); + + /** + * the DH wrapper. + */ + class SrsDH + { + private: + DH* pdh; + public: + SrsDH(); + virtual ~SrsDH(); + public: + /** + * initialize dh, generate the public and private key. + * @param ensure_128bytes_public_key whether ensure public key is 128bytes, + * sometimes openssl generate 127bytes public key. + * default to false to donot ensure. + */ + virtual int initialize(bool ensure_128bytes_public_key = false); + /** + * copy the public key. + * @param pkey the bytes to copy the public key. + * @param pkey_size the max public key size, output the actual public key size. + * user should never ignore this size. + * @remark, when ensure_128bytes_public_key, the size always 128. + */ + virtual int copy_public_key(char* pkey, int32_t& pkey_size); + /** + * generate and copy the shared key. + * generate the shared key with peer public key. + * @param ppkey peer public key. + * @param ppkey_size the size of ppkey. + * @param skey the computed shared key. + * @param skey_size the max shared key size, output the actual shared key size. + * user should never ignore this size. + */ + virtual int copy_shared_key(const char* ppkey, int32_t ppkey_size, char* skey, int32_t& skey_size); + private: + virtual int do_initialize(); + }; /** * the schema type. */ @@ -85,6 +132,18 @@ namespace _srs_internal // 4bytes int32_t offset; + public: + key_block(); + virtual ~key_block(); + public: + // parse key block from c1s1. + // if created, user must free it by srs_key_block_free + // @stream contains c1s1_key_bytes the key start bytes + int parse(SrsStream* stream); + private: + // calc the offset of key, + // the key->offset cannot be used as the offset of key. + int calc_valid_offset(); }; /** @@ -111,115 +170,173 @@ namespace _srs_internal // (764-4-offset-32)bytes char* random1; int random1_size; + public: + digest_block(); + virtual ~digest_block(); + public: + // parse digest block from c1s1. + // if created, user must free it by srs_digest_block_free + // @stream contains c1s1_digest_bytes the digest start bytes + int parse(SrsStream* stream); + private: + // calc the offset of digest, + // the key->offset cannot be used as the offset of digest. + int calc_valid_offset(); }; - // the digest key generate size. - #define __SRS_OpensslHashSize 512 - extern u_int8_t SrsGenuineFMSKey[]; - extern u_int8_t SrsGenuineFPKey[]; - int openssl_HMACsha256(const void* key, int key_size, const void* data, int data_size, void* digest); - int openssl_generate_key(char* public_key, int32_t size); + class c1s1; /** - * the DH wrapper. + * the c1s1 strategy, use schema0 or schema1. + * the template method class to defines common behaviors, + * while the concrete class to implements in schema0 or schema1. */ - class SrsDH + class c1s1_strategy { - private: - DH* pdh; + protected: + key_block key; + digest_block digest; public: - SrsDH(); - virtual ~SrsDH(); + c1s1_strategy(); + virtual ~c1s1_strategy(); public: /** - * initialize dh, generate the public and private key. - * @param ensure_128bytes_public_key whether ensure public key is 128bytes, - * sometimes openssl generate 127bytes public key. - * default to false to donot ensure. + * get the scema. */ - virtual int initialize(bool ensure_128bytes_public_key = false); + virtual srs_schema_type schema() = 0; /** - * copy the public key. - * @param pkey the bytes to copy the public key. - * @param pkey_size the max public key size, output the actual public key size. - * user should never ignore this size. - * @remark, when ensure_128bytes_public_key, the size always 128. + * get the digest. */ - virtual int copy_public_key(char* pkey, int32_t& pkey_size); + virtual char* get_digest(); /** - * generate and copy the shared key. - * generate the shared key with peer public key. - * @param ppkey peer public key. - * @param ppkey_size the size of ppkey. - * @param skey the computed shared key. - * @param skey_size the max shared key size, output the actual shared key size. - * user should never ignore this size. + * get the key. */ - virtual int copy_shared_key(const char* ppkey, int32_t ppkey_size, char* skey, int32_t& skey_size); - private: - virtual int do_initialize(); + virtual char* get_key(); + /** + * copy to bytes. + * @param size must be 1536. + */ + virtual int dump(c1s1* owner, char* _c1s1, int size); + /** + * server: parse the c1s1, discovery the key and digest by schema. + * use the c1_validate_digest() to valid the digest of c1. + */ + virtual int parse(char* _c1s1, int size) = 0; + public: + /** + * client: create and sign c1 by schema. + * sign the c1, generate the digest. + * calc_c1_digest(c1, schema) { + * get c1s1-joined from c1 by specified schema + * digest-data = HMACsha256(c1s1-joined, FPKey, 30) + * return digest-data; + * } + * random fill 1536bytes c1 // also fill the c1-128bytes-key + * time = time() // c1[0-3] + * version = [0x80, 0x00, 0x07, 0x02] // c1[4-7] + * schema = choose schema0 or schema1 + * digest-data = calc_c1_digest(c1, schema) + * copy digest-data to c1 + */ + virtual int c1_create(c1s1* owner); + /** + * server: validate the parsed c1 schema + */ + virtual int c1_validate_digest(c1s1* owner, bool& is_valid); + /** + * server: create and sign the s1 from c1. + * // decode c1 try schema0 then schema1 + * c1-digest-data = get-c1-digest-data(schema0) + * if c1-digest-data equals to calc_c1_digest(c1, schema0) { + * c1-key-data = get-c1-key-data(schema0) + * schema = schema0 + * } else { + * c1-digest-data = get-c1-digest-data(schema1) + * if c1-digest-data not equals to calc_c1_digest(c1, schema1) { + * switch to simple handshake. + * return + * } + * c1-key-data = get-c1-key-data(schema1) + * schema = schema1 + * } + * + * // generate s1 + * random fill 1536bytes s1 + * time = time() // c1[0-3] + * version = [0x04, 0x05, 0x00, 0x01] // s1[4-7] + * s1-key-data=shared_key=DH_compute_key(peer_pub_key=c1-key-data) + * get c1s1-joined by specified schema + * s1-digest-data = HMACsha256(c1s1-joined, FMSKey, 36) + * copy s1-digest-data and s1-key-data to s1. + * @param c1, to get the peer_pub_key of client. + */ + virtual int s1_create(c1s1* owner, c1s1* c1); + /** + * server: validate the parsed s1 schema + */ + virtual int s1_validate_digest(c1s1* owner, bool& is_valid); + public: + /** + * calc the digest for c1 + */ + virtual int calc_c1_digest(c1s1* owner, char*& c1_digest); + /** + * calc the digest for s1 + */ + virtual int calc_s1_digest(c1s1* owner, char*& s1_digest); + /** + * copy whole c1s1 to bytes. + * @param size must always be 1536 with digest, and 1504 without digest. + */ + virtual int copy_to(c1s1* owner, char* bytes, int size, bool with_digest) = 0; + /** + * copy time and version to stream. + */ + virtual void copy_time_version(SrsStream* stream, c1s1* owner); + /** + * copy key to stream. + */ + virtual void copy_key(SrsStream* stream); + /** + * copy digest to stream. + */ + virtual void copy_digest(SrsStream* stream, bool with_digest); }; - // calc the offset of key, - // the key->offset cannot be used as the offset of key. - int srs_key_block_get_offset(key_block* key); - - // create new key block data. - // if created, user must free it by srs_key_block_free - void srs_key_block_init(key_block* key); - - // parse key block from c1s1. - // if created, user must free it by srs_key_block_free - // @c1s1_key_bytes the key start bytes, maybe c1s1 or c1s1+764 - int srs_key_block_parse(key_block* key, char* c1s1_key_bytes); - - // free the block data create by - // srs_key_block_init or srs_key_block_parse - void srs_key_block_free(key_block* key); - - // calc the offset of digest, - // the key->offset cannot be used as the offset of digest. - int srs_digest_block_get_offset(digest_block* digest); - - // create new digest block data. - // if created, user must free it by srs_digest_block_free - void srs_digest_block_init(digest_block* digest); - - // parse digest block from c1s1. - // if created, user must free it by srs_digest_block_free - // @c1s1_digest_bytes the digest start bytes, maybe c1s1 or c1s1+764 - int srs_digest_block_parse(digest_block* digest, char* c1s1_digest_bytes); - - // free the block data create by - // srs_digest_block_init or srs_digest_block_parse - void srs_digest_block_free(digest_block* digest); - /** - * copy whole c1s1 to bytes. - */ - void srs_schema0_copy_to(char* bytes, bool with_digest, - int32_t time, int32_t version, key_block* key, digest_block* digest); - void srs_schema1_copy_to(char* bytes, bool with_digest, - int32_t time, int32_t version, digest_block* digest, key_block* key); - - /** - * c1s1 is splited by digest: - * c1s1-part1: n bytes (time, version, key and digest-part1). - * digest-data: 32bytes - * c1s1-part2: (1536-n-32)bytes (digest-part2) - * @return a new allocated bytes, user must free it. + * c1s1 schema0 + * key: 764bytes + * digest: 764bytes */ - char* srs_bytes_join_schema0(int32_t time, int32_t version, key_block* key, digest_block* digest); + class c1s1_strategy_schema0 : public c1s1_strategy + { + public: + c1s1_strategy_schema0(); + virtual ~c1s1_strategy_schema0(); + public: + virtual srs_schema_type schema(); + virtual int parse(char* _c1s1, int size); + public: + virtual int copy_to(c1s1* owner, char* bytes, int size, bool with_digest); + }; /** - * c1s1 is splited by digest: - * c1s1-part1: n bytes (time, version and digest-part1). - * digest-data: 32bytes - * c1s1-part2: (1536-n-32)bytes (digest-part2 and key) - * @return a new allocated bytes, user must free it. + * c1s1 schema1 + * digest: 764bytes + * key: 764bytes */ - char* srs_bytes_join_schema1(int32_t time, int32_t version, digest_block* digest, key_block* key); - + class c1s1_strategy_schema1 : public c1s1_strategy + { + public: + c1s1_strategy_schema1(); + virtual ~c1s1_strategy_schema1(); + public: + virtual srs_schema_type schema(); + virtual int parse(char* _c1s1, int size); + public: + virtual int copy_to(c1s1* owner, char* bytes, int size, bool with_digest); + }; + /** * c1s1 schema0 * time: 4bytes @@ -236,43 +353,42 @@ namespace _srs_internal class c1s1 { public: - union block { - key_block key; - digest_block digest; - }; - // 4bytes int32_t time; // 4bytes int32_t version; - // 764bytes - // if schema0, use key - // if schema1, use digest - block block0; - // 764bytes - // if schema0, use digest - // if schema1, use key - block block1; - - // the logic schema - srs_schema_type schema; - + // 764bytes+764bytes + c1s1_strategy* payload; + public: c1s1(); virtual ~c1s1(); + public: + /** + * get the scema. + */ + virtual srs_schema_type schema(); /** * get the digest key. */ virtual char* get_digest(); + /** + * get the key. + */ + virtual char* get_key(); + public: /** * copy to bytes. + * @param size, must always be 1536. */ - virtual void dump(char* _c1s1); + virtual int dump(char* _c1s1, int size); /** * server: parse the c1s1, discovery the key and digest by schema. + * @param size, must always be 1536. * use the c1_validate_digest() to valid the digest of c1. + * use the s1_validate_digest() to valid the digest of s1. */ - virtual int parse(char* _c1s1, srs_schema_type _schema); - + virtual int parse(char* _c1s1, int size, srs_schema_type _schema); + public: /** * client: create and sign c1 by schema. * sign the c1, generate the digest. @@ -293,18 +409,38 @@ namespace _srs_internal * server: validate the parsed c1 schema */ virtual int c1_validate_digest(bool& is_valid); + public: /** * server: create and sign the s1 from c1. + * // decode c1 try schema0 then schema1 + * c1-digest-data = get-c1-digest-data(schema0) + * if c1-digest-data equals to calc_c1_digest(c1, schema0) { + * c1-key-data = get-c1-key-data(schema0) + * schema = schema0 + * } else { + * c1-digest-data = get-c1-digest-data(schema1) + * if c1-digest-data not equals to calc_c1_digest(c1, schema1) { + * switch to simple handshake. + * return + * } + * c1-key-data = get-c1-key-data(schema1) + * schema = schema1 + * } + * + * // generate s1 + * random fill 1536bytes s1 + * time = time() // c1[0-3] + * version = [0x04, 0x05, 0x00, 0x01] // s1[4-7] + * s1-key-data=shared_key=DH_compute_key(peer_pub_key=c1-key-data) + * get c1s1-joined by specified schema + * s1-digest-data = HMACsha256(c1s1-joined, FMSKey, 36) + * copy s1-digest-data and s1-key-data to s1. */ virtual int s1_create(c1s1* c1); /** * server: validate the parsed s1 schema */ virtual int s1_validate_digest(bool& is_valid); - private: - virtual int calc_s1_digest(char*& digest); - virtual int calc_c1_digest(char*& digest); - virtual void destroy_blocks(); }; /** @@ -318,19 +454,21 @@ namespace _srs_internal public: char random[1504]; char digest[32]; - + public: c2s2(); virtual ~c2s2(); - + public: /** * copy to bytes. + * @param size, must always be 1536. */ - virtual void dump(char* _c2s2); + virtual int dump(char* _c2s2, int size); /** * parse the c2s2 + * @param size, must always be 1536. */ - virtual void parse(char* _c2s2); - + virtual int parse(char* _c2s2, int size); + public: /** * create c2. * random fill c2s2 1536 bytes @@ -345,7 +483,7 @@ namespace _srs_internal * validate the c2 from client. */ virtual int c2_validate(c1s1* s1, bool& is_valid); - + public: /** * create s2. * random fill c2s2 1536 bytes diff --git a/trunk/src/rtmp/srs_protocol_io.cpp b/trunk/src/protocol/srs_rtmp_io.cpp similarity index 90% rename from trunk/src/rtmp/srs_protocol_io.cpp rename to trunk/src/protocol/srs_rtmp_io.cpp index a0dd8999c4..61add9afc9 100644 --- a/trunk/src/rtmp/srs_protocol_io.cpp +++ b/trunk/src/protocol/srs_rtmp_io.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -21,7 +21,15 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include +#include + +ISrsBufferReader::ISrsBufferReader() +{ +} + +ISrsBufferReader::~ISrsBufferReader() +{ +} ISrsBufferWriter::ISrsBufferWriter() { diff --git a/trunk/src/rtmp/srs_protocol_io.hpp b/trunk/src/protocol/srs_rtmp_io.hpp similarity index 92% rename from trunk/src/rtmp/srs_protocol_io.hpp rename to trunk/src/protocol/srs_rtmp_io.hpp index 9112f70876..61b4d95a9b 100644 --- a/trunk/src/rtmp/srs_protocol_io.hpp +++ b/trunk/src/protocol/srs_rtmp_io.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -25,14 +25,15 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define SRS_RTMP_PROTOCOL_IO_HPP /* -#include +#include */ #include +// for srs-librtmp, @see https://github.com/simple-rtmp-server/srs/issues/213 +#ifndef _WIN32 #include - -#include +#endif /** * the system io reader/writer architecture: @@ -59,6 +60,19 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +----------------------------------+ */ +/** +* the reader for the buffer to read from whatever channel. +*/ +class ISrsBufferReader +{ +public: + ISrsBufferReader(); + virtual ~ISrsBufferReader(); +// for protocol/amf0/msg-codec +public: + virtual int read(void* buf, size_t size, ssize_t* nread) = 0; +}; + /** * the writer for the buffer to write to whatever channel. */ diff --git a/trunk/research/librtmp/srs_research_public.h b/trunk/src/protocol/srs_rtmp_msg_array.cpp similarity index 52% rename from trunk/research/librtmp/srs_research_public.h rename to trunk/src/protocol/srs_rtmp_msg_array.cpp index 63d13190b2..047c3b71f1 100644 --- a/trunk/research/librtmp/srs_research_public.h +++ b/trunk/src/protocol/srs_rtmp_msg_array.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -21,47 +21,45 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef SRS_RESEARH_PUBLIC_HPP -#define SRS_RESEARH_PUBLIC_HPP +#include -/* -#include "srs_research_public.h" -*/ +#include -#include -#include - -char* format_time() +SrsMessageArray::SrsMessageArray(int max_msgs) { - struct timeval tv; - static char buf[23]; + srs_assert(max_msgs > 0); - memset(buf, 0, sizeof(buf)); + msgs = new SrsSharedPtrMessage*[max_msgs]; + max = max_msgs; - // clock time - if (gettimeofday(&tv, NULL) == -1) { - return buf; + zero(max_msgs); +} + +SrsMessageArray::~SrsMessageArray() +{ + // we just free the msgs itself, + // both delete and delete[] is ok, + // for each msg in msgs is already freed by send_and_free_messages. + srs_freep(msgs); +} + +void SrsMessageArray::free(int count) +{ + // initialize + for (int i = 0; i < count; i++) { + SrsSharedPtrMessage* msg = msgs[i]; + srs_freep(msg); + + msgs[i] = NULL; } - - // to calendar time - struct tm* tm; - if ((tm = localtime(&tv.tv_sec)) == NULL) { - return buf; +} + +void SrsMessageArray::zero(int count) +{ + // initialize + for (int i = 0; i < count; i++) { + msgs[i] = NULL; } - - snprintf(buf, sizeof(buf), - "%d-%02d-%02d %02d:%02d:%02d.%03d", - 1900 + tm->tm_year, 1 + tm->tm_mon, tm->tm_mday, - tm->tm_hour, tm->tm_min, tm->tm_sec, - (int)(tv.tv_usec / 1000)); - - return buf; } -#define trace(msg, ...) printf("[%s]", format_time());printf(msg, ##__VA_ARGS__);printf("\n") -#define verbose(msg, ...) printf("[%s]", format_time());printf(msg, ##__VA_ARGS__);printf("\n") -#if 1 -#undef verbose -#define verbose(msg, ...) (void)0 -#endif -#endif + diff --git a/trunk/src/rtmp/srs_protocol_msg_array.hpp b/trunk/src/protocol/srs_rtmp_msg_array.hpp similarity index 77% rename from trunk/src/rtmp/srs_protocol_msg_array.hpp rename to trunk/src/protocol/srs_rtmp_msg_array.hpp index 5d3bf894ec..a600c5cafa 100644 --- a/trunk/src/rtmp/srs_protocol_msg_array.hpp +++ b/trunk/src/protocol/srs_rtmp_msg_array.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -25,7 +25,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define SRS_RTMP_PROTOCOL_MSG_ARRAY_HPP /* -#include +#include */ #include @@ -37,9 +37,11 @@ class SrsSharedPtrMessage; * when need to get some messages, for instance, from Consumer queue, * create a message array, whose msgs can used to accept the msgs, * then send each message and set to NULL. -* @remark: when error, the message array will free the msg not sent out. +* +* @remark: user must free all msgs in array, for the SRS2.0 protocol stack +* provides an api to send messages, @see send_and_free_messages */ -class SrsSharedPtrMessageArray +class SrsMessageArray { public: /** @@ -48,16 +50,26 @@ class SrsSharedPtrMessageArray * where send(msg) will always send and free it. */ SrsSharedPtrMessage** msgs; - int size; + int max; public: /** * create msg array, initialize array to NULL ptrs. */ - SrsSharedPtrMessageArray(int _size); + SrsMessageArray(int max_msgs); /** * free the msgs not sent out(not NULL). */ - virtual ~SrsSharedPtrMessageArray(); + virtual ~SrsMessageArray(); +public: + /** + * free specified count of messages. + */ + virtual void free(int count); +private: + /** + * zero initialize the message array. + */ + virtual void zero(int count); }; #endif diff --git a/trunk/src/rtmp/srs_protocol_rtmp.cpp b/trunk/src/protocol/srs_rtmp_sdk.cpp similarity index 93% rename from trunk/src/rtmp/srs_protocol_rtmp.cpp rename to trunk/src/protocol/srs_rtmp_sdk.cpp index 3af25e5315..2b0ba4200a 100644 --- a/trunk/src/rtmp/srs_protocol_rtmp.cpp +++ b/trunk/src/protocol/srs_rtmp_sdk.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -21,17 +21,21 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include +#include #include -#include -#include -#include -#include +#include +#include +#include +#include #include #include +// for srs-librtmp, @see https://github.com/simple-rtmp-server/srs/issues/213 +#ifndef _WIN32 #include +#endif + using namespace std; /** @@ -87,6 +91,7 @@ SrsRequest* SrsRequest::copy() { SrsRequest* cp = new SrsRequest(); + cp->ip = ip; cp->app = app; cp->objectEncoding = objectEncoding; cp->pageUrl = pageUrl; @@ -255,12 +260,12 @@ int SrsHandshakeBytes::create_c0c1() srs_random_generate(c0c1, 1537); // plain text required. - static SrsStream stream; + SrsStream stream; if ((ret = stream.initialize(c0c1, 9)) != ERROR_SUCCESS) { return ret; } stream.write_1bytes(0x03); - stream.write_4bytes(::time(NULL)); + stream.write_4bytes((int32_t)::time(NULL)); stream.write_4bytes(0x00); return ret; @@ -283,14 +288,14 @@ int SrsHandshakeBytes::create_s0s1s2(const char* c1) return ret; } stream.write_1bytes(0x03); - stream.write_4bytes(::time(NULL)); - // s2 time2 copy from c1 + stream.write_4bytes((int32_t)::time(NULL)); + // s1 time2 copy from c1 if (c0c1) { stream.write_bytes(c0c1 + 1, 4); } // if c1 specified, copy c1 to s2. - // @see: https://github.com/winlinvip/simple-rtmp-server/issues/46 + // @see: https://github.com/simple-rtmp-server/srs/issues/46 if (c1) { memcpy(s0s1s2 + 1537, c1, 1536); } @@ -314,7 +319,7 @@ int SrsHandshakeBytes::create_c2() if ((ret = stream.initialize(c2, 8)) != ERROR_SUCCESS) { return ret; } - stream.write_4bytes(::time(NULL)); + stream.write_4bytes((int32_t)::time(NULL)); // c2 time2 copy from s1 if (s0s1s2) { stream.write_bytes(s0s1s2 + 1, 4); @@ -356,21 +361,26 @@ int64_t SrsRtmpClient::get_send_bytes() return protocol->get_send_bytes(); } -int SrsRtmpClient::recv_message(SrsMessage** pmsg) +int SrsRtmpClient::recv_message(SrsCommonMessage** pmsg) { return protocol->recv_message(pmsg); } -int SrsRtmpClient::decode_message(SrsMessage* msg, SrsPacket** ppacket) +int SrsRtmpClient::decode_message(SrsCommonMessage* msg, SrsPacket** ppacket) { return protocol->decode_message(msg, ppacket); } -int SrsRtmpClient::send_and_free_message(SrsMessage* msg, int stream_id) +int SrsRtmpClient::send_and_free_message(SrsSharedPtrMessage* msg, int stream_id) { return protocol->send_and_free_message(msg, stream_id); } +int SrsRtmpClient::send_and_free_messages(SrsSharedPtrMessage** msgs, int nb_msgs, int stream_id) +{ + return protocol->send_and_free_messages(msgs, nb_msgs, stream_id); +} + int SrsRtmpClient::send_and_free_packet(SrsPacket* packet, int stream_id) { return protocol->send_and_free_packet(packet, stream_id); @@ -442,7 +452,7 @@ int SrsRtmpClient::connect_app(string app, string tc_url, int srs_pid = 0; return connect_app2(app, tc_url, req, debug_srs_upnode, - srs_server_ip, srs_server, srs_primary, srs_authors, + srs_server_ip, srs_server, srs_primary, srs_authors, srs_version, srs_id, srs_pid); } @@ -459,13 +469,17 @@ int SrsRtmpClient::connect_app2( SrsConnectAppPacket* pkt = new SrsConnectAppPacket(); pkt->command_object->set("app", SrsAmf0Any::str(app.c_str())); - pkt->command_object->set("flashVer", SrsAmf0Any::str("WIN 12,0,0,41")); + pkt->command_object->set("flashVer", SrsAmf0Any::str("WIN 15,0,0,239")); if (req) { pkt->command_object->set("swfUrl", SrsAmf0Any::str(req->swfUrl.c_str())); } else { pkt->command_object->set("swfUrl", SrsAmf0Any::str()); } - pkt->command_object->set("tcUrl", SrsAmf0Any::str(tc_url.c_str())); + if (req && req->tcUrl != "") { + pkt->command_object->set("tcUrl", SrsAmf0Any::str(req->tcUrl.c_str())); + } else { + pkt->command_object->set("tcUrl", SrsAmf0Any::str(tc_url.c_str())); + } pkt->command_object->set("fpad", SrsAmf0Any::boolean(false)); pkt->command_object->set("capabilities", SrsAmf0Any::number(239)); pkt->command_object->set("audioCodecs", SrsAmf0Any::number(3575)); @@ -478,7 +492,7 @@ int SrsRtmpClient::connect_app2( } pkt->command_object->set("objectEncoding", SrsAmf0Any::number(0)); - // @see https://github.com/winlinvip/simple-rtmp-server/issues/160 + // @see https://github.com/simple-rtmp-server/srs/issues/160 // the debug_srs_upnode is config in vhost and default to true. if (debug_srs_upnode && req && req->args) { srs_freep(pkt->args); @@ -500,13 +514,13 @@ int SrsRtmpClient::connect_app2( } // expect connect _result - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; SrsConnectAppResPacket* pkt = NULL; if ((ret = expect_message(&msg, &pkt)) != ERROR_SUCCESS) { srs_error("expect connect app response message failed. ret=%d", ret); return ret; } - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); SrsAutoFree(SrsConnectAppResPacket, pkt); // server info @@ -557,13 +571,13 @@ int SrsRtmpClient::create_stream(int& stream_id) // CreateStream _result. if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; SrsCreateStreamResPacket* pkt = NULL; if ((ret = expect_message(&msg, &pkt)) != ERROR_SUCCESS) { srs_error("expect create stream response message failed. ret=%d", ret); return ret; } - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); SrsAutoFree(SrsCreateStreamResPacket, pkt); srs_info("get create stream response message"); @@ -691,13 +705,13 @@ int SrsRtmpClient::fmle_publish(string stream, int& stream_id) // expect result of CreateStream if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; SrsCreateStreamResPacket* pkt = NULL; if ((ret = expect_message(&msg, &pkt)) != ERROR_SUCCESS) { srs_error("expect create stream response message failed. ret=%d", ret); return ret; } - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); SrsAutoFree(SrsCreateStreamResPacket, pkt); srs_info("get create stream response message"); @@ -731,6 +745,23 @@ SrsRtmpServer::~SrsRtmpServer() srs_freep(hs_bytes); } +void SrsRtmpServer::set_auto_response(bool v) +{ + protocol->set_auto_response(v); +} + +#ifdef SRS_PERF_MERGED_READ +void SrsRtmpServer::set_merge_read(bool v, IMergeReadHandler* handler) +{ + protocol->set_merge_read(v, handler); +} + +void SrsRtmpServer::set_recv_buffer(int buffer_size) +{ + protocol->set_recv_buffer(buffer_size); +} +#endif + void SrsRtmpServer::set_recv_timeout(int64_t timeout_us) { protocol->set_recv_timeout(timeout_us); @@ -761,21 +792,26 @@ int64_t SrsRtmpServer::get_send_bytes() return protocol->get_send_bytes(); } -int SrsRtmpServer::recv_message(SrsMessage** pmsg) +int SrsRtmpServer::recv_message(SrsCommonMessage** pmsg) { return protocol->recv_message(pmsg); } -int SrsRtmpServer::decode_message(SrsMessage* msg, SrsPacket** ppacket) +int SrsRtmpServer::decode_message(SrsCommonMessage* msg, SrsPacket** ppacket) { return protocol->decode_message(msg, ppacket); } -int SrsRtmpServer::send_and_free_message(SrsMessage* msg, int stream_id) +int SrsRtmpServer::send_and_free_message(SrsSharedPtrMessage* msg, int stream_id) { return protocol->send_and_free_message(msg, stream_id); } +int SrsRtmpServer::send_and_free_messages(SrsSharedPtrMessage** msgs, int nb_msgs, int stream_id) +{ + return protocol->send_and_free_messages(msgs, nb_msgs, stream_id); +} + int SrsRtmpServer::send_and_free_packet(SrsPacket* packet, int stream_id) { return protocol->send_and_free_packet(packet, stream_id); @@ -807,13 +843,13 @@ int SrsRtmpServer::connect_app(SrsRequest* req) { int ret = ERROR_SUCCESS; - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; SrsConnectAppPacket* pkt = NULL; if ((ret = expect_message(&msg, &pkt)) != ERROR_SUCCESS) { srs_error("expect connect app message failed. ret=%d", ret); return ret; } - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); SrsAutoFree(SrsConnectAppPacket, pkt); srs_info("get connect app message"); @@ -970,7 +1006,7 @@ int SrsRtmpServer::identify_client(int stream_id, SrsRtmpConnType& type, string& int ret = ERROR_SUCCESS; while (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; if ((ret = protocol->recv_message(&msg)) != ERROR_SUCCESS) { if (!srs_is_client_gracefully_close(ret)) { srs_error("recv identify client message failed. ret=%d", ret); @@ -978,7 +1014,7 @@ int SrsRtmpServer::identify_client(int stream_id, SrsRtmpConnType& type, string& return ret; } - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); SrsMessageHeader& h = msg->header; if (h.is_ackledgement() || h.is_set_chunk_size() || h.is_window_ackledgement_size() || h.is_user_control_message()) { @@ -1013,7 +1049,7 @@ int SrsRtmpServer::identify_client(int stream_id, SrsRtmpConnType& type, string& } // call msg, // support response null first, - // @see https://github.com/winlinvip/simple-rtmp-server/issues/106 + // @see https://github.com/simple-rtmp-server/srs/issues/106 // TODO: FIXME: response in right way, or forward in edge mode. SrsCallPacket* call = dynamic_cast(pkt); if (call) { @@ -1092,10 +1128,10 @@ int SrsRtmpServer::start_play(int stream_id) pkt->data->set(StatusClientId, SrsAmf0Any::str(RTMP_SIG_CLIENT_ID)); if ((ret = protocol->send_and_free_packet(pkt, stream_id)) != ERROR_SUCCESS) { - srs_error("send onStatus(NetStream.Play.Reset) message failed. ret=%d", ret); + srs_error("send onStatus(NetStream.Play.Start) message failed. ret=%d", ret); return ret; } - srs_info("send onStatus(NetStream.Play.Reset) message success."); + srs_info("send onStatus(NetStream.Play.Start) message success."); } // |RtmpSampleAccess(false, false) @@ -1103,7 +1139,7 @@ int SrsRtmpServer::start_play(int stream_id) SrsSampleAccessPacket* pkt = new SrsSampleAccessPacket(); // allow audio/video sample. - // @see: https://github.com/winlinvip/simple-rtmp-server/issues/49 + // @see: https://github.com/simple-rtmp-server/srs/issues/49 pkt->audio_sample_access = true; pkt->video_sample_access = true; @@ -1202,7 +1238,7 @@ int SrsRtmpServer::start_fmle_publish(int stream_id) // FCPublish double fc_publish_tid = 0; if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; SrsFMLEStartPacket* pkt = NULL; if ((ret = expect_message(&msg, &pkt)) != ERROR_SUCCESS) { srs_error("recv FCPublish message failed. ret=%d", ret); @@ -1210,7 +1246,7 @@ int SrsRtmpServer::start_fmle_publish(int stream_id) } srs_info("recv FCPublish request message success."); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); SrsAutoFree(SrsFMLEStartPacket, pkt); fc_publish_tid = pkt->transaction_id; @@ -1228,7 +1264,7 @@ int SrsRtmpServer::start_fmle_publish(int stream_id) // createStream double create_stream_tid = 0; if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; SrsCreateStreamPacket* pkt = NULL; if ((ret = expect_message(&msg, &pkt)) != ERROR_SUCCESS) { srs_error("recv createStream message failed. ret=%d", ret); @@ -1236,7 +1272,7 @@ int SrsRtmpServer::start_fmle_publish(int stream_id) } srs_info("recv createStream request message success."); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); SrsAutoFree(SrsCreateStreamPacket, pkt); create_stream_tid = pkt->transaction_id; @@ -1253,7 +1289,7 @@ int SrsRtmpServer::start_fmle_publish(int stream_id) // publish if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; SrsPublishPacket* pkt = NULL; if ((ret = expect_message(&msg, &pkt)) != ERROR_SUCCESS) { srs_error("recv publish message failed. ret=%d", ret); @@ -1261,7 +1297,7 @@ int SrsRtmpServer::start_fmle_publish(int stream_id) } srs_info("recv publish request message success."); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); SrsAutoFree(SrsPublishPacket, pkt); } // publish response onFCPublish(NetStream.Publish.Start) @@ -1386,7 +1422,7 @@ int SrsRtmpServer::identify_create_stream_client(SrsCreateStreamPacket* req, int } while (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; if ((ret = protocol->recv_message(&msg)) != ERROR_SUCCESS) { if (!srs_is_client_gracefully_close(ret)) { srs_error("recv identify client message failed. ret=%d", ret); @@ -1394,7 +1430,7 @@ int SrsRtmpServer::identify_create_stream_client(SrsCreateStreamPacket* req, int return ret; } - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); SrsMessageHeader& h = msg->header; if (h.is_ackledgement() || h.is_set_chunk_size() || h.is_window_ackledgement_size() || h.is_user_control_message()) { diff --git a/trunk/src/rtmp/srs_protocol_rtmp.hpp b/trunk/src/protocol/srs_rtmp_sdk.hpp similarity index 83% rename from trunk/src/rtmp/srs_protocol_rtmp.hpp rename to trunk/src/protocol/srs_rtmp_sdk.hpp index 4379b9c187..0ef5ac731b 100644 --- a/trunk/src/rtmp/srs_protocol_rtmp.hpp +++ b/trunk/src/protocol/srs_rtmp_sdk.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -25,33 +25,37 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define SRS_RTMP_PROTOCOL_RTMP_HPP /* -#include +#include */ #include #include -#include +#include +#include class SrsProtocol; class ISrsProtocolReaderWriter; -class ISrsMessage; class SrsCommonMessage; class SrsCreateStreamPacket; class SrsFMLEStartPacket; class SrsPublishPacket; class SrsOnMetaDataPacket; class SrsPlayPacket; -class SrsMessage; +class SrsCommonMessage; class SrsPacket; class SrsAmf0Object; +class IMergeReadHandler; /** * the original request from client. */ class SrsRequest { +public: + // client ip. + std::string ip; public: /** * tcUrl: rtmp://request_vhost:port/app/stream @@ -81,12 +85,12 @@ class SrsRequest std::string stream; // for play live stream, // used to specified the stop when exceed the duration. - // @see https://github.com/winlinvip/simple-rtmp-server/issues/45 + // @see https://github.com/simple-rtmp-server/srs/issues/45 // in ms. double duration; // the token in the connect request, // used for edge traverse to origin authentication, - // @see https://github.com/winlinvip/simple-rtmp-server/issues/104 + // @see https://github.com/simple-rtmp-server/srs/issues/104 SrsAmf0Object* args; public: SrsRequest(); @@ -205,14 +209,14 @@ class SrsRtmpClient * never NULL if decode success. * @remark, drop message when msg is empty or payload length is empty. */ - virtual int recv_message(SrsMessage** pmsg); + virtual int recv_message(SrsCommonMessage** pmsg); /** * decode bytes oriented RTMP message to RTMP packet, * @param ppacket, output decoded packet, * always NULL if error, never NULL if success. * @return error when unknown packet, error when decode failed. */ - virtual int decode_message(SrsMessage* msg, SrsPacket** ppacket); + virtual int decode_message(SrsCommonMessage* msg, SrsPacket** ppacket); /** * send the RTMP message and always free it. * user must never free or use the msg after this method, @@ -220,7 +224,16 @@ class SrsRtmpClient * @param msg, the msg to send out, never be NULL. * @param stream_id, the stream id of packet to send over, 0 for control message. */ - virtual int send_and_free_message(SrsMessage* msg, int stream_id); + virtual int send_and_free_message(SrsSharedPtrMessage* msg, int stream_id); + /** + * send the RTMP message and always free it. + * user must never free or use the msg after this method, + * for it will always free the msg. + * @param msgs, the msgs to send out, never be NULL. + * @param nb_msgs, the size of msgs to send out. + * @param stream_id, the stream id of packet to send over, 0 for control message. + */ + virtual int send_and_free_messages(SrsSharedPtrMessage** msgs, int nb_msgs, int stream_id); /** * send the RTMP packet and always free it. * user must never free or use the packet after this method, @@ -305,7 +318,7 @@ class SrsRtmpClient * if need to set timeout, use set timeout of SrsProtocol. */ template - int expect_message(SrsMessage** pmsg, T** ppacket) + int expect_message(SrsCommonMessage** pmsg, T** ppacket) { return protocol->expect_message(pmsg, ppacket); } @@ -327,6 +340,31 @@ class SrsRtmpServer virtual ~SrsRtmpServer(); // protocol methods proxy public: + /** + * set the auto response message when recv for protocol stack. + * @param v, whether auto response message when recv message. + * @see: https://github.com/simple-rtmp-server/srs/issues/217 + */ + virtual void set_auto_response(bool v); +#ifdef SRS_PERF_MERGED_READ + /** + * to improve read performance, merge some packets then read, + * when it on and read small bytes, we sleep to wait more data., + * that is, we merge some data to read together. + * @param v true to ename merged read. + * @param handler the handler when merge read is enabled. + * @see https://github.com/simple-rtmp-server/srs/issues/241 + */ + virtual void set_merge_read(bool v, IMergeReadHandler* handler); + /** + * create buffer with specifeid size. + * @param buffer the size of buffer. + * @remark when MR(SRS_PERF_MERGED_READ) disabled, always set to 8K. + * @remark when buffer changed, the previous ptr maybe invalid. + * @see https://github.com/simple-rtmp-server/srs/issues/241 + */ + virtual void set_recv_buffer(int buffer_size); +#endif /** * set/get the recv timeout in us. * if timeout, recv/send message return ERROR_SOCKET_TIMEOUT. @@ -353,14 +391,14 @@ class SrsRtmpServer * never NULL if decode success. * @remark, drop message when msg is empty or payload length is empty. */ - virtual int recv_message(SrsMessage** pmsg); + virtual int recv_message(SrsCommonMessage** pmsg); /** * decode bytes oriented RTMP message to RTMP packet, * @param ppacket, output decoded packet, * always NULL if error, never NULL if success. * @return error when unknown packet, error when decode failed. */ - virtual int decode_message(SrsMessage* msg, SrsPacket** ppacket); + virtual int decode_message(SrsCommonMessage* msg, SrsPacket** ppacket); /** * send the RTMP message and always free it. * user must never free or use the msg after this method, @@ -368,7 +406,19 @@ class SrsRtmpServer * @param msg, the msg to send out, never be NULL. * @param stream_id, the stream id of packet to send over, 0 for control message. */ - virtual int send_and_free_message(SrsMessage* msg, int stream_id); + virtual int send_and_free_message(SrsSharedPtrMessage* msg, int stream_id); + /** + * send the RTMP message and always free it. + * user must never free or use the msg after this method, + * for it will always free the msg. + * @param msgs, the msgs to send out, never be NULL. + * @param nb_msgs, the size of msgs to send out. + * @param stream_id, the stream id of packet to send over, 0 for control message. + * + * @remark performance issue, to support 6k+ 250kbps client, + * @see https://github.com/simple-rtmp-server/srs/issues/194 + */ + virtual int send_and_free_messages(SrsSharedPtrMessage** msgs, int nb_msgs, int stream_id); /** * send the RTMP packet and always free it. * user must never free or use the packet after this method, @@ -475,7 +525,7 @@ class SrsRtmpServer * if need to set timeout, use set timeout of SrsProtocol. */ template - int expect_message(SrsMessage** pmsg, T** ppacket) + int expect_message(SrsCommonMessage** pmsg, T** ppacket) { return protocol->expect_message(pmsg, ppacket); } diff --git a/trunk/src/rtmp/srs_protocol_stack.cpp b/trunk/src/protocol/srs_rtmp_stack.cpp similarity index 84% rename from trunk/src/rtmp/srs_protocol_stack.cpp rename to trunk/src/protocol/srs_rtmp_stack.cpp index 397095b5ff..332f458c06 100644 --- a/trunk/src/rtmp/srs_protocol_stack.cpp +++ b/trunk/src/protocol/srs_rtmp_stack.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -21,14 +21,22 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include +#include -#include -#include +#include +#include #include #include #include +#include +#include +// for srs-librtmp, @see https://github.com/simple-rtmp-server/srs/issues/213 +#ifndef _WIN32 +#include +#endif + +#include using namespace std; // when got a messae header, there must be some data, @@ -161,22 +169,6 @@ messages. // the same as the timestamp of Type 0 chunk. #define RTMP_FMT_TYPE3 3 -/**************************************************************************** -***************************************************************************** -****************************************************************************/ -/** -* 6.1. Chunk Format -* Extended timestamp: 0 or 4 bytes -* This field MUST be sent when the normal timsestamp is set to -* 0xffffff, it MUST NOT be sent if the normal timestamp is set to -* anything else. So for values less than 0xffffff the normal -* timestamp field SHOULD be used in which case the extended timestamp -* MUST NOT be present. For values greater than or equal to 0xffffff -* the normal timestamp field MUST NOT be used and MUST be set to -* 0xffffff and the extended timestamp MUST be sent. -*/ -#define RTMP_EXTENDED_TIMESTAMP 0xFFFFFF - /**************************************************************************** ***************************************************************************** ****************************************************************************/ @@ -197,8 +189,6 @@ messages. #define RTMP_AMF0_COMMAND_UNPUBLISH "FCUnpublish" #define RTMP_AMF0_COMMAND_PUBLISH "publish" #define RTMP_AMF0_DATA_SAMPLE_ACCESS "|RtmpSampleAccess" -#define RTMP_AMF0_DATA_SET_DATAFRAME "@setDataFrame" -#define RTMP_AMF0_DATA_ON_METADATA "onMetaData" /** * band width check method name, which will be invoked by client. @@ -384,27 +374,279 @@ void SrsMessageHeader::initialize_video(int size, u_int32_t time, int stream) perfer_cid = RTMP_CID_Video; } -SrsMessage::SrsMessage() +SrsCommonMessage::SrsCommonMessage() { payload = NULL; size = 0; } -SrsMessage::~SrsMessage() +SrsCommonMessage::~SrsCommonMessage() +{ + srs_freep(payload); +} + +SrsSharedPtrMessage::SrsSharedPtrPayload::SrsSharedPtrPayload() +{ + payload = NULL; + size = 0; + shared_count = 0; +} + +SrsSharedPtrMessage::SrsSharedPtrPayload::~SrsSharedPtrPayload() +{ + srs_freep(payload); +} + +SrsSharedPtrMessage::SrsSharedPtrMessage() +{ + ptr = NULL; +} + +SrsSharedPtrMessage::~SrsSharedPtrMessage() +{ + if (ptr) { + if (ptr->shared_count == 0) { + srs_freep(ptr); + } else { + ptr->shared_count--; + } + } +} + +int SrsSharedPtrMessage::create(SrsCommonMessage* msg) +{ + int ret = ERROR_SUCCESS; + + if ((ret = create(&msg->header, msg->payload, msg->size)) != ERROR_SUCCESS) { + return ret; + } + + // to prevent double free of payload: + // initialize already attach the payload of msg, + // detach the payload to transfer the owner to shared ptr. + msg->payload = NULL; + msg->size = 0; + + return ret; +} + +int SrsSharedPtrMessage::create(SrsMessageHeader* pheader, char* payload, int size) +{ + int ret = ERROR_SUCCESS; + + if (ptr) { + ret = ERROR_SYSTEM_ASSERT_FAILED; + srs_error("should not set the payload twice. ret=%d", ret); + srs_assert(false); + + return ret; + } + + ptr = new SrsSharedPtrPayload(); + + // direct attach the data. + if (pheader) { + ptr->header.message_type = pheader->message_type; + ptr->header.payload_length = size; + ptr->header.perfer_cid = pheader->perfer_cid; + this->timestamp = pheader->timestamp; + this->stream_id = pheader->stream_id; + } + ptr->payload = payload; + ptr->size = size; + + // message can access it. + this->payload = ptr->payload; + this->size = ptr->size; + + return ret; +} + +int SrsSharedPtrMessage::count() +{ + srs_assert(ptr); + return ptr->shared_count; +} + +bool SrsSharedPtrMessage::check(int stream_id) +{ + // we donot use the complex basic header, + // ensure the basic header is 1bytes. + if (ptr->header.perfer_cid < 2) { + srs_info("change the chunk_id=%d to default=%d", + ptr->header.perfer_cid, RTMP_CID_ProtocolControl); + ptr->header.perfer_cid = RTMP_CID_ProtocolControl; + } + + // we assume that the stream_id in a group must be the same. + if (this->stream_id == stream_id) { + return true; + } + this->stream_id = stream_id; + + return false; +} + +bool SrsSharedPtrMessage::is_av() +{ + return ptr->header.message_type == RTMP_MSG_AudioMessage + || ptr->header.message_type == RTMP_MSG_VideoMessage; +} + +bool SrsSharedPtrMessage::is_audio() +{ + return ptr->header.message_type == RTMP_MSG_AudioMessage; +} + +bool SrsSharedPtrMessage::is_video() +{ + return ptr->header.message_type == RTMP_MSG_VideoMessage; +} + +int SrsSharedPtrMessage::chunk_header(char* cache, int nb_cache, bool c0) +{ + if (c0) { + return srs_chunk_header_c0( + ptr->header.perfer_cid, timestamp, ptr->header.payload_length, + ptr->header.message_type, stream_id, + cache, nb_cache); + } else { + return srs_chunk_header_c3( + ptr->header.perfer_cid, timestamp, + cache, nb_cache); + } +} + +SrsSharedPtrMessage* SrsSharedPtrMessage::copy() +{ + srs_assert(ptr); + + SrsSharedPtrMessage* copy = new SrsSharedPtrMessage(); + + copy->ptr = ptr; + ptr->shared_count++; + + copy->timestamp = timestamp; + copy->stream_id = stream_id; + copy->payload = ptr->payload; + copy->size = ptr->size; + + return copy; +} + +SrsPacket::SrsPacket() +{ +} + +SrsPacket::~SrsPacket() +{ +} + +int SrsPacket::encode(int& psize, char*& ppayload) +{ + int ret = ERROR_SUCCESS; + + int size = get_size(); + char* payload = NULL; + + SrsStream stream; + + if (size > 0) { + payload = new char[size]; + + if ((ret = stream.initialize(payload, size)) != ERROR_SUCCESS) { + srs_error("initialize the stream failed. ret=%d", ret); + srs_freep(payload); + return ret; + } + } + + if ((ret = encode_packet(&stream)) != ERROR_SUCCESS) { + srs_error("encode the packet failed. ret=%d", ret); + srs_freep(payload); + return ret; + } + + psize = size; + ppayload = payload; + srs_verbose("encode the packet success. size=%d", size); + + return ret; +} + +int SrsPacket::decode(SrsStream* stream) +{ + int ret = ERROR_SUCCESS; + + srs_assert(stream != NULL); + + ret = ERROR_SYSTEM_PACKET_INVALID; + srs_error("current packet is not support to decode. ret=%d", ret); + + return ret; +} + +int SrsPacket::get_prefer_cid() +{ + return 0; +} + +int SrsPacket::get_message_type() +{ + return 0; +} + +int SrsPacket::get_size() +{ + return 0; +} + +int SrsPacket::encode_packet(SrsStream* stream) { + int ret = ERROR_SUCCESS; + + srs_assert(stream != NULL); + + ret = ERROR_SYSTEM_PACKET_INVALID; + srs_error("current packet is not support to encode. ret=%d", ret); + + return ret; } SrsProtocol::AckWindowSize::AckWindowSize() { - ack_window_size = acked_size = 0; + ack_window_size = 0; + acked_size = 0; } SrsProtocol::SrsProtocol(ISrsProtocolReaderWriter* io) { - in_buffer = new SrsBuffer(); + in_buffer = new SrsFastBuffer(); skt = io; - in_chunk_size = out_chunk_size = SRS_CONSTS_RTMP_PROTOCOL_CHUNK_SIZE; + in_chunk_size = SRS_CONSTS_RTMP_PROTOCOL_CHUNK_SIZE; + out_chunk_size = SRS_CONSTS_RTMP_PROTOCOL_CHUNK_SIZE; + + nb_out_iovs = SRS_CONSTS_IOVS_MAX; + out_iovs = (iovec*)malloc(sizeof(iovec) * nb_out_iovs); + // each chunk consumers atleast 2 iovs + srs_assert(nb_out_iovs >= 2); + + warned_c0c3_cache_dry = false; + auto_response_when_recv = true; + + cs_cache = NULL; + if (SRS_PERF_CHUNK_STREAM_CACHE > 0) { + cs_cache = new SrsChunkStream*[SRS_PERF_CHUNK_STREAM_CACHE]; + } + for (int cid = 0; cid < SRS_PERF_CHUNK_STREAM_CACHE; cid++) { + SrsChunkStream* cs = new SrsChunkStream(cid); + // set the perfer cid of chunk, + // which will copy to the message received. + cs->header.perfer_cid = cid; + + cs_cache[cid] = cs; + } } SrsProtocol::~SrsProtocol() @@ -420,9 +662,72 @@ SrsProtocol::~SrsProtocol() chunk_streams.clear(); } + if (true) { + std::vector::iterator it; + for (it = manual_response_queue.begin(); it != manual_response_queue.end(); ++it) { + SrsPacket* pkt = *it; + srs_freep(pkt); + } + manual_response_queue.clear(); + } + srs_freep(in_buffer); + + // alloc by malloc, use free directly. + if (out_iovs) { + free(out_iovs); + out_iovs = NULL; + } + + // free all chunk stream cache. + for (int i = 0; i < SRS_PERF_CHUNK_STREAM_CACHE; i++) { + SrsChunkStream* cs = cs_cache[i]; + srs_freep(cs); + } + srs_freep(cs_cache); +} + +void SrsProtocol::set_auto_response(bool v) +{ + auto_response_when_recv = v; +} + +int SrsProtocol::manual_response_flush() +{ + int ret = ERROR_SUCCESS; + + if (manual_response_queue.empty()) { + return ret; + } + + std::vector::iterator it; + for (it = manual_response_queue.begin(); it != manual_response_queue.end();) { + SrsPacket* pkt = *it; + + // erase this packet, the send api always free it. + it = manual_response_queue.erase(it); + + // use underlayer api to send, donot flush again. + if ((ret = do_send_and_free_packet(pkt, 0)) != ERROR_SUCCESS) { + return ret; + } + } + + return ret; +} + +#ifdef SRS_PERF_MERGED_READ +void SrsProtocol::set_merge_read(bool v, IMergeReadHandler* handler) +{ + in_buffer->set_merge_read(v, handler); } +void SrsProtocol::set_recv_buffer(int buffer_size) +{ + in_buffer->set_buffer(buffer_size); +} +#endif + void SrsProtocol::set_recv_timeout(int64_t timeout_us) { return skt->set_recv_timeout(timeout_us); @@ -453,14 +758,14 @@ int64_t SrsProtocol::get_send_bytes() return skt->get_send_bytes(); } -int SrsProtocol::recv_message(SrsMessage** pmsg) +int SrsProtocol::recv_message(SrsCommonMessage** pmsg) { *pmsg = NULL; int ret = ERROR_SUCCESS; while (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; if ((ret = recv_interlaced_message(&msg)) != ERROR_SUCCESS) { if (ret != ERROR_SOCKET_TIMEOUT && !srs_is_client_gracefully_close(ret)) { @@ -499,7 +804,7 @@ int SrsProtocol::recv_message(SrsMessage** pmsg) return ret; } -int SrsProtocol::decode_message(SrsMessage* msg, SrsPacket** ppacket) +int SrsProtocol::decode_message(SrsCommonMessage* msg, SrsPacket** ppacket) { *ppacket = NULL; @@ -532,134 +837,286 @@ int SrsProtocol::decode_message(SrsMessage* msg, SrsPacket** ppacket) return ret; } -int SrsProtocol::do_send_message(SrsMessage* msg, SrsPacket* packet) +int SrsProtocol::do_send_messages(SrsSharedPtrMessage** msgs, int nb_msgs) { int ret = ERROR_SUCCESS; - // always not NULL msg. - srs_assert(msg); +#ifdef SRS_PERF_COMPLEX_SEND + int iov_index = 0; + iovec* iovs = out_iovs + iov_index; - // we donot use the complex basic header, - // ensure the basic header is 1bytes. - if (msg->header.perfer_cid < 2) { - srs_warn("change the chunk_id=%d to default=%d", msg->header.perfer_cid, RTMP_CID_ProtocolControl); - msg->header.perfer_cid = RTMP_CID_ProtocolControl; - } + int c0c3_cache_index = 0; + char* c0c3_cache = out_c0c3_caches + c0c3_cache_index; - // p set to current write position, - // it's ok when payload is NULL and size is 0. - char* p = msg->payload; - // to directly set the field. - char* pp = NULL; + // try to send use the c0c3 header cache, + // if cache is consumed, try another loop. + for (int i = 0; i < nb_msgs; i++) { + SrsSharedPtrMessage* msg = msgs[i]; + + if (!msg) { + continue; + } - // always write the header event payload is empty. - do { - // generate the header. - char* pheader = out_header_cache; + // ignore empty message. + if (!msg->payload || msg->size <= 0) { + srs_info("ignore empty message."); + continue; + } + + // p set to current write position, + // it's ok when payload is NULL and size is 0. + char* p = msg->payload; + char* pend = msg->payload + msg->size; - if (p == msg->payload) { - // write new chunk stream header, fmt is 0 - *pheader++ = 0x00 | (msg->header.perfer_cid & 0x3F); + // always write the header event payload is empty. + while (p < pend) { + // always has header + int nb_cache = SRS_CONSTS_C0C3_HEADERS_MAX - c0c3_cache_index; + int nbh = msg->chunk_header(c0c3_cache, nb_cache, p == msg->payload); + srs_assert(nbh > 0); - // chunk message header, 11 bytes - // timestamp, 3bytes, big-endian - u_int32_t timestamp = (u_int32_t)msg->header.timestamp; - if (timestamp < RTMP_EXTENDED_TIMESTAMP) { - pp = (char*)×tamp; - *pheader++ = pp[2]; - *pheader++ = pp[1]; - *pheader++ = pp[0]; - } else { - *pheader++ = 0xFF; - *pheader++ = 0xFF; - *pheader++ = 0xFF; + // header iov + iovs[0].iov_base = c0c3_cache; + iovs[0].iov_len = nbh; + + // payload iov + int payload_size = srs_min(out_chunk_size, pend - p); + iovs[1].iov_base = p; + iovs[1].iov_len = payload_size; + + // consume sendout bytes. + p += payload_size; + + // realloc the iovs if exceed, + // for we donot know how many messges maybe to send entirely, + // we just alloc the iovs, it's ok. + if (iov_index >= nb_out_iovs - 2) { + srs_warn("resize iovs %d => %d, max_msgs=%d", + nb_out_iovs, nb_out_iovs + SRS_CONSTS_IOVS_MAX, + SRS_PERF_MW_MSGS); + + nb_out_iovs += SRS_CONSTS_IOVS_MAX; + int realloc_size = sizeof(iovec) * nb_out_iovs; + out_iovs = (iovec*)realloc(out_iovs, realloc_size); } - // message_length, 3bytes, big-endian - pp = (char*)&msg->header.payload_length; - *pheader++ = pp[2]; - *pheader++ = pp[1]; - *pheader++ = pp[0]; + // to next pair of iovs + iov_index += 2; + iovs = out_iovs + iov_index; + + // to next c0c3 header cache + c0c3_cache_index += nbh; + c0c3_cache = out_c0c3_caches + c0c3_cache_index; + + // the cache header should never be realloc again, + // for the ptr is set to iovs, so we just warn user to set larger + // and use another loop to send again. + int c0c3_left = SRS_CONSTS_C0C3_HEADERS_MAX - c0c3_cache_index; + if (c0c3_left < SRS_CONSTS_RTMP_MAX_FMT0_HEADER_SIZE) { + // only warn once for a connection. + if (!warned_c0c3_cache_dry) { + srs_warn("c0c3 cache header too small, recoment to %d", + SRS_CONSTS_C0C3_HEADERS_MAX + SRS_CONSTS_RTMP_MAX_FMT0_HEADER_SIZE); + warned_c0c3_cache_dry = true; + } + + // when c0c3 cache dry, + // sendout all messages and reset the cache, then send again. + if ((ret = do_iovs_send(out_iovs, iov_index)) != ERROR_SUCCESS) { + return ret; + } + + // reset caches, while these cache ensure + // atleast we can sendout a chunk. + iov_index = 0; + iovs = out_iovs + iov_index; + + c0c3_cache_index = 0; + c0c3_cache = out_c0c3_caches + c0c3_cache_index; + } + } + } + + // maybe the iovs already sendout when c0c3 cache dry, + // so just ignore when no iovs to send. + if (iov_index <= 0) { + return ret; + } + srs_info("mw %d msgs in %d iovs, max_msgs=%d, nb_out_iovs=%d", + nb_msgs, iov_index, SRS_PERF_MW_MSGS, nb_out_iovs); + + return do_iovs_send(out_iovs, iov_index); +#else + // try to send use the c0c3 header cache, + // if cache is consumed, try another loop. + for (int i = 0; i < nb_msgs; i++) { + SrsSharedPtrMessage* msg = msgs[i]; + + if (!msg) { + continue; + } + + // ignore empty message. + if (!msg->payload || msg->size <= 0) { + srs_info("ignore empty message."); + continue; + } + + // p set to current write position, + // it's ok when payload is NULL and size is 0. + char* p = msg->payload; + char* pend = msg->payload + msg->size; + + // always write the header event payload is empty. + while (p < pend) { + // for simple send, send each chunk one by one + iovec* iovs = out_iovs; + char* c0c3_cache = out_c0c3_caches; + int nb_cache = SRS_CONSTS_C0C3_HEADERS_MAX; + + // always has header + int nbh = msg->chunk_header(c0c3_cache, nb_cache, p == msg->payload); + srs_assert(nbh > 0); - // message_type, 1bytes - *pheader++ = msg->header.message_type; + // header iov + iovs[0].iov_base = c0c3_cache; + iovs[0].iov_len = nbh; - // message_length, 3bytes, little-endian - pp = (char*)&msg->header.stream_id; - *pheader++ = pp[0]; - *pheader++ = pp[1]; - *pheader++ = pp[2]; - *pheader++ = pp[3]; + // payload iov + int payload_size = srs_min(out_chunk_size, pend - p); + iovs[1].iov_base = p; + iovs[1].iov_len = payload_size; - // chunk extended timestamp header, 0 or 4 bytes, big-endian - if(timestamp >= RTMP_EXTENDED_TIMESTAMP) { - pp = (char*)×tamp; - *pheader++ = pp[3]; - *pheader++ = pp[2]; - *pheader++ = pp[1]; - *pheader++ = pp[0]; + // consume sendout bytes. + p += payload_size; + + if ((ret = skt->writev(iovs, 2, NULL)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("send packet with writev failed. ret=%d", ret); + } + return ret; + } + } + } + + return ret; +#endif +} + +int SrsProtocol::do_iovs_send(iovec* iovs, int size) +{ + int ret = ERROR_SUCCESS; + + // the limits of writev iovs. + // for srs-librtmp, @see https://github.com/simple-rtmp-server/srs/issues/213 +#ifndef _WIN32 + static int limits = sysconf(_SC_IOV_MAX); +#else + static int limits = 1024; +#endif + + // send in a time. + if (size < limits) { + if ((ret = skt->writev(iovs, size, NULL)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("send with writev failed. ret=%d", ret); } + return ret; + } + return ret; + } + + // send in multiple times. + int cur_iov = 0; + while (cur_iov < size) { + int cur_count = srs_min(limits, size - cur_iov); + if ((ret = skt->writev(iovs + cur_iov, cur_count, NULL)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("send with writev failed. ret=%d", ret); + } + return ret; + } + cur_iov += cur_count; + } + + return ret; +} + +int SrsProtocol::do_send_and_free_packet(SrsPacket* packet, int stream_id) +{ + int ret = ERROR_SUCCESS; + + srs_assert(packet); + SrsAutoFree(SrsPacket, packet); + + int size = 0; + char* payload = NULL; + if ((ret = packet->encode(size, payload)) != ERROR_SUCCESS) { + srs_error("encode RTMP packet to bytes oriented RTMP message failed. ret=%d", ret); + return ret; + } + + // encode packet to payload and size. + if (size <= 0 || payload == NULL) { + srs_warn("packet is empty, ignore empty message."); + return ret; + } + + // to message + SrsMessageHeader header; + header.payload_length = size; + header.message_type = packet->get_message_type(); + header.stream_id = stream_id; + header.perfer_cid = packet->get_prefer_cid(); + + ret = do_simple_send(&header, payload, size); + srs_freep(payload); + if (ret == ERROR_SUCCESS) { + ret = on_send_packet(&header, packet); + } + + return ret; +} + +int SrsProtocol::do_simple_send(SrsMessageHeader* mh, char* payload, int size) +{ + int ret = ERROR_SUCCESS; + + // we directly send out the packet, + // use very simple algorithm, not very fast, + // but it's ok. + char* p = payload; + char* end = p + size; + char c0c3[SRS_CONSTS_RTMP_MAX_FMT0_HEADER_SIZE]; + while (p < end) { + int nbh = 0; + if (p == payload) { + nbh = srs_chunk_header_c0( + mh->perfer_cid, mh->timestamp, mh->payload_length, + mh->message_type, mh->stream_id, + c0c3, sizeof(c0c3)); } else { - // write no message header chunk stream, fmt is 3 - // @remark, if perfer_cid > 0x3F, that is, use 2B/3B chunk header, - // SRS will rollback to 1B chunk header. - *pheader++ = 0xC0 | (msg->header.perfer_cid & 0x3F); - - // chunk extended timestamp header, 0 or 4 bytes, big-endian - // 6.1.3. Extended Timestamp - // This field is transmitted only when the normal time stamp in the - // chunk message header is set to 0x00ffffff. If normal time stamp is - // set to any value less than 0x00ffffff, this field MUST NOT be - // present. This field MUST NOT be present if the timestamp field is not - // present. Type 3 chunks MUST NOT have this field. - // adobe changed for Type3 chunk: - // FMLE always sendout the extended-timestamp, - // must send the extended-timestamp to FMS, - // must send the extended-timestamp to flash-player. - // @see: ngx_rtmp_prepare_message - // @see: http://blog.csdn.net/win_lin/article/details/13363699 - u_int32_t timestamp = (u_int32_t)msg->header.timestamp; - if (timestamp >= RTMP_EXTENDED_TIMESTAMP) { - pp = (char*)×tamp; - *pheader++ = pp[3]; - *pheader++ = pp[2]; - *pheader++ = pp[1]; - *pheader++ = pp[0]; - } + nbh = srs_chunk_header_c3( + mh->perfer_cid, mh->timestamp, + c0c3, sizeof(c0c3)); } + srs_assert(nbh > 0);; - // sendout header and payload by writev. - // decrease the sys invoke count to get higher performance. - int payload_size = msg->size - (p - msg->payload); - payload_size = srs_min(payload_size, out_chunk_size); - - // always has header - int header_size = pheader - out_header_cache; - srs_assert(header_size > 0); + iovec iovs[2]; + iovs[0].iov_base = c0c3; + iovs[0].iov_len = nbh; - // send by writev - iovec iov[2]; - iov[0].iov_base = out_header_cache; - iov[0].iov_len = header_size; - iov[1].iov_base = p; - iov[1].iov_len = payload_size; + int payload_size = srs_min(end - p, out_chunk_size); + iovs[1].iov_base = p; + iovs[1].iov_len = payload_size; + p += payload_size; - ssize_t nwrite; - if ((ret = skt->writev(iov, 2, &nwrite)) != ERROR_SUCCESS) { - srs_error("send with writev failed. ret=%d", ret); + if ((ret = skt->writev(iovs, 2, NULL)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("send packet with writev failed. ret=%d", ret); + } return ret; } - - // consume sendout bytes when not empty packet. - if (msg->payload && msg->size > 0) { - p += payload_size; - } - } while (p < msg->payload + msg->size); - - // only process the callback event when with packet - if (packet && (ret = on_send_packet(msg, packet)) != ERROR_SUCCESS) { - srs_error("hook the send message failed. ret=%d", ret); - return ret; } return ret; @@ -777,7 +1234,7 @@ int SrsProtocol::do_decode_message(SrsMessageHeader& header, SrsStream* stream, srs_info("decode the AMF0/AMF3 command(unpublish message)."); *ppacket = packet = new SrsFMLEStartPacket(); return packet->decode(stream); - } else if(command == RTMP_AMF0_DATA_SET_DATAFRAME || command == RTMP_AMF0_DATA_ON_METADATA) { + } else if(command == SRS_CONSTS_RTMP_SET_DATAFRAME || command == SRS_CONSTS_RTMP_ON_METADATA) { srs_info("decode the AMF0/AMF3 data(onMetaData message)."); *ppacket = packet = new SrsOnMetaDataPacket(); return packet->decode(stream); @@ -832,16 +1289,51 @@ int SrsProtocol::do_decode_message(SrsMessageHeader& header, SrsStream* stream, return ret; } -int SrsProtocol::send_and_free_message(SrsMessage* msg, int stream_id) +int SrsProtocol::send_and_free_message(SrsSharedPtrMessage* msg, int stream_id) { - if (msg) { - msg->header.stream_id = stream_id; + return send_and_free_messages(&msg, 1, stream_id); +} + +int SrsProtocol::send_and_free_messages(SrsSharedPtrMessage** msgs, int nb_msgs, int stream_id) +{ + // always not NULL msg. + srs_assert(msgs); + srs_assert(nb_msgs > 0); + + // update the stream id in header. + for (int i = 0; i < nb_msgs; i++) { + SrsSharedPtrMessage* msg = msgs[i]; + + if (!msg) { + continue; + } + + // check perfer cid and stream, + // when one msg stream id is ok, ignore left. + if (msg->check(stream_id)) { + break; + } } // donot use the auto free to free the msg, // for performance issue. - int ret = do_send_message(msg, NULL); - srs_freep(msg); + int ret = do_send_messages(msgs, nb_msgs); + + for (int i = 0; i < nb_msgs; i++) { + SrsSharedPtrMessage* msg = msgs[i]; + srs_freep(msg); + } + + // donot flush when send failed + if (ret != ERROR_SUCCESS) { + return ret; + } + + // flush messages in manual queue + if ((ret = manual_response_flush()) != ERROR_SUCCESS) { + return ret; + } + return ret; } @@ -849,90 +1341,67 @@ int SrsProtocol::send_and_free_packet(SrsPacket* packet, int stream_id) { int ret = ERROR_SUCCESS; - srs_assert(packet); - SrsAutoFree(SrsPacket, packet); - - int size = 0; - char* payload = NULL; - if ((ret = packet->encode(size, payload)) != ERROR_SUCCESS) { - srs_error("encode RTMP packet to bytes oriented RTMP message failed. ret=%d", ret); + if ((ret = do_send_and_free_packet(packet, stream_id)) != ERROR_SUCCESS) { return ret; } - // encode packet to payload and size. - if (size <= 0 || payload == NULL) { - srs_warn("packet is empty, ignore empty message."); + // flush messages in manual queue + if ((ret = manual_response_flush()) != ERROR_SUCCESS) { return ret; } - // to message - SrsMessage* msg = new SrsCommonMessage(); - - msg->payload = payload; - msg->size = size; - - msg->header.payload_length = size; - msg->header.message_type = packet->get_message_type(); - msg->header.stream_id = stream_id; - msg->header.perfer_cid = packet->get_prefer_cid(); - - // donot use the auto free to free the msg, - // for performance issue. - ret = do_send_message(msg, packet); - srs_freep(msg); - return ret; } -int SrsProtocol::recv_interlaced_message(SrsMessage** pmsg) +int SrsProtocol::recv_interlaced_message(SrsCommonMessage** pmsg) { int ret = ERROR_SUCCESS; // chunk stream basic header. char fmt = 0; int cid = 0; - int bh_size = 0; - if ((ret = read_basic_header(fmt, cid, bh_size)) != ERROR_SUCCESS) { + if ((ret = read_basic_header(fmt, cid)) != ERROR_SUCCESS) { if (ret != ERROR_SOCKET_TIMEOUT && !srs_is_client_gracefully_close(ret)) { srs_error("read basic header failed. ret=%d", ret); } return ret; } - srs_verbose("read basic header success. fmt=%d, cid=%d, bh_size=%d", fmt, cid, bh_size); + srs_verbose("read basic header success. fmt=%d, cid=%d", fmt, cid); - // once we got the chunk message header, - // that is there is a real message in cache, - // increase the timeout to got it. - // For example, in the play loop, we set timeout to 100ms, - // when we got a chunk header, we should increase the timeout, - // or we maybe timeout and disconnect the client. - int64_t timeout_us = skt->get_recv_timeout(); - if (!skt->is_never_timeout(timeout_us)) { - int64_t pkt_timeout_us = srs_max(timeout_us, SRS_MIN_RECV_TIMEOUT_US); - skt->set_recv_timeout(pkt_timeout_us); - srs_verbose("change recv timeout_us " - "from %"PRId64" to %"PRId64"", timeout_us, pkt_timeout_us); - } + // the cid must not negative. + srs_assert(cid >= 0); // get the cached chunk stream. SrsChunkStream* chunk = NULL; - if (chunk_streams.find(cid) == chunk_streams.end()) { - chunk = chunk_streams[cid] = new SrsChunkStream(cid); - // set the perfer cid of chunk, - // which will copy to the message received. - chunk->header.perfer_cid = cid; - srs_verbose("cache new chunk stream: fmt=%d, cid=%d", fmt, cid); - } else { - chunk = chunk_streams[cid]; + // use chunk stream cache to get the chunk info. + // @see https://github.com/simple-rtmp-server/srs/issues/249 + if (cid < SRS_PERF_CHUNK_STREAM_CACHE) { + // chunk stream cache hit. + srs_verbose("cs-cache hit, cid=%d", cid); + // already init, use it direclty + chunk = cs_cache[cid]; srs_verbose("cached chunk stream: fmt=%d, cid=%d, size=%d, message(type=%d, size=%d, time=%"PRId64", sid=%d)", chunk->fmt, chunk->cid, (chunk->msg? chunk->msg->size : 0), chunk->header.message_type, chunk->header.payload_length, chunk->header.timestamp, chunk->header.stream_id); + } else { + // chunk stream cache miss, use map. + if (chunk_streams.find(cid) == chunk_streams.end()) { + chunk = chunk_streams[cid] = new SrsChunkStream(cid); + // set the perfer cid of chunk, + // which will copy to the message received. + chunk->header.perfer_cid = cid; + srs_verbose("cache new chunk stream: fmt=%d, cid=%d", fmt, cid); + } else { + chunk = chunk_streams[cid]; + srs_verbose("cached chunk stream: fmt=%d, cid=%d, size=%d, message(type=%d, size=%d, time=%"PRId64", sid=%d)", + chunk->fmt, chunk->cid, (chunk->msg? chunk->msg->size : 0), chunk->header.message_type, chunk->header.payload_length, + chunk->header.timestamp, chunk->header.stream_id); + } } // chunk stream message header - int mh_size = 0; - if ((ret = read_message_header(chunk, fmt, bh_size, mh_size)) != ERROR_SUCCESS) { + if ((ret = read_message_header(chunk, fmt)) != ERROR_SUCCESS) { if (ret != ERROR_SOCKET_TIMEOUT && !srs_is_client_gracefully_close(ret)) { srs_error("read message header failed. ret=%d", ret); } @@ -944,32 +1413,25 @@ int SrsProtocol::recv_interlaced_message(SrsMessage** pmsg) chunk->header.payload_length, chunk->header.timestamp, chunk->header.stream_id); // read msg payload from chunk stream. - SrsMessage* msg = NULL; - int payload_size = 0; - if ((ret = read_message_payload(chunk, bh_size, mh_size, payload_size, &msg)) != ERROR_SUCCESS) { + SrsCommonMessage* msg = NULL; + if ((ret = read_message_payload(chunk, &msg)) != ERROR_SUCCESS) { if (ret != ERROR_SOCKET_TIMEOUT && !srs_is_client_gracefully_close(ret)) { srs_error("read message payload failed. ret=%d", ret); } return ret; } - // reset the recv timeout - if (!skt->is_never_timeout(timeout_us)) { - skt->set_recv_timeout(timeout_us); - srs_verbose("reset recv timeout_us to %"PRId64"", timeout_us); - } - // not got an entire RTMP message, try next chunk. if (!msg) { - srs_verbose("get partial message success. chunk_payload_size=%d, size=%d, message(type=%d, size=%d, time=%"PRId64", sid=%d)", - payload_size, (msg? msg->size : (chunk->msg? chunk->msg->size : 0)), chunk->header.message_type, chunk->header.payload_length, + srs_verbose("get partial message success. size=%d, message(type=%d, size=%d, time=%"PRId64", sid=%d)", + (msg? msg->size : (chunk->msg? chunk->msg->size : 0)), chunk->header.message_type, chunk->header.payload_length, chunk->header.timestamp, chunk->header.stream_id); return ret; } *pmsg = msg; - srs_info("get entire message success. chunk_payload_size=%d, size=%d, message(type=%d, size=%d, time=%"PRId64", sid=%d)", - payload_size, (msg? msg->size : (chunk->msg? chunk->msg->size : 0)), chunk->header.message_type, chunk->header.payload_length, + srs_info("get entire message success. size=%d, message(type=%d, size=%d, time=%"PRId64", sid=%d)", + (msg? msg->size : (chunk->msg? chunk->msg->size : 0)), chunk->header.message_type, chunk->header.payload_length, chunk->header.timestamp, chunk->header.stream_id); return ret; @@ -1019,59 +1481,52 @@ int SrsProtocol::recv_interlaced_message(SrsMessage** pmsg) * Chunk stream IDs with values 64-319 could be represented by both 2- * byte version and 3-byte version of this field. */ -int SrsProtocol::read_basic_header(char& fmt, int& cid, int& bh_size) +int SrsProtocol::read_basic_header(char& fmt, int& cid) { int ret = ERROR_SUCCESS; - int required_size = 1; - if ((ret = in_buffer->grow(skt, required_size)) != ERROR_SUCCESS) { + if ((ret = in_buffer->grow(skt, 1)) != ERROR_SUCCESS) { if (ret != ERROR_SOCKET_TIMEOUT && !srs_is_client_gracefully_close(ret)) { - srs_error("read 1bytes basic header failed. required_size=%d, ret=%d", required_size, ret); + srs_error("read 1bytes basic header failed. required_size=%d, ret=%d", 1, ret); } return ret; } - char* p = in_buffer->bytes(); - - fmt = (*p >> 6) & 0x03; - cid = *p & 0x3f; - bh_size = 1; + fmt = in_buffer->read_1byte(); + cid = fmt & 0x3f; + fmt = (fmt >> 6) & 0x03; // 2-63, 1B chunk header if (cid > 1) { - srs_verbose("%dbytes basic header parsed. fmt=%d, cid=%d", bh_size, fmt, cid); + srs_verbose("basic header parsed. fmt=%d, cid=%d", fmt, cid); return ret; } // 64-319, 2B chunk header if (cid == 0) { - required_size = 2; - if ((ret = in_buffer->grow(skt, required_size)) != ERROR_SUCCESS) { + if ((ret = in_buffer->grow(skt, 1)) != ERROR_SUCCESS) { if (ret != ERROR_SOCKET_TIMEOUT && !srs_is_client_gracefully_close(ret)) { - srs_error("read 2bytes basic header failed. required_size=%d, ret=%d", required_size, ret); + srs_error("read 2bytes basic header failed. required_size=%d, ret=%d", 1, ret); } return ret; } cid = 64; - cid += (u_int8_t)*(++p); - bh_size = 2; - srs_verbose("%dbytes basic header parsed. fmt=%d, cid=%d", bh_size, fmt, cid); + cid += (u_int8_t)in_buffer->read_1byte(); + srs_verbose("2bytes basic header parsed. fmt=%d, cid=%d", fmt, cid); // 64-65599, 3B chunk header } else if (cid == 1) { - required_size = 3; - if ((ret = in_buffer->grow(skt, 3)) != ERROR_SUCCESS) { + if ((ret = in_buffer->grow(skt, 2)) != ERROR_SUCCESS) { if (ret != ERROR_SOCKET_TIMEOUT && !srs_is_client_gracefully_close(ret)) { - srs_error("read 3bytes basic header failed. required_size=%d, ret=%d", required_size, ret); + srs_error("read 3bytes basic header failed. required_size=%d, ret=%d", 2, ret); } return ret; } cid = 64; - cid += (u_int8_t)*(++p); - cid += ((u_int8_t)*(++p)) * 256; - bh_size = 3; - srs_verbose("%dbytes basic header parsed. fmt=%d, cid=%d", bh_size, fmt, cid); + cid += (u_int8_t)in_buffer->read_1byte(); + cid += ((u_int8_t)in_buffer->read_1byte()) * 256; + srs_verbose("3bytes basic header parsed. fmt=%d, cid=%d", fmt, cid); } else { srs_error("invalid path, impossible basic header."); srs_assert(false); @@ -1092,7 +1547,7 @@ int SrsProtocol::read_basic_header(char& fmt, int& cid, int& bh_size) * fmt=2, 0x8X * fmt=3, 0xCX */ -int SrsProtocol::read_message_header(SrsChunkStream* chunk, char fmt, int bh_size, int& mh_size) +int SrsProtocol::read_message_header(SrsChunkStream* chunk, char fmt) { int ret = ERROR_SUCCESS; @@ -1131,7 +1586,7 @@ int SrsProtocol::read_message_header(SrsChunkStream* chunk, char fmt, int bh_siz // 0x04 where: message_type=4(protocol control user-control message) // 0x00 0x06 where: event Ping(0x06) // 0x00 0x00 0x0d 0x0f where: event data 4bytes ping timestamp. - // @see: https://github.com/winlinvip/simple-rtmp-server/issues/98 + // @see: https://github.com/simple-rtmp-server/srs/issues/98 if (chunk->cid == RTMP_CID_ProtocolControl && fmt == RTMP_FMT_TYPE1) { srs_warn("accept cid=2, fmt=1 to make librtmp happy."); } else { @@ -1160,17 +1615,15 @@ int SrsProtocol::read_message_header(SrsChunkStream* chunk, char fmt, int bh_siz // read message header from socket to buffer. static char mh_sizes[] = {11, 7, 3, 0}; - mh_size = mh_sizes[(int)fmt]; + int mh_size = mh_sizes[(int)fmt]; srs_verbose("calc chunk message header size. fmt=%d, mh_size=%d", fmt, mh_size); - int required_size = bh_size + mh_size; - if ((ret = in_buffer->grow(skt, required_size)) != ERROR_SUCCESS) { + if (mh_size > 0 && (ret = in_buffer->grow(skt, mh_size)) != ERROR_SUCCESS) { if (ret != ERROR_SOCKET_TIMEOUT && !srs_is_client_gracefully_close(ret)) { - srs_error("read %dbytes message header failed. required_size=%d, ret=%d", mh_size, required_size, ret); + srs_error("read %dbytes message header failed. ret=%d", mh_size, ret); } return ret; } - char* p = in_buffer->bytes() + bh_size; /** * parse the message header. @@ -1186,6 +1639,8 @@ int SrsProtocol::read_message_header(SrsChunkStream* chunk, char fmt, int bh_siz */ // see also: ngx_rtmp_recv if (fmt <= RTMP_FMT_TYPE2) { + char* p = in_buffer->read_slice(mh_size); + char* pp = (char*)&chunk->header.timestamp_delta; pp[2] = *p++; pp[1] = *p++; @@ -1282,14 +1737,16 @@ int SrsProtocol::read_message_header(SrsChunkStream* chunk, char fmt, int bh_siz // read extended-timestamp if (chunk->extended_timestamp) { mh_size += 4; - required_size = bh_size + mh_size; srs_verbose("read header ext time. fmt=%d, ext_time=%d, mh_size=%d", fmt, chunk->extended_timestamp, mh_size); - if ((ret = in_buffer->grow(skt, required_size)) != ERROR_SUCCESS) { + if ((ret = in_buffer->grow(skt, 4)) != ERROR_SUCCESS) { if (ret != ERROR_SOCKET_TIMEOUT && !srs_is_client_gracefully_close(ret)) { - srs_error("read %dbytes message header failed. required_size=%d, ret=%d", mh_size, required_size, ret); + srs_error("read %dbytes message header failed. required_size=%d, ret=%d", mh_size, 4, ret); } return ret; } + // the ptr to the slice maybe invalid when grow() + // reset the p to get 4bytes slice. + char* p = in_buffer->read_slice(4); u_int32_t timestamp = 0x00; char* pp = (char*)×tamp; @@ -1299,7 +1756,7 @@ int SrsProtocol::read_message_header(SrsChunkStream* chunk, char fmt, int bh_siz pp[0] = *p++; // always use 31bits timestamp, for some server may use 32bits extended timestamp. - // @see https://github.com/winlinvip/simple-rtmp-server/issues/111 + // @see https://github.com/simple-rtmp-server/srs/issues/111 timestamp &= 0x7fffffff; /** @@ -1319,7 +1776,7 @@ int SrsProtocol::read_message_header(SrsChunkStream* chunk, char fmt, int bh_siz * @remark, srs always send the extended-timestamp, to keep simple, * and compatible with adobe products. */ - u_int32_t chunk_timestamp = chunk->header.timestamp; + u_int32_t chunk_timestamp = (u_int32_t)chunk->header.timestamp; /** * if chunk_timestamp<=0, the chunk previous packet has no extended-timestamp, @@ -1331,6 +1788,7 @@ int SrsProtocol::read_message_header(SrsChunkStream* chunk, char fmt, int bh_siz */ if (!is_first_chunk_of_msg && chunk_timestamp > 0 && chunk_timestamp != timestamp) { mh_size -= 4; + in_buffer->skip(-4); srs_info("no 4bytes extended timestamp in the continued chunk"); } else { chunk->header.timestamp = timestamp; @@ -1373,15 +1831,12 @@ int SrsProtocol::read_message_header(SrsChunkStream* chunk, char fmt, int bh_siz return ret; } -int SrsProtocol::read_message_payload(SrsChunkStream* chunk, int bh_size, int mh_size, int& payload_size, SrsMessage** pmsg) +int SrsProtocol::read_message_payload(SrsChunkStream* chunk, SrsCommonMessage** pmsg) { int ret = ERROR_SUCCESS; // empty message if (chunk->header.payload_length <= 0) { - // need erase the header in buffer. - in_buffer->erase(bh_size + mh_size); - srs_trace("get an empty RTMP " "message(type=%d, size=%d, time=%"PRId64", sid=%d)", chunk->header.message_type, chunk->header.payload_length, chunk->header.timestamp, chunk->header.stream_id); @@ -1394,7 +1849,7 @@ int SrsProtocol::read_message_payload(SrsChunkStream* chunk, int bh_size, int mh srs_assert(chunk->header.payload_length > 0); // the chunk payload size. - payload_size = chunk->header.payload_length - chunk->msg->size; + int payload_size = chunk->header.payload_length - chunk->msg->size; payload_size = srs_min(payload_size, in_chunk_size); srs_verbose("chunk payload size is %d, message_size=%d, received_size=%d, in_chunk_size=%d", payload_size, chunk->header.payload_length, chunk->msg->size, in_chunk_size); @@ -1402,23 +1857,20 @@ int SrsProtocol::read_message_payload(SrsChunkStream* chunk, int bh_size, int mh // create msg payload if not initialized if (!chunk->msg->payload) { chunk->msg->payload = new char[chunk->header.payload_length]; - memset(chunk->msg->payload, 0, chunk->header.payload_length); - srs_verbose("create empty payload for RTMP message. size=%d", chunk->header.payload_length); + srs_verbose("create payload for RTMP message. size=%d", chunk->header.payload_length); } // read payload to buffer - int required_size = bh_size + mh_size + payload_size; - if ((ret = in_buffer->grow(skt, required_size)) != ERROR_SUCCESS) { + if ((ret = in_buffer->grow(skt, payload_size)) != ERROR_SUCCESS) { if (ret != ERROR_SOCKET_TIMEOUT && !srs_is_client_gracefully_close(ret)) { - srs_error("read payload failed. required_size=%d, ret=%d", required_size, ret); + srs_error("read payload failed. required_size=%d, ret=%d", payload_size, ret); } return ret; } - memcpy(chunk->msg->payload + chunk->msg->size, in_buffer->bytes() + bh_size + mh_size, payload_size); - in_buffer->erase(bh_size + mh_size + payload_size); + memcpy(chunk->msg->payload + chunk->msg->size, in_buffer->read_slice(payload_size), payload_size); chunk->msg->size += payload_size; - srs_verbose("chunk payload read completed. bh_size=%d, mh_size=%d, payload_size=%d", bh_size, mh_size, payload_size); + srs_verbose("chunk payload read completed. payload_size=%d", payload_size); // got entire RTMP message? if (chunk->header.payload_length == chunk->msg->size) { @@ -1438,7 +1890,7 @@ int SrsProtocol::read_message_payload(SrsChunkStream* chunk, int bh_size, int mh return ret; } -int SrsProtocol::on_recv_message(SrsMessage* msg) +int SrsProtocol::on_recv_message(SrsCommonMessage* msg) { int ret = ERROR_SUCCESS; @@ -1480,7 +1932,7 @@ int SrsProtocol::on_recv_message(SrsMessage* msg) if (pkt->ackowledgement_window_size > 0) { in_ack_size.ack_window_size = pkt->ackowledgement_window_size; - // @remakr, we ignore this message, for user noneed to care. + // @remark, we ignore this message, for user noneed to care. // but it's important for dev, for client/server will block if required // ack msg not arrived. srs_info("set ack window size to %d", pkt->ackowledgement_window_size); @@ -1495,19 +1947,19 @@ int SrsProtocol::on_recv_message(SrsMessage* msg) // for some server, the actual chunk size can greater than the max value(65536), // so we just warning the invalid chunk size, and actually use it is ok, - // @see: https://github.com/winlinvip/simple-rtmp-server/issues/160 + // @see: https://github.com/simple-rtmp-server/srs/issues/160 if (pkt->chunk_size < SRS_CONSTS_RTMP_MIN_CHUNK_SIZE || pkt->chunk_size > SRS_CONSTS_RTMP_MAX_CHUNK_SIZE) { srs_warn("accept chunk size %d, but should in [%d, %d], " - "@see: https://github.com/winlinvip/simple-rtmp-server/issues/160", + "@see: https://github.com/simple-rtmp-server/srs/issues/160", pkt->chunk_size, SRS_CONSTS_RTMP_MIN_CHUNK_SIZE, SRS_CONSTS_RTMP_MAX_CHUNK_SIZE); } - + in_chunk_size = pkt->chunk_size; - srs_trace("input chunk size to %d", pkt->chunk_size); + break; } case RTMP_MSG_UserControlMessage: { @@ -1531,14 +1983,16 @@ int SrsProtocol::on_recv_message(SrsMessage* msg) return ret; } -int SrsProtocol::on_send_packet(SrsMessage* msg, SrsPacket* packet) +int SrsProtocol::on_send_packet(SrsMessageHeader* mh, SrsPacket* packet) { int ret = ERROR_SUCCESS; - // should never be raw bytes oriented RTMP message. - srs_assert(packet); + // ignore raw bytes oriented RTMP message. + if (packet == NULL) { + return ret; + } - switch (msg->header.message_type) { + switch (mh->message_type) { case RTMP_MSG_SetChunkSize: { SrsSetChunkSizePacket* pkt = dynamic_cast(packet); srs_assert(pkt != NULL); @@ -1585,8 +2039,17 @@ int SrsProtocol::response_acknowledgement_message() int ret = ERROR_SUCCESS; SrsAcknowledgementPacket* pkt = new SrsAcknowledgementPacket(); - in_ack_size.acked_size = pkt->sequence_number = skt->get_recv_bytes(); - if ((ret = send_and_free_packet(pkt, 0)) != ERROR_SUCCESS) { + in_ack_size.acked_size = skt->get_recv_bytes(); + pkt->sequence_number = (int32_t)in_ack_size.acked_size; + + // cache the message and use flush to send. + if (!auto_response_when_recv) { + manual_response_queue.push_back(pkt); + return ret; + } + + // use underlayer api to send, donot flush again. + if ((ret = do_send_and_free_packet(pkt, 0)) != ERROR_SUCCESS) { srs_error("send acknowledgement failed. ret=%d", ret); return ret; } @@ -1606,7 +2069,14 @@ int SrsProtocol::response_ping_message(int32_t timestamp) pkt->event_type = SrcPCUCPingResponse; pkt->event_data = timestamp; - if ((ret = send_and_free_packet(pkt, 0)) != ERROR_SUCCESS) { + // cache the message and use flush to send. + if (!auto_response_when_recv) { + manual_response_queue.push_back(pkt); + return ret; + } + + // use underlayer api to send, donot flush again. + if ((ret = do_send_and_free_packet(pkt, 0)) != ERROR_SUCCESS) { srs_error("send ping response failed. ret=%d", ret); return ret; } @@ -1629,191 +2099,6 @@ SrsChunkStream::~SrsChunkStream() srs_freep(msg); } -SrsCommonMessage::SrsCommonMessage() -{ -} - -SrsCommonMessage::~SrsCommonMessage() -{ - srs_freep(payload); -} - -SrsSharedPtrMessage::__SrsSharedPtr::__SrsSharedPtr() -{ - payload = NULL; - size = 0; - shared_count = 0; -} - -SrsSharedPtrMessage::__SrsSharedPtr::~__SrsSharedPtr() -{ - srs_freep(payload); -} - -SrsSharedPtrMessage::SrsSharedPtrMessage() -{ - ptr = NULL; -} - -SrsSharedPtrMessage::~SrsSharedPtrMessage() -{ - if (ptr) { - if (ptr->shared_count == 0) { - srs_freep(ptr); - } else { - ptr->shared_count--; - } - } -} - -int SrsSharedPtrMessage::create(SrsMessage* msg) -{ - int ret = ERROR_SUCCESS; - - if ((ret = create(&msg->header, msg->payload, msg->size)) != ERROR_SUCCESS) { - return ret; - } - - // to prevent double free of payload: - // initialize already attach the payload of msg, - // detach the payload to transfer the owner to shared ptr. - msg->payload = NULL; - msg->size = 0; - - return ret; -} - -int SrsSharedPtrMessage::create(SrsMessageHeader* pheader, char* payload, int size) -{ - int ret = ERROR_SUCCESS; - - srs_assert(pheader != NULL); - if (ptr) { - ret = ERROR_SYSTEM_ASSERT_FAILED; - srs_error("should not set the payload twice. ret=%d", ret); - srs_assert(false); - - return ret; - } - - header = *pheader; - header.payload_length = size; - - ptr = new __SrsSharedPtr(); - - // direct attach the data. - ptr->payload = payload; - ptr->size = size; - - // message can access it. - SrsMessage::payload = ptr->payload; - SrsMessage::size = ptr->size; - - return ret; -} - -int SrsSharedPtrMessage::count() -{ - srs_assert(ptr); - return ptr->shared_count; -} - -SrsSharedPtrMessage* SrsSharedPtrMessage::copy() -{ - srs_assert(ptr); - - SrsSharedPtrMessage* copy = new SrsSharedPtrMessage(); - - copy->header = header; - - copy->ptr = ptr; - ptr->shared_count++; - - copy->payload = ptr->payload; - copy->size = ptr->size; - - return copy; -} - -SrsPacket::SrsPacket() -{ -} - -SrsPacket::~SrsPacket() -{ -} - -int SrsPacket::encode(int& psize, char*& ppayload) -{ - int ret = ERROR_SUCCESS; - - int size = get_size(); - char* payload = NULL; - - SrsStream stream; - - if (size > 0) { - payload = new char[size]; - - if ((ret = stream.initialize(payload, size)) != ERROR_SUCCESS) { - srs_error("initialize the stream failed. ret=%d", ret); - srs_freep(payload); - return ret; - } - } - - if ((ret = encode_packet(&stream)) != ERROR_SUCCESS) { - srs_error("encode the packet failed. ret=%d", ret); - srs_freep(payload); - return ret; - } - - psize = size; - ppayload = payload; - srs_verbose("encode the packet success. size=%d", size); - - return ret; -} - -int SrsPacket::decode(SrsStream* stream) -{ - int ret = ERROR_SUCCESS; - - srs_assert(stream != NULL); - - ret = ERROR_SYSTEM_PACKET_INVALID; - srs_error("current packet is not support to decode. ret=%d", ret); - - return ret; -} - -int SrsPacket::get_prefer_cid() -{ - return 0; -} - -int SrsPacket::get_message_type() -{ - return 0; -} - -int SrsPacket::get_size() -{ - return 0; -} - -int SrsPacket::encode_packet(SrsStream* stream) -{ - int ret = ERROR_SUCCESS; - - srs_assert(stream != NULL); - - ret = ERROR_SYSTEM_PACKET_INVALID; - srs_error("current packet is not support to encode. ret=%d", ret); - - return ret; -} - SrsConnectAppPacket::SrsConnectAppPacket() { command_name = RTMP_AMF0_COMMAND_CONNECT; @@ -1865,7 +2150,7 @@ int SrsConnectAppPacket::decode(SrsStream* stream) if (!stream->empty()) { srs_freep(args); - // see: https://github.com/winlinvip/simple-rtmp-server/issues/186 + // see: https://github.com/simple-rtmp-server/srs/issues/186 // the args maybe any amf0, for instance, a string. we should drop if not object. SrsAmf0Any* any = NULL; if ((ret = SrsAmf0Any::discovery(stream, &any)) != ERROR_SUCCESS) { @@ -3526,7 +3811,7 @@ int SrsSampleAccessPacket::encode_packet(SrsStream* stream) SrsOnMetaDataPacket::SrsOnMetaDataPacket() { - name = RTMP_AMF0_DATA_ON_METADATA; + name = SRS_CONSTS_RTMP_ON_METADATA; metadata = SrsAmf0Any::object(); } @@ -3545,7 +3830,7 @@ int SrsOnMetaDataPacket::decode(SrsStream* stream) } // ignore the @setDataFrame - if (name == RTMP_AMF0_DATA_SET_DATAFRAME) { + if (name == SRS_CONSTS_RTMP_SET_DATAFRAME) { if ((ret = srs_amf0_read_string(stream, name)) != ERROR_SUCCESS) { srs_error("decode metadata name failed. ret=%d", ret); return ret; diff --git a/trunk/src/rtmp/srs_protocol_stack.hpp b/trunk/src/protocol/srs_rtmp_stack.hpp similarity index 78% rename from trunk/src/rtmp/srs_protocol_stack.hpp rename to trunk/src/protocol/srs_rtmp_stack.hpp index 5a2ed265aa..2026f950ba 100644 --- a/trunk/src/rtmp/srs_protocol_stack.hpp +++ b/trunk/src/protocol/srs_rtmp_stack.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -25,27 +25,52 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define SRS_RTMP_PROTOCOL_STACK_HPP /* -#include +#include */ #include #include +#include #include +// for srs-librtmp, @see https://github.com/simple-rtmp-server/srs/issues/213 +#ifndef _WIN32 +#include +#endif + #include #include #include +#include class ISrsProtocolReaderWriter; -class SrsBuffer; +class SrsFastBuffer; class SrsPacket; class SrsStream; class SrsAmf0Object; class SrsAmf0Any; class SrsMessageHeader; -class SrsMessage; +class SrsCommonMessage; class SrsChunkStream; +class SrsSharedPtrMessage; +class IMergeReadHandler; + +/**************************************************************************** +***************************************************************************** +****************************************************************************/ +/** +* 6.1. Chunk Format +* Extended timestamp: 0 or 4 bytes +* This field MUST be sent when the normal timsestamp is set to +* 0xffffff, it MUST NOT be sent if the normal timestamp is set to +* anything else. So for values less than 0xffffff the normal +* timestamp field SHOULD be used in which case the extended timestamp +* MUST NOT be present. For values greater than or equal to 0xffffff +* the normal timestamp field MUST NOT be used and MUST be set to +* 0xffffff and the extended timestamp MUST be sent. +*/ +#define RTMP_EXTENDED_TIMESTAMP 0xFFFFFF /** * 4.1. Message Header @@ -56,7 +81,6 @@ class SrsMessageHeader /** * 3bytes. * Three-byte field that contains a timestamp delta of the message. - * The 4 bytes are packed in the big-endian order. * @remark, only used for decoding message from chunk stream. */ int32_t timestamp_delta; @@ -75,7 +99,7 @@ class SrsMessageHeader /** * 4bytes. * Four-byte field that identifies the stream of the message. These - * bytes are set in big-endian format. + * bytes are set in little-endian format. */ int32_t stream_id; @@ -127,15 +151,10 @@ class SrsMessageHeader /** * message is raw data RTMP message, bytes oriented, * protcol always recv RTMP message, and can send RTMP message or RTMP packet. -* the shared-ptr message is a special RTMP message, use ref-count for performance issue. -* -* @remark, never directly new SrsMessage, the constructor is protected, -* for in the SrsMessage, we never know whether we should free the message, -* for SrsCommonMessage, we should free the payload, -* while for SrsSharedPtrMessage, we should use ref-count to free it. -* so, use these two concrete message, SrsCommonMessage or SrsSharedPtrMessage instread. +* the common message is read from underlay protocol sdk. +* while the shared ptr message used to copy and send. */ -class SrsMessage +class SrsCommonMessage { // 4.1. Message Header public: @@ -149,16 +168,207 @@ class SrsMessage */ int size; /** - * the payload of message, the SrsMessage never know about the detail of payload, + * the payload of message, the SrsCommonMessage never know about the detail of payload, * user must use SrsProtocol.decode_message to get concrete packet. * @remark, not all message payload can be decoded to packet. for example, * video/audio packet use raw bytes, no video/audio packet. */ char* payload; -protected: - SrsMessage(); public: - virtual ~SrsMessage(); + SrsCommonMessage(); +public: + virtual ~SrsCommonMessage(); +}; + +/** +* the message header for shared ptr message. +* only the message for all msgs are same. +*/ +struct SrsSharedMessageHeader +{ + /** + * 3bytes. + * Three-byte field that represents the size of the payload in bytes. + * It is set in big-endian format. + */ + int32_t payload_length; + /** + * 1byte. + * One byte field to represent the message type. A range of type IDs + * (1-7) are reserved for protocol control messages. + */ + int8_t message_type; + /** + * get the perfered cid(chunk stream id) which sendout over. + * set at decoding, and canbe used for directly send message, + * for example, dispatch to all connections. + */ + int perfer_cid; +}; + +/** +* shared ptr message. +* for audio/video/data message that need less memory copy. +* and only for output. +* +* create first object by constructor and create(), +* use copy if need reference count message. +* +*/ +class SrsSharedPtrMessage +{ +// 4.1. Message Header +public: + // the header can shared, only set the timestamp and stream id. + // @see https://github.com/simple-rtmp-server/srs/issues/251 + //SrsSharedMessageHeader header; + /** + * Four-byte field that contains a timestamp of the message. + * The 4 bytes are packed in the big-endian order. + * @remark, used as calc timestamp when decode and encode time. + * @remark, we use 64bits for large time for jitter detect and hls. + */ + int64_t timestamp; + /** + * 4bytes. + * Four-byte field that identifies the stream of the message. These + * bytes are set in big-endian format. + */ + int32_t stream_id; +// 4.2. Message Payload +public: + /** + * current message parsed size, + * size <= header.payload_length + * for the payload maybe sent in multiple chunks. + */ + int size; + /** + * the payload of message, the SrsCommonMessage never know about the detail of payload, + * user must use SrsProtocol.decode_message to get concrete packet. + * @remark, not all message payload can be decoded to packet. for example, + * video/audio packet use raw bytes, no video/audio packet. + */ + char* payload; +private: + class SrsSharedPtrPayload + { + public: + // shared message header. + // @see https://github.com/simple-rtmp-server/srs/issues/251 + SrsSharedMessageHeader header; + // actual shared payload. + char* payload; + // size of payload. + int size; + // the reference count + int shared_count; + public: + SrsSharedPtrPayload(); + virtual ~SrsSharedPtrPayload(); + }; + SrsSharedPtrPayload* ptr; +public: + SrsSharedPtrMessage(); + virtual ~SrsSharedPtrMessage(); +public: + /** + * create shared ptr message, + * copy header, manage the payload of msg, + * set the payload to NULL to prevent double free. + * @remark payload of msg set to NULL if success. + */ + virtual int create(SrsCommonMessage* msg); + /** + * create shared ptr message, + * from the header and payload. + * @remark user should never free the payload. + * @param pheader, the header to copy to the message. NULL to ignore. + */ + virtual int create(SrsMessageHeader* pheader, char* payload, int size); + /** + * get current reference count. + * when this object created, count set to 0. + * if copy() this object, count increase 1. + * if this or copy deleted, free payload when count is 0, or count--. + * @remark, assert object is created. + */ + virtual int count(); + /** + * check perfer cid and stream id. + * @return whether stream id already set. + */ + virtual bool check(int stream_id); +public: + virtual bool is_av(); + virtual bool is_audio(); + virtual bool is_video(); +public: + /** + * generate the chunk header to cache. + * @return the size of header. + */ + virtual int chunk_header(char* cache, int nb_cache, bool c0); +public: + /** + * copy current shared ptr message, use ref-count. + * @remark, assert object is created. + */ + virtual SrsSharedPtrMessage* copy(); +}; + +/** + * the decoded message payload. + * @remark we seperate the packet from message, + * for the packet focus on logic and domain data, + * the message bind to the protocol and focus on protocol, such as header. + * we can merge the message and packet, using OOAD hierachy, packet extends from message, + * it's better for me to use components -- the message use the packet as payload. + */ +class SrsPacket +{ +public: + SrsPacket(); + virtual ~SrsPacket(); +public: + /** + * the subpacket can override this encode, + * for example, video and audio will directly set the payload withou memory copy, + * other packet which need to serialize/encode to bytes by override the + * get_size and encode_packet. + */ + virtual int encode(int& size, char*& payload); + // decode functions for concrete packet to override. +public: + /** + * subpacket must override to decode packet from stream. + * @remark never invoke the super.decode, it always failed. + */ + virtual int decode(SrsStream* stream); + // encode functions for concrete packet to override. +public: + /** + * the cid(chunk id) specifies the chunk to send data over. + * generally, each message perfer some cid, for example, + * all protocol control messages perfer RTMP_CID_ProtocolControl, + * SrsSetWindowAckSizePacket is protocol control message. + */ + virtual int get_prefer_cid(); + /** + * subpacket must override to provide the right message type. + * the message type set the RTMP message type in header. + */ + virtual int get_message_type(); +protected: + /** + * subpacket can override to calc the packet size. + */ + virtual int get_size(); + /** + * subpacket can override to encode the payload to stream. + * @remark never invoke the super.encode_packet, it always failed. + */ + virtual int encode_packet(SrsStream* stream); }; /** @@ -196,9 +406,15 @@ class SrsProtocol */ std::map chunk_streams; /** + * cache some frequently used chunk header. + * cs_cache, the chunk stream cache. + * @see https://github.com/simple-rtmp-server/srs/issues/249 + */ + SrsChunkStream** cs_cache; + /** * bytes buffer cache, recv from skt, provide services for stream. */ - SrsBuffer* in_buffer; + SrsFastBuffer* in_buffer; /** * input chunk size, default to 128, set by peer packet. */ @@ -207,25 +423,77 @@ class SrsProtocol * input ack size, when to send the acked packet. */ AckWindowSize in_ack_size; + /** + * whether auto response when recv messages. + * default to true for it's very easy to use the protocol stack. + * @see: https://github.com/simple-rtmp-server/srs/issues/217 + */ + bool auto_response_when_recv; + /** + * when not auto response message, manual flush the messages in queue. + */ + std::vector manual_response_queue; // peer out private: + /** + * cache for multiple messages send, + * initialize to iovec[SRS_CONSTS_IOVS_MAX] and realloc when consumed, + * it's ok to realloc the iovs cache, for all ptr is ok. + */ + iovec* out_iovs; + int nb_out_iovs; /** * output header cache. * used for type0, 11bytes(or 15bytes with extended timestamp) header. * or for type3, 1bytes(or 5bytes with extended timestamp) header. + * the c0c3 caches must use unit SRS_CONSTS_RTMP_MAX_FMT0_HEADER_SIZE bytes. + * + * @remark, the c0c3 cache cannot be realloc. */ - char out_header_cache[SRS_CONSTS_RTMP_MAX_FMT0_HEADER_SIZE]; + char out_c0c3_caches[SRS_CONSTS_C0C3_HEADERS_MAX]; + // whether warned user to increase the c0c3 header cache. + bool warned_c0c3_cache_dry; /** * output chunk size, default to 128, set by config. */ int32_t out_chunk_size; public: - /** - * use io to create the protocol stack, - * @param io, provides io interfaces, user must free it. - */ SrsProtocol(ISrsProtocolReaderWriter* io); virtual ~SrsProtocol(); +public: + /** + * set the auto response message when recv for protocol stack. + * @param v, whether auto response message when recv message. + * @see: https://github.com/simple-rtmp-server/srs/issues/217 + */ + virtual void set_auto_response(bool v); + /** + * flush for manual response when the auto response is disabled + * by set_auto_response(false), we default use auto response, so donot + * need to call this api(the protocol sdk will auto send message). + * @see the auto_response_when_recv and manual_response_queue. + */ + virtual int manual_response_flush(); +public: +#ifdef SRS_PERF_MERGED_READ + /** + * to improve read performance, merge some packets then read, + * when it on and read small bytes, we sleep to wait more data., + * that is, we merge some data to read together. + * @param v true to ename merged read. + * @param handler the handler when merge read is enabled. + * @see https://github.com/simple-rtmp-server/srs/issues/241 + */ + virtual void set_merge_read(bool v, IMergeReadHandler* handler); + /** + * create buffer with specifeid size. + * @param buffer the size of buffer. + * @remark when MR(SRS_PERF_MERGED_READ) disabled, always set to 8K. + * @remark when buffer changed, the previous ptr maybe invalid. + * @see https://github.com/simple-rtmp-server/srs/issues/241 + */ + virtual void set_recv_buffer(int buffer_size); +#endif public: /** * set/get the recv timeout in us. @@ -254,14 +522,14 @@ class SrsProtocol * never NULL if decode success. * @remark, drop message when msg is empty or payload length is empty. */ - virtual int recv_message(SrsMessage** pmsg); + virtual int recv_message(SrsCommonMessage** pmsg); /** * decode bytes oriented RTMP message to RTMP packet, * @param ppacket, output decoded packet, * always NULL if error, never NULL if success. * @return error when unknown packet, error when decode failed. */ - virtual int decode_message(SrsMessage* msg, SrsPacket** ppacket); + virtual int decode_message(SrsCommonMessage* msg, SrsPacket** ppacket); /** * send the RTMP message and always free it. * user must never free or use the msg after this method, @@ -269,7 +537,16 @@ class SrsProtocol * @param msg, the msg to send out, never be NULL. * @param stream_id, the stream id of packet to send over, 0 for control message. */ - virtual int send_and_free_message(SrsMessage* msg, int stream_id); + virtual int send_and_free_message(SrsSharedPtrMessage* msg, int stream_id); + /** + * send the RTMP message and always free it. + * user must never free or use the msg after this method, + * for it will always free the msg. + * @param msgs, the msgs to send out, never be NULL. + * @param nb_msgs, the size of msgs to send out. + * @param stream_id, the stream id of packet to send over, 0 for control message. + */ + virtual int send_and_free_messages(SrsSharedPtrMessage** msgs, int nb_msgs, int stream_id); /** * send the RTMP packet and always free it. * user must never free or use the packet after this method, @@ -295,7 +572,7 @@ class SrsProtocol * if need to set timeout, use set timeout of SrsProtocol. */ template - int expect_message(SrsMessage** pmsg, T** ppacket) + int expect_message(SrsCommonMessage** pmsg, T** ppacket) { *pmsg = NULL; *ppacket = NULL; @@ -303,7 +580,7 @@ class SrsProtocol int ret = ERROR_SUCCESS; while (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; if ((ret = recv_message(&msg)) != ERROR_SUCCESS) { if (ret != ERROR_SOCKET_TIMEOUT && !srs_is_client_gracefully_close(ret)) { srs_error("recv message failed. ret=%d", ret); @@ -339,10 +616,23 @@ class SrsProtocol } private: /** - * send out the message, donot free it, the caller must free the param msg. - * @param packet the packet of message, NULL for raw message. + * send out the messages, donot free it, + * the caller must free the param msgs. + */ + virtual int do_send_messages(SrsSharedPtrMessage** msgs, int nb_msgs); + /** + * send iovs. send multiple times if exceed limits. + */ + virtual int do_iovs_send(iovec* iovs, int size); + /** + * underlayer api for send and free packet. + */ + virtual int do_send_and_free_packet(SrsPacket* packet, int stream_id); + /** + * use simple algorithm to send the header and bytes. + * @remark, for do_send_and_free_packet to send. */ - virtual int do_send_message(SrsMessage* msg, SrsPacket* packet); + virtual int do_simple_send(SrsMessageHeader* mh, char* payload, int size); /** * imp for decode_message */ @@ -353,33 +643,30 @@ class SrsProtocol * return success and pmsg set to NULL if no entire message got, * return success and pmsg set to entire message if got one. */ - virtual int recv_interlaced_message(SrsMessage** pmsg); + virtual int recv_interlaced_message(SrsCommonMessage** pmsg); /** * read the chunk basic header(fmt, cid) from chunk stream. * user can discovery a SrsChunkStream by cid. - * @bh_size return the chunk basic header size, to remove the used bytes when finished. */ - virtual int read_basic_header(char& fmt, int& cid, int& bh_size); + virtual int read_basic_header(char& fmt, int& cid); /** * read the chunk message header(timestamp, payload_length, message_type, stream_id) * from chunk stream and save to SrsChunkStream. - * @mh_size return the chunk message header size, to remove the used bytes when finished. */ - virtual int read_message_header(SrsChunkStream* chunk, char fmt, int bh_size, int& mh_size); + virtual int read_message_header(SrsChunkStream* chunk, char fmt); /** * read the chunk payload, remove the used bytes in buffer, * if got entire message, set the pmsg. - * @payload_size read size in this roundtrip, generally a chunk size or left message size. */ - virtual int read_message_payload(SrsChunkStream* chunk, int bh_size, int mh_size, int& payload_size, SrsMessage** pmsg); + virtual int read_message_payload(SrsChunkStream* chunk, SrsCommonMessage** pmsg); /** * when recv message, update the context. */ - virtual int on_recv_message(SrsMessage* msg); + virtual int on_recv_message(SrsCommonMessage* msg); /** * when message sentout, update the context. */ - virtual int on_send_packet(SrsMessage* msg, SrsPacket* packet); + virtual int on_send_packet(SrsMessageHeader* mh, SrsPacket* packet); private: /** * auto response the ack message. @@ -419,7 +706,7 @@ class SrsChunkStream /** * partially read message. */ - SrsMessage* msg; + SrsCommonMessage* msg; /** * decoded msg count, to identify whether the chunk stream is fresh. */ @@ -429,129 +716,6 @@ class SrsChunkStream virtual ~SrsChunkStream(); }; -/** -* the common message used free the payload in common way. -*/ -class SrsCommonMessage : public SrsMessage -{ -public: - SrsCommonMessage(); - virtual ~SrsCommonMessage(); -}; - -/** -* shared ptr message. -* for audio/video/data message that need less memory copy. -* and only for output. -* -* create first object by constructor and create(), -* use copy if need reference count message. -* -* Usage: -* SrsSharedPtrMessage msg; -* -*/ -class SrsSharedPtrMessage : public SrsMessage -{ -private: - class __SrsSharedPtr - { - public: - char* payload; - int size; - int shared_count; - - __SrsSharedPtr(); - virtual ~__SrsSharedPtr(); - }; - __SrsSharedPtr* ptr; -public: - SrsSharedPtrMessage(); - virtual ~SrsSharedPtrMessage(); -public: - /** - * create shared ptr message, - * copy header, manage the payload of msg, - * set the payload to NULL to prevent double free. - * @remark payload of msg set to NULL if success. - */ - virtual int create(SrsMessage* msg); - /** - * create shared ptr message, - * from the header and payload. - * @remark user should never free the payload. - */ - virtual int create(SrsMessageHeader* pheader, char* payload, int size); - /** - * get current reference count. - * when this object created, count set to 0. - * if copy() this object, count increase 1. - * if this or copy deleted, free payload when count is 0, or count--. - * @remark, assert object is created. - */ - virtual int count(); -public: - /** - * copy current shared ptr message, use ref-count. - * @remark, assert object is created. - */ - virtual SrsSharedPtrMessage* copy(); -}; - -/** -* the decoded message payload. -* @remark we seperate the packet from message, -* for the packet focus on logic and domain data, -* the message bind to the protocol and focus on protocol, such as header. -* we can merge the message and packet, using OOAD hierachy, packet extends from message, -* it's better for me to use components -- the message use the packet as payload. -*/ -class SrsPacket -{ -public: - SrsPacket(); - virtual ~SrsPacket(); -public: - /** - * the subpacket can override this encode, - * for example, video and audio will directly set the payload withou memory copy, - * other packet which need to serialize/encode to bytes by override the - * get_size and encode_packet. - */ - virtual int encode(int& size, char*& payload); -// decode functions for concrete packet to override. -public: - /** - * subpacket must override to decode packet from stream. - * @remark never invoke the super.decode, it always failed. - */ - virtual int decode(SrsStream* stream); -// encode functions for concrete packet to override. -public: - /** - * the cid(chunk id) specifies the chunk to send data over. - * generally, each message perfer some cid, for example, - * all protocol control messages perfer RTMP_CID_ProtocolControl, - * SrsSetWindowAckSizePacket is protocol control message. - */ - virtual int get_prefer_cid(); - /** - * subpacket must override to provide the right message type. - * the message type set the RTMP message type in header. - */ - virtual int get_message_type(); -protected: - /** - * subpacket can override to calc the packet size. - */ - virtual int get_size(); - /** - * subpacket can override to encode the payload to stream. - * @remark never invoke the super.encode_packet, it always failed. - */ - virtual int encode_packet(SrsStream* stream); -}; - /** * 4.1.1. connect * The client sends the connect command to the server to request @@ -572,10 +736,12 @@ class SrsConnectAppPacket : public SrsPacket * Command information object which has the name-value pairs. * @remark: alloc in packet constructor, user can directly use it, * user should never alloc it again which will cause memory leak. + * @remark, never be NULL. */ SrsAmf0Object* command_object; /** * Any optional information + * @remark, optional, init to and maybe NULL. */ SrsAmf0Object* args; public: @@ -608,11 +774,13 @@ class SrsConnectAppResPacket : public SrsPacket double transaction_id; /** * Name-value pairs that describe the properties(fmsver etc.) of the connection. + * @remark, never be NULL. */ SrsAmf0Object* props; /** * Name-value pairs that describe the response from|the server. ‘code’, * ‘level’, ‘description’ are names of few among such information. + * @remark, never be NULL. */ SrsAmf0Object* info; public: @@ -650,10 +818,12 @@ class SrsCallPacket : public SrsPacket /** * If there exists any command info this * is set, else this is set to null type. + * @remark, optional, init to and maybe NULL. */ SrsAmf0Any* command_object; /** * Any optional arguments to be provided + * @remark, optional, init to and maybe NULL. */ SrsAmf0Any* arguments; public: @@ -686,10 +856,12 @@ class SrsCallResPacket : public SrsPacket double transaction_id; /** * If there exists any command info this is set, else this is set to null type. + * @remark, optional, init to and maybe NULL. */ SrsAmf0Any* command_object; /** * Response from the method that was called. + * @remark, optional, init to and maybe NULL. */ SrsAmf0Any* response; public: @@ -724,6 +896,7 @@ class SrsCreateStreamPacket : public SrsPacket double transaction_id; /** * If there exists any command info this is set, else this is set to null type. + * @remark, never be NULL, an AMF0 null instance. */ SrsAmf0Any* command_object; // null public: @@ -756,6 +929,7 @@ class SrsCreateStreamResPacket : public SrsPacket double transaction_id; /** * If there exists any command info this is set, else this is set to null type. + * @remark, never be NULL, an AMF0 null instance. */ SrsAmf0Any* command_object; // null /** @@ -793,6 +967,7 @@ class SrsCloseStreamPacket : public SrsPacket double transaction_id; /** * Command information object does not exist. Set to null type. + * @remark, never be NULL, an AMF0 null instance. */ SrsAmf0Any* command_object; // null public: @@ -819,6 +994,7 @@ class SrsFMLEStartPacket : public SrsPacket double transaction_id; /** * If there exists any command info this is set, else this is set to null type. + * @remark, never be NULL, an AMF0 null instance. */ SrsAmf0Any* command_object; // null /** @@ -859,10 +1035,12 @@ class SrsFMLEStartResPacket : public SrsPacket double transaction_id; /** * If there exists any command info this is set, else this is set to null type. + * @remark, never be NULL, an AMF0 null instance. */ SrsAmf0Any* command_object; // null /** * the optional args, set to undefined. + * @remark, never be NULL, an AMF0 undefined instance. */ SrsAmf0Any* args; // undefined public: @@ -900,6 +1078,7 @@ class SrsPublishPacket : public SrsPacket double transaction_id; /** * Command information object does not exist. Set to null type. + * @remark, never be NULL, an AMF0 null instance. */ SrsAmf0Any* command_object; // null /** @@ -952,6 +1131,7 @@ class SrsPausePacket : public SrsPacket double transaction_id; /** * Command information object does not exist. Set to null type. + * @remark, never be NULL, an AMF0 null instance. */ SrsAmf0Any* command_object; // null /** @@ -990,6 +1170,7 @@ class SrsPlayPacket : public SrsPacket double transaction_id; /** * Command information does not exist. Set to null type. + * @remark, never be NULL, an AMF0 null instance. */ SrsAmf0Any* command_object; // null /** @@ -1069,12 +1250,14 @@ class SrsPlayResPacket : public SrsPacket double transaction_id; /** * Command information does not exist. Set to null type. + * @remark, never be NULL, an AMF0 null instance. */ SrsAmf0Any* command_object; // null /** * If the play command is successful, the client receives OnStatus message from * server which is NetStream.Play.Start. If the specified stream is not found, * NetStream.Play.StreamNotFound is received. + * @remark, never be NULL, an AMF0 object instance. */ SrsAmf0Object* desc; public: @@ -1105,6 +1288,7 @@ class SrsOnBWDonePacket : public SrsPacket double transaction_id; /** * Command information does not exist. Set to null type. + * @remark, never be NULL, an AMF0 null instance. */ SrsAmf0Any* args; // null public: @@ -1121,7 +1305,7 @@ class SrsOnBWDonePacket : public SrsPacket /** * onStatus command, AMF0 Call -* @remark, user must set the stream_id by SrsMessage.set_packet(). +* @remark, user must set the stream_id by SrsCommonMessage.set_packet(). */ class SrsOnStatusCallPacket : public SrsPacket { @@ -1136,11 +1320,13 @@ class SrsOnStatusCallPacket : public SrsPacket double transaction_id; /** * Command information does not exist. Set to null type. + * @remark, never be NULL, an AMF0 null instance. */ SrsAmf0Any* args; // null /** * Name-value pairs that describe the response from the server. * ‘code’,‘level’, ‘description’ are names of few among such information. + * @remark, never be NULL, an AMF0 object instance. */ SrsAmf0Object* data; public: @@ -1176,11 +1362,13 @@ class SrsBandwidthPacket : public SrsPacket double transaction_id; /** * Command information does not exist. Set to null type. + * @remark, never be NULL, an AMF0 null instance. */ SrsAmf0Any* args; // null /** * Name-value pairs that describe the response from the server. * ‘code’,‘level’, ‘description’ are names of few among such information. + * @remark, never be NULL, an AMF0 object instance. */ SrsAmf0Object* data; public: @@ -1226,7 +1414,7 @@ class SrsBandwidthPacket : public SrsPacket /** * onStatus data, AMF0 Data -* @remark, user must set the stream_id by SrsMessage.set_packet(). +* @remark, user must set the stream_id by SrsCommonMessage.set_packet(). */ class SrsOnStatusDataPacket : public SrsPacket { @@ -1238,6 +1426,7 @@ class SrsOnStatusDataPacket : public SrsPacket /** * Name-value pairs that describe the response from the server. * ‘code’, are names of few among such information. + * @remark, never be NULL, an AMF0 object instance. */ SrsAmf0Object* data; public: @@ -1254,7 +1443,7 @@ class SrsOnStatusDataPacket : public SrsPacket /** * AMF0Data RtmpSampleAccess -* @remark, user must set the stream_id by SrsMessage.set_packet(). +* @remark, user must set the stream_id by SrsCommonMessage.set_packet(). */ class SrsSampleAccessPacket : public SrsPacket { @@ -1265,13 +1454,13 @@ class SrsSampleAccessPacket : public SrsPacket std::string command_name; /** * whether allow access the sample of video. - * @see: https://github.com/winlinvip/simple-rtmp-server/issues/49 + * @see: https://github.com/simple-rtmp-server/srs/issues/49 * @see: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/net/NetStream.html#videoSampleAccess */ bool video_sample_access; /** * whether allow access the sample of audio. - * @see: https://github.com/winlinvip/simple-rtmp-server/issues/49 + * @see: https://github.com/simple-rtmp-server/srs/issues/49 * @see: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/net/NetStream.html#audioSampleAccess */ bool audio_sample_access; @@ -1301,6 +1490,7 @@ class SrsOnMetaDataPacket : public SrsPacket std::string name; /** * Metadata of stream. + * @remark, never be NULL, an AMF0 object instance. */ SrsAmf0Object* metadata; public: diff --git a/trunk/src/protocol/srs_rtmp_utility.cpp b/trunk/src/protocol/srs_rtmp_utility.cpp new file mode 100644 index 0000000000..2ac6b75ddf --- /dev/null +++ b/trunk/src/protocol/srs_rtmp_utility.cpp @@ -0,0 +1,348 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include + +#include +using namespace std; + +#include +#include +#include +#include +#include + +void srs_discovery_tc_url( + string tcUrl, + string& schema, string& host, string& vhost, + string& app, string& port, std::string& param +) { + size_t pos = std::string::npos; + std::string url = tcUrl; + + if ((pos = url.find("://")) != std::string::npos) { + schema = url.substr(0, pos); + url = url.substr(schema.length() + 3); + srs_info("discovery schema=%s", schema.c_str()); + } + + if ((pos = url.find("/")) != std::string::npos) { + host = url.substr(0, pos); + url = url.substr(host.length() + 1); + srs_info("discovery host=%s", host.c_str()); + } + + port = SRS_CONSTS_RTMP_DEFAULT_PORT; + if ((pos = host.find(":")) != std::string::npos) { + port = host.substr(pos + 1); + host = host.substr(0, pos); + srs_info("discovery host=%s, port=%s", host.c_str(), port.c_str()); + } + + app = url; + vhost = host; + srs_vhost_resolve(vhost, app, param); +} + +void srs_vhost_resolve(string& vhost, string& app, string& param) +{ + // get original param + size_t pos = 0; + if ((pos = app.find("?")) != std::string::npos) { + param = app.substr(pos); + } + + // filter tcUrl + app = srs_string_replace(app, ",", "?"); + app = srs_string_replace(app, "...", "?"); + app = srs_string_replace(app, "&&", "?"); + app = srs_string_replace(app, "=", "?"); + + if ((pos = app.find("?")) == std::string::npos) { + return; + } + + std::string query = app.substr(pos + 1); + app = app.substr(0, pos); + + if ((pos = query.find("vhost?")) != std::string::npos) { + query = query.substr(pos + 6); + if (!query.empty()) { + vhost = query; + } + if ((pos = vhost.find("?")) != std::string::npos) { + vhost = vhost.substr(0, pos); + } + } +} + +void srs_random_generate(char* bytes, int size) +{ + static bool _random_initialized = false; + if (!_random_initialized) { + srand(0); + _random_initialized = true; + srs_trace("srand initialized the random."); + } + + for (int i = 0; i < size; i++) { + // the common value in [0x0f, 0xf0] + bytes[i] = 0x0f + (rand() % (256 - 0x0f - 0x0f)); + } +} + +string srs_generate_tc_url(string ip, string vhost, string app, string port, string param) +{ + string tcUrl = "rtmp://"; + + if (vhost == SRS_CONSTS_RTMP_DEFAULT_VHOST) { + tcUrl += ip; + } else { + tcUrl += vhost; + } + + if (port != SRS_CONSTS_RTMP_DEFAULT_PORT) { + tcUrl += ":"; + tcUrl += port; + } + + tcUrl += "/"; + tcUrl += app; + tcUrl += param; + + return tcUrl; +} + +/** +* compare the memory in bytes. +*/ +bool srs_bytes_equals(void* pa, void* pb, int size) +{ + u_int8_t* a = (u_int8_t*)pa; + u_int8_t* b = (u_int8_t*)pb; + + if (!a && !b) { + return true; + } + + if (!a || !b) { + return false; + } + + for(int i = 0; i < size; i++){ + if(a[i] != b[i]){ + return false; + } + } + + return true; +} + +int srs_chunk_header_c0( + int perfer_cid, u_int32_t timestamp, int32_t payload_length, + int8_t message_type, int32_t stream_id, + char* cache, int nb_cache +) +{ + // to directly set the field. + char* pp = NULL; + + // generate the header. + char* p = cache; + + // no header. + if (nb_cache < SRS_CONSTS_RTMP_MAX_FMT0_HEADER_SIZE) { + return 0; + } + + // write new chunk stream header, fmt is 0 + *p++ = 0x00 | (perfer_cid & 0x3F); + + // chunk message header, 11 bytes + // timestamp, 3bytes, big-endian + if (timestamp < RTMP_EXTENDED_TIMESTAMP) { + pp = (char*)×tamp; + *p++ = pp[2]; + *p++ = pp[1]; + *p++ = pp[0]; + } else { + *p++ = 0xFF; + *p++ = 0xFF; + *p++ = 0xFF; + } + + // message_length, 3bytes, big-endian + pp = (char*)&payload_length; + *p++ = pp[2]; + *p++ = pp[1]; + *p++ = pp[0]; + + // message_type, 1bytes + *p++ = message_type; + + // stream_id, 4bytes, little-endian + pp = (char*)&stream_id; + *p++ = pp[0]; + *p++ = pp[1]; + *p++ = pp[2]; + *p++ = pp[3]; + + // for c0 + // chunk extended timestamp header, 0 or 4 bytes, big-endian + // + // for c3: + // chunk extended timestamp header, 0 or 4 bytes, big-endian + // 6.1.3. Extended Timestamp + // This field is transmitted only when the normal time stamp in the + // chunk message header is set to 0x00ffffff. If normal time stamp is + // set to any value less than 0x00ffffff, this field MUST NOT be + // present. This field MUST NOT be present if the timestamp field is not + // present. Type 3 chunks MUST NOT have this field. + // adobe changed for Type3 chunk: + // FMLE always sendout the extended-timestamp, + // must send the extended-timestamp to FMS, + // must send the extended-timestamp to flash-player. + // @see: ngx_rtmp_prepare_message + // @see: http://blog.csdn.net/win_lin/article/details/13363699 + // TODO: FIXME: extract to outer. + if (timestamp >= RTMP_EXTENDED_TIMESTAMP) { + pp = (char*)×tamp; + *p++ = pp[3]; + *p++ = pp[2]; + *p++ = pp[1]; + *p++ = pp[0]; + } + + // always has header + return p - cache; +} + +int srs_chunk_header_c3( + int perfer_cid, u_int32_t timestamp, + char* cache, int nb_cache +) +{ + // to directly set the field. + char* pp = NULL; + + // generate the header. + char* p = cache; + + // no header. + if (nb_cache < SRS_CONSTS_RTMP_MAX_FMT3_HEADER_SIZE) { + return 0; + } + + // write no message header chunk stream, fmt is 3 + // @remark, if perfer_cid > 0x3F, that is, use 2B/3B chunk header, + // SRS will rollback to 1B chunk header. + *p++ = 0xC0 | (perfer_cid & 0x3F); + + // for c0 + // chunk extended timestamp header, 0 or 4 bytes, big-endian + // + // for c3: + // chunk extended timestamp header, 0 or 4 bytes, big-endian + // 6.1.3. Extended Timestamp + // This field is transmitted only when the normal time stamp in the + // chunk message header is set to 0x00ffffff. If normal time stamp is + // set to any value less than 0x00ffffff, this field MUST NOT be + // present. This field MUST NOT be present if the timestamp field is not + // present. Type 3 chunks MUST NOT have this field. + // adobe changed for Type3 chunk: + // FMLE always sendout the extended-timestamp, + // must send the extended-timestamp to FMS, + // must send the extended-timestamp to flash-player. + // @see: ngx_rtmp_prepare_message + // @see: http://blog.csdn.net/win_lin/article/details/13363699 + // TODO: FIXME: extract to outer. + if (timestamp >= RTMP_EXTENDED_TIMESTAMP) { + pp = (char*)×tamp; + *p++ = pp[3]; + *p++ = pp[2]; + *p++ = pp[1]; + *p++ = pp[0]; + } + + // always has header + return p - cache; +} + +int srs_do_rtmp_create_msg(char type, u_int32_t timestamp, char* data, int size, int stream_id, SrsSharedPtrMessage** ppmsg) +{ + int ret = ERROR_SUCCESS; + + *ppmsg = NULL; + SrsSharedPtrMessage* msg = NULL; + + if (type == SrsCodecFlvTagAudio) { + SrsMessageHeader header; + header.initialize_audio(size, timestamp, stream_id); + + msg = new SrsSharedPtrMessage(); + if ((ret = msg->create(&header, data, size)) != ERROR_SUCCESS) { + srs_freep(msg); + return ret; + } + } else if (type == SrsCodecFlvTagVideo) { + SrsMessageHeader header; + header.initialize_video(size, timestamp, stream_id); + + msg = new SrsSharedPtrMessage(); + if ((ret = msg->create(&header, data, size)) != ERROR_SUCCESS) { + srs_freep(msg); + return ret; + } + } else if (type == SrsCodecFlvTagScript) { + SrsMessageHeader header; + header.initialize_amf0_script(size, stream_id); + + msg = new SrsSharedPtrMessage(); + if ((ret = msg->create(&header, data, size)) != ERROR_SUCCESS) { + srs_freep(msg); + return ret; + } + } else { + ret = ERROR_STREAM_CASTER_FLV_TAG; + srs_error("rtmp unknown tag type=%#x. ret=%d", type, ret); + return ret; + } + + *ppmsg = msg; + + return ret; +} + +int srs_rtmp_create_msg(char type, u_int32_t timestamp, char* data, int size, int stream_id, SrsSharedPtrMessage** ppmsg) +{ + int ret = ERROR_SUCCESS; + + // only when failed, we must free the data. + if ((ret = srs_do_rtmp_create_msg(type, timestamp, data, size, stream_id, ppmsg)) != ERROR_SUCCESS) { + srs_freep(data); + return ret; + } + + return ret; +} + diff --git a/trunk/src/rtmp/srs_protocol_utility.hpp b/trunk/src/protocol/srs_rtmp_utility.hpp similarity index 73% rename from trunk/src/rtmp/srs_protocol_utility.hpp rename to trunk/src/protocol/srs_rtmp_utility.hpp index 359c4f26ee..0a63d8160e 100644 --- a/trunk/src/rtmp/srs_protocol_utility.hpp +++ b/trunk/src/protocol/srs_rtmp_utility.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -25,7 +25,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define SRS_RTMP_PROTOCOL_CONSTS_HPP /* -#include +#include */ #include @@ -33,6 +33,9 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include +class SrsMessageHeader; +class SrsSharedPtrMessage; + /** * parse the tcUrl, output the schema, host, vhost, app and port. * @param tcUrl, the input tcUrl, for example, @@ -85,5 +88,35 @@ extern std::string srs_generate_tc_url( */ extern bool srs_bytes_equals(void* pa, void* pb, int size); +/** +* generate the c0 chunk header for msg. +* @param cache, the cache to write header. +* @param nb_cache, the size of cache. +* @return the size of header. 0 if cache not enough. +*/ +extern int srs_chunk_header_c0( + int perfer_cid, u_int32_t timestamp, int32_t payload_length, + int8_t message_type, int32_t stream_id, + char* cache, int nb_cache +); + +/** +* generate the c3 chunk header for msg. +* @param cache, the cache to write header. +* @param nb_cache, the size of cache. +* @return the size of header. 0 if cache not enough. +*/ +extern int srs_chunk_header_c3( + int perfer_cid, u_int32_t timestamp, + char* cache, int nb_cache +); + +/** +* create shared ptr message from bytes. +* @param data the packet bytes. user should never free it. +* @param ppmsg output the shared ptr message. user should free it. +*/ +extern int srs_rtmp_create_msg(char type, u_int32_t timestamp, char* data, int size, int stream_id, SrsSharedPtrMessage** ppmsg); + #endif diff --git a/trunk/src/protocol/srs_rtsp_stack.cpp b/trunk/src/protocol/srs_rtsp_stack.cpp new file mode 100644 index 0000000000..1bc90ac938 --- /dev/null +++ b/trunk/src/protocol/srs_rtsp_stack.cpp @@ -0,0 +1,1184 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include + +#include +#include +using namespace std; + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef SRS_AUTO_STREAM_CASTER + +#define SRS_RTSP_BUFFER 4096 + +// get the status text of code. +string srs_generate_rtsp_status_text(int status) +{ + static std::map _status_map; + if (_status_map.empty()) { + _status_map[SRS_CONSTS_RTSP_Continue ] = SRS_CONSTS_RTSP_Continue_str ; + _status_map[SRS_CONSTS_RTSP_OK ] = SRS_CONSTS_RTSP_OK_str ; + _status_map[SRS_CONSTS_RTSP_Created ] = SRS_CONSTS_RTSP_Created_str ; + _status_map[SRS_CONSTS_RTSP_LowOnStorageSpace ] = SRS_CONSTS_RTSP_LowOnStorageSpace_str ; + _status_map[SRS_CONSTS_RTSP_MultipleChoices ] = SRS_CONSTS_RTSP_MultipleChoices_str ; + _status_map[SRS_CONSTS_RTSP_MovedPermanently ] = SRS_CONSTS_RTSP_MovedPermanently_str ; + _status_map[SRS_CONSTS_RTSP_MovedTemporarily ] = SRS_CONSTS_RTSP_MovedTemporarily_str ; + _status_map[SRS_CONSTS_RTSP_SeeOther ] = SRS_CONSTS_RTSP_SeeOther_str ; + _status_map[SRS_CONSTS_RTSP_NotModified ] = SRS_CONSTS_RTSP_NotModified_str ; + _status_map[SRS_CONSTS_RTSP_UseProxy ] = SRS_CONSTS_RTSP_UseProxy_str ; + _status_map[SRS_CONSTS_RTSP_BadRequest ] = SRS_CONSTS_RTSP_BadRequest_str ; + _status_map[SRS_CONSTS_RTSP_Unauthorized ] = SRS_CONSTS_RTSP_Unauthorized_str ; + _status_map[SRS_CONSTS_RTSP_PaymentRequired ] = SRS_CONSTS_RTSP_PaymentRequired_str ; + _status_map[SRS_CONSTS_RTSP_Forbidden ] = SRS_CONSTS_RTSP_Forbidden_str ; + _status_map[SRS_CONSTS_RTSP_NotFound ] = SRS_CONSTS_RTSP_NotFound_str ; + _status_map[SRS_CONSTS_RTSP_MethodNotAllowed ] = SRS_CONSTS_RTSP_MethodNotAllowed_str ; + _status_map[SRS_CONSTS_RTSP_NotAcceptable ] = SRS_CONSTS_RTSP_NotAcceptable_str ; + _status_map[SRS_CONSTS_RTSP_ProxyAuthenticationRequired ] = SRS_CONSTS_RTSP_ProxyAuthenticationRequired_str ; + _status_map[SRS_CONSTS_RTSP_RequestTimeout ] = SRS_CONSTS_RTSP_RequestTimeout_str ; + _status_map[SRS_CONSTS_RTSP_Gone ] = SRS_CONSTS_RTSP_Gone_str ; + _status_map[SRS_CONSTS_RTSP_LengthRequired ] = SRS_CONSTS_RTSP_LengthRequired_str ; + _status_map[SRS_CONSTS_RTSP_PreconditionFailed ] = SRS_CONSTS_RTSP_PreconditionFailed_str ; + _status_map[SRS_CONSTS_RTSP_RequestEntityTooLarge ] = SRS_CONSTS_RTSP_RequestEntityTooLarge_str ; + _status_map[SRS_CONSTS_RTSP_RequestURITooLarge ] = SRS_CONSTS_RTSP_RequestURITooLarge_str ; + _status_map[SRS_CONSTS_RTSP_UnsupportedMediaType ] = SRS_CONSTS_RTSP_UnsupportedMediaType_str ; + _status_map[SRS_CONSTS_RTSP_ParameterNotUnderstood ] = SRS_CONSTS_RTSP_ParameterNotUnderstood_str ; + _status_map[SRS_CONSTS_RTSP_ConferenceNotFound ] = SRS_CONSTS_RTSP_ConferenceNotFound_str ; + _status_map[SRS_CONSTS_RTSP_NotEnoughBandwidth ] = SRS_CONSTS_RTSP_NotEnoughBandwidth_str ; + _status_map[SRS_CONSTS_RTSP_SessionNotFound ] = SRS_CONSTS_RTSP_SessionNotFound_str ; + _status_map[SRS_CONSTS_RTSP_MethodNotValidInThisState ] = SRS_CONSTS_RTSP_MethodNotValidInThisState_str ; + _status_map[SRS_CONSTS_RTSP_HeaderFieldNotValidForResource ] = SRS_CONSTS_RTSP_HeaderFieldNotValidForResource_str ; + _status_map[SRS_CONSTS_RTSP_InvalidRange ] = SRS_CONSTS_RTSP_InvalidRange_str ; + _status_map[SRS_CONSTS_RTSP_ParameterIsReadOnly ] = SRS_CONSTS_RTSP_ParameterIsReadOnly_str ; + _status_map[SRS_CONSTS_RTSP_AggregateOperationNotAllowed ] = SRS_CONSTS_RTSP_AggregateOperationNotAllowed_str ; + _status_map[SRS_CONSTS_RTSP_OnlyAggregateOperationAllowed ] = SRS_CONSTS_RTSP_OnlyAggregateOperationAllowed_str ; + _status_map[SRS_CONSTS_RTSP_UnsupportedTransport ] = SRS_CONSTS_RTSP_UnsupportedTransport_str ; + _status_map[SRS_CONSTS_RTSP_DestinationUnreachable ] = SRS_CONSTS_RTSP_DestinationUnreachable_str ; + _status_map[SRS_CONSTS_RTSP_InternalServerError ] = SRS_CONSTS_RTSP_InternalServerError_str ; + _status_map[SRS_CONSTS_RTSP_NotImplemented ] = SRS_CONSTS_RTSP_NotImplemented_str ; + _status_map[SRS_CONSTS_RTSP_BadGateway ] = SRS_CONSTS_RTSP_BadGateway_str ; + _status_map[SRS_CONSTS_RTSP_ServiceUnavailable ] = SRS_CONSTS_RTSP_ServiceUnavailable_str ; + _status_map[SRS_CONSTS_RTSP_GatewayTimeout ] = SRS_CONSTS_RTSP_GatewayTimeout_str ; + _status_map[SRS_CONSTS_RTSP_RTSPVersionNotSupported ] = SRS_CONSTS_RTSP_RTSPVersionNotSupported_str ; + _status_map[SRS_CONSTS_RTSP_OptionNotSupported ] = SRS_CONSTS_RTSP_OptionNotSupported_str ; + } + + std::string status_text; + if (_status_map.find(status) == _status_map.end()) { + status_text = "Status Unknown"; + } else { + status_text = _status_map[status]; + } + + return status_text; +} + +std::string srs_generate_rtsp_method_str(SrsRtspMethod method) +{ + switch (method) { + case SrsRtspMethodDescribe: return SRS_METHOD_DESCRIBE; + case SrsRtspMethodAnnounce: return SRS_METHOD_ANNOUNCE; + case SrsRtspMethodGetParameter: return SRS_METHOD_GET_PARAMETER; + case SrsRtspMethodOptions: return SRS_METHOD_OPTIONS; + case SrsRtspMethodPause: return SRS_METHOD_PAUSE; + case SrsRtspMethodPlay: return SRS_METHOD_PLAY; + case SrsRtspMethodRecord: return SRS_METHOD_RECORD; + case SrsRtspMethodRedirect: return SRS_METHOD_REDIRECT; + case SrsRtspMethodSetup: return SRS_METHOD_SETUP; + case SrsRtspMethodSetParameter: return SRS_METHOD_SET_PARAMETER; + case SrsRtspMethodTeardown: return SRS_METHOD_TEARDOWN; + default: return "Unknown"; + } +} + +SrsRtpPacket::SrsRtpPacket() +{ + version = 2; + padding = 0; + extension = 0; + csrc_count = 0; + marker = 1; + + payload_type = 0; + sequence_number = 0; + timestamp = 0; + ssrc = 0; + + payload = new SrsSimpleBuffer(); + audio_samples = new SrsCodecSample(); + chunked = false; + completed = false; +} + +SrsRtpPacket::~SrsRtpPacket() +{ + srs_freep(payload); + srs_freep(audio_samples); +} + +void SrsRtpPacket::copy(SrsRtpPacket* src) +{ + version = src->version; + padding = src->padding; + extension = src->extension; + csrc_count = src->csrc_count; + marker = src->marker; + payload_type = src->payload_type; + sequence_number = src->sequence_number; + timestamp = src->timestamp; + ssrc = src->ssrc; + + chunked = src->chunked; + completed = src->completed; + audio_samples = new SrsCodecSample(); +} + +void SrsRtpPacket::reap(SrsRtpPacket* src) +{ + copy(src); + + srs_freep(payload); + payload = src->payload; + src->payload = NULL; + + srs_freep(audio_samples); + audio_samples = src->audio_samples; + src->audio_samples = NULL; +} + +int SrsRtpPacket::decode(SrsStream* stream) +{ + int ret = ERROR_SUCCESS; + + // 12bytes header + if (!stream->require(12)) { + ret = ERROR_RTP_HEADER_CORRUPT; + srs_error("rtsp: rtp header corrupt. ret=%d", ret); + return ret; + } + + int8_t vv = stream->read_1bytes(); + version = (vv >> 6) & 0x03; + padding = (vv >> 5) & 0x01; + extension = (vv >> 4) & 0x01; + csrc_count = vv & 0x0f; + + int8_t mv = stream->read_1bytes(); + marker = (mv >> 7) & 0x01; + payload_type = mv & 0x7f; + + sequence_number = stream->read_2bytes(); + timestamp = stream->read_4bytes(); + ssrc = stream->read_4bytes(); + + // TODO: FIXME: check sequence number. + + // video codec. + if (payload_type == 96) { + return decode_96(stream); + } else if (payload_type == 97) { + return decode_97(stream); + } + + return ret; +} + +int SrsRtpPacket::decode_97(SrsStream* stream) +{ + int ret = ERROR_SUCCESS; + + // atleast 2bytes content. + if (!stream->require(2)) { + ret = ERROR_RTP_TYPE97_CORRUPT; + srs_error("rtsp: rtp type97 corrupt. ret=%d", ret); + return ret; + } + + int8_t hasv = stream->read_1bytes(); + int8_t lasv = stream->read_1bytes(); + u_int16_t au_size = ((hasv << 5) & 0xE0) | ((lasv >> 3) & 0x1f); + + if (!stream->require(au_size)) { + ret = ERROR_RTP_TYPE97_CORRUPT; + srs_error("rtsp: rtp type97 au_size corrupt. ret=%d", ret); + return ret; + } + + int required_size = 0; + + // append left bytes to payload. + payload->append( + stream->data() + stream->pos() + au_size, + stream->size() - stream->pos() - au_size + ); + char* p = payload->bytes(); + + for (int i = 0; i < au_size; i += 2) { + hasv = stream->read_1bytes(); + lasv = stream->read_1bytes(); + + u_int16_t sample_size = ((hasv << 5) & 0xE0) | ((lasv >> 3) & 0x1f); + // TODO: FIXME: finger out how to parse the size of sample. + if (sample_size < 0x100 && stream->require(required_size + sample_size + 0x100)) { + sample_size = sample_size | 0x100; + } + + char* sample = p + required_size; + required_size += sample_size; + + if (!stream->require(required_size)) { + ret = ERROR_RTP_TYPE97_CORRUPT; + srs_error("rtsp: rtp type97 samples corrupt. ret=%d", ret); + return ret; + } + + if ((ret = audio_samples->add_sample_unit(sample, sample_size)) != ERROR_SUCCESS) { + srs_error("rtsp: rtp type97 add sample failed. ret=%d", ret); + return ret; + } + } + + // parsed ok. + completed = true; + + return ret; +} + +int SrsRtpPacket::decode_96(SrsStream* stream) +{ + int ret = ERROR_SUCCESS; + + // atleast 2bytes content. + if (!stream->require(2)) { + ret = ERROR_RTP_TYPE96_CORRUPT; + srs_error("rtsp: rtp type96 corrupt. ret=%d", ret); + return ret; + } + + // frame type + // 0... .... reserverd + // .11. .... NALU[0]&0x60 + // ...1 11.. FU indicator + // .... ..00 reserverd + int8_t ftv = stream->read_1bytes(); + int8_t nalu_0x60 = ftv & 0x60; + int8_t fu_indicator = ftv & 0x1c; + + // nri, whatever + // 10.. .... first chunk. + // 00.. .... continous chunk. + // 01.. .... last chunk. + // ...1 1111 NALU[0]&0x1f + int8_t nriv = stream->read_1bytes(); + bool first_chunk = (nriv & 0xC0) == 0x80; + bool last_chunk = (nriv & 0xC0) == 0x40; + bool contious_chunk = (nriv & 0xC0) == 0x00; + int8_t nalu_0x1f = nriv & 0x1f; + + // chunked, generate the first byte NALU. + if (fu_indicator == 0x1c && (first_chunk || last_chunk || contious_chunk)) { + chunked = true; + completed = last_chunk; + + // generate and append the first byte NALU. + if (first_chunk) { + int8_t nalu_byte0 = nalu_0x60 | nalu_0x1f; + payload->append((char*)&nalu_byte0, 1); + } + + payload->append(stream->data() + stream->pos(), stream->size() - stream->pos()); + return ret; + } + + // no chunked, append to payload. + stream->skip(-2); + payload->append(stream->data() + stream->pos(), stream->size() - stream->pos()); + completed = true; + + return ret; +} + +SrsRtspSdp::SrsRtspSdp() +{ + state = SrsRtspSdpStateOthers; +} + +SrsRtspSdp::~SrsRtspSdp() +{ +} + +int SrsRtspSdp::parse(string token) +{ + int ret = ERROR_SUCCESS; + + if (token.empty()) { + srs_info("rtsp: ignore empty token."); + return ret; + } + + size_t pos = string::npos; + + char* start = (char*)token.data(); + char* end = start + (int)token.length(); + char* p = start; + + // key, first 2bytes. + // v=0 + // o=- 0 0 IN IP4 127.0.0.1 + // s=No Name + // c=IN IP4 192.168.43.23 + // t=0 0 + // a=tool:libavformat 53.9.0 + // m=video 0 RTP/AVP 96 + // b=AS:850 + // a=rtpmap:96 H264/90000 + // a=fmtp:96 packetization-mode=1; sprop-parameter-sets=Z2QAKKzRwFAFu/8ALQAiEAAAAwAQAAADAwjxgxHg,aOmrLIs= + // a=control:streamid=0 + // m=audio 0 RTP/AVP 97 + // b=AS:49 + // a=rtpmap:97 MPEG4-GENERIC/44100/2 + // a=fmtp:97 profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3; config=139056E5A0 + // a=control:streamid=1 + char key = p[0]; + p += 2; + + // left bytes as attr string. + std::string attr_str; + if (end - p) { + attr_str.append(p, end - p); + } + + // parse the attributes from left bytes. + std::vector attrs; + while (p < end) { + // parse an attribute, split by SP. + char* pa = p; + for (; p < end && p[0] != SRS_RTSP_SP; p++) { + } + std::string attr; + if (p > pa) { + attr.append(pa, p - pa); + attrs.push_back(attr); + } + p++; + } + + // parse the first attr as desc, update the first elem for desc. + // for example, the value can be "tool", "AS", "rtpmap", "fmtp", "control" + std::string desc_key; + if (attrs.size() > 0) { + std::string attr = attrs.at(0); + if ((pos = attr.find(":")) != string::npos) { + desc_key = attr.substr(0, pos); + attr = attr.substr(pos + 1); + attr_str = attr_str.substr(pos + 1); + attrs[0] = attr; + } else { + desc_key = attr; + } + } + + // interpret the attribute according by key. + switch (key) { + case 'v': version = attr_str; break; + case 'o': + owner_username = (attrs.size() > 0)? attrs[0]:""; + owner_session_id = (attrs.size() > 1)? attrs[1]:""; + owner_session_version = (attrs.size() > 2)? attrs[2]:""; + owner_network_type = (attrs.size() > 3)? attrs[3]:""; + owner_address_type = (attrs.size() > 4)? attrs[4]:""; + owner_address = (attrs.size() > 5)? attrs[5]:""; + break; + case 's': session_name = attr_str; break; + case 'c': + connection_network_type = (attrs.size() > 0)? attrs[0]:""; + connection_address_type = (attrs.size() > 0)? attrs[0]:""; + connection_address = (attrs.size() > 0)? attrs[0]:""; + break; + case 'a': + if (desc_key == "tool") { + tool = attr_str; + } else if (desc_key == "rtpmap") { + if (state == SrsRtspSdpStateVideo) { + video_codec = (attrs.size() > 1)? attrs[1]:""; + if ((pos = video_codec.find("/")) != string::npos) { + video_sample_rate = video_codec.substr(pos + 1); + video_codec = video_codec.substr(0, pos); + } + } else if (state == SrsRtspSdpStateAudio) { + audio_codec = (attrs.size() > 1)? attrs[1]:""; + if ((pos = audio_codec.find("/")) != string::npos) { + audio_sample_rate = audio_codec.substr(pos + 1); + audio_codec = audio_codec.substr(0, pos); + } + if ((pos = audio_sample_rate.find("/")) != string::npos) { + audio_channel = audio_sample_rate.substr(pos + 1); + audio_sample_rate = audio_sample_rate.substr(0, pos); + } + } + } else if (desc_key == "fmtp") { + for (int i = 1; i < (int)attrs.size(); i++) { + std::string attr = attrs.at(i); + if ((ret = parse_fmtp_attribute(attr)) != ERROR_SUCCESS) { + srs_error("rtsp: parse fmtp failed, attr=%s. ret=%d", attr.c_str(), ret); + return ret; + } + } + } else if (desc_key == "control") { + for (int i = 0; i < (int)attrs.size(); i++) { + std::string attr = attrs.at(i); + if ((ret = parse_control_attribute(attr)) != ERROR_SUCCESS) { + srs_error("rtsp: parse control failed, attr=%s. ret=%d", attr.c_str(), ret); + return ret; + } + } + } + break; + case 'm': + if (desc_key == "video") { + state = SrsRtspSdpStateVideo; + video_port = (attrs.size() > 1)? attrs[1]:""; + video_protocol = (attrs.size() > 2)? attrs[2]:""; + video_transport_format = (attrs.size() > 3)? attrs[3]:""; + } else if (desc_key == "audio") { + state = SrsRtspSdpStateAudio; + audio_port = (attrs.size() > 1)? attrs[1]:""; + audio_protocol = (attrs.size() > 2)? attrs[2]:""; + audio_transport_format = (attrs.size() > 3)? attrs[3]:""; + } + break; + case 'b': + if (desc_key == "AS") { + if (state == SrsRtspSdpStateVideo) { + video_bandwidth_kbps = (attrs.size() > 0)? attrs[0]:""; + } else if (state == SrsRtspSdpStateAudio) { + audio_bandwidth_kbps = (attrs.size() > 0)? attrs[0]:""; + } + } + break; + case 't': + default: break; + } + + return ret; +} + +int SrsRtspSdp::parse_fmtp_attribute(string attr) +{ + int ret = ERROR_SUCCESS; + + size_t pos = string::npos; + std::string token = attr; + + while (!token.empty()) { + std::string item = token; + if ((pos = item.find(";")) != string::npos) { + item = token.substr(0, pos); + token = token.substr(pos + 1); + } else { + token = ""; + } + + std::string item_key = item, item_value; + if ((pos = item.find("=")) != string::npos) { + item_key = item.substr(0, pos); + item_value = item.substr(pos + 1); + } + + if (state == SrsRtspSdpStateVideo) { + if (item_key == "packetization-mode") { + video_packetization_mode = item_value; + } else if (item_key == "sprop-parameter-sets") { + video_sps = item_value; + if ((pos = video_sps.find(",")) != string::npos) { + video_pps = video_sps.substr(pos + 1); + video_sps = video_sps.substr(0, pos); + } + // decode the sps/pps by base64 + video_sps = base64_decode(video_sps); + video_pps = base64_decode(video_pps); + } + } else if (state == SrsRtspSdpStateAudio) { + if (item_key == "profile-level-id") { + audio_profile_level_id = item_value; + } else if (item_key == "mode") { + audio_mode = item_value; + } else if (item_key == "sizelength") { + audio_size_length = item_value; + } else if (item_key == "indexlength") { + audio_index_length = item_value; + } else if (item_key == "indexdeltalength") { + audio_index_delta_length = item_value; + } else if (item_key == "config") { + if (item_value.length() <= 0) { + ret = ERROR_RTSP_AUDIO_CONFIG; + srs_error("rtsp: audio config failed. ret=%d", ret); + return ret; + } + + char* tmp_sh = new char[item_value.length()]; + SrsAutoFree(char, tmp_sh); + int nb_tmp_sh = ff_hex_to_data((u_int8_t*)tmp_sh, item_value.c_str()); + srs_assert(nb_tmp_sh > 0); + audio_sh.append(tmp_sh, nb_tmp_sh); + } + } + } + + return ret; +} + +int SrsRtspSdp::parse_control_attribute(string attr) +{ + int ret = ERROR_SUCCESS; + + size_t pos = string::npos; + std::string token = attr; + + while (!token.empty()) { + std::string item = token; + if ((pos = item.find(";")) != string::npos) { + item = token.substr(0, pos); + token = token.substr(pos + 1); + } else { + token = ""; + } + + std::string item_key = item, item_value; + if ((pos = item.find("=")) != string::npos) { + item_key = item.substr(0, pos); + item_value = item.substr(pos + 1); + } + + if (state == SrsRtspSdpStateVideo) { + if (item_key == "streamid") { + video_stream_id = item_value; + } + } else if (state == SrsRtspSdpStateAudio) { + if (item_key == "streamid") { + audio_stream_id = item_value; + } + } + } + + return ret; +} + +string SrsRtspSdp::base64_decode(string value) +{ + if (value.empty()) { + return ""; + } + + int nb_output = (int)(value.length() * 2); + u_int8_t* output = new u_int8_t[nb_output]; + SrsAutoFree(u_int8_t, output); + + int ret = srs_av_base64_decode(output, (char*)value.c_str(), nb_output); + if (ret <= 0) { + return ""; + } + + std::string plaintext; + plaintext.append((char*)output, ret); + return plaintext; +} + +SrsRtspTransport::SrsRtspTransport() +{ + client_port_min = 0; + client_port_max = 0; +} + +SrsRtspTransport::~SrsRtspTransport() +{ +} + +int SrsRtspTransport::parse(string attr) +{ + int ret = ERROR_SUCCESS; + + size_t pos = string::npos; + std::string token = attr; + + while (!token.empty()) { + std::string item = token; + if ((pos = item.find(";")) != string::npos) { + item = token.substr(0, pos); + token = token.substr(pos + 1); + } else { + token = ""; + } + + std::string item_key = item, item_value; + if ((pos = item.find("=")) != string::npos) { + item_key = item.substr(0, pos); + item_value = item.substr(pos + 1); + } + + if (transport.empty()) { + transport = item_key; + if ((pos = transport.find("/")) != string::npos) { + profile = transport.substr(pos + 1); + transport = transport.substr(0, pos); + } + if ((pos = profile.find("/")) != string::npos) { + lower_transport = profile.substr(pos + 1); + profile = profile.substr(0, pos); + } + } + + if (item_key == "unicast" || item_key == "multicast") { + cast_type = item_key; + } else if (item_key == "mode") { + mode = item_value; + } else if (item_key == "client_port") { + std::string sport = item_value; + std::string eport = item_value; + if ((pos = eport.find("-")) != string::npos) { + sport = eport.substr(0, pos); + eport = eport.substr(pos + 1); + } + client_port_min = ::atoi(sport.c_str()); + client_port_max = ::atoi(eport.c_str()); + } + } + + return ret; +} + +SrsRtspRequest::SrsRtspRequest() +{ + seq = 0; + content_length = 0; + stream_id = 0; + sdp = NULL; + transport = NULL; +} + +SrsRtspRequest::~SrsRtspRequest() +{ + srs_freep(sdp); + srs_freep(transport); +} + +bool SrsRtspRequest::is_options() +{ + return method == SRS_METHOD_OPTIONS; +} + +bool SrsRtspRequest::is_announce() +{ + return method == SRS_METHOD_ANNOUNCE; +} + +bool SrsRtspRequest::is_setup() +{ + return method == SRS_METHOD_SETUP; +} + +bool SrsRtspRequest::is_record() +{ + return method == SRS_METHOD_RECORD; +} + +SrsRtspResponse::SrsRtspResponse(int cseq) +{ + seq = cseq; + status = SRS_CONSTS_RTSP_OK; +} + +SrsRtspResponse::~SrsRtspResponse() +{ +} + +int SrsRtspResponse::encode(stringstream& ss) +{ + int ret = ERROR_SUCCESS; + + // status line + ss << SRS_RTSP_VERSION << SRS_RTSP_SP + << status << SRS_RTSP_SP + << srs_generate_rtsp_status_text(status) << SRS_RTSP_CRLF; + + // cseq + ss << SRS_RTSP_TOKEN_CSEQ << ":" << SRS_RTSP_SP << seq << SRS_RTSP_CRLF; + + // others. + ss << "Cache-Control: no-store" << SRS_RTSP_CRLF + << "Pragma: no-cache" << SRS_RTSP_CRLF + << "Server: " << RTMP_SIG_SRS_SERVER << SRS_RTSP_CRLF; + + // session if specified. + if (!session.empty()) { + ss << SRS_RTSP_TOKEN_SESSION << ":" << session << SRS_RTSP_CRLF; + } + + if ((ret = encode_header(ss)) != ERROR_SUCCESS) { + srs_error("rtsp: encode header failed. ret=%d", ret); + return ret; + }; + + // header EOF. + ss << SRS_RTSP_CRLF; + + return ret; +} + +int SrsRtspResponse::encode_header(std::stringstream& ss) +{ + return ERROR_SUCCESS; +} + +SrsRtspOptionsResponse::SrsRtspOptionsResponse(int cseq) : SrsRtspResponse(cseq) +{ + methods = (SrsRtspMethod)(SrsRtspMethodDescribe | SrsRtspMethodOptions + | SrsRtspMethodPause | SrsRtspMethodPlay | SrsRtspMethodSetup | SrsRtspMethodTeardown + | SrsRtspMethodAnnounce | SrsRtspMethodRecord); +} + +SrsRtspOptionsResponse::~SrsRtspOptionsResponse() +{ +} + +int SrsRtspOptionsResponse::encode_header(stringstream& ss) +{ + SrsRtspMethod rtsp_methods[] = { + SrsRtspMethodDescribe, + SrsRtspMethodAnnounce, + SrsRtspMethodGetParameter, + SrsRtspMethodOptions, + SrsRtspMethodPause, + SrsRtspMethodPlay, + SrsRtspMethodRecord, + SrsRtspMethodRedirect, + SrsRtspMethodSetup, + SrsRtspMethodSetParameter, + SrsRtspMethodTeardown, + }; + + ss << SRS_RTSP_TOKEN_PUBLIC << ":" << SRS_RTSP_SP; + + bool appended = false; + int nb_methods = (int)(sizeof(rtsp_methods) / sizeof(SrsRtspMethod)); + for (int i = 0; i < nb_methods; i++) { + SrsRtspMethod method = rtsp_methods[i]; + if (((int)methods & (int)method) != (int)method) { + continue; + } + + if (appended) { + ss << ", "; + } + ss << srs_generate_rtsp_method_str(method); + appended = true; + } + ss << SRS_RTSP_CRLF; + + return ERROR_SUCCESS; +} + +SrsRtspSetupResponse::SrsRtspSetupResponse(int seq) : SrsRtspResponse(seq) +{ + local_port_min = 0; + local_port_max = 0; +} + +SrsRtspSetupResponse::~SrsRtspSetupResponse() +{ +} + +int SrsRtspSetupResponse::encode_header(stringstream& ss) +{ + ss << SRS_RTSP_TOKEN_SESSION << ":" << SRS_RTSP_SP << session << SRS_RTSP_CRLF; + ss << SRS_RTSP_TOKEN_TRANSPORT << ":" << SRS_RTSP_SP + << "RTP/AVP;unicast;client_port=" << client_port_min << "-" << client_port_max << ";" + << "server_port=" << local_port_min << "-" << local_port_max + << SRS_RTSP_CRLF; + return ERROR_SUCCESS; +} + +SrsRtspStack::SrsRtspStack(ISrsProtocolReaderWriter* s) +{ + buf = new SrsSimpleBuffer(); + skt = s; +} + +SrsRtspStack::~SrsRtspStack() +{ + srs_freep(buf); +} + +int SrsRtspStack::recv_message(SrsRtspRequest** preq) +{ + int ret = ERROR_SUCCESS; + + SrsRtspRequest* req = new SrsRtspRequest(); + if ((ret = do_recv_message(req)) != ERROR_SUCCESS) { + srs_freep(req); + return ret; + } + + *preq = req; + + return ret; +} + +int SrsRtspStack::send_message(SrsRtspResponse* res) +{ + int ret = ERROR_SUCCESS; + + std::stringstream ss; + // encode the message to string. + res->encode(ss); + + std::string str = ss.str(); + srs_assert(!str.empty()); + + if ((ret = skt->write((char*)str.c_str(), (int)str.length(), NULL)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("rtsp: send response failed. ret=%d", ret); + } + return ret; + } + srs_info("rtsp: send response ok"); + + return ret; +} + +int SrsRtspStack::do_recv_message(SrsRtspRequest* req) +{ + int ret = ERROR_SUCCESS; + + // parse request line. + if ((ret = recv_token_normal(req->method)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("rtsp: parse method failed. ret=%d", ret); + } + return ret; + } + + if ((ret = recv_token_normal(req->uri)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("rtsp: parse uri failed. ret=%d", ret); + } + return ret; + } + + if ((ret = recv_token_eof(req->version)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("rtsp: parse version failed. ret=%d", ret); + } + return ret; + } + + // parse headers. + for (;;) { + // parse the header name + std::string token; + if ((ret = recv_token_normal(token)) != ERROR_SUCCESS) { + if (ret == ERROR_RTSP_REQUEST_HEADER_EOF) { + ret = ERROR_SUCCESS; + srs_info("rtsp: message header parsed"); + break; + } + if (!srs_is_client_gracefully_close(ret)) { + srs_error("rtsp: parse token failed. ret=%d", ret); + } + return ret; + } + + // parse the header value according by header name + if (token == SRS_RTSP_TOKEN_CSEQ) { + std::string seq; + if ((ret = recv_token_eof(seq)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("rtsp: parse %s failed. ret=%d", SRS_RTSP_TOKEN_CSEQ, ret); + } + return ret; + } + req->seq = ::atol(seq.c_str()); + } else if (token == SRS_RTSP_TOKEN_CONTENT_TYPE) { + std::string ct; + if ((ret = recv_token_eof(ct)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("rtsp: parse %s failed. ret=%d", SRS_RTSP_TOKEN_CONTENT_TYPE, ret); + } + return ret; + } + req->content_type = ct; + } else if (token == SRS_RTSP_TOKEN_CONTENT_LENGTH) { + std::string cl; + if ((ret = recv_token_eof(cl)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("rtsp: parse %s failed. ret=%d", SRS_RTSP_TOKEN_CONTENT_LENGTH, ret); + } + return ret; + } + req->content_length = ::atol(cl.c_str()); + } else if (token == SRS_RTSP_TOKEN_TRANSPORT) { + std::string transport; + if ((ret = recv_token_eof(transport)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("rtsp: parse %s failed. ret=%d", SRS_RTSP_TOKEN_TRANSPORT, ret); + } + return ret; + } + if (!req->transport) { + req->transport = new SrsRtspTransport(); + } + if ((ret = req->transport->parse(transport)) != ERROR_SUCCESS) { + srs_error("rtsp: parse transport failed, transport=%s. ret=%d", transport.c_str(), ret); + return ret; + } + } else if (token == SRS_RTSP_TOKEN_SESSION) { + if ((ret = recv_token_eof(req->session)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("rtsp: parse %s failed. ret=%d", SRS_RTSP_TOKEN_SESSION, ret); + } + return ret; + } + } else { + // unknown header name, parse util EOF. + SrsRtspTokenState state = SrsRtspTokenStateNormal; + while (state == SrsRtspTokenStateNormal) { + std::string value; + if ((ret = recv_token(value, state)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("rtsp: parse token failed. ret=%d", ret); + } + return ret; + } + srs_trace("rtsp: ignore header %s=%s", token.c_str(), value.c_str()); + } + } + } + + // for setup, parse the stream id from uri. + if (req->is_setup()) { + size_t pos = string::npos; + std::string stream_id; + if ((pos = req->uri.rfind("/")) != string::npos) { + stream_id = req->uri.substr(pos + 1); + } + if ((pos = stream_id.find("=")) != string::npos) { + stream_id = stream_id.substr(pos + 1); + } + req->stream_id = ::atoi(stream_id.c_str()); + srs_info("rtsp: setup stream id=%d", req->stream_id); + } + + // parse rdp body. + long consumed = 0; + while (consumed < req->content_length) { + if (!req->sdp) { + req->sdp = new SrsRtspSdp(); + } + + int nb_token = 0; + std::string token; + if ((ret = recv_token_util_eof(token, &nb_token)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("rtsp: parse sdp token failed. ret=%d", ret); + } + return ret; + } + consumed += nb_token; + + if ((ret = req->sdp->parse(token)) != ERROR_SUCCESS) { + srs_error("rtsp: sdp parse token failed, token=%s. ret=%d", token.c_str(), ret); + return ret; + } + srs_info("rtsp: %s", token.c_str()); + } + srs_info("rtsp: sdp parsed, size=%d", consumed); + + return ret; +} + +int SrsRtspStack::recv_token_normal(std::string& token) +{ + int ret = ERROR_SUCCESS; + + SrsRtspTokenState state; + + if ((ret = recv_token(token, state)) != ERROR_SUCCESS) { + if (ret == ERROR_RTSP_REQUEST_HEADER_EOF) { + return ret; + } + if (!srs_is_client_gracefully_close(ret)) { + srs_error("rtsp: parse token failed. ret=%d", ret); + } + return ret; + } + + if (state != SrsRtspTokenStateNormal) { + ret = ERROR_RTSP_TOKEN_NOT_NORMAL; + srs_error("rtsp: parse normal token failed, state=%d. ret=%d", state, ret); + return ret; + } + + return ret; +} + +int SrsRtspStack::recv_token_eof(std::string& token) +{ + int ret = ERROR_SUCCESS; + + SrsRtspTokenState state; + + if ((ret = recv_token(token, state)) != ERROR_SUCCESS) { + if (ret == ERROR_RTSP_REQUEST_HEADER_EOF) { + return ret; + } + if (!srs_is_client_gracefully_close(ret)) { + srs_error("rtsp: parse token failed. ret=%d", ret); + } + return ret; + } + + if (state != SrsRtspTokenStateEOF) { + ret = ERROR_RTSP_TOKEN_NOT_NORMAL; + srs_error("rtsp: parse eof token failed, state=%d. ret=%d", state, ret); + return ret; + } + + return ret; +} + +int SrsRtspStack::recv_token_util_eof(std::string& token, int* pconsumed) +{ + int ret = ERROR_SUCCESS; + + SrsRtspTokenState state; + + // use 0x00 as ignore the normal token flag. + if ((ret = recv_token(token, state, 0x00, pconsumed)) != ERROR_SUCCESS) { + if (ret == ERROR_RTSP_REQUEST_HEADER_EOF) { + return ret; + } + if (!srs_is_client_gracefully_close(ret)) { + srs_error("rtsp: parse token failed. ret=%d", ret); + } + return ret; + } + + if (state != SrsRtspTokenStateEOF) { + ret = ERROR_RTSP_TOKEN_NOT_NORMAL; + srs_error("rtsp: parse eof token failed, state=%d. ret=%d", state, ret); + return ret; + } + + return ret; +} + +int SrsRtspStack::recv_token(std::string& token, SrsRtspTokenState& state, char normal_ch, int* pconsumed) +{ + int ret = ERROR_SUCCESS; + + // whatever, default to error state. + state = SrsRtspTokenStateError; + + // when buffer is empty, append bytes first. + bool append_bytes = buf->length() == 0; + + // parse util token. + for (;;) { + // append bytes if required. + if (append_bytes) { + append_bytes = false; + + char buffer[SRS_RTSP_BUFFER]; + ssize_t nb_read = 0; + if ((ret = skt->read(buffer, SRS_RTSP_BUFFER, &nb_read)) != ERROR_SUCCESS) { + if (!srs_is_client_gracefully_close(ret)) { + srs_error("rtsp: io read failed. ret=%d", ret); + } + return ret; + } + srs_info("rtsp: io read %d bytes", nb_read); + + buf->append(buffer, nb_read); + } + + // parse one by one. + char* start = buf->bytes(); + char* end = start + buf->length(); + char* p = start; + + // find util SP/CR/LF, max 2 EOF, to finger out the EOF of message. + for (; p < end && p[0] != normal_ch && p[0] != SRS_RTSP_CR && p[0] != SRS_RTSP_LF; p++) { + } + + // matched. + if (p < end) { + // finger out the state. + if (p[0] == normal_ch) { + state = SrsRtspTokenStateNormal; + } else { + state = SrsRtspTokenStateEOF; + } + + // got the token. + int nb_token = p - start; + // trim last ':' character. + if (nb_token && p[-1] == ':') { + nb_token--; + } + if (nb_token) { + token.append(start, nb_token); + } else { + ret = ERROR_RTSP_REQUEST_HEADER_EOF; + } + + // ignore SP/CR/LF + for (int i = 0; i < 2 && p < end && (p[0] == normal_ch || p[0] == SRS_RTSP_CR || p[0] == SRS_RTSP_LF); p++, i++) { + } + + // consume the token bytes. + srs_assert(p - start); + buf->erase(p - start); + if (pconsumed) { + *pconsumed = p - start; + } + break; + } + + // append more and parse again. + append_bytes = true; + } + + return ret; +} + +#endif + diff --git a/trunk/src/protocol/srs_rtsp_stack.hpp b/trunk/src/protocol/srs_rtsp_stack.hpp new file mode 100644 index 0000000000..1d00e989c0 --- /dev/null +++ b/trunk/src/protocol/srs_rtsp_stack.hpp @@ -0,0 +1,716 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2015 SRS(simple-rtmp-server) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef SRS_PROTOCOL_RTSP_STACK_HPP +#define SRS_PROTOCOL_RTSP_STACK_HPP + +/* +#include +*/ + +#include + +#include +#include + +#include + +#ifdef SRS_AUTO_STREAM_CASTER + +class SrsStream; +class SrsSimpleBuffer; +class SrsCodecSample; +class ISrsProtocolReaderWriter; + +// rtsp specification +// CR = +#define SRS_RTSP_CR SRS_CONSTS_CR // 0x0D +// LF = +#define SRS_RTSP_LF SRS_CONSTS_LF // 0x0A +// SP = +#define SRS_RTSP_SP ' ' // 0x20 + +// 4 RTSP Message, @see rtsp-rfc2326-1998.pdf, page 37 +// Lines are terminated by CRLF, but +// receivers should be prepared to also interpret CR and LF by +// themselves as line terminators. +#define SRS_RTSP_CRLF "\r\n" // 0x0D0A +#define SRS_RTSP_CRLFCRLF "\r\n\r\n" // 0x0D0A0D0A + +// RTSP token +#define SRS_RTSP_TOKEN_CSEQ "CSeq" +#define SRS_RTSP_TOKEN_PUBLIC "Public" +#define SRS_RTSP_TOKEN_CONTENT_TYPE "Content-Type" +#define SRS_RTSP_TOKEN_CONTENT_LENGTH "Content-Length" +#define SRS_RTSP_TOKEN_TRANSPORT "Transport" +#define SRS_RTSP_TOKEN_SESSION "Session" + +// RTSP methods +#define SRS_METHOD_OPTIONS "OPTIONS" +#define SRS_METHOD_DESCRIBE "DESCRIBE" +#define SRS_METHOD_ANNOUNCE "ANNOUNCE" +#define SRS_METHOD_SETUP "SETUP" +#define SRS_METHOD_PLAY "PLAY" +#define SRS_METHOD_PAUSE "PAUSE" +#define SRS_METHOD_TEARDOWN "TEARDOWN" +#define SRS_METHOD_GET_PARAMETER "GET_PARAMETER" +#define SRS_METHOD_SET_PARAMETER "SET_PARAMETER" +#define SRS_METHOD_REDIRECT "REDIRECT" +#define SRS_METHOD_RECORD "RECORD" +// Embedded (Interleaved) Binary Data + +// RTSP-Version +#define SRS_RTSP_VERSION "RTSP/1.0" + +/** +* the rtsp sdp parse state. +*/ +enum SrsRtspSdpState +{ + /** + * other sdp properties. + */ + SrsRtspSdpStateOthers, + /** + * parse sdp audio state. + */ + SrsRtspSdpStateAudio, + /** + * parse sdp video state. + */ + SrsRtspSdpStateVideo, +}; + +/** +* 10 Method Definitions, @see rtsp-rfc2326-1998.pdf, page 57 +* The method token indicates the method to be performed on the resource +* identified by the Request-URI. The method is case-sensitive. New +* methods may be defined in the future. Method names may not start with +* a $ character (decimal 24) and must be a token. Methods are +* summarized in Table 2. +* Notes on Table 2: PAUSE is recommended, but not required in that a +* fully functional server can be built that does not support this +* method, for example, for live feeds. If a server does not support a +* particular method, it MUST return "501 Not Implemented" and a client +* SHOULD not try this method again for this server. +*/ +enum SrsRtspMethod +{ + SrsRtspMethodDescribe = 0x0001, + SrsRtspMethodAnnounce = 0x0002, + SrsRtspMethodGetParameter = 0x0004, + SrsRtspMethodOptions = 0x0008, + SrsRtspMethodPause = 0x0010, + SrsRtspMethodPlay = 0x0020, + SrsRtspMethodRecord = 0x0040, + SrsRtspMethodRedirect = 0x0080, + SrsRtspMethodSetup = 0x0100, + SrsRtspMethodSetParameter = 0x0200, + SrsRtspMethodTeardown = 0x0400, +}; + +/** +* the state of rtsp token. +*/ +enum SrsRtspTokenState +{ + /** + * parse token failed, default state. + */ + SrsRtspTokenStateError = 100, + /** + * when SP follow the token. + */ + SrsRtspTokenStateNormal = 101, + /** + * when CRLF follow the token. + */ + SrsRtspTokenStateEOF = 102, +}; + +/** +* the rtp packet. +* 5. RTP Data Transfer Protocol, @see rtp-rfc3550-2003.pdf, page 12 +*/ +class SrsRtpPacket +{ +public: + /** + * version (V): 2 bits + * This eld identi es the version of RTP. The version de ned by this speci cation is two (2). + * (The value 1 is used by the rst draft version of RTP and the value 0 is used by the protocol + * initially implemented in the \vat" audio tool.) + */ + int8_t version; //2bits + /** + * padding (P): 1 bit + * If the padding bit is set, the packet contains one or more additional padding octets at the + * end which are not part of the payload. The last octet of the padding contains a count of + * how many padding octets should be ignored, including itself. Padding may be needed by + * some encryption algorithms with xed block sizes or for carrying several RTP packets in a + * lower-layer protocol data unit. + */ + int8_t padding; //1bit + /** + * extension (X): 1 bit + * If the extension bit is set, the xed header must be followed by exactly one header extension, + * with a format de ned in Section 5.3.1. + */ + int8_t extension; //1bit + /** + * CSRC count (CC): 4 bits + * The CSRC count contains the number of CSRC identi ers that follow the xed header. + */ + int8_t csrc_count; //4bits + /** + * marker (M): 1 bit + * The interpretation of the marker is de ned by a pro le. It is intended to allow signi cant + * events such as frame boundaries to be marked in the packet stream. A pro le may de ne + * additional marker bits or specify that there is no marker bit by changing the number of bits + * in the payload type eld (see Section 5.3). + */ + int8_t marker; //1bit + /** + * payload type (PT): 7 bits + * This eld identi es the format of the RTP payload and determines its interpretation by the + * application. A pro le may specify a default static mapping of payload type codes to payload + * formats. Additional payload type codes may be de ned dynamically through non-RTP means + * (see Section 3). A set of default mappings for audio and video is speci ed in the companion + * RFC 3551 [1]. An RTP source may change the payload type during a session, but this eld + * should not be used for multiplexing separate media streams (see Section 5.2). + * A receiver must ignore packets with payload types that it does not understand. + */ + int8_t payload_type; //7bits + /** + * sequence number: 16 bits + * The sequence number increments by one for each RTP data packet sent, and may be used + * by the receiver to detect packet loss and to restore packet sequence. The initial value of the + * sequence number should be random (unpredictable) to make known-plaintext attacks on + * encryption more dicult, even if the source itself does not encrypt according to the method + * in Section 9.1, because the packets may flow through a translator that does. Techniques for + * choosing unpredictable numbers are discussed in [17]. + */ + u_int16_t sequence_number; //16bits + /** + * timestamp: 32 bits + * The timestamp reflects the sampling instant of the rst octet in the RTP data packet. The + * sampling instant must be derived from a clock that increments monotonically and linearly + * in time to allow synchronization and jitter calculations (see Section 6.4.1). The resolution + * of the clock must be sucient for the desired synchronization accuracy and for measuring + * packet arrival jitter (one tick per video frame is typically not sucient). The clock frequency + * is dependent on the format of data carried as payload and is speci ed statically in the pro le + * or payload format speci cation that de nes the format, or may be speci ed dynamically for + * payload formats de ned through non-RTP means. If RTP packets are generated periodically, + * the nominal sampling instant as determined from the sampling clock is to be used, not a + * reading of the system clock. As an example, for xed-rate audio the timestamp clock would + * likely increment by one for each sampling period. If an audio application reads blocks covering + * 160 sampling periods from the input device, the timestamp would be increased by 160 for + * each such block, regardless of whether the block is transmitted in a packet or dropped as + * silent. + * + * The initial value of the timestamp should be random, as for the sequence number. Several + * consecutive RTP packets will have equal timestamps if they are (logically) generated at once, + * e.g., belong to the same video frame. Consecutive RTP packets may contain timestamps that + * are not monotonic if the data is not transmitted in the order it was sampled, as in the case + * of MPEG interpolated video frames. (The sequence numbers of the packets as transmitted + * will still be monotonic.) + * + * RTP timestamps from di erent media streams may advance at di erent rates and usually + * have independent, random o sets. Therefore, although these timestamps are sucient to + * reconstruct the timing of a single stream, directly comparing RTP timestamps from di erent + * media is not e ective for synchronization. Instead, for each medium the RTP timestamp + * is related to the sampling instant by pairing it with a timestamp from a reference clock + * (wallclock) that represents the time when the data corresponding to the RTP timestamp was + * sampled. The reference clock is shared by all media to be synchronized. The timestamp + * pairs are not transmitted in every data packet, but at a lower rate in RTCP SR packets as + * described in Section 6.4. + * + * The sampling instant is chosen as the point of reference for the RTP timestamp because it is + * known to the transmitting endpoint and has a common de nition for all media, independent + * of encoding delays or other processing. The purpose is to allow synchronized presentation of + * all media sampled at the same time. + * + * Applications transmitting stored data rather than data sampled in real time typically use a + * virtual presentation timeline derived from wallclock time to determine when the next frame + * or other unit of each medium in the stored data should be presented. In this case, the RTP + * timestamp would reflect the presentation time for each unit. That is, the RTP timestamp for + * each unit would be related to the wallclock time at which the unit becomes current on the + * virtual presentation timeline. Actual presentation occurs some time later as determined by + * the receiver. + * + * An example describing live audio narration of prerecorded video illustrates the signi cance + * of choosing the sampling instant as the reference point. In this scenario, the video would + * be presented locally for the narrator to view and would be simultaneously transmitted using + * RTP. The \sampling instant" of a video frame transmitted in RTP would be established by + * referencing its timestamp to the wallclock time when that video frame was presented to the + * narrator. The sampling instant for the audio RTP packets containing the narrator's speech + * would be established by referencing the same wallclock time when the audio was sampled. + * The audio and video may even be transmitted by di erent hosts if the reference clocks on + * the two hosts are synchronized by some means such as NTP. A receiver can then synchronize + * presentation of the audio and video packets by relating their RTP timestamps using the + * timestamp pairs in RTCP SR packets. + */ + u_int32_t timestamp; //32bits + /** + * SSRC: 32 bits + * The SSRC eld identi es the synchronization source. This identi er should be chosen + * randomly, with the intent that no two synchronization sources within the same RTP session + * will have the same SSRC identi er. An example algorithm for generating a random identi er + * is presented in Appendix A.6. Although the probability of multiple sources choosing the same + * identi er is low, all RTP implementations must be prepared to detect and resolve collisions. + * Section 8 describes the probability of collision along with a mechanism for resolving collisions + * and detecting RTP-level forwarding loops based on the uniqueness of the SSRC identi er. If + * a source changes its source transport address, it must also choose a new SSRC identi er to + * avoid being interpreted as a looped source (see Section 8.2). + */ + u_int32_t ssrc; //32bits + + // the payload. + SrsSimpleBuffer* payload; + // whether transport in chunked payload. + bool chunked; + // whether message is completed. + // normal message always completed. + // while chunked completed when the last chunk arriaved. + bool completed; + + /** + * the audio samples, one rtp packets may contains multiple audio samples. + */ + SrsCodecSample* audio_samples; +public: + SrsRtpPacket(); + virtual ~SrsRtpPacket(); +public: + /** + * copy the header from src. + */ + virtual void copy(SrsRtpPacket* src); + /** + * reap the src to this packet, reap the payload. + */ + virtual void reap(SrsRtpPacket* src); + /** + * decode rtp packet from stream. + */ + virtual int decode(SrsStream* stream); +private: + virtual int decode_97(SrsStream* stream); + virtual int decode_96(SrsStream* stream); +}; + +/** +* the sdp in announce, @see rtsp-rfc2326-1998.pdf, page 159 +* Appendix C: Use of SDP for RTSP Session Descriptions +* The Session Description Protocol (SDP, RFC 2327 [6]) may be used to +* describe streams or presentations in RTSP. +*/ +class SrsRtspSdp +{ +private: + SrsRtspSdpState state; +public: + /** + * the version of sdp. + */ + std::string version; + /** + * the owner/creator of sdp. + */ + std::string owner_username; + std::string owner_session_id; + std::string owner_session_version; + std::string owner_network_type; + std::string owner_address_type; + std::string owner_address; + /** + * the session name of sdp. + */ + std::string session_name; + /** + * the connection info of sdp. + */ + std::string connection_network_type; + std::string connection_address_type; + std::string connection_address; + /** + * the tool attribute of sdp. + */ + std::string tool; + /** + * the video attribute of sdp. + */ + std::string video_port; + std::string video_protocol; + std::string video_transport_format; + std::string video_bandwidth_kbps; + std::string video_codec; + std::string video_sample_rate; + std::string video_stream_id; + // fmtp + std::string video_packetization_mode; + std::string video_sps; // sequence header: sps. + std::string video_pps; // sequence header: pps. + /** + * the audio attribute of sdp. + */ + std::string audio_port; + std::string audio_protocol; + std::string audio_transport_format; + std::string audio_bandwidth_kbps; + std::string audio_codec; + std::string audio_sample_rate; + std::string audio_channel; + std::string audio_stream_id; + // fmtp + std::string audio_profile_level_id; + std::string audio_mode; + std::string audio_size_length; + std::string audio_index_length; + std::string audio_index_delta_length; + std::string audio_sh; // sequence header. +public: + SrsRtspSdp(); + virtual ~SrsRtspSdp(); +public: + /** + * parse a line of token for sdp. + */ + virtual int parse(std::string token); +private: + /** + * generally, the fmtp is the sequence header for video or audio. + */ + virtual int parse_fmtp_attribute(std::string attr); + /** + * generally, the control is the stream info for video or audio. + */ + virtual int parse_control_attribute(std::string attr); + /** + * decode the string by base64. + */ + virtual std::string base64_decode(std::string value); +}; + +/** +* the rtsp transport. +* 12.39 Transport, @see rtsp-rfc2326-1998.pdf, page 115 +* This request header indicates which transport protocol is to be used +* and configures its parameters such as destination address, +* compression, multicast time-to-live and destination port for a single +* stream. It sets those values not already determined by a presentation +* description. +*/ +class SrsRtspTransport +{ +public: + // The syntax for the transport specifier is + // transport/profile/lower-transport + std::string transport; + std::string profile; + std::string lower_transport; + // unicast | multicast + // mutually exclusive indication of whether unicast or multicast + // delivery will be attempted. Default value is multicast. + // Clients that are capable of handling both unicast and + // multicast transmission MUST indicate such capability by + // including two full transport-specs with separate parameters + // for each. + std::string cast_type; + // The mode parameter indicates the methods to be supported for + // this session. Valid values are PLAY and RECORD. If not + // provided, the default is PLAY. + std::string mode; + // This parameter provides the unicast RTP/RTCP port pair on + // which the client has chosen to receive media data and control + // information. It is specified as a range, e.g., + // client_port=3456-3457. + // where client will use port in: + // [client_port_min, client_port_max) + int client_port_min; + int client_port_max; +public: + SrsRtspTransport(); + virtual ~SrsRtspTransport(); +public: + /** + * parse a line of token for transport. + */ + virtual int parse(std::string attr); +}; + +/** +* the rtsp request message. +* 6 Request, @see rtsp-rfc2326-1998.pdf, page 39 +* A request message from a client to a server or vice versa includes, +* within the first line of that message, the method to be applied to +* the resource, the identifier of the resource, and the protocol +* version in use. +* Request = Request-Line ; Section 6.1 +* *( general-header ; Section 5 +* | request-header ; Section 6.2 +* | entity-header ) ; Section 8.1 +* CRLF +* [ message-body ] ; Section 4.3 +*/ +class SrsRtspRequest +{ +public: + /** + * 6.1 Request Line + * Request-Line = Method SP Request-URI SP RTSP-Version CRLF + */ + std::string method; + std::string uri; + std::string version; + /** + * 12.17 CSeq + * The CSeq field specifies the sequence number for an RTSP requestresponse + * pair. This field MUST be present in all requests and + * responses. For every RTSP request containing the given sequence + * number, there will be a corresponding response having the same + * number. Any retransmitted request must contain the same sequence + * number as the original (i.e. the sequence number is not incremented + * for retransmissions of the same request). + */ + long seq; + /** + * 12.16 Content-Type, @see rtsp-rfc2326-1998.pdf, page 99 + * See [H14.18]. Note that the content types suitable for RTSP are + * likely to be restricted in practice to presentation descriptions and + * parameter-value types. + */ + std::string content_type; + /** + * 12.14 Content-Length, @see rtsp-rfc2326-1998.pdf, page 99 + * This field contains the length of the content of the method (i.e. + * after the double CRLF following the last header). Unlike HTTP, it + * MUST be included in all messages that carry content beyond the header + * portion of the message. If it is missing, a default value of zero is + * assumed. It is interpreted according to [H14.14]. + */ + long content_length; + /** + * the session id. + */ + std::string session; + + /** + * the sdp in announce, NULL for no sdp. + */ + SrsRtspSdp* sdp; + /** + * the transport in setup, NULL for no transport. + */ + SrsRtspTransport* transport; + /** + * for setup message, parse the stream id from uri. + */ + int stream_id; +public: + SrsRtspRequest(); + virtual ~SrsRtspRequest(); +public: + virtual bool is_options(); + virtual bool is_announce(); + virtual bool is_setup(); + virtual bool is_record(); +}; + +/** +* the rtsp response message. +* 7 Response, @see rtsp-rfc2326-1998.pdf, page 43 +* [H6] applies except that HTTP-Version is replaced by RTSP-Version. +* Also, RTSP defines additional status codes and does not define some +* HTTP codes. The valid response codes and the methods they can be used +* with are defined in Table 1. +* After receiving and interpreting a request message, the recipient +* responds with an RTSP response message. +* Response = Status-Line ; Section 7.1 +* *( general-header ; Section 5 +* | response-header ; Section 7.1.2 +* | entity-header ) ; Section 8.1 +* CRLF +* [ message-body ] ; Section 4.3 +*/ +class SrsRtspResponse +{ +public: + /** + * 7.1 Status-Line + * The first line of a Response message is the Status-Line, consisting + * of the protocol version followed by a numeric status code, and the + * textual phrase associated with the status code, with each element + * separated by SP characters. No CR or LF is allowed except in the + * final CRLF sequence. + * Status-Line = RTSP-Version SP Status-Code SP Reason-Phrase CRLF + */ + // @see about the version of rtsp, see SRS_RTSP_VERSION + // @see about the status of rtsp, see SRS_CONSTS_RTSP_OK + int status; + /** + * 12.17 CSeq, @see rtsp-rfc2326-1998.pdf, page 99 + * The CSeq field specifies the sequence number for an RTSP requestresponse + * pair. This field MUST be present in all requests and + * responses. For every RTSP request containing the given sequence + * number, there will be a corresponding response having the same + * number. Any retransmitted request must contain the same sequence + * number as the original (i.e. the sequence number is not incremented + * for retransmissions of the same request). + */ + long seq; + /** + * the session id. + */ + std::string session; +public: + SrsRtspResponse(int cseq); + virtual ~SrsRtspResponse(); +public: + /** + * encode message to string. + */ + virtual int encode(std::stringstream& ss); +protected: + /** + * sub classes override this to encode the headers. + */ + virtual int encode_header(std::stringstream& ss); +}; + +/** +* 10.1 OPTIONS, @see rtsp-rfc2326-1998.pdf, page 59 +* The behavior is equivalent to that described in [H9.2]. An OPTIONS +* request may be issued at any time, e.g., if the client is about to +* try a nonstandard request. It does not influence server state. +*/ +class SrsRtspOptionsResponse : public SrsRtspResponse +{ +public: + /** + * join of SrsRtspMethod + */ + SrsRtspMethod methods; +public: + SrsRtspOptionsResponse(int cseq); + virtual ~SrsRtspOptionsResponse(); +protected: + virtual int encode_header(std::stringstream& ss); +}; + +/** +* 10.4 SETUP, @see rtsp-rfc2326-1998.pdf, page 65 +* The SETUP request for a URI specifies the transport mechanism to be +* used for the streamed media. A client can issue a SETUP request for a +* stream that is already playing to change transport parameters, which +* a server MAY allow. If it does not allow this, it MUST respond with +* error "455 Method Not Valid In This State". For the benefit of any +* intervening firewalls, a client must indicate the transport +* parameters even if it has no influence over these parameters, for +* example, where the server advertises a fixed multicast address. +*/ +class SrsRtspSetupResponse : public SrsRtspResponse +{ +public: + // the client specified port. + int client_port_min; + int client_port_max; + // client will use the port in: + // [local_port_min, local_port_max) + int local_port_min; + int local_port_max; + // session. + std::string session; +public: + SrsRtspSetupResponse(int cseq); + virtual ~SrsRtspSetupResponse(); +protected: + virtual int encode_header(std::stringstream& ss); +}; + +/** +* the rtsp protocol stack to parse the rtsp packets. +*/ +class SrsRtspStack +{ +private: + /** + * cached bytes buffer. + */ + SrsSimpleBuffer* buf; + /** + * underlayer socket object, send/recv bytes. + */ + ISrsProtocolReaderWriter* skt; +public: + SrsRtspStack(ISrsProtocolReaderWriter* s); + virtual ~SrsRtspStack(); +public: + /** + * recv rtsp message from underlayer io. + * @param preq the output rtsp request message, which user must free it. + * @return an int error code. + * ERROR_RTSP_REQUEST_HEADER_EOF indicates request header EOF. + */ + virtual int recv_message(SrsRtspRequest** preq); + /** + * send rtsp message over underlayer io. + * @param res the rtsp response message, which user should never free it. + * @return an int error code. + */ + virtual int send_message(SrsRtspResponse* res); +private: + /** + * recv the rtsp message. + */ + virtual int do_recv_message(SrsRtspRequest* req); + /** + * read a normal token from io, error when token state is not normal. + */ + virtual int recv_token_normal(std::string& token); + /** + * read a normal token from io, error when token state is not eof. + */ + virtual int recv_token_eof(std::string& token); + /** + * read the token util got eof, for example, to read the response status Reason-Phrase + * @param pconsumed, output the token parsed length. NULL to ignore. + */ + virtual int recv_token_util_eof(std::string& token, int* pconsumed = NULL); + /** + * read a token from io, split by SP, endswith CRLF: + * token1 SP token2 SP ... tokenN CRLF + * @param token, output the read token. + * @param state, output the token parse state. + * @param normal_ch, the char to indicates the normal token. + * the SP use to indicates the normal token, @see SRS_RTSP_SP + * the 0x00 use to ignore normal token flag. @see recv_token_util_eof + * @param pconsumed, output the token parsed length. NULL to ignore. + */ + virtual int recv_token(std::string& token, SrsRtspTokenState& state, char normal_ch = SRS_RTSP_SP, int* pconsumed = NULL); +}; + +#endif + +#endif + diff --git a/trunk/src/rtmp/srs_protocol_utility.cpp b/trunk/src/rtmp/srs_protocol_utility.cpp deleted file mode 100644 index ccf99b8d57..0000000000 --- a/trunk/src/rtmp/srs_protocol_utility.cpp +++ /dev/null @@ -1,157 +0,0 @@ -/* -The MIT License (MIT) - -Copyright (c) 2013-2014 winlin - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -#include - -#include -using namespace std; - -#include -#include - -void srs_discovery_tc_url( - string tcUrl, - string& schema, string& host, string& vhost, - string& app, string& port, std::string& param -) { - size_t pos = std::string::npos; - std::string url = tcUrl; - - if ((pos = url.find("://")) != std::string::npos) { - schema = url.substr(0, pos); - url = url.substr(schema.length() + 3); - srs_info("discovery schema=%s", schema.c_str()); - } - - if ((pos = url.find("/")) != std::string::npos) { - host = url.substr(0, pos); - url = url.substr(host.length() + 1); - srs_info("discovery host=%s", host.c_str()); - } - - port = SRS_CONSTS_RTMP_DEFAULT_PORT; - if ((pos = host.find(":")) != std::string::npos) { - port = host.substr(pos + 1); - host = host.substr(0, pos); - srs_info("discovery host=%s, port=%s", host.c_str(), port.c_str()); - } - - app = url; - vhost = host; - srs_vhost_resolve(vhost, app, param); -} - -void srs_vhost_resolve(string& vhost, string& app, string& param) -{ - // get original param - size_t pos = 0; - if ((pos = app.find("?")) != std::string::npos) { - param = app.substr(pos); - } - - // filter tcUrl - app = srs_string_replace(app, ",", "?"); - app = srs_string_replace(app, "...", "?"); - app = srs_string_replace(app, "&&", "?"); - app = srs_string_replace(app, "=", "?"); - - if ((pos = app.find("?")) == std::string::npos) { - return; - } - - std::string query = app.substr(pos + 1); - app = app.substr(0, pos); - - if ((pos = query.find("vhost?")) != std::string::npos) { - query = query.substr(pos + 6); - if (!query.empty()) { - vhost = query; - } - if ((pos = vhost.find("?")) != std::string::npos) { - vhost = vhost.substr(0, pos); - } - } -} - -void srs_random_generate(char* bytes, int size) -{ - static bool _random_initialized = false; - if (!_random_initialized) { - srand(0); - _random_initialized = true; - srs_trace("srand initialized the random."); - } - - for (int i = 0; i < size; i++) { - // the common value in [0x0f, 0xf0] - bytes[i] = 0x0f + (rand() % (256 - 0x0f - 0x0f)); - } -} - -string srs_generate_tc_url(string ip, string vhost, string app, string port, string param) -{ - string tcUrl = "rtmp://"; - - if (vhost == SRS_CONSTS_RTMP_DEFAULT_VHOST) { - tcUrl += ip; - } else { - tcUrl += vhost; - } - - if (port != SRS_CONSTS_RTMP_DEFAULT_PORT) { - tcUrl += ":"; - tcUrl += port; - } - - tcUrl += "/"; - tcUrl += app; - tcUrl += param; - - return tcUrl; -} - -/** -* compare the memory in bytes. -*/ -bool srs_bytes_equals(void* pa, void* pb, int size) -{ - u_int8_t* a = (u_int8_t*)pa; - u_int8_t* b = (u_int8_t*)pb; - - if (!a && !b) { - return true; - } - - if (!a || !b) { - return false; - } - - for(int i = 0; i < size; i++){ - if(a[i] != b[i]){ - return false; - } - } - - return true; -} - diff --git a/trunk/src/srs/init b/trunk/src/srs/init deleted file mode 100644 index 9489480dc9..0000000000 --- a/trunk/src/srs/init +++ /dev/null @@ -1,3 +0,0 @@ -#ifndef _srs_icpp_init_stub -#define _srs_icpp_init_stub -#endif diff --git a/trunk/src/srs/srs.upp b/trunk/src/srs/srs.upp deleted file mode 100755 index 63c2cfa74b..0000000000 --- a/trunk/src/srs/srs.upp +++ /dev/null @@ -1,144 +0,0 @@ -file - main readonly separator, - ..\main\srs_main_server.cpp, - auto readonly separator, - ..\..\objs\srs_auto_headers.hpp, - libs readonly separator, - ..\libs\srs_librtmp.hpp, - ..\libs\srs_librtmp.cpp, - ..\libs\srs_lib_bandwidth.hpp, - ..\libs\srs_lib_bandwidth.cpp, - ..\libs\srs_lib_simple_socket.hpp, - ..\libs\srs_lib_simple_socket.cpp, - core readonly separator, - ..\core\srs_core.hpp, - ..\core\srs_core.cpp, - ..\core\srs_core_autofree.hpp, - ..\core\srs_core_autofree.cpp, - kernel readonly separator, - ..\kernel\srs_kernel_buffer.hpp, - ..\kernel\srs_kernel_buffer.cpp, - ..\kernel\srs_kernel_codec.hpp, - ..\kernel\srs_kernel_codec.cpp, - ..\kernel\srs_kernel_consts.hpp, - ..\kernel\srs_kernel_consts.cpp, - ..\kernel\srs_kernel_error.hpp, - ..\kernel\srs_kernel_error.cpp, - ..\kernel\srs_kernel_file.hpp, - ..\kernel\srs_kernel_file.cpp, - ..\kernel\srs_kernel_flv.hpp, - ..\kernel\srs_kernel_flv.cpp, - ..\kernel\srs_kernel_log.hpp, - ..\kernel\srs_kernel_log.cpp, - ..\kernel\srs_kernel_stream.hpp, - ..\kernel\srs_kernel_stream.cpp, - ..\kernel\srs_kernel_utility.hpp, - ..\kernel\srs_kernel_utility.cpp, - rtmp-protocol readonly separator, - ..\rtmp\srs_protocol_amf0.hpp, - ..\rtmp\srs_protocol_amf0.cpp, - ..\rtmp\srs_protocol_handshake.hpp, - ..\rtmp\srs_protocol_handshake.cpp, - ..\rtmp\srs_protocol_io.hpp, - ..\rtmp\srs_protocol_io.cpp, - ..\rtmp\srs_protocol_msg_array.hpp, - ..\rtmp\srs_protocol_msg_array.cpp, - ..\rtmp\srs_protocol_rtmp.hpp, - ..\rtmp\srs_protocol_rtmp.cpp, - ..\rtmp\srs_protocol_stack.hpp, - ..\rtmp\srs_protocol_stack.cpp, - ..\rtmp\srs_protocol_utility.hpp, - ..\rtmp\srs_protocol_utility.cpp, - app readonly separator, - ..\app\srs_app_avc_aac.hpp, - ..\app\srs_app_avc_aac.cpp, - ..\app\srs_app_bandwidth.hpp, - ..\app\srs_app_bandwidth.cpp, - ..\app\srs_app_conn.hpp, - ..\app\srs_app_conn.cpp, - ..\app\srs_app_config.hpp, - ..\app\srs_app_config.cpp, - ..\app\srs_app_dvr.hpp, - ..\app\srs_app_dvr.cpp, - ..\app\srs_app_edge.hpp, - ..\app\srs_app_edge.cpp, - ..\app\srs_app_empty.hpp, - ..\app\srs_app_empty.cpp, - ..\app\srs_app_encoder.hpp, - ..\app\srs_app_encoder.cpp, - ..\app\srs_app_ffmpeg.hpp, - ..\app\srs_app_ffmpeg.cpp, - ..\app\srs_app_forward.hpp, - ..\app\srs_app_forward.cpp, - ..\app\srs_app_heartbeat.hpp, - ..\app\srs_app_heartbeat.cpp, - ..\app\srs_app_hls.hpp, - ..\app\srs_app_hls.cpp, - ..\app\srs_app_http.hpp, - ..\app\srs_app_http.cpp, - ..\app\srs_app_http_api.hpp, - ..\app\srs_app_http_api.cpp, - ..\app\srs_app_http_client.hpp, - ..\app\srs_app_http_client.cpp, - ..\app\srs_app_http_conn.hpp, - ..\app\srs_app_http_conn.cpp, - ..\app\srs_app_http_hooks.hpp, - ..\app\srs_app_http_hooks.cpp, - ..\app\srs_app_ingest.hpp, - ..\app\srs_app_ingest.cpp, - ..\app\srs_app_json.hpp, - ..\app\srs_app_json.cpp, - ..\app\srs_app_kbps.hpp, - ..\app\srs_app_kbps.cpp, - ..\app\srs_app_log.hpp, - ..\app\srs_app_log.cpp, - ..\app\srs_app_refer.hpp, - ..\app\srs_app_refer.cpp, - ..\app\srs_app_reload.hpp, - ..\app\srs_app_reload.cpp, - ..\app\srs_app_rtmp_conn.hpp, - ..\app\srs_app_rtmp_conn.cpp, - ..\app\srs_app_pithy_print.hpp, - ..\app\srs_app_pithy_print.cpp, - ..\app\srs_app_server.hpp, - ..\app\srs_app_server.cpp, - ..\app\srs_app_st.hpp, - ..\app\srs_app_st.cpp, - ..\app\srs_app_st_socket.hpp, - ..\app\srs_app_st_socket.cpp, - ..\app\srs_app_source.hpp, - ..\app\srs_app_source.cpp, - ..\app\srs_app_thread.hpp, - ..\app\srs_app_thread.cpp, - ..\app\srs_app_utility.hpp, - ..\app\srs_app_utility.cpp, - utest readonly separator, - ..\utest\srs_utest.hpp, - ..\utest\srs_utest.cpp, - ..\utest\srs_utest_amf0.hpp, - ..\utest\srs_utest_amf0.cpp, - ..\utest\srs_utest_config.hpp, - ..\utest\srs_utest_config.cpp, - ..\utest\srs_utest_core.hpp, - ..\utest\srs_utest_core.cpp, - ..\utest\srs_utest_kernel.hpp, - ..\utest\srs_utest_kernel.cpp, - ..\utest\srs_utest_protocol.hpp, - ..\utest\srs_utest_protocol.cpp, - ..\utest\srs_utest_reload.hpp, - ..\utest\srs_utest_reload.cpp, - research readonly separator, - ..\..\research\librtmp\srs_bandwidth_check.c, - ..\..\research\librtmp\srs_detect_rtmp.c, - ..\..\research\librtmp\srs_flv_injecter.c, - ..\..\research\librtmp\srs_flv_parser.c, - ..\..\research\librtmp\srs_ingest_flv.c, - ..\..\research\librtmp\srs_ingest_rtmp.c, - ..\..\research\librtmp\srs_play.c, - ..\..\research\librtmp\srs_publish.c, - ..\..\research\librtmp\srs_research_public.h, - ..\..\research\hls\ts_info.cc; - -mainconfig - "" = "MAIN"; - diff --git a/trunk/src/utest/srs_utest.cpp b/trunk/src/utest/srs_utest.cpp index 594903b7c9..3df98f6011 100644 --- a/trunk/src/utest/srs_utest.cpp +++ b/trunk/src/utest/srs_utest.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -45,7 +45,7 @@ MockEmptyLog::~MockEmptyLog() { } -void __srs_bytes_print(char* pa, int size) +void srs_bytes_print(char* pa, int size) { for(int i = 0; i < size; i++) { char v = pa[i]; diff --git a/trunk/src/utest/srs_utest.hpp b/trunk/src/utest/srs_utest.hpp index ea2170cdba..70cd2402c1 100644 --- a/trunk/src/utest/srs_utest.hpp +++ b/trunk/src/utest/srs_utest.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -33,11 +33,11 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include -#define __UTEST_DEV -#undef __UTEST_DEV +#define SRS_UTEST_DEV +#undef SRS_UTEST_DEV // enable all utest. -#ifndef __UTEST_DEV +#ifndef SRS_UTEST_DEV #define ENABLE_UTEST_AMF0 #define ENABLE_UTEST_CONFIG #define ENABLE_UTEST_CORE @@ -47,7 +47,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #endif // disable some for fast dev, compile and startup. -#ifdef __UTEST_DEV +#ifdef SRS_UTEST_DEV #undef ENABLE_UTEST_AMF0 #undef ENABLE_UTEST_CONFIG #undef ENABLE_UTEST_CORE @@ -56,7 +56,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #undef ENABLE_UTEST_RELOAD #endif -#ifdef __UTEST_DEV +#ifdef SRS_UTEST_DEV #define ENABLE_UTEST_RELOAD #endif @@ -79,7 +79,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // * {ASSERT|EXPECT}_NEAR(v1, v2, abs_error): Tests that v1 and v2 are within the given distance to each other. // print the bytes. -void __srs_bytes_print(char* pa, int size); +void srs_bytes_print(char* pa, int size); class MockEmptyLog : public SrsFastLog { diff --git a/trunk/src/utest/srs_utest_amf0.cpp b/trunk/src/utest/srs_utest_amf0.cpp index 009a5dfbab..e625c1e7fd 100644 --- a/trunk/src/utest/srs_utest_amf0.cpp +++ b/trunk/src/utest/srs_utest_amf0.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/trunk/src/utest/srs_utest_amf0.hpp b/trunk/src/utest/srs_utest_amf0.hpp index b16f4c044c..61c9be713f 100644 --- a/trunk/src/utest/srs_utest_amf0.hpp +++ b/trunk/src/utest/srs_utest_amf0.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -29,7 +29,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include -#include +#include #endif diff --git a/trunk/src/utest/srs_utest_config.cpp b/trunk/src/utest/srs_utest_config.cpp index 072a16a7aa..521db66855 100644 --- a/trunk/src/utest/srs_utest_config.cpp +++ b/trunk/src/utest/srs_utest_config.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -28,6 +28,7 @@ using namespace std; #include #include #include +#include MockSrsConfigBuffer::MockSrsConfigBuffer(string buf) { @@ -78,977 +79,566 @@ int MockSrsConfig::parse(string buf) #ifdef ENABLE_UTEST_CONFIG // full.conf -std::string __full_conf = "" - "# all config for srs \n" - " \n" - "############################################################################################# \n" - "# RTMP sections \n" - "############################################################################################# \n" - "# the rtmp listen ports, split by space. \n" - "listen 1935; \n" - "# the pid file \n" - "# to ensure only one process can use a pid file \n" - "# and provides the current running process id, for script, \n" - "# for example, init.d script to manage the server. \n" - "# default: ./objs/srs.pid \n" - "pid ./objs/srs.pid; \n" - "# the default chunk size is 128, max is 65536, \n" - "# some client does not support chunk size change, \n" - "# however, most clients supports it and it can improve \n" - "# performance about 10%. \n" - "# default: 60000 \n" - "chunk_size 60000; \n" - "# the logs dir. \n" - "# if enabled ffmpeg, each stracoding stream will create a log file. \n" - "# /dev/null to disable the log. \n" - "# default: ./objs \n" - "ff_log_dir ./objs; \n" - "# the log tank, console or file. \n" - "# if console, print log to console. \n" - "# if file, write log to file. requires srs_log_file if log to file. \n" - "# default: file. \n" - "srs_log_tank file; \n" - "# the log level, for all log tanks. \n" - "# can be: verbose, info, trace, warn, error \n" - "# default: trace \n" - "srs_log_level trace; \n" - "# when srs_log_tank is file, specifies the log file. \n" - "# default: ./objs/srs.log \n" - "srs_log_file ./objs/srs.log; \n" - "# the max connections. \n" - "# if exceed the max connections, server will drop the new connection. \n" - "# default: 12345 \n" - "max_connections 1000; \n" - "# whether start as deamon \n" - "# @remark: donot support reload. \n" - "# default: on \n" - "daemon on; \n" - "# heartbeat to api server \n" - "heartbeat { \n" - " # whether heartbeat is enalbed. \n" - " # default: off \n" - " enabled on; \n" - " # the interval seconds for heartbeat, \n" - " # recommend 0.3,0.6,0.9,1.2,1.5,1.8,2.1,2.4,2.7,3,...,6,9,12,.... \n" - " # default: 9.9 \n" - " interval 9.3; \n" - " # when startup, srs will heartbeat to this api. \n" - " # @remark: must be a restful http api url, where SRS will POST with following data: \n" - " # { \n" - " # \"device_id\": \"my-srs-device\", \n" - " # \"ip\": \"192.168.1.100\" \n" - " # } \n" - " # default: http://127.0.0.1:8085/api/v1/servers \n" - " url http://127.0.0.1:8085/api/v1/servers; \n" - " # the id of devide. \n" - " device_id \"my-srs-device\"; \n" - " # whether report with summaries \n" - " # if true, put /api/v1/summaries to the request data: \n" - " # { \n" - " # \"summaries\": summaries object. \n" - " # } \n" - " # @remark: optional config. \n" - " # default: off \n" - " summaries off; \n" - "} \n" - " \n" - "############################################################################################# \n" - "# HTTP sections \n" - "############################################################################################# \n" - "# api of srs. \n" - "# the http api config, export for external program to manage srs. \n" - "# user can access http api of srs in browser directly, for instance, to access by: \n" - "# curl http://192.168.1.170:1985/api/v1/reload \n" - "# which will reload srs, like cmd killall -1 srs, but the js can also invoke the http api, \n" - "# where the cli can only be used in shell/terminate. \n" - "http_api { \n" - " # whether http api is enabled. \n" - " # default: off \n" - " enabled on; \n" - " # the http api port \n" - " # default: 1985 \n" - " listen 1985; \n" - "} \n" - "# embeded http server in srs. \n" - "# the http streaming config, for HLS/HDS/DASH/HTTPProgressive \n" - "# global config for http streaming, user must config the http section for each vhost. \n" - "# the embed http server used to substitute nginx in ./objs/nginx, \n" - "# for example, srs runing in arm, can provides RTMP and HTTP service, only with srs installed. \n" - "# user can access the http server pages, generally: \n" - "# curl http://192.168.1.170:80/srs.html \n" - "# which will show srs version and welcome to srs. \n" - "# @remark, the http embeded stream need to config the vhost, for instance, the __defaultVhost__ \n" - "# need to open the feature http of vhost. \n" - "http_stream { \n" - " # whether http streaming service is enabled. \n" - " # default: off \n" - " enabled on; \n" - " # the http streaming port \n" - " # @remark, if use lower port, for instance 80, user must start srs by root. \n" - " # default: 8080 \n" - " listen 8080; \n" - " # the default dir for http root. \n" - " # default: ./objs/nginx/html \n" - " dir ./objs/nginx/html; \n" - "} \n" - "# system statistics section. \n" - "# the main cycle will retrieve the system stat, \n" - "# for example, the cpu/mem/network/disk-io data, \n" - "# the http api, for instance, /api/v1/summaries will show these data. \n" - "# @remark the heartbeat depends on the network, \n" - "# for example, the eth0 maybe the device which index is 0. \n" - "stats { \n" - " # the index of device ip. \n" - " # we may retrieve more than one network device. \n" - " # default: 0 \n" - " network 0; \n" - " # the device name to stat the disk iops. \n" - " # ignore the device of /proc/diskstats if not configed. \n" - " disk sda sdb xvda xvdb; \n" - "} \n" - " \n" - "############################################################################################# \n" - "# RTMP/HTTP VHOST sections \n" - "############################################################################################# \n" - "# vhost list, the __defaultVhost__ is the default vhost \n" - "# for example, user use ip to access the stream: rtmp://192.168.1.2/live/livestream. \n" - "# for which cannot identify the required vhost. \n" - "vhost __defaultVhost__ { \n" - "} \n" - " \n" - "# vhost for edge, edge and origin is the same vhost \n" - "vhost same.edge.srs.com { \n" - " # the mode of vhost, local or remote. \n" - " # local: vhost is origin vhost, which provides stream source. \n" - " # remote: vhost is edge vhost, which pull/push to origin. \n" - " # default: local \n" - " mode remote; \n" - " # for edge(remote mode), user must specifies the origin server \n" - " # format as: [:port] \n" - " # @remark user can specifies multiple origin for error backup, by space, \n" - " # for example, 192.168.1.100:1935 192.168.1.101:1935 192.168.1.102:1935 \n" - " origin 127.0.0.1:1935 localhost:1935; \n" - " # for edge, whether open the token traverse mode, \n" - " # if token traverse on, all connections of edge will forward to origin to check(auth), \n" - " # it's very important for the edge to do the token auth. \n" - " # the better way is use http callback to do the token auth by the edge, \n" - " # but if user prefer origin check(auth), the token_traverse if better solution. \n" - " # default: off \n" - " token_traverse off; \n" - "} \n" - "# vhost for edge, change vhost. \n" - "vhost change.edge.srs.com { \n" - " mode remote; \n" - " # TODO: FIXME: support extra params. \n" - " origin 127.0.0.1:1935 localhost:1935 { \n" - " # specify the vhost to override the vhost in client request. \n" - " vhost edge2.srs.com; \n" - " # specify the refer(pageUrl) to override the refer in client request. \n" - " refer http://srs/index.html; \n" - " } \n" - "} \n" - " \n" - "# vhost for dvr \n" - "vhost dvr.srs.com { \n" - " # dvr RTMP stream to file, \n" - " # start to record to file when encoder publish, \n" - " # reap flv according by specified dvr_plan. \n" - " # http callbacks: \n" - " dvr { \n" - " # whether enabled dvr features \n" - " # default: off \n" - " enabled on; \n" - " # the dvr output path. \n" - " # the app dir is auto created under the dvr_path. \n" - " # for example, for rtmp stream: \n" - " # rtmp://127.0.0.1/live/livestream \n" - " # http://127.0.0.1/live/livestream.m3u8 \n" - " # where dvr_path is /dvr, srs will create the following files: \n" - " # /dvr/live the app dir for all streams. \n" - " # /dvr/live/livestream.{time}.flv the dvr flv file. \n" - " # @remark, the time use system timestamp in ms, user can use http callback to rename it. \n" - " # in a word, the dvr_path is for vhost. \n" - " # default: ./objs/nginx/html \n" - " dvr_path ./objs/nginx/html; \n" - " # the dvr plan. canbe: \n" - " # session reap flv when session end(unpublish). \n" - " # segment reap flv when flv duration exceed the specified dvr_duration. \n" - " # default: session \n" - " dvr_plan session; \n" - " # the param for plan(segment), in seconds. \n" - " # default: 30 \n" - " dvr_duration 30; \n" - " dvr_wait_keyframe on; \n" - " # about the stream monotonically increasing: \n" - " # 1. video timestamp is monotonically increasing, \n" - " # 2. audio timestamp is monotonically increasing, \n" - " # 3. video and audio timestamp is interleaved monotonically increasing. \n" - " # it's specified by RTMP specification, @see 3. Byte Order, Alignment, and Time Format \n" - " # however, some encoder cannot provides this feature, please set this to off to ignore time jitter. \n" - " # the time jitter algorithm: \n" - " # 1. full, to ensure stream start at zero, and ensure stream monotonically increasing. \n" - " # 2. zero, only ensure sttream start at zero, ignore timestamp jitter. \n" - " # 3. off, disable the time jitter algorithm, like atc. \n" - " # default: full \n" - " time_jitter full; \n" - " } \n" - "} \n" - " \n" - "# vhost for ingest \n" - "vhost ingest.srs.com { \n" - " # ingest file/stream/device then push to SRS over RTMP. \n" - " # the name/id used to identify the ingest, must be unique in global. \n" - " # ingest id is used in reload or http api management. \n" - " ingest livestream { \n" - " # whether enabled ingest features \n" - " # default: off \n" - " enabled on; \n" - " # input file/stream/device \n" - " # @remark only support one input. \n" - " input { \n" - " # the type of input. \n" - " # can be file/stream/device, that is, \n" - " # file: ingest file specifies by url. \n" - " # stream: ingest stream specifeis by url. \n" - " # device: not support yet. \n" - " # default: file \n" - " type file; \n" - " # the url of file/stream. \n" - " url ./doc/source.200kbps.768x320.flv; \n" - " } \n" - " # the ffmpeg \n" - " ffmpeg ./objs/ffmpeg/bin/ffmpeg; \n" - " # the transcode engine, @see all.transcode.srs.com \n" - " # @remark, the output is specified following. \n" - " engine { \n" - " # @see enabled of transcode engine. \n" - " # if disabled or vcodec/acodec not specified, use copy. \n" - " # default: off. \n" - " enabled off; \n" - " # output stream. variables: \n" - " # [vhost] current vhost which start the ingest. \n" - " # [port] system RTMP stream port. \n" - " output rtmp://127.0.0.1:[port]/live?vhost=[vhost]/livestream; \n" - " } \n" - " } \n" - "} \n" - " \n" - "# vhost for http \n" - "vhost http.srs.com { \n" - " # http vhost specified config \n" - " http { \n" - " # whether enabled the http streaming service for vhost. \n" - " # default: off \n" - " enabled on; \n" - " # the virtual directory root for this vhost to mount at \n" - " # for example, if mount to /hls, user access by http://server/hls \n" - " # default: / \n" - " mount /hls; \n" - " # main dir of vhost, \n" - " # to delivery HTTP stream of this vhost. \n" - " # default: ./objs/nginx/html \n" - " dir ./objs/nginx/html/hls; \n" - " } \n" - "} \n" - " \n" - "# the vhost with hls specified. \n" - "vhost with-hls.srs.com { \n" - " hls { \n" - " # whether the hls is enabled. \n" - " # if off, donot write hls(ts and m3u8) when publish. \n" - " # default: off \n" - " enabled on; \n" - " # the hls output path. \n" - " # the app dir is auto created under the hls_path. \n" - " # for example, for rtmp stream: \n" - " # rtmp://127.0.0.1/live/livestream \n" - " # http://127.0.0.1/live/livestream.m3u8 \n" - " # where hls_path is /hls, srs will create the following files: \n" - " # /hls/live the app dir for all streams. \n" - " # /hls/live/livestream.m3u8 the HLS m3u8 file. \n" - " # /hls/live/livestream-1.ts the HLS media/ts file. \n" - " # in a word, the hls_path is for vhost. \n" - " # default: ./objs/nginx/html \n" - " hls_path ./objs/nginx/html; \n" - " # the hls fragment in seconds, the duration of a piece of ts. \n" - " # default: 10 \n" - " hls_fragment 10; \n" - " # the hls window in seconds, the number of ts in m3u8. \n" - " # default: 60 \n" - " hls_window 60; \n" - " } \n" - "} \n" - "# the vhost with hls disabled. \n" - "vhost no-hls.srs.com { \n" - " hls { \n" - " # whether the hls is enabled. \n" - " # if off, donot write hls(ts and m3u8) when publish. \n" - " # default: off \n" - " enabled off; \n" - " } \n" - "} \n" - " \n" - "# the http hook callback vhost, srs will invoke the hooks for specified events. \n" - "vhost hooks.callback.srs.com { \n" - " http_hooks { \n" - " # whether the http hooks enalbe. \n" - " # default off. \n" - " enabled on; \n" - " # when client connect to vhost/app, call the hook, \n" - " # the request in the POST data string is a object encode by json: \n" - " # { \n" - " # \"action\": \"on_connect\", \n" - " # \"client_id\": 1985, \n" - " # \"ip\": \"192.168.1.10\", \"vhost\": \"video.test.com\", \"app\": \"live\", \n" - " # \"tcUrl\": \"rtmp://video.test.com/live?key=d2fa801d08e3f90ed1e1670e6e52651a\", \n" - " # \"pageUrl\": \"http://www.test.com/live.html\" \n" - " # } \n" - " # if valid, the hook must return HTTP code 200(Stauts OK) and response \n" - " # an int value specifies the error code(0 corresponding to success): \n" - " # 0 \n" - " # support multiple api hooks, format: \n" - " # on_connect http://xxx/api0 http://xxx/api1 http://xxx/apiN \n" - " on_connect http://127.0.0.1:8085/api/v1/clients http://localhost:8085/api/v1/clients; \n" - " # when client close/disconnect to vhost/app/stream, call the hook, \n" - " # the request in the POST data string is a object encode by json: \n" - " # { \n" - " # \"action\": \"on_close\", \n" - " # \"client_id\": 1985, \n" - " # \"ip\": \"192.168.1.10\", \"vhost\": \"video.test.com\", \"app\": \"live\" \n" - " # } \n" - " # if valid, the hook must return HTTP code 200(Stauts OK) and response \n" - " # an int value specifies the error code(0 corresponding to success): \n" - " # 0 \n" - " # support multiple api hooks, format: \n" - " # on_close http://xxx/api0 http://xxx/api1 http://xxx/apiN \n" - " on_close http://127.0.0.1:8085/api/v1/clients http://localhost:8085/api/v1/clients; \n" - " # when client(encoder) publish to vhost/app/stream, call the hook, \n" - " # the request in the POST data string is a object encode by json: \n" - " # { \n" - " # \"action\": \"on_publish\", \n" - " # \"client_id\": 1985, \n" - " # \"ip\": \"192.168.1.10\", \"vhost\": \"video.test.com\", \"app\": \"live\", \n" - " # \"stream\": \"livestream\" \n" - " # } \n" - " # if valid, the hook must return HTTP code 200(Stauts OK) and response \n" - " # an int value specifies the error code(0 corresponding to success): \n" - " # 0 \n" - " # support multiple api hooks, format: \n" - " # on_publish http://xxx/api0 http://xxx/api1 http://xxx/apiN \n" - " on_publish http://127.0.0.1:8085/api/v1/streams http://localhost:8085/api/v1/streams; \n" - " # when client(encoder) stop publish to vhost/app/stream, call the hook, \n" - " # the request in the POST data string is a object encode by json: \n" - " # { \n" - " # \"action\": \"on_unpublish\", \n" - " # \"client_id\": 1985, \n" - " # \"ip\": \"192.168.1.10\", \"vhost\": \"video.test.com\", \"app\": \"live\", \n" - " # \"stream\": \"livestream\" \n" - " # } \n" - " # if valid, the hook must return HTTP code 200(Stauts OK) and response \n" - " # an int value specifies the error code(0 corresponding to success): \n" - " # 0 \n" - " # support multiple api hooks, format: \n" - " # on_unpublish http://xxx/api0 http://xxx/api1 http://xxx/apiN \n" - " on_unpublish http://127.0.0.1:8085/api/v1/streams http://localhost:8085/api/v1/streams; \n" - " # when client start to play vhost/app/stream, call the hook, \n" - " # the request in the POST data string is a object encode by json: \n" - " # { \n" - " # \"action\": \"on_play\", \n" - " # \"client_id\": 1985, \n" - " # \"ip\": \"192.168.1.10\", \"vhost\": \"video.test.com\", \"app\": \"live\", \n" - " # \"stream\": \"livestream\" \n" - " # } \n" - " # if valid, the hook must return HTTP code 200(Stauts OK) and response \n" - " # an int value specifies the error code(0 corresponding to success): \n" - " # 0 \n" - " # support multiple api hooks, format: \n" - " # on_play http://xxx/api0 http://xxx/api1 http://xxx/apiN \n" - " on_play http://127.0.0.1:8085/api/v1/sessions http://localhost:8085/api/v1/sessions; \n" - " # when client stop to play vhost/app/stream, call the hook, \n" - " # the request in the POST data string is a object encode by json: \n" - " # { \n" - " # \"action\": \"on_stop\", \n" - " # \"client_id\": 1985, \n" - " # \"ip\": \"192.168.1.10\", \"vhost\": \"video.test.com\", \"app\": \"live\", \n" - " # \"stream\": \"livestream\" \n" - " # } \n" - " # if valid, the hook must return HTTP code 200(Stauts OK) and response \n" - " # an int value specifies the error code(0 corresponding to success): \n" - " # 0 \n" - " # support multiple api hooks, format: \n" - " # on_stop http://xxx/api0 http://xxx/api1 http://xxx/apiN \n" - " on_stop http://127.0.0.1:8085/api/v1/sessions http://localhost:8085/api/v1/sessions; \n" - " } \n" - "} \n" - " \n" - "# the vhost for min delay, donot cache any stream. \n" - "vhost min.delay.com { \n" - " # whether cache the last gop. \n" - " # if on, cache the last gop and dispatch to client, \n" - " # to enabled fast startup for client, client play immediately. \n" - " # if off, send the latest media data to client, \n" - " # client need to wait for the next Iframe to decode and show the video. \n" - " # set to off if requires min delay; \n" - " # set to on if requires client fast startup. \n" - " # default: on \n" - " gop_cache off; \n" - " # the max live queue length in seconds. \n" - " # if the messages in the queue exceed the max length, \n" - " # drop the old whole gop. \n" - " # default: 30 \n" - " queue_length 10; \n" - "} \n" - " \n" - "# the vhost for srs debug info, whether send args in connect(tcUrl). \n" - "vhost debug.srs.com { \n" - " # when upnode(forward to, edge push to, edge pull from) is srs, \n" - " # it's strongly recommend to open the debug_srs_upnode, \n" - " # when connect to upnode, it will take the debug info, \n" - " # for example, the id, source id, pid. \n" - " # please see: https://github.com/winlinvip/simple-rtmp-server/wiki/v1_CN_SrsLog \n" - " # default: on \n" - " debug_srs_upnode on; \n" - "} \n" - " \n" - "# the vhost for antisuck. \n" - "vhost refer.anti_suck.com { \n" - " # the common refer for play and publish. \n" - " # if the page url of client not in the refer, access denied. \n" - " # if not specified this field, allow all. \n" - " # default: not specified. \n" - " refer github.com github.io; \n" - " # refer for publish clients specified. \n" - " # the common refer is not overrided by this. \n" - " # if not specified this field, allow all. \n" - " # default: not specified. \n" - " refer_publish github.com github.io; \n" - " # refer for play clients specified. \n" - " # the common refer is not overrided by this. \n" - " # if not specified this field, allow all. \n" - " # default: not specified. \n" - " refer_play github.com github.io; \n" - "} \n" - " \n" - "# the vhost which forward publish streams. \n" - "vhost same.vhost.forward.srs.com { \n" - " # forward all publish stream to the specified server. \n" - " # this used to split/forward the current stream for cluster active-standby, \n" - " # active-active for cdn to build high available fault tolerance system. \n" - " # format: {ip}:{port} {ip_N}:{port_N} \n" - " # or specify the vhost by params, @see: change.vhost.forward.srs.com \n" - " # if vhost not specified, use the request vhost instead. \n" - " forward 127.0.0.1:1936 127.0.0.1:1937; \n" - "} \n" - "# TODO: FIXME: support extra params. \n" - "# [plan] the vhost which forward publish streams to other vhosts. \n" - "vhost change.vhost.forward.srs.com { \n" - " forward 127.0.0.1:1936 127.0.0.1:1937 { \n" - " # specify the vhost to override the vhost in client request. \n" - " vhost forward2.srs.com; \n" - " # specify the refer(pageUrl) to override the refer in client request. \n" - " refer http://srs/index.html; \n" - " } \n" - " forward 127.0.0.1:1938 { \n" - " vhost forward3.srs.com; \n" - " } \n" - "} \n" - " \n" - "# the mirror filter of ffmpeg, @see: http://ffmpeg.org/ffmpeg-filters.html#Filtering-Introduction \n" - "vhost mirror.transcode.srs.com { \n" - " transcode { \n" - " enabled on; \n" - " ffmpeg ./objs/ffmpeg/bin/ffmpeg; \n" - " engine mirror { \n" - " enabled on; \n" - " vfilter { \n" - " vf 'split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2'; \n" - " } \n" - " vcodec libx264; \n" - " vbitrate 300; \n" - " vfps 20; \n" - " vwidth 768; \n" - " vheight 320; \n" - " vthreads 2; \n" - " vprofile baseline; \n" - " vpreset superfast; \n" - " vparams { \n" - " } \n" - " acodec libaacplus; \n" - " abitrate 45; \n" - " asample_rate 44100; \n" - " achannels 2; \n" - " aparams { \n" - " } \n" - " output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; \n" - " } \n" - " } \n" - "} \n" - "# \n" - "# the drawtext filter of ffmpeg, @see: http://ffmpeg.org/ffmpeg-filters.html#drawtext-1 \n" - "# remark: we remove the libfreetype which always cause build failed, you must add it manual if needed. \n" - "# \n" - "####################################################################################################### \n" - "# the crop filter of ffmpeg, @see: http://ffmpeg.org/ffmpeg-filters.html#crop \n" - "vhost crop.transcode.srs.com { \n" - " transcode { \n" - " enabled on; \n" - " ffmpeg ./objs/ffmpeg/bin/ffmpeg; \n" - " engine crop { \n" - " enabled on; \n" - " vfilter { \n" - " vf 'crop=in_w-20:in_h-160:10:80'; \n" - " } \n" - " vcodec libx264; \n" - " vbitrate 300; \n" - " vfps 20; \n" - " vwidth 768; \n" - " vheight 320; \n" - " vthreads 2; \n" - " vprofile baseline; \n" - " vpreset superfast; \n" - " vparams { \n" - " } \n" - " acodec libaacplus; \n" - " abitrate 45; \n" - " asample_rate 44100; \n" - " achannels 2; \n" - " aparams { \n" - " } \n" - " output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; \n" - " } \n" - " } \n" - "} \n" - "# the logo filter of ffmpeg, @see: http://ffmpeg.org/ffmpeg-filters.html#overlay \n" - "vhost logo.transcode.srs.com { \n" - " transcode { \n" - " enabled on; \n" - " ffmpeg ./objs/ffmpeg/bin/ffmpeg; \n" - " engine logo { \n" - " enabled on; \n" - " vfilter { \n" - " i ./doc/ffmpeg-logo.png; \n" - " filter_complex 'overlay=10:10'; \n" - " } \n" - " vcodec libx264; \n" - " vbitrate 300; \n" - " vfps 20; \n" - " vwidth 768; \n" - " vheight 320; \n" - " vthreads 2; \n" - " vprofile baseline; \n" - " vpreset superfast; \n" - " vparams { \n" - " } \n" - " acodec libaacplus; \n" - " abitrate 45; \n" - " asample_rate 44100; \n" - " achannels 2; \n" - " aparams { \n" - " } \n" - " output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; \n" - " } \n" - " } \n" - "} \n" - "# audio transcode only. \n" - "# for example, FMLE publish audio codec in mp3, and donot support HLS output, \n" - "# we can transcode the audio to aac and copy video to the new stream with HLS. \n" - "vhost audio.transcode.srs.com { \n" - " transcode { \n" - " enabled on; \n" - " ffmpeg ./objs/ffmpeg/bin/ffmpeg; \n" - " engine acodec { \n" - " enabled on; \n" - " vcodec copy; \n" - " acodec libaacplus; \n" - " abitrate 45; \n" - " asample_rate 44100; \n" - " achannels 2; \n" - " aparams { \n" - " } \n" - " output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; \n" - " } \n" - " } \n" - "} \n" - "# disable video, transcode/copy audio. \n" - "# for example, publish pure audio stream. \n" - "vhost vn.transcode.srs.com { \n" - " transcode { \n" - " enabled on; \n" - " ffmpeg ./objs/ffmpeg/bin/ffmpeg; \n" - " engine vn { \n" - " enabled on; \n" - " vcodec vn; \n" - " acodec libaacplus; \n" - " abitrate 45; \n" - " asample_rate 44100; \n" - " achannels 2; \n" - " aparams { \n" - " } \n" - " output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; \n" - " } \n" - " } \n" - "} \n" - "# ffmpeg-copy(forward implements by ffmpeg). \n" - "# copy the video and audio to a new stream. \n" - "vhost copy.transcode.srs.com { \n" - " transcode { \n" - " enabled on; \n" - " ffmpeg ./objs/ffmpeg/bin/ffmpeg; \n" - " engine copy { \n" - " enabled on; \n" - " vcodec copy; \n" - " acodec copy; \n" - " output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; \n" - " } \n" - " } \n" - "} \n" - "# transcode all app and stream of vhost \n" - "vhost all.transcode.srs.com { \n" - " # the streaming transcode configs. \n" - " transcode { \n" - " # whether the transcode enabled. \n" - " # if off, donot transcode. \n" - " # default: off. \n" - " enabled on; \n" - " # the ffmpeg \n" - " ffmpeg ./objs/ffmpeg/bin/ffmpeg; \n" - " # the transcode engine for matched stream. \n" - " # all matched stream will transcoded to the following stream. \n" - " # the transcode set name(ie. hd) is optional and not used. \n" - " engine ffsuper { \n" - " # whether the engine is enabled \n" - " # default: off. \n" - " enabled on; \n" - " # input format, can be: \n" - " # off, do not specifies the format, ffmpeg will guess it. \n" - " # flv, for flv or RTMP stream. \n" - " # other format, for example, mp4/aac whatever. \n" - " # default: flv \n" - " iformat flv; \n" - " # ffmpeg filters, follows the main input. \n" - " vfilter { \n" - " # the logo input file. \n" - " i ./doc/ffmpeg-logo.png; \n" - " # the ffmpeg complex filter. \n" - " # for filters, @see: http://ffmpeg.org/ffmpeg-filters.html \n" - " filter_complex 'overlay=10:10'; \n" - " } \n" - " # video encoder name. can be: \n" - " # libx264: use h.264(libx264) video encoder. \n" - " # copy: donot encoder the video stream, copy it. \n" - " # vn: disable video output. \n" - " vcodec libx264; \n" - " # video bitrate, in kbps \n" - " vbitrate 1500; \n" - " # video framerate. \n" - " vfps 25; \n" - " # video width, must be even numbers. \n" - " vwidth 768; \n" - " # video height, must be even numbers. \n" - " vheight 320; \n" - " # the max threads for ffmpeg to used. \n" - " vthreads 12; \n" - " # x264 profile, @see x264 -help, can be: \n" - " # high,main,baseline \n" - " vprofile main; \n" - " # x264 preset, @see x264 -help, can be: \n" - " # ultrafast,superfast,veryfast,faster,fast \n" - " # medium,slow,slower,veryslow,placebo \n" - " vpreset medium; \n" - " # other x264 or ffmpeg video params \n" - " vparams { \n" - " # ffmpeg options, @see: http://ffmpeg.org/ffmpeg.html \n" - " t 100; \n" - " # 264 params, @see: http://ffmpeg.org/ffmpeg-codecs.html#libx264 \n" - " coder 1; \n" - " b_strategy 2; \n" - " bf 3; \n" - " refs 10; \n" - " } \n" - " # audio encoder name. can be: \n" - " # libaacplus: use aac(libaacplus) audio encoder. \n" - " # copy: donot encoder the audio stream, copy it. \n" - " # an: disable audio output. \n" - " acodec libaacplus; \n" - " # audio bitrate, in kbps. [16, 72] for libaacplus. \n" - " abitrate 70; \n" - " # audio sample rate. for flv/rtmp, it must be: \n" - " # 44100,22050,11025,5512 \n" - " asample_rate 44100; \n" - " # audio channel, 1 for mono, 2 for stereo. \n" - " achannels 2; \n" - " # other ffmpeg audio params \n" - " aparams { \n" - " # audio params, @see: http://ffmpeg.org/ffmpeg-codecs.html#Audio-Encoders \n" - " profile:a aac_low; \n" - " } \n" - " # output format, can be: \n" - " # off, do not specifies the format, ffmpeg will guess it. \n" - " # flv, for flv or RTMP stream. \n" - " # other format, for example, mp4/aac whatever. \n" - " # default: flv \n" - " oformat flv; \n" - " # output stream. variables: \n" - " # [vhost] the input stream vhost. \n" - " # [port] the intput stream port. \n" - " # [app] the input stream app. \n" - " # [stream] the input stream name. \n" - " # [engine] the tanscode engine name. \n" - " output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; \n" - " } \n" - " engine ffhd { \n" - " enabled on; \n" - " vcodec libx264; \n" - " vbitrate 1200; \n" - " vfps 25; \n" - " vwidth 1382; \n" - " vheight 576; \n" - " vthreads 6; \n" - " vprofile main; \n" - " vpreset medium; \n" - " vparams { \n" - " } \n" - " acodec libaacplus; \n" - " abitrate 70; \n" - " asample_rate 44100; \n" - " achannels 2; \n" - " aparams { \n" - " } \n" - " output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; \n" - " } \n" - " engine ffsd { \n" - " enabled on; \n" - " vcodec libx264; \n" - " vbitrate 800; \n" - " vfps 25; \n" - " vwidth 1152; \n" - " vheight 480; \n" - " vthreads 4; \n" - " vprofile main; \n" - " vpreset fast; \n" - " vparams { \n" - " } \n" - " acodec libaacplus; \n" - " abitrate 60; \n" - " asample_rate 44100; \n" - " achannels 2; \n" - " aparams { \n" - " } \n" - " output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; \n" - " } \n" - " engine fffast { \n" - " enabled on; \n" - " vcodec libx264; \n" - " vbitrate 300; \n" - " vfps 20; \n" - " vwidth 768; \n" - " vheight 320; \n" - " vthreads 2; \n" - " vprofile baseline; \n" - " vpreset superfast; \n" - " vparams { \n" - " } \n" - " acodec libaacplus; \n" - " abitrate 45; \n" - " asample_rate 44100; \n" - " achannels 2; \n" - " aparams { \n" - " } \n" - " output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; \n" - " } \n" - " engine vcopy { \n" - " enabled on; \n" - " vcodec copy; \n" - " acodec libaacplus; \n" - " abitrate 45; \n" - " asample_rate 44100; \n" - " achannels 2; \n" - " aparams { \n" - " } \n" - " output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; \n" - " } \n" - " engine acopy { \n" - " enabled on; \n" - " vcodec libx264; \n" - " vbitrate 300; \n" - " vfps 20; \n" - " vwidth 768; \n" - " vheight 320; \n" - " vthreads 2; \n" - " vprofile baseline; \n" - " vpreset superfast; \n" - " vparams { \n" - " } \n" - " acodec copy; \n" - " output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; \n" - " } \n" - " engine copy { \n" - " enabled on; \n" - " vcodec copy; \n" - " acodec copy; \n" - " output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; \n" - " } \n" - " } \n" - "} \n" - "# transcode all stream using the empty ffmpeg demo, donothing. \n" - "vhost ffempty.transcode.srs.com { \n" - " transcode { \n" - " enabled on; \n" - " ffmpeg ./objs/research/ffempty; \n" - " engine empty { \n" - " enabled on; \n" - " vcodec libx264; \n" - " vbitrate 300; \n" - " vfps 20; \n" - " vwidth 768; \n" - " vheight 320; \n" - " vthreads 2; \n" - " vprofile baseline; \n" - " vpreset superfast; \n" - " vparams { \n" - " } \n" - " acodec libaacplus; \n" - " abitrate 45; \n" - " asample_rate 44100; \n" - " achannels 2; \n" - " aparams { \n" - " } \n" - " output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; \n" - " } \n" - " } \n" - "} \n" - "# transcode all app and stream of app \n" - "vhost app.transcode.srs.com { \n" - " # the streaming transcode configs. \n" - " # if app specified, transcode all streams of app. \n" - " transcode live { \n" - " enabled on; \n" - " ffmpeg ./objs/ffmpeg/bin/ffmpeg; \n" - " engine { \n" - " enabled off; \n" - " } \n" - " } \n" - "} \n" - "# transcode specified stream. \n" - "vhost stream.transcode.srs.com { \n" - " # the streaming transcode configs. \n" - " # if stream specified, transcode the matched stream. \n" - " transcode live/livestream { \n" - " enabled on; \n" - " ffmpeg ./objs/ffmpeg/bin/ffmpeg; \n" - " engine { \n" - " enabled off; \n" - " } \n" - " } \n" - "} \n" - " \n" - "# vhost for bandwidth check \n" - "# generally, the bandcheck vhost must be: bandcheck.srs.com, \n" - "# or need to modify the vhost of client. \n" - "vhost bandcheck.srs.com { \n" - " enabled on; \n" - " chunk_size 65000; \n" - " # bandwidth check config. \n" - " bandcheck { \n" - " # whether support bandwidth check, \n" - " # default: off. \n" - " enabled on; \n" - " # the key for server to valid, \n" - " # if invalid key, server disconnect and abort the bandwidth check. \n" - " key \"35c9b402c12a7246868752e2878f7e0e\"; \n" - " # the interval in seconds for bandwidth check, \n" - " # server donot allow new test request. \n" - " # default: 30 \n" - " interval 30; \n" - " # the max available check bandwidth in kbps. \n" - " # to avoid attack of bandwidth check. \n" - " # default: 1000 \n" - " limit_kbps 4000; \n" - " } \n" - "} \n" - " \n" - "# set the chunk size of vhost. \n" - "vhost chunksize.srs.com { \n" - " # the default chunk size is 128, max is 65536, \n" - " # some client does not support chunk size change, \n" - " # vhost chunk size will override the global value. \n" - " # default: global chunk size. \n" - " chunk_size 128; \n" - "} \n" - " \n" - "# vhost for time jitter \n" - "vhost jitter.srs.com { \n" - " # about the stream monotonically increasing: \n" - " # 1. video timestamp is monotonically increasing, \n" - " # 2. audio timestamp is monotonically increasing, \n" - " # 3. video and audio timestamp is interleaved monotonically increasing. \n" - " # it's specified by RTMP specification, @see 3. Byte Order, Alignment, and Time Format \n" - " # however, some encoder cannot provides this feature, please set this to off to ignore time jitter. \n" - " # the time jitter algorithm: \n" - " # 1. full, to ensure stream start at zero, and ensure stream monotonically increasing. \n" - " # 2. zero, only ensure sttream start at zero, ignore timestamp jitter. \n" - " # 3. off, disable the time jitter algorithm, like atc. \n" - " # default: full \n" - " time_jitter full; \n" - "} \n" - " \n" - "# vhost for atc. \n" - "vhost atc.srs.com { \n" - " # vhost for atc for hls/hds/rtmp backup. \n" - " # generally, atc default to off, server delivery rtmp stream to client(flash) timestamp from 0. \n" - " # when atc is on, server delivery rtmp stream by absolute time. \n" - " # atc is used, for instance, encoder will copy stream to master and slave server, \n" - " # server use atc to delivery stream to edge/client, where stream time from master/slave server \n" - " # is always the same, client/tools can slice RTMP stream to HLS according to the same time, \n" - " # if the time not the same, the HLS stream cannot slice to support system backup. \n" - " # \n" - " # @see http://www.adobe.com/cn/devnet/adobe-media-server/articles/varnish-sample-for-failover.html \n" - " # @see http://www.baidu.com/#wd=hds%20hls%20atc \n" - " # \n" - " # default: off \n" - " atc on; \n" - " # whether enable the auto atc, \n" - " # if enabled, detect the bravo_atc=\"true\" in onMetaData packet, \n" - " # set atc to on if matched. \n" - " # always ignore the onMetaData if atc_auto is off. \n" - " # default: on \n" - " atc_auto on; \n" - "} \n" - " \n" - "# the vhost disabled. \n" - "vhost removed.srs.com { \n" - " # whether the vhost is enabled. \n" - " # if off, all request access denied. \n" - " # default: on \n" - " enabled off; \n" - "} \n" - " \n" - "# config for the pithy print, \n" - "# which always print constant message specified by interval, \n" - "# whatever the clients in concurrency. \n" - "pithy_print { \n" - " # shared print interval for all publish clients, in milliseconds. \n" - " # default: 10000 \n" - " publish 10000; \n" - " # shared print interval for all play clients, in milliseconds. \n" - " # default: 10000 \n" - " play 10000; \n" - " # shared print interval for all forwarders, in milliseconds. \n" - " # default: 10000 \n" - " forwarder 10000; \n" - " # shared print interval for all encoders, in milliseconds. \n" - " # default: 10000 \n" - " encoder 10000; \n" - " # shared print interval for all ingesters, in milliseconds. \n" - " # default: 10000 \n" - " ingester 10000; \n" - " # shared print interval for all hls, in milliseconds. \n" - " # default: 10000 \n" - " hls 10000; \n" - " # shared print interval for all edge, in milliseconds. \n" - " # default: 10000 \n" - " edge 10000; \n" - "} \n" +std::string _full_conf = "" +"listen 1935; \n " +"pid ./objs/srs.pid; \n " +"chunk_size 60000; \n " +"ff_log_dir ./objs; \n " +"srs_log_tank file; \n " +"srs_log_level trace; \n " +"srs_log_file ./objs/srs.log; \n " +"max_connections 1000; \n " +"daemon on; \n " +" \n " +"heartbeat { \n " +" enabled off; \n " +" interval 9.3; \n " +" url http://127.0.0.1:8085/api/v1/servers; \n " +" device_id \"my-srs-device\"; \n " +" summaries off; \n " +"} \n " +" \n " +"stats { \n " +" network 0; \n " +" disk sda sdb xvda xvdb; \n " +"} \n " +" \n " +"http_api { \n " +" enabled on; \n " +" listen 1985; \n " +" crossdomain on; \n " +"} \n " +"http_server { \n " +" enabled on; \n " +" listen 8080; \n " +" dir ./objs/nginx/html; \n " +"} \n " +" \n " +"stream_caster { \n " +" enabled off; \n " +" caster mpegts_over_udp; \n " +" output rtmp://127.0.0.1/live/livestream; \n " +" listen 8935; \n " +" rtp_port_min 57200; \n " +" rtp_port_max 57300; \n " +"} \n " +"stream_caster { \n " +" enabled off; \n " +" caster mpegts_over_udp; \n " +" output rtmp://127.0.0.1/live/livestream; \n " +" listen 8935; \n " +"} \n " +"stream_caster { \n " +" enabled off; \n " +" caster rtsp; \n " +" output rtmp://127.0.0.1/[app]/[stream]; \n " +" listen 554; \n " +" rtp_port_min 57200; \n " +" rtp_port_max 57300; \n " +"} \n " +" \n " +"vhost __defaultVhost__ { \n " +"} \n " +" \n " +"vhost security.srs.com { \n " +" security { \n " +" enabled on; \n " +" allow play all; \n " +" allow publish all; \n " +" } \n " +"} \n " +" \n " +"vhost mrw.srs.com { \n " +" min_latency off; \n " +" mr { \n " +" enabled on; \n " +" latency 350; \n " +" } \n " +" mw_latency 350; \n " +"} \n " +" \n " +"vhost same.edge.srs.com { \n " +" mode remote; \n " +" origin 127.0.0.1:1935 localhost:1935; \n " +" token_traverse off; \n " +"} \n " +" \n " +"vhost dvr.srs.com { \n " +" dvr { \n " +" enabled on; \n " +" dvr_plan session; \n " +" dvr_path ./objs/nginx/html; \n " +" dvr_duration 30; \n " +" dvr_wait_keyframe on; \n " +" time_jitter full; \n " +" \n " +" } \n " +"} \n " +" \n " +"vhost ingest.srs.com { \n " +" ingest livestream { \n " +" enabled on; \n " +" input { \n " +" type file; \n " +" url ./doc/source.200kbps.768x320.flv; \n " +" } \n " +" ffmpeg ./objs/ffmpeg/bin/ffmpeg; \n " +" engine { \n " +" enabled off; \n " +" output rtmp://127.0.0.1:[port]/live?vhost=[vhost]/livestream; \n " +" } \n " +" } \n " +"} \n " +" \n " +"vhost http.static.srs.com { \n " +" http { \n " +" enabled on; \n " +" mount [vhost]/hls; \n " +" dir ./objs/nginx/html/hls; \n " +" } \n " +"} \n " +" \n " +"vhost http.remux.srs.com { \n " +" http_remux { \n " +" enabled on; \n " +" fast_cache 30; \n " +" mount [vhost]/[app]/[stream].flv; \n " +" } \n " +"} \n " +" \n " +"vhost with-hls.srs.com { \n " +" hls { \n " +" enabled on; \n " +" hls_fragment 10; \n " +" hls_td_ratio 1.5; \n " +" hls_window 60; \n " +" hls_on_error ignore; \n " +" hls_storage disk; \n " +" hls_path ./objs/nginx/html; \n " +" hls_mount [vhost]/[app]/[stream].m3u8; \n " +" hls_acodec aac; \n " +" hls_vcodec h264; \n " +" } \n " +"} \n " +"vhost no-hls.srs.com { \n " +" hls { \n " +" enabled off; \n " +" } \n " +"} \n " +" \n " +"vhost hooks.callback.srs.com { \n " +" http_hooks { \n " +" enabled on; \n " +" on_connect http://127.0.0.1:8085/api/v1/clients http://localhost:8085/api/v1/clients; \n " +" on_close http://127.0.0.1:8085/api/v1/clients http://localhost:8085/api/v1/clients; \n " +" on_publish http://127.0.0.1:8085/api/v1/streams http://localhost:8085/api/v1/streams; \n " +" on_unpublish http://127.0.0.1:8085/api/v1/streams http://localhost:8085/api/v1/streams; \n " +" on_play http://127.0.0.1:8085/api/v1/sessions http://localhost:8085/api/v1/sessions; \n " +" on_stop http://127.0.0.1:8085/api/v1/sessions http://localhost:8085/api/v1/sessions; \n " +" on_dvr http://127.0.0.1:8085/api/v1/dvrs http://localhost:8085/api/v1/dvrs; \n " +" } \n " +"} \n " +" \n " +"vhost debug.srs.com { \n " +" debug_srs_upnode on; \n " +"} \n " +" \n " +"vhost min.delay.com { \n " +" min_latency on; \n " +" mr { \n " +" enabled off; \n " +" } \n " +" mw_latency 100; \n " +" gop_cache off; \n " +" queue_length 10; \n " +"} \n " +" \n " +"vhost refer.anti_suck.com { \n " +" refer github.com github.io; \n " +" refer_publish github.com github.io; \n " +" refer_play github.com github.io; \n " +"} \n " +" \n " +"vhost same.vhost.forward.srs.com { \n " +" forward 127.0.0.1:1936 127.0.0.1:1937; \n " +"} \n " +" \n " +"vhost example.transcode.srs.com { \n " +" transcode { \n " +" enabled on; \n " +" ffmpeg ./objs/ffmpeg/bin/ffmpeg; \n " +" engine example { \n " +" enabled on; \n " +" iformat flv; \n " +" vfilter { \n " +" i ./doc/ffmpeg-logo.png; \n " +" filter_complex 'overlay=10:10'; \n " +" } \n " +" vcodec libx264; \n " +" vbitrate 1500; \n " +" vfps 25; \n " +" vwidth 768; \n " +" vheight 320; \n " +" vthreads 12; \n " +" vprofile main; \n " +" vpreset medium; \n " +" vparams { \n " +" t 100; \n " +" coder 1; \n " +" b_strategy 2; \n " +" bf 3; \n " +" refs 10; \n " +" } \n " +" acodec libaacplus; \n " +" abitrate 70; \n " +" asample_rate 44100; \n " +" achannels 2; \n " +" aparams { \n " +" profile:a aac_low; \n " +" } \n " +" oformat flv; \n " +" output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; \n " +" } \n " +" } \n " +"} \n " +"vhost mirror.transcode.srs.com { \n " +" transcode { \n " +" enabled on; \n " +" ffmpeg ./objs/ffmpeg/bin/ffmpeg; \n " +" engine mirror { \n " +" enabled on; \n " +" vfilter { \n " +" vf 'split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2'; \n " +" } \n " +" vcodec libx264; \n " +" vbitrate 300; \n " +" vfps 20; \n " +" vwidth 768; \n " +" vheight 320; \n " +" vthreads 2; \n " +" vprofile baseline; \n " +" vpreset superfast; \n " +" vparams { \n " +" } \n " +" acodec libaacplus; \n " +" abitrate 45; \n " +" asample_rate 44100; \n " +" achannels 2; \n " +" aparams { \n " +" } \n " +" output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; \n " +" } \n " +" } \n " +"} \n " +"vhost crop.transcode.srs.com { \n " +" transcode { \n " +" enabled on; \n " +" ffmpeg ./objs/ffmpeg/bin/ffmpeg; \n " +" engine crop { \n " +" enabled on; \n " +" vfilter { \n " +" vf 'crop=in_w-20:in_h-160:10:80'; \n " +" } \n " +" vcodec libx264; \n " +" vbitrate 300; \n " +" vfps 20; \n " +" vwidth 768; \n " +" vheight 320; \n " +" vthreads 2; \n " +" vprofile baseline; \n " +" vpreset superfast; \n " +" vparams { \n " +" } \n " +" acodec libaacplus; \n " +" abitrate 45; \n " +" asample_rate 44100; \n " +" achannels 2; \n " +" aparams { \n " +" } \n " +" output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; \n " +" } \n " +" } \n " +"} \n " +"vhost logo.transcode.srs.com { \n " +" transcode { \n " +" enabled on; \n " +" ffmpeg ./objs/ffmpeg/bin/ffmpeg; \n " +" engine logo { \n " +" enabled on; \n " +" vfilter { \n " +" i ./doc/ffmpeg-logo.png; \n " +" filter_complex 'overlay=10:10'; \n " +" } \n " +" vcodec libx264; \n " +" vbitrate 300; \n " +" vfps 20; \n " +" vwidth 768; \n " +" vheight 320; \n " +" vthreads 2; \n " +" vprofile baseline; \n " +" vpreset superfast; \n " +" vparams { \n " +" } \n " +" acodec libaacplus; \n " +" abitrate 45; \n " +" asample_rate 44100; \n " +" achannels 2; \n " +" aparams { \n " +" } \n " +" output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; \n " +" } \n " +" } \n " +"} \n " +"vhost audio.transcode.srs.com { \n " +" transcode { \n " +" enabled on; \n " +" ffmpeg ./objs/ffmpeg/bin/ffmpeg; \n " +" engine acodec { \n " +" enabled on; \n " +" vcodec copy; \n " +" acodec libaacplus; \n " +" abitrate 45; \n " +" asample_rate 44100; \n " +" achannels 2; \n " +" aparams { \n " +" } \n " +" output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; \n " +" } \n " +" } \n " +"} \n " +"vhost vn.transcode.srs.com { \n " +" transcode { \n " +" enabled on; \n " +" ffmpeg ./objs/ffmpeg/bin/ffmpeg; \n " +" engine vn { \n " +" enabled on; \n " +" vcodec vn; \n " +" acodec libaacplus; \n " +" abitrate 45; \n " +" asample_rate 44100; \n " +" achannels 2; \n " +" aparams { \n " +" } \n " +" output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; \n " +" } \n " +" } \n " +"} \n " +"vhost copy.transcode.srs.com { \n " +" transcode { \n " +" enabled on; \n " +" ffmpeg ./objs/ffmpeg/bin/ffmpeg; \n " +" engine copy { \n " +" enabled on; \n " +" vcodec copy; \n " +" acodec copy; \n " +" output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; \n " +" } \n " +" } \n " +"} \n " +"vhost all.transcode.srs.com { \n " +" transcode { \n " +" enabled on; \n " +" ffmpeg ./objs/ffmpeg/bin/ffmpeg; \n " +" engine ffsuper { \n " +" enabled on; \n " +" iformat flv; \n " +" vfilter { \n " +" i ./doc/ffmpeg-logo.png; \n " +" filter_complex 'overlay=10:10'; \n " +" } \n " +" vcodec libx264; \n " +" vbitrate 1500; \n " +" vfps 25; \n " +" vwidth 768; \n " +" vheight 320; \n " +" vthreads 12; \n " +" vprofile main; \n " +" vpreset medium; \n " +" vparams { \n " +" t 100; \n " +" coder 1; \n " +" b_strategy 2; \n " +" bf 3; \n " +" refs 10; \n " +" } \n " +" acodec libaacplus; \n " +" abitrate 70; \n " +" asample_rate 44100; \n " +" achannels 2; \n " +" aparams { \n " +" profile:a aac_low; \n " +" } \n " +" oformat flv; \n " +" output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; \n " +" } \n " +" engine ffhd { \n " +" enabled on; \n " +" vcodec libx264; \n " +" vbitrate 1200; \n " +" vfps 25; \n " +" vwidth 1382; \n " +" vheight 576; \n " +" vthreads 6; \n " +" vprofile main; \n " +" vpreset medium; \n " +" vparams { \n " +" } \n " +" acodec libaacplus; \n " +" abitrate 70; \n " +" asample_rate 44100; \n " +" achannels 2; \n " +" aparams { \n " +" } \n " +" output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; \n " +" } \n " +" engine ffsd { \n " +" enabled on; \n " +" vcodec libx264; \n " +" vbitrate 800; \n " +" vfps 25; \n " +" vwidth 1152; \n " +" vheight 480; \n " +" vthreads 4; \n " +" vprofile main; \n " +" vpreset fast; \n " +" vparams { \n " +" } \n " +" acodec libaacplus; \n " +" abitrate 60; \n " +" asample_rate 44100; \n " +" achannels 2; \n " +" aparams { \n " +" } \n " +" output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; \n " +" } \n " +" engine fffast { \n " +" enabled on; \n " +" vcodec libx264; \n " +" vbitrate 300; \n " +" vfps 20; \n " +" vwidth 768; \n " +" vheight 320; \n " +" vthreads 2; \n " +" vprofile baseline; \n " +" vpreset superfast; \n " +" vparams { \n " +" } \n " +" acodec libaacplus; \n " +" abitrate 45; \n " +" asample_rate 44100; \n " +" achannels 2; \n " +" aparams { \n " +" } \n " +" output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; \n " +" } \n " +" engine vcopy { \n " +" enabled on; \n " +" vcodec copy; \n " +" acodec libaacplus; \n " +" abitrate 45; \n " +" asample_rate 44100; \n " +" achannels 2; \n " +" aparams { \n " +" } \n " +" output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; \n " +" } \n " +" engine acopy { \n " +" enabled on; \n " +" vcodec libx264; \n " +" vbitrate 300; \n " +" vfps 20; \n " +" vwidth 768; \n " +" vheight 320; \n " +" vthreads 2; \n " +" vprofile baseline; \n " +" vpreset superfast; \n " +" vparams { \n " +" } \n " +" acodec copy; \n " +" output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; \n " +" } \n " +" engine copy { \n " +" enabled on; \n " +" vcodec copy; \n " +" acodec copy; \n " +" output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; \n " +" } \n " +" } \n " +"} \n " +"vhost ffempty.transcode.srs.com { \n " +" transcode { \n " +" enabled on; \n " +" ffmpeg ./objs/research/ffempty; \n " +" engine empty { \n " +" enabled on; \n " +" vcodec libx264; \n " +" vbitrate 300; \n " +" vfps 20; \n " +" vwidth 768; \n " +" vheight 320; \n " +" vthreads 2; \n " +" vprofile baseline; \n " +" vpreset superfast; \n " +" vparams { \n " +" } \n " +" acodec libaacplus; \n " +" abitrate 45; \n " +" asample_rate 44100; \n " +" achannels 2; \n " +" aparams { \n " +" } \n " +" output rtmp://127.0.0.1:[port]/[app]?vhost=[vhost]/[stream]_[engine]; \n " +" } \n " +" } \n " +"} \n " +"vhost app.transcode.srs.com { \n " +" transcode live { \n " +" enabled on; \n " +" ffmpeg ./objs/ffmpeg/bin/ffmpeg; \n " +" engine { \n " +" enabled off; \n " +" } \n " +" } \n " +"} \n " +"vhost stream.transcode.srs.com { \n " +" transcode live/livestream { \n " +" enabled on; \n " +" ffmpeg ./objs/ffmpeg/bin/ffmpeg; \n " +" engine { \n " +" enabled off; \n " +" } \n " +" } \n " +"} \n " +" \n " +"vhost bandcheck.srs.com { \n " +" enabled on; \n " +" chunk_size 65000; \n " +" bandcheck { \n " +" enabled on; \n " +" key \"35c9b402c12a7246868752e2878f7e0e\"; \n " +" interval 30; \n " +" limit_kbps 4000; \n " +" } \n " +"} \n " +" \n " +"vhost chunksize.srs.com { \n " +" chunk_size 128; \n " +"} \n " +" \n " +"vhost jitter.srs.com { \n " +" time_jitter full; \n " +"} \n " +" \n " +"vhost atc.srs.com { \n " +" atc on; \n " +" atc_auto on; \n " +"} \n " +" \n " +"vhost removed.srs.com { \n " +" enabled off; \n " +"} \n " +" \n " +"pithy_print_ms 10000; \n " ; VOID TEST(ConfigTest, CheckMacros) @@ -1092,7 +682,7 @@ VOID TEST(ConfigTest, CheckMacros) #ifndef SRS_CONF_DEFAULT_TIME_JITTER EXPECT_TRUE(false); #endif -#ifndef SRS_CONF_DEFAULT_QUEUE_LENGTH +#ifndef SRS_PERF_PLAY_QUEUE EXPECT_TRUE(false); #endif #ifndef SRS_CONF_DEFAULT_PAUSED_LENGTH @@ -1131,25 +721,7 @@ VOID TEST(ConfigTest, CheckMacros) #ifndef SRS_CONF_DEFAULT_STATS_NETWORK_DEVICE_INDEX EXPECT_TRUE(false); #endif -#ifndef SRS_CONF_DEFAULT_STAGE_PLAY_USER_INTERVAL_MS - EXPECT_TRUE(false); -#endif -#ifndef SRS_CONF_DEFAULT_STAGE_PUBLISH_USER_INTERVAL_MS - EXPECT_TRUE(false); -#endif -#ifndef SRS_CONF_DEFAULT_STAGE_FORWARDER_INTERVAL_MS - EXPECT_TRUE(false); -#endif -#ifndef SRS_CONF_DEFAULT_STAGE_ENCODER_INTERVAL_MS - EXPECT_TRUE(false); -#endif -#ifndef SRS_CONF_DEFAULT_STAGE_INGESTER_INTERVAL_MS - EXPECT_TRUE(false); -#endif -#ifndef SRS_CONF_DEFAULT_STAGE_HLS_INTERVAL_MS - EXPECT_TRUE(false); -#endif -#ifndef SRS_CONF_DEFAULT_STAGE_EDGE_INTERVAL_MS +#ifndef SRS_CONF_DEFAULT_PITHY_PRINT_MS EXPECT_TRUE(false); #endif #ifndef SRS_CONF_DEFAULT_INGEST_TYPE_FILE @@ -1784,7 +1356,7 @@ VOID TEST(ConfigMainTest, ParseMinConf) MockSrsConfig conf; EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_MIN_OK_CONF)); - vector listens = conf.get_listen(); + vector listens = conf.get_listens(); EXPECT_EQ(1, (int)listens.size()); EXPECT_STREQ("1935", listens.at(0).c_str()); } @@ -1805,9 +1377,9 @@ VOID TEST(ConfigMainTest, ParseInvalidDirective2) VOID TEST(ConfigMainTest, ParseFullConf) { MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(__full_conf)); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_full_conf)); - vector listens = conf.get_listen(); + vector listens = conf.get_listens(); EXPECT_EQ(1, (int)listens.size()); EXPECT_STREQ("1935", listens.at(0).c_str()); @@ -1820,7 +1392,7 @@ VOID TEST(ConfigMainTest, ParseFullConf) EXPECT_EQ(1000, conf.get_max_connections()); EXPECT_TRUE(conf.get_deamon()); - EXPECT_TRUE(conf.get_heartbeat_enabled()); + EXPECT_FALSE(conf.get_heartbeat_enabled()); EXPECT_EQ(9300, conf.get_heartbeat_interval()); EXPECT_STREQ("http://127.0.0.1:8085/api/v1/servers", conf.get_heartbeat_url().c_str()); EXPECT_STREQ("my-srs-device", conf.get_heartbeat_device_id().c_str()); @@ -1831,19 +1403,13 @@ VOID TEST(ConfigMainTest, ParseFullConf) EXPECT_EQ(4, (int)conf.get_stats_disk_device()->args.size()); EXPECT_TRUE(conf.get_http_api_enabled()); - EXPECT_EQ(1985, conf.get_http_api_listen()); + EXPECT_STREQ("1985", conf.get_http_api_listen().c_str()); EXPECT_TRUE(conf.get_http_stream_enabled()); - EXPECT_EQ(8080, conf.get_http_stream_listen()); + EXPECT_STREQ("8080", conf.get_http_stream_listen().c_str()); EXPECT_STREQ("./objs/nginx/html", conf.get_http_stream_dir().c_str()); - EXPECT_EQ(10000, conf.get_pithy_print_publish()); - EXPECT_EQ(10000, conf.get_pithy_print_play()); - EXPECT_EQ(10000, conf.get_pithy_print_forwarder()); - EXPECT_EQ(10000, conf.get_pithy_print_encoder()); - EXPECT_EQ(10000, conf.get_pithy_print_ingester()); - EXPECT_EQ(10000, conf.get_pithy_print_hls()); - EXPECT_EQ(10000, conf.get_pithy_print_edge()); + EXPECT_EQ(10000, conf.get_pithy_print_ms()); EXPECT_TRUE(NULL != conf.get_vhost("__defaultVhost__")); EXPECT_TRUE(NULL != conf.get_vhost("same.edge.srs.com")); @@ -1948,7 +1514,7 @@ VOID TEST(ConfigMainTest, ParseFullConf) EXPECT_TRUE(conf.get_dvr_wait_keyframe(vhost)); EXPECT_TRUE(SrsRtmpJitterAlgorithmFULL == conf.get_dvr_time_jitter(vhost)); EXPECT_FALSE(conf.get_vhost_http_enabled(vhost)); - EXPECT_STREQ("/", conf.get_vhost_http_mount(vhost).c_str()); + EXPECT_STREQ("[vhost]/", conf.get_vhost_http_mount(vhost).c_str()); EXPECT_STREQ("./objs/nginx/html", conf.get_vhost_http_dir(vhost).c_str()); //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// @@ -1959,7 +1525,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_same_edge) { string vhost; MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(__full_conf)); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_full_conf)); vhost = "same.edge.srs.com"; EXPECT_TRUE(conf.get_vhost_enabled(vhost)); @@ -2039,7 +1605,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_change_edge) { string vhost; MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(__full_conf)); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_full_conf)); vhost = "change.edge.srs.com"; // TODO: FIXME: implements it. @@ -2115,7 +1681,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_dvr) { string vhost; MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(__full_conf)); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_full_conf)); vhost = "dvr.srs.com"; EXPECT_TRUE(conf.get_vhost_enabled(vhost)); @@ -2190,7 +1756,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_ingest) { string vhost; MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(__full_conf)); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_full_conf)); vhost = "ingest.srs.com"; EXPECT_TRUE(conf.get_vhost_enabled(vhost)); @@ -2286,7 +1852,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_http) { string vhost; MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(__full_conf)); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_full_conf)); vhost = "http.srs.com"; EXPECT_TRUE(conf.get_vhost_enabled(vhost)); @@ -2355,16 +1921,16 @@ VOID TEST(ConfigMainTest, ParseFullConf_http) EXPECT_EQ(30, conf.get_dvr_duration(vhost)); EXPECT_TRUE(conf.get_dvr_wait_keyframe(vhost)); EXPECT_TRUE(SrsRtmpJitterAlgorithmFULL == conf.get_dvr_time_jitter(vhost)); - EXPECT_TRUE(conf.get_vhost_http_enabled(vhost)); - EXPECT_STREQ("/hls", conf.get_vhost_http_mount(vhost).c_str()); - EXPECT_STREQ("./objs/nginx/html/hls", conf.get_vhost_http_dir(vhost).c_str()); + EXPECT_FALSE(conf.get_vhost_http_enabled(vhost)); + EXPECT_STREQ("[vhost]/", conf.get_vhost_http_mount(vhost).c_str()); + EXPECT_STREQ("./objs/nginx/html", conf.get_vhost_http_dir(vhost).c_str()); } VOID TEST(ConfigMainTest, ParseFullConf_hls_enabled) { string vhost; MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(__full_conf)); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_full_conf)); vhost = "with-hls.srs.com"; EXPECT_TRUE(conf.get_vhost_enabled(vhost)); @@ -2434,7 +2000,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_hls_enabled) EXPECT_TRUE(conf.get_dvr_wait_keyframe(vhost)); EXPECT_TRUE(SrsRtmpJitterAlgorithmFULL == conf.get_dvr_time_jitter(vhost)); EXPECT_FALSE(conf.get_vhost_http_enabled(vhost)); - EXPECT_STREQ("/", conf.get_vhost_http_mount(vhost).c_str()); + EXPECT_STREQ("[vhost]/", conf.get_vhost_http_mount(vhost).c_str()); EXPECT_STREQ("./objs/nginx/html", conf.get_vhost_http_dir(vhost).c_str()); } @@ -2442,7 +2008,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_hls_disabled) { string vhost; MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(__full_conf)); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_full_conf)); vhost = "no-hls.srs.com"; EXPECT_TRUE(conf.get_vhost_enabled(vhost)); @@ -2512,7 +2078,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_hls_disabled) EXPECT_TRUE(conf.get_dvr_wait_keyframe(vhost)); EXPECT_TRUE(SrsRtmpJitterAlgorithmFULL == conf.get_dvr_time_jitter(vhost)); EXPECT_FALSE(conf.get_vhost_http_enabled(vhost)); - EXPECT_STREQ("/", conf.get_vhost_http_mount(vhost).c_str()); + EXPECT_STREQ("[vhost]/", conf.get_vhost_http_mount(vhost).c_str()); EXPECT_STREQ("./objs/nginx/html", conf.get_vhost_http_dir(vhost).c_str()); } @@ -2520,7 +2086,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_http_hooks) { string vhost; MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(__full_conf)); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_full_conf)); vhost = "hooks.callback.srs.com"; EXPECT_TRUE(conf.get_vhost_enabled(vhost)); @@ -2621,7 +2187,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_http_hooks) EXPECT_TRUE(conf.get_dvr_wait_keyframe(vhost)); EXPECT_TRUE(SrsRtmpJitterAlgorithmFULL == conf.get_dvr_time_jitter(vhost)); EXPECT_FALSE(conf.get_vhost_http_enabled(vhost)); - EXPECT_STREQ("/", conf.get_vhost_http_mount(vhost).c_str()); + EXPECT_STREQ("[vhost]/", conf.get_vhost_http_mount(vhost).c_str()); EXPECT_STREQ("./objs/nginx/html", conf.get_vhost_http_dir(vhost).c_str()); } @@ -2629,7 +2195,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_min_delay) { string vhost; MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(__full_conf)); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_full_conf)); vhost = "min.delay.com"; EXPECT_TRUE(conf.get_vhost_enabled(vhost)); @@ -2700,7 +2266,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_min_delay) EXPECT_TRUE(conf.get_dvr_wait_keyframe(vhost)); EXPECT_TRUE(SrsRtmpJitterAlgorithmFULL == conf.get_dvr_time_jitter(vhost)); EXPECT_FALSE(conf.get_vhost_http_enabled(vhost)); - EXPECT_STREQ("/", conf.get_vhost_http_mount(vhost).c_str()); + EXPECT_STREQ("[vhost]/", conf.get_vhost_http_mount(vhost).c_str()); EXPECT_STREQ("./objs/nginx/html", conf.get_vhost_http_dir(vhost).c_str()); } @@ -2708,7 +2274,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_refer_anti_suck) { string vhost; MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(__full_conf)); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_full_conf)); vhost = "refer.anti_suck.com"; EXPECT_TRUE(conf.get_vhost_enabled(vhost)); @@ -2794,7 +2360,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_refer_anti_suck) EXPECT_TRUE(conf.get_dvr_wait_keyframe(vhost)); EXPECT_TRUE(SrsRtmpJitterAlgorithmFULL == conf.get_dvr_time_jitter(vhost)); EXPECT_FALSE(conf.get_vhost_http_enabled(vhost)); - EXPECT_STREQ("/", conf.get_vhost_http_mount(vhost).c_str()); + EXPECT_STREQ("[vhost]/", conf.get_vhost_http_mount(vhost).c_str()); EXPECT_STREQ("./objs/nginx/html", conf.get_vhost_http_dir(vhost).c_str()); } @@ -2802,7 +2368,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_forward_same_vhost) { string vhost; MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(__full_conf)); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_full_conf)); vhost = "same.vhost.forward.srs.com"; EXPECT_TRUE(conf.get_vhost_enabled(vhost)); @@ -2878,7 +2444,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_forward_same_vhost) EXPECT_TRUE(conf.get_dvr_wait_keyframe(vhost)); EXPECT_TRUE(SrsRtmpJitterAlgorithmFULL == conf.get_dvr_time_jitter(vhost)); EXPECT_FALSE(conf.get_vhost_http_enabled(vhost)); - EXPECT_STREQ("/", conf.get_vhost_http_mount(vhost).c_str()); + EXPECT_STREQ("[vhost]/", conf.get_vhost_http_mount(vhost).c_str()); EXPECT_STREQ("./objs/nginx/html", conf.get_vhost_http_dir(vhost).c_str()); } @@ -2886,7 +2452,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_forward_change_vhost) { string vhost; MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(__full_conf)); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_full_conf)); vhost = "change.vhost.forward.srs.com"; // TODO: FIXME: implements it. @@ -2958,7 +2524,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_forward_change_vhost) EXPECT_TRUE(conf.get_dvr_wait_keyframe(vhost)); EXPECT_TRUE(SrsRtmpJitterAlgorithmFULL == conf.get_dvr_time_jitter(vhost)); EXPECT_FALSE(conf.get_vhost_http_enabled(vhost)); - EXPECT_STREQ("/", conf.get_vhost_http_mount(vhost).c_str()); + EXPECT_STREQ("[vhost]/", conf.get_vhost_http_mount(vhost).c_str()); EXPECT_STREQ("./objs/nginx/html", conf.get_vhost_http_dir(vhost).c_str());*/ } @@ -2966,7 +2532,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_mirror) { string vhost; MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(__full_conf)); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_full_conf)); vhost = "mirror.transcode.srs.com"; EXPECT_TRUE(conf.get_vhost_enabled(vhost)); @@ -3048,7 +2614,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_mirror) EXPECT_TRUE(conf.get_dvr_wait_keyframe(vhost)); EXPECT_TRUE(SrsRtmpJitterAlgorithmFULL == conf.get_dvr_time_jitter(vhost)); EXPECT_FALSE(conf.get_vhost_http_enabled(vhost)); - EXPECT_STREQ("/", conf.get_vhost_http_mount(vhost).c_str()); + EXPECT_STREQ("[vhost]/", conf.get_vhost_http_mount(vhost).c_str()); EXPECT_STREQ("./objs/nginx/html", conf.get_vhost_http_dir(vhost).c_str()); } @@ -3056,7 +2622,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_crop) { string vhost; MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(__full_conf)); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_full_conf)); vhost = "crop.transcode.srs.com"; EXPECT_TRUE(conf.get_vhost_enabled(vhost)); @@ -3138,7 +2704,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_crop) EXPECT_TRUE(conf.get_dvr_wait_keyframe(vhost)); EXPECT_TRUE(SrsRtmpJitterAlgorithmFULL == conf.get_dvr_time_jitter(vhost)); EXPECT_FALSE(conf.get_vhost_http_enabled(vhost)); - EXPECT_STREQ("/", conf.get_vhost_http_mount(vhost).c_str()); + EXPECT_STREQ("[vhost]/", conf.get_vhost_http_mount(vhost).c_str()); EXPECT_STREQ("./objs/nginx/html", conf.get_vhost_http_dir(vhost).c_str()); } @@ -3146,7 +2712,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_logo) { string vhost; MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(__full_conf)); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_full_conf)); vhost = "logo.transcode.srs.com"; EXPECT_TRUE(conf.get_vhost_enabled(vhost)); @@ -3228,7 +2794,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_logo) EXPECT_TRUE(conf.get_dvr_wait_keyframe(vhost)); EXPECT_TRUE(SrsRtmpJitterAlgorithmFULL == conf.get_dvr_time_jitter(vhost)); EXPECT_FALSE(conf.get_vhost_http_enabled(vhost)); - EXPECT_STREQ("/", conf.get_vhost_http_mount(vhost).c_str()); + EXPECT_STREQ("[vhost]/", conf.get_vhost_http_mount(vhost).c_str()); EXPECT_STREQ("./objs/nginx/html", conf.get_vhost_http_dir(vhost).c_str()); } @@ -3236,7 +2802,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_audio) { string vhost; MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(__full_conf)); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_full_conf)); vhost = "audio.transcode.srs.com"; EXPECT_TRUE(conf.get_vhost_enabled(vhost)); @@ -3312,7 +2878,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_audio) EXPECT_TRUE(conf.get_dvr_wait_keyframe(vhost)); EXPECT_TRUE(SrsRtmpJitterAlgorithmFULL == conf.get_dvr_time_jitter(vhost)); EXPECT_FALSE(conf.get_vhost_http_enabled(vhost)); - EXPECT_STREQ("/", conf.get_vhost_http_mount(vhost).c_str()); + EXPECT_STREQ("[vhost]/", conf.get_vhost_http_mount(vhost).c_str()); EXPECT_STREQ("./objs/nginx/html", conf.get_vhost_http_dir(vhost).c_str()); } @@ -3320,7 +2886,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_vn) { string vhost; MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(__full_conf)); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_full_conf)); vhost = "vn.transcode.srs.com"; EXPECT_TRUE(conf.get_vhost_enabled(vhost)); @@ -3396,7 +2962,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_vn) EXPECT_TRUE(conf.get_dvr_wait_keyframe(vhost)); EXPECT_TRUE(SrsRtmpJitterAlgorithmFULL == conf.get_dvr_time_jitter(vhost)); EXPECT_FALSE(conf.get_vhost_http_enabled(vhost)); - EXPECT_STREQ("/", conf.get_vhost_http_mount(vhost).c_str()); + EXPECT_STREQ("[vhost]/", conf.get_vhost_http_mount(vhost).c_str()); EXPECT_STREQ("./objs/nginx/html", conf.get_vhost_http_dir(vhost).c_str()); } @@ -3404,7 +2970,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_copy) { string vhost; MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(__full_conf)); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_full_conf)); vhost = "copy.transcode.srs.com"; EXPECT_TRUE(conf.get_vhost_enabled(vhost)); @@ -3476,7 +3042,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_copy) EXPECT_TRUE(conf.get_dvr_wait_keyframe(vhost)); EXPECT_TRUE(SrsRtmpJitterAlgorithmFULL == conf.get_dvr_time_jitter(vhost)); EXPECT_FALSE(conf.get_vhost_http_enabled(vhost)); - EXPECT_STREQ("/", conf.get_vhost_http_mount(vhost).c_str()); + EXPECT_STREQ("[vhost]/", conf.get_vhost_http_mount(vhost).c_str()); EXPECT_STREQ("./objs/nginx/html", conf.get_vhost_http_dir(vhost).c_str()); } @@ -3484,7 +3050,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_all) { string vhost; MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(__full_conf)); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_full_conf)); vhost = "all.transcode.srs.com"; EXPECT_TRUE(conf.get_vhost_enabled(vhost)); @@ -3694,7 +3260,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_all) EXPECT_TRUE(conf.get_dvr_wait_keyframe(vhost)); EXPECT_TRUE(SrsRtmpJitterAlgorithmFULL == conf.get_dvr_time_jitter(vhost)); EXPECT_FALSE(conf.get_vhost_http_enabled(vhost)); - EXPECT_STREQ("/", conf.get_vhost_http_mount(vhost).c_str()); + EXPECT_STREQ("[vhost]/", conf.get_vhost_http_mount(vhost).c_str()); EXPECT_STREQ("./objs/nginx/html", conf.get_vhost_http_dir(vhost).c_str()); } @@ -3702,7 +3268,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_ffempty) { string vhost; MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(__full_conf)); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_full_conf)); vhost = "ffempty.transcode.srs.com"; EXPECT_TRUE(conf.get_vhost_enabled(vhost)); @@ -3784,7 +3350,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_ffempty) EXPECT_TRUE(conf.get_dvr_wait_keyframe(vhost)); EXPECT_TRUE(SrsRtmpJitterAlgorithmFULL == conf.get_dvr_time_jitter(vhost)); EXPECT_FALSE(conf.get_vhost_http_enabled(vhost)); - EXPECT_STREQ("/", conf.get_vhost_http_mount(vhost).c_str()); + EXPECT_STREQ("[vhost]/", conf.get_vhost_http_mount(vhost).c_str()); EXPECT_STREQ("./objs/nginx/html", conf.get_vhost_http_dir(vhost).c_str()); } @@ -3792,7 +3358,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_app) { string vhost; MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(__full_conf)); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_full_conf)); vhost = "app.transcode.srs.com"; EXPECT_TRUE(conf.get_vhost_enabled(vhost)); @@ -3874,7 +3440,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_app) EXPECT_TRUE(conf.get_dvr_wait_keyframe(vhost)); EXPECT_TRUE(SrsRtmpJitterAlgorithmFULL == conf.get_dvr_time_jitter(vhost)); EXPECT_FALSE(conf.get_vhost_http_enabled(vhost)); - EXPECT_STREQ("/", conf.get_vhost_http_mount(vhost).c_str()); + EXPECT_STREQ("[vhost]/", conf.get_vhost_http_mount(vhost).c_str()); EXPECT_STREQ("./objs/nginx/html", conf.get_vhost_http_dir(vhost).c_str()); } @@ -3882,7 +3448,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_stream) { string vhost; MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(__full_conf)); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_full_conf)); vhost = "stream.transcode.srs.com"; EXPECT_TRUE(conf.get_vhost_enabled(vhost)); @@ -3964,7 +3530,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_transcode_stream) EXPECT_TRUE(conf.get_dvr_wait_keyframe(vhost)); EXPECT_TRUE(SrsRtmpJitterAlgorithmFULL == conf.get_dvr_time_jitter(vhost)); EXPECT_FALSE(conf.get_vhost_http_enabled(vhost)); - EXPECT_STREQ("/", conf.get_vhost_http_mount(vhost).c_str()); + EXPECT_STREQ("[vhost]/", conf.get_vhost_http_mount(vhost).c_str()); EXPECT_STREQ("./objs/nginx/html", conf.get_vhost_http_dir(vhost).c_str()); } @@ -3972,7 +3538,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_bandcheck) { string vhost; MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(__full_conf)); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_full_conf)); vhost = "bandcheck.srs.com"; EXPECT_TRUE(conf.get_vhost_enabled(vhost)); @@ -4043,7 +3609,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_bandcheck) EXPECT_TRUE(conf.get_dvr_wait_keyframe(vhost)); EXPECT_TRUE(SrsRtmpJitterAlgorithmFULL == conf.get_dvr_time_jitter(vhost)); EXPECT_FALSE(conf.get_vhost_http_enabled(vhost)); - EXPECT_STREQ("/", conf.get_vhost_http_mount(vhost).c_str()); + EXPECT_STREQ("[vhost]/", conf.get_vhost_http_mount(vhost).c_str()); EXPECT_STREQ("./objs/nginx/html", conf.get_vhost_http_dir(vhost).c_str()); } @@ -4051,7 +3617,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_chunksize) { string vhost; MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(__full_conf)); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_full_conf)); vhost = "chunksize.srs.com"; EXPECT_TRUE(conf.get_vhost_enabled(vhost)); @@ -4122,7 +3688,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_chunksize) EXPECT_TRUE(conf.get_dvr_wait_keyframe(vhost)); EXPECT_TRUE(SrsRtmpJitterAlgorithmFULL == conf.get_dvr_time_jitter(vhost)); EXPECT_FALSE(conf.get_vhost_http_enabled(vhost)); - EXPECT_STREQ("/", conf.get_vhost_http_mount(vhost).c_str()); + EXPECT_STREQ("[vhost]/", conf.get_vhost_http_mount(vhost).c_str()); EXPECT_STREQ("./objs/nginx/html", conf.get_vhost_http_dir(vhost).c_str()); } @@ -4130,7 +3696,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_jitter) { string vhost; MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(__full_conf)); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_full_conf)); vhost = "jitter.srs.com"; EXPECT_TRUE(conf.get_vhost_enabled(vhost)); @@ -4201,7 +3767,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_jitter) EXPECT_TRUE(conf.get_dvr_wait_keyframe(vhost)); EXPECT_TRUE(SrsRtmpJitterAlgorithmFULL == conf.get_dvr_time_jitter(vhost)); EXPECT_FALSE(conf.get_vhost_http_enabled(vhost)); - EXPECT_STREQ("/", conf.get_vhost_http_mount(vhost).c_str()); + EXPECT_STREQ("[vhost]/", conf.get_vhost_http_mount(vhost).c_str()); EXPECT_STREQ("./objs/nginx/html", conf.get_vhost_http_dir(vhost).c_str()); } @@ -4209,7 +3775,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_atc) { string vhost; MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(__full_conf)); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_full_conf)); vhost = "atc.srs.com"; EXPECT_TRUE(conf.get_vhost_enabled(vhost)); @@ -4280,7 +3846,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_atc) EXPECT_TRUE(conf.get_dvr_wait_keyframe(vhost)); EXPECT_TRUE(SrsRtmpJitterAlgorithmFULL == conf.get_dvr_time_jitter(vhost)); EXPECT_FALSE(conf.get_vhost_http_enabled(vhost)); - EXPECT_STREQ("/", conf.get_vhost_http_mount(vhost).c_str()); + EXPECT_STREQ("[vhost]/", conf.get_vhost_http_mount(vhost).c_str()); EXPECT_STREQ("./objs/nginx/html", conf.get_vhost_http_dir(vhost).c_str()); } @@ -4288,7 +3854,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_removed) { string vhost; MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(__full_conf)); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_full_conf)); vhost = "removed.srs.com"; EXPECT_FALSE(conf.get_vhost_enabled(vhost)); @@ -4359,7 +3925,7 @@ VOID TEST(ConfigMainTest, ParseFullConf_removed) EXPECT_TRUE(conf.get_dvr_wait_keyframe(vhost)); EXPECT_TRUE(SrsRtmpJitterAlgorithmFULL == conf.get_dvr_time_jitter(vhost)); EXPECT_FALSE(conf.get_vhost_http_enabled(vhost)); - EXPECT_STREQ("/", conf.get_vhost_http_mount(vhost).c_str()); + EXPECT_STREQ("[vhost]/", conf.get_vhost_http_mount(vhost).c_str()); EXPECT_STREQ("./objs/nginx/html", conf.get_vhost_http_dir(vhost).c_str()); } @@ -5471,82 +5037,12 @@ VOID TEST(ConfigMainTest, CheckConf_pithy_print) { if (true) { MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_MIN_OK_CONF"pithy_print{}")); - } - - if (true) { - MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS != conf.parse(_MIN_OK_CONF"pithy_prints{}")); - } - - if (true) { - MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_MIN_OK_CONF"pithy_print{publish 10000;}")); - } - - if (true) { - MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS != conf.parse(_MIN_OK_CONF"pithy_print{publishs 10000;}")); - } - - if (true) { - MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_MIN_OK_CONF"pithy_print{play 10000;}")); - } - - if (true) { - MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS != conf.parse(_MIN_OK_CONF"pithy_print{plays 10000;}")); - } - - if (true) { - MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_MIN_OK_CONF"pithy_print{forwarder 10000;}")); - } - - if (true) { - MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS != conf.parse(_MIN_OK_CONF"pithy_print{forwarders 10000;}")); - } - - if (true) { - MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_MIN_OK_CONF"pithy_print{encoder 10000;}")); - } - - if (true) { - MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS != conf.parse(_MIN_OK_CONF"pithy_print{encoders 10000;}")); - } - - if (true) { - MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_MIN_OK_CONF"pithy_print{ingester 10000;}")); - } - - if (true) { - MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS != conf.parse(_MIN_OK_CONF"pithy_print{ingesters 10000;}")); - } - - if (true) { - MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_MIN_OK_CONF"pithy_print{hls 10000;}")); - } - - if (true) { - MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS != conf.parse(_MIN_OK_CONF"pithy_print{hlss 10000;}")); - } - - if (true) { - MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_MIN_OK_CONF"pithy_print{edge 10000;}")); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_MIN_OK_CONF"pithy_print_ms 1000;")); } if (true) { MockSrsConfig conf; - EXPECT_TRUE(ERROR_SUCCESS != conf.parse(_MIN_OK_CONF"pithy_print{edges 10000;}")); + EXPECT_TRUE(ERROR_SUCCESS != conf.parse(_MIN_OK_CONF"pithy_print_mss 1000;")); } } diff --git a/trunk/src/utest/srs_utest_config.hpp b/trunk/src/utest/srs_utest_config.hpp index c77f68e967..6635c5cdfa 100644 --- a/trunk/src/utest/srs_utest_config.hpp +++ b/trunk/src/utest/srs_utest_config.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/trunk/src/utest/srs_utest_core.cpp b/trunk/src/utest/srs_utest_core.cpp index 6b1e43bc01..77166b0849 100644 --- a/trunk/src/utest/srs_utest_core.cpp +++ b/trunk/src/utest/srs_utest_core.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/trunk/src/utest/srs_utest_core.hpp b/trunk/src/utest/srs_utest_core.hpp index 9f8cffd91f..0a33b31447 100644 --- a/trunk/src/utest/srs_utest_core.hpp +++ b/trunk/src/utest/srs_utest_core.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/trunk/src/utest/srs_utest_kernel.cpp b/trunk/src/utest/srs_utest_kernel.cpp index c0a9d333e9..74a85ca905 100644 --- a/trunk/src/utest/srs_utest_kernel.cpp +++ b/trunk/src/utest/srs_utest_kernel.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -28,7 +28,7 @@ using namespace std; #include #include #include -#include +#include #include #define MAX_MOCK_DATA_SIZE 1024 * 1024 @@ -203,7 +203,7 @@ int MockBufferReader::read(void* buf, size_t size, ssize_t* nread) VOID TEST(KernelBufferTest, DefaultObject) { - SrsBuffer b; + SrsSimpleBuffer b; EXPECT_EQ(0, b.length()); EXPECT_EQ(NULL, b.bytes()); @@ -211,7 +211,7 @@ VOID TEST(KernelBufferTest, DefaultObject) VOID TEST(KernelBufferTest, AppendBytes) { - SrsBuffer b; + SrsSimpleBuffer b; char winlin[] = "winlin"; b.append(winlin, strlen(winlin)); @@ -231,7 +231,7 @@ VOID TEST(KernelBufferTest, AppendBytes) VOID TEST(KernelBufferTest, EraseBytes) { - SrsBuffer b; + SrsSimpleBuffer b; b.erase(0); b.erase(-1); @@ -265,22 +265,21 @@ VOID TEST(KernelBufferTest, EraseBytes) EXPECT_EQ(0, b.length()); } -VOID TEST(KernelBufferTest, Grow) +VOID TEST(KernelFastBufferTest, Grow) { - SrsBuffer b; + SrsFastBuffer b; MockBufferReader r("winlin"); b.grow(&r, 1); - EXPECT_EQ(6, b.length()); - EXPECT_EQ('w', b.bytes()[0]); + EXPECT_EQ('w', b.read_1byte()); b.grow(&r, 3); - EXPECT_EQ(6, b.length()); - EXPECT_EQ('n', b.bytes()[2]); + b.skip(1); + EXPECT_EQ('n', b.read_1byte()); b.grow(&r, 100); - EXPECT_EQ(102, b.length()); - EXPECT_EQ('l', b.bytes()[99]); + b.skip(99); + EXPECT_EQ('w', b.read_1byte()); } /** @@ -461,7 +460,7 @@ VOID TEST(KernelFlvTest, FlvEncoderWriteMetadata) }; char pts[] = { (char)0x00, (char)0x00, (char)0x00, (char)19 }; - ASSERT_TRUE(ERROR_SUCCESS == enc.write_metadata(md, 8)); + ASSERT_TRUE(ERROR_SUCCESS == enc.write_metadata(18, md, 8)); ASSERT_TRUE(11 + 8 + 4 == fs.offset); EXPECT_TRUE(srs_bytes_equals(tag_header, fs.data, 11)); diff --git a/trunk/src/utest/srs_utest_kernel.hpp b/trunk/src/utest/srs_utest_kernel.hpp index 44b7fd6765..e9433e050b 100644 --- a/trunk/src/utest/srs_utest_kernel.hpp +++ b/trunk/src/utest/srs_utest_kernel.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -31,7 +31,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include -#include +#include class MockBufferReader: public ISrsBufferReader { diff --git a/trunk/src/utest/srs_utest_protocol.cpp b/trunk/src/utest/srs_utest_protocol.cpp index 0c1bd47c8d..c51d962719 100644 --- a/trunk/src/utest/srs_utest_protocol.cpp +++ b/trunk/src/utest/srs_utest_protocol.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -26,13 +26,13 @@ using namespace std; #include #include -#include -#include -#include +#include +#include +#include #include #include -#include -#include +#include +#include MockEmptyIO::MockEmptyIO() { @@ -219,7 +219,7 @@ VOID TEST(ProtocolHandshakeTest, OpensslSha256) (char)0xfd, (char)0x48, (char)0xaa, (char)0xc1, (char)0xfa, (char)0xbf, (char)0x33, (char)0x87, (char)0x5c, (char)0x0d, (char)0xe5, (char)0x34, (char)0x24, (char)0x70, (char)0x14, (char)0x1e, (char)0x4a, (char)0x48, (char)0x07, (char)0x6e, (char)0xaf, (char)0xbf, (char)0xfe, (char)0x34, (char)0x1e, (char)0x1e, (char)0x19, (char)0xfc, (char)0xb5, (char)0x8a, (char)0x4f, (char)0x3c, (char)0xb4, (char)0xcf, (char)0xde, (char)0x24, (char)0x79, (char)0x65, (char)0x17, (char)0x22, (char)0x3f, (char)0xc0, (char)0x06, (char)0x76, (char)0x4e, (char)0x3c, (char)0xfb, (char)0xc3, (char)0xd0, (char)0x7f, (char)0x7b, (char)0x87, (char)0x5c, (char)0xeb, (char)0x97, (char)0x87, }; - char digest[__SRS_OpensslHashSize]; + char digest[SRS_OpensslHashSize]; ASSERT_EQ(ERROR_SUCCESS, openssl_HMACsha256( SrsGenuineFPKey, 30, @@ -285,14 +285,15 @@ VOID TEST(ProtocolHandshakeTest, VerifyFPC0C1) c1s1 c1; // the schema of data must be schema0: key-digest. - ASSERT_EQ(ERROR_SUCCESS, c1.parse(c0c1 + 1, srs_schema0)); + ASSERT_EQ(ERROR_SUCCESS, c1.parse(c0c1 + 1, 1536, srs_schema0)); EXPECT_EQ((int32_t)0x000f64d0, c1.time); EXPECT_EQ((int32_t)0x80000702, c1.version); // manually validate the c1 // @see: calc_c1_digest - char* c1s1_joined_bytes = srs_bytes_join_schema0(c1.time, c1.version, &c1.block0.key, &c1.block1.digest); + char* c1s1_joined_bytes = new char[1536 -32]; SrsAutoFree(char, c1s1_joined_bytes); + ASSERT_EQ(ERROR_SUCCESS, c1.payload->copy_to(&c1, c1s1_joined_bytes, 1536 - 32, false)); bool is_valid; ASSERT_EQ(ERROR_SUCCESS, c1.c1_validate_digest(is_valid)); @@ -304,14 +305,14 @@ VOID TEST(ProtocolHandshakeTest, VerifyFPC0C1) (char)0xf4, (char)0x21, (char)0xa8, (char)0x65, (char)0xce, (char)0xf8, (char)0x8e, (char)0xcc, (char)0x16, (char)0x1e, (char)0xbb, (char)0xd8, (char)0x0e, (char)0xcb, (char)0xd2, (char)0x48, (char)0x37, (char)0xaf, (char)0x4e, (char)0x67, (char)0x45, (char)0xf1, (char)0x79, (char)0x69, (char)0xd2, (char)0xee, (char)0xa4, (char)0xb5, (char)0x01, (char)0xbf, (char)0x57, (char)0x0f, (char)0x68, (char)0x37, (char)0xbe, (char)0x4e, (char)0xff, (char)0xc9, (char)0xb9, (char)0x92, (char)0x23, (char)0x06, (char)0x75, (char)0xa0, (char)0x42, (char)0xe4, (char)0x0a, (char)0x30, (char)0xf0, (char)0xaf, (char)0xb0, (char)0x54, (char)0x88, (char)0x7c, (char)0xc0, (char)0xc1, (char)0x0c, (char)0x6d, (char)0x01, (char)0x36, (char)0x63, (char)0xf3, (char)0x3d, (char)0xbc, (char)0x72, (char)0xf6, (char)0x96, (char)0xc8, (char)0x87, (char)0xab, (char)0x8b, (char)0x0c, (char)0x91, (char)0x2f, (char)0x42, (char)0x2a, (char)0x11, (char)0xf6, (char)0x2d, (char)0x5e }; - EXPECT_TRUE(srs_bytes_equals(c1.block0.key.key, key, 128)); + EXPECT_TRUE(srs_bytes_equals(c1.get_key(), key, 128)); // 32bytes digest char digest[] = { (char)0x6c, (char)0x96, (char)0x9f, (char)0x26, (char)0xeb, (char)0xdc, (char)0x61, (char)0xc4, (char)0x8f, (char)0xd3, (char)0x2b, (char)0x81, (char)0x86, (char)0x6c, (char)0x9c, (char)0xc2, (char)0xb1, (char)0xb5, (char)0xbc, (char)0xa6, (char)0xd6, (char)0xd6, (char)0x1d, (char)0xce, (char)0x93, (char)0x78, (char)0xb3, (char)0xec, (char)0xa8, (char)0x64, (char)0x19, (char)0x13 }; - EXPECT_TRUE(srs_bytes_equals(c1.block1.digest.digest, digest, 32)); + EXPECT_TRUE(srs_bytes_equals(c1.get_digest(), digest, 32)); } VOID TEST(ProtocolHandshakeTest, ComplexHandshake) @@ -348,22 +349,22 @@ VOID TEST(ProtocolHandshakeTest, ComplexHandshake) bool is_valid; c1s1 c1; - ASSERT_EQ(ERROR_SUCCESS, c1.parse(hs_bytes->c0c1 + 1, srs_schema0)); + ASSERT_EQ(ERROR_SUCCESS, c1.parse(hs_bytes->c0c1 + 1, 1536, srs_schema0)); ASSERT_EQ(ERROR_SUCCESS, c1.c1_validate_digest(is_valid)); ASSERT_TRUE(is_valid); c1s1 s1; - ASSERT_EQ(ERROR_SUCCESS, s1.parse(hs_bytes->s0s1s2 + 1, c1.schema)); + ASSERT_EQ(ERROR_SUCCESS, s1.parse(hs_bytes->s0s1s2 + 1, 1536, c1.schema())); ASSERT_EQ(ERROR_SUCCESS, s1.s1_validate_digest(is_valid)); ASSERT_TRUE(is_valid); c2s2 c2; - c2.parse(hs_bytes->c2); + c2.parse(hs_bytes->c2, 1536); ASSERT_EQ(ERROR_SUCCESS, c2.c2_validate(&s1, is_valid)); ASSERT_TRUE(is_valid); c2s2 s2; - s2.parse(hs_bytes->s0s1s2 + 1 + 1536); + s2.parse(hs_bytes->s0s1s2 + 1 + 1536, 1536); ASSERT_EQ(ERROR_SUCCESS, s2.s2_validate(&c1, is_valid)); ASSERT_TRUE(is_valid); } @@ -529,7 +530,7 @@ VOID TEST(ProtocolMsgArrayTest, MessageArray) EXPECT_EQ(0, msg.count()); if (true) { - SrsSharedPtrMessageArray arr(3); + SrsMessageArray arr(3); arr.msgs[0] = msg.copy(); EXPECT_EQ(1, msg.count()); @@ -540,18 +541,18 @@ VOID TEST(ProtocolMsgArrayTest, MessageArray) arr.msgs[2] = msg.copy(); EXPECT_EQ(3, msg.count()); } - EXPECT_EQ(0, msg.count()); + EXPECT_EQ(3, msg.count()); if (true) { - SrsSharedPtrMessageArray arr(3); + SrsMessageArray arr(3); arr.msgs[0] = msg.copy(); - EXPECT_EQ(1, msg.count()); + EXPECT_EQ(4, msg.count()); arr.msgs[2] = msg.copy(); - EXPECT_EQ(2, msg.count()); + EXPECT_EQ(5, msg.count()); } - EXPECT_EQ(0, msg.count()); + EXPECT_EQ(5, msg.count()); } /** @@ -634,9 +635,9 @@ VOID TEST(ProtocolStackTest, ProtocolRecvMessage) }; bio.in_buffer.append(data, sizeof(data)); - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); SrsPacket* pkt = NULL; EXPECT_TRUE(ERROR_SUCCESS == proto.decode_message(msg, &pkt)); @@ -653,7 +654,7 @@ VOID TEST(ProtocolStackTest, ProtocolRecvMessage) // 0x04 where: message_type=4(protocol control user-control message) // 0x00 0x06 where: event Ping(0x06) // 0x00 0x00 0x0d 0x0f where: event data 4bytes ping timestamp. -// @see: https://github.com/winlinvip/simple-rtmp-server/issues/98 +// @see: https://github.com/simple-rtmp-server/srs/issues/98 VOID TEST(ProtocolStackTest, ProtocolRecvMessageBug98) { MockBufferIO bio; @@ -670,9 +671,9 @@ VOID TEST(ProtocolStackTest, ProtocolRecvMessageBug98) }; bio.in_buffer.append(data, sizeof(data)); - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); SrsPacket* pkt = NULL; EXPECT_TRUE(ERROR_SUCCESS == proto.decode_message(msg, &pkt)); @@ -705,9 +706,9 @@ VOID TEST(ProtocolStackTest, ProtocolRecvAckSizeMessage) }; bio.in_buffer.append(data, sizeof(data)); - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); SrsPacket* pkt = NULL; EXPECT_TRUE(ERROR_SUCCESS == proto.decode_message(msg, &pkt)); @@ -739,9 +740,9 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVMessage) }; bio.in_buffer.append(data, sizeof(data)); - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); } @@ -766,9 +767,9 @@ VOID TEST(ProtocolStackTest, ProtocolRecvAMessage) }; bio.in_buffer.append(data, sizeof(data)); - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_audio()); } @@ -812,9 +813,9 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVMessage2Trunk) }; bio.in_buffer.append(data, sizeof(data)); - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); } @@ -903,15 +904,15 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVAMessage) } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_audio()); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); } } @@ -1021,15 +1022,15 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVAFmt1) } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_audio()); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); } } @@ -1137,15 +1138,15 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVAFmt2) } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_audio()); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); } } @@ -1252,15 +1253,15 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVAFmt3) } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_audio()); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); } } @@ -1395,25 +1396,25 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVAVMessage) } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x10, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_audio()); EXPECT_EQ(0x15, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x20, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); @@ -1563,25 +1564,25 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVAVFmt1) } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x10, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_audio()); EXPECT_EQ(0x15, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x22, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); @@ -1729,25 +1730,25 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVAVFmt2) } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x10, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_audio()); EXPECT_EQ(0x15, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x22, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); @@ -1894,25 +1895,25 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVAVFmt3) } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x10, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_audio()); EXPECT_EQ(0x15, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x20, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); @@ -2094,33 +2095,33 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVAVVMessage) } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x10, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_audio()); EXPECT_EQ(0x15, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x20, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x30, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); @@ -2314,33 +2315,33 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVAVVFmt1) } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x10, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_audio()); EXPECT_EQ(0x15, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x20, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x30, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); @@ -2530,33 +2531,33 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVAVVFmt2) } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x10, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_audio()); EXPECT_EQ(0x15, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x20, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x30, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); @@ -2744,33 +2745,33 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVAVVFmt3) } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x10, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_audio()); EXPECT_EQ(0x15, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x20, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x30, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); @@ -2964,33 +2965,33 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVAVVFmt11) } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x10, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_audio()); EXPECT_EQ(0x15, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x20, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x40, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); @@ -3186,33 +3187,33 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVAVVFmt11Length) } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x10, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_audio()); EXPECT_EQ(0x15, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x20, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x40, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); @@ -3404,33 +3405,33 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVAVVFmt12) } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x10, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_audio()); EXPECT_EQ(0x15, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x20, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x40, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); @@ -3625,35 +3626,35 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVAVVFmt12Length) } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x10, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); EXPECT_EQ(0x110, msg->header.payload_length); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_audio()); EXPECT_EQ(0x15, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x20, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); EXPECT_EQ(0x120, msg->header.payload_length); } if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x40, msg->header.timestamp); EXPECT_EQ(0x01, msg->header.stream_id); @@ -3696,9 +3697,9 @@ VOID TEST(ProtocolStackTest, ProtocolRecvExtTimeMessage) }; bio.in_buffer.append(data, sizeof(data)); - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x10, msg->header.timestamp); } @@ -3738,9 +3739,9 @@ VOID TEST(ProtocolStackTest, ProtocolRecvExtTimeMessage2) }; bio.in_buffer.append(data, sizeof(data)); - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); EXPECT_EQ(0x7f010203, msg->header.timestamp); } @@ -3750,7 +3751,7 @@ VOID TEST(ProtocolStackTest, ProtocolRecvExtTimeMessage2) * always use 31bits timestamp. */ // always use 31bits timestamp, for some server may use 32bits extended timestamp. -// @see https://github.com/winlinvip/simple-rtmp-server/issues/111 +// @see https://github.com/simple-rtmp-server/srs/issues/111 VOID TEST(ProtocolStackTest, ProtocolRecvExtTimeMessage3) { MockBufferIO bio; @@ -3782,9 +3783,9 @@ VOID TEST(ProtocolStackTest, ProtocolRecvExtTimeMessage3) }; bio.in_buffer.append(data, sizeof(data)); - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); // always use 31bits timestamp EXPECT_EQ(0x7f010203, msg->header.timestamp); @@ -3855,9 +3856,9 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVExtTime2Trunk) }; bio.in_buffer.append(data, sizeof(data)); - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); // 0xCX with extended timestamp. EXPECT_EQ(0x00010203, msg->header.timestamp); @@ -3910,9 +3911,9 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVExtTime2Trunk2) }; bio.in_buffer.append(data, sizeof(data)); - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); // 0xCX without extended timestamp. EXPECT_EQ(0x00010203, msg->header.timestamp); @@ -3961,9 +3962,9 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVCid1BMin) }; bio.in_buffer.append(data, sizeof(data)); - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); // 1B cid(6bits), min is 2 EXPECT_EQ(0x02, msg->header.perfer_cid); @@ -4012,9 +4013,9 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVCid1BNormal) }; bio.in_buffer.append(data, sizeof(data)); - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); // 1B cid(6bits), cid in 2-63 EXPECT_EQ(0x09, msg->header.perfer_cid); @@ -4063,9 +4064,9 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVCid1BMax) }; bio.in_buffer.append(data, sizeof(data)); - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); // 1B cid(6bits), max is 63 EXPECT_EQ(0x3F, msg->header.perfer_cid); @@ -4114,9 +4115,9 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVCid2BMin) }; bio.in_buffer.append(data, sizeof(data)); - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); // 2B cid(8bits), min is 64 EXPECT_EQ(64, msg->header.perfer_cid); @@ -4165,9 +4166,9 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVCid2BNormal) }; bio.in_buffer.append(data, sizeof(data)); - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); // 2B cid(8bits), cid in 64-319 EXPECT_EQ(0x10+64, msg->header.perfer_cid); @@ -4216,9 +4217,9 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVCid2BNormal2) }; bio.in_buffer.append(data, sizeof(data)); - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); // 2B cid(8bits), cid in 64-319 EXPECT_EQ(0x11+64, msg->header.perfer_cid); @@ -4267,9 +4268,9 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVCid2BMax) }; bio.in_buffer.append(data, sizeof(data)); - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); // 2B cid(68bits), max is 319 EXPECT_EQ(319, msg->header.perfer_cid); @@ -4318,9 +4319,9 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVCid3BMin) }; bio.in_buffer.append(data, sizeof(data)); - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); // 3B cid(16bits), min is 64 EXPECT_EQ(64, msg->header.perfer_cid); @@ -4369,9 +4370,9 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVCid3BNormal) }; bio.in_buffer.append(data, sizeof(data)); - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); // 3B cid(16bits), cid in 64-65599 EXPECT_EQ(0x10*256+64, msg->header.perfer_cid); @@ -4420,9 +4421,9 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVCid3BNormal2) }; bio.in_buffer.append(data, sizeof(data)); - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); // 3B cid(16bits), cid in 64-65599 EXPECT_EQ(0x01 + (0x10*256) + 64, msg->header.perfer_cid); @@ -4471,9 +4472,9 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVCid3BNormal3) }; bio.in_buffer.append(data, sizeof(data)); - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); // 3B cid(16bits), cid in 64-65599 EXPECT_EQ(0xFF + (0x10*256) + 64, msg->header.perfer_cid); @@ -4522,9 +4523,9 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVCid3BNormal4) }; bio.in_buffer.append(data, sizeof(data)); - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); // 3B cid(16bits), cid in 64-65599 EXPECT_EQ(0x02 + (0x10*256) + 64, msg->header.perfer_cid); @@ -4573,9 +4574,9 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVCid3BMax) }; bio.in_buffer.append(data, sizeof(data)); - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); // 2B cid(16bits), max is 65599 EXPECT_EQ(65599, msg->header.perfer_cid); @@ -4611,9 +4612,9 @@ VOID TEST(ProtocolStackTest, ProtocolRecvV0LenMessage) }; bio.in_buffer.append(data, sizeof(data)); - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_video()); // protocol stack will ignore the empty video message. EXPECT_EQ(4, msg->header.payload_length); @@ -4629,12 +4630,15 @@ VOID TEST(ProtocolStackTest, ProtocolSendVMessage) char data[] = {0x01, 0x02, 0x03, 0x04}; - SrsMessage* msg = new SrsCommonMessage(); + SrsCommonMessage* msg = new SrsCommonMessage(); msg->size = sizeof(data); msg->payload = new char[msg->size]; memcpy(msg->payload, data, msg->size); - EXPECT_TRUE(ERROR_SUCCESS == proto.send_and_free_message(msg, 0)); + SrsSharedPtrMessage m; + ASSERT_TRUE(ERROR_SUCCESS == m.create(msg)); + + EXPECT_TRUE(ERROR_SUCCESS == proto.send_and_free_message(m.copy(), 0)); EXPECT_EQ(16, bio.out_buffer.length()); } @@ -5319,7 +5323,7 @@ VOID TEST(ProtocolStackTest, ProtocolRecvVMessageFmtInvalid) }; bio.in_buffer.append(data, sizeof(data)); - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; EXPECT_FALSE(ERROR_SUCCESS == proto.recv_message(&msg)); } @@ -5339,14 +5343,17 @@ VOID TEST(ProtocolStackTest, ProtocolAckSizeFlow) } if (true) { - SrsMessage* msg = new SrsCommonMessage(); + SrsCommonMessage* msg = new SrsCommonMessage(); msg->header.payload_length = msg->size = 4096; msg->payload = new char[msg->size]; msg->header.message_type = 9; EXPECT_TRUE(msg->header.is_video()); + + SrsSharedPtrMessage m; + ASSERT_TRUE(ERROR_SUCCESS == m.create(msg)); - EXPECT_TRUE(ERROR_SUCCESS == proto.send_and_free_message(msg, 1)); + EXPECT_TRUE(ERROR_SUCCESS == proto.send_and_free_message(m.copy(), 1)); } // copy output to input @@ -5357,16 +5364,16 @@ VOID TEST(ProtocolStackTest, ProtocolAckSizeFlow) // recv SrsSetWindowAckSizePacket if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); ASSERT_TRUE(msg->header.is_window_ackledgement_size()); } // recv video if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); ASSERT_TRUE(msg->header.is_video()); } @@ -5377,22 +5384,25 @@ VOID TEST(ProtocolStackTest, ProtocolAckSizeFlow) } // recv auto send acked size. #1 if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); ASSERT_TRUE(msg->header.is_ackledgement()); } // send again if (true) { - SrsMessage* msg = new SrsCommonMessage(); + SrsCommonMessage* msg = new SrsCommonMessage(); msg->header.payload_length = msg->size = 4096; msg->payload = new char[msg->size]; msg->header.message_type = 9; EXPECT_TRUE(msg->header.is_video()); + + SrsSharedPtrMessage m; + ASSERT_TRUE(ERROR_SUCCESS == m.create(msg)); - EXPECT_TRUE(ERROR_SUCCESS == proto.send_and_free_message(msg, 1)); + EXPECT_TRUE(ERROR_SUCCESS == proto.send_and_free_message(m.copy(), 1)); } // copy output to input if (true) { @@ -5401,9 +5411,9 @@ VOID TEST(ProtocolStackTest, ProtocolAckSizeFlow) } // recv video if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); ASSERT_TRUE(msg->header.is_video()); } @@ -5414,9 +5424,9 @@ VOID TEST(ProtocolStackTest, ProtocolAckSizeFlow) } // recv auto send acked size. #2 if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); ASSERT_TRUE(msg->header.is_ackledgement()); } } @@ -5443,9 +5453,9 @@ VOID TEST(ProtocolStackTest, ProtocolPingFlow) } // recv ping if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); EXPECT_TRUE(msg->header.is_user_control_message()); } @@ -5457,9 +5467,9 @@ VOID TEST(ProtocolStackTest, ProtocolPingFlow) } // recv ping if (true) { - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.recv_message(&msg)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); ASSERT_TRUE(msg->header.is_user_control_message()); SrsPacket* pkt = NULL; @@ -5518,10 +5528,10 @@ VOID TEST(ProtocolStackTest, ProtocolExcpectMessage) }; bio.in_buffer.append(data, sizeof(data)); - SrsMessage* msg = NULL; + SrsCommonMessage* msg = NULL; SrsConnectAppPacket* pkt = NULL; ASSERT_TRUE(ERROR_SUCCESS == proto.expect_message(&msg, &pkt)); - SrsAutoFree(SrsMessage, msg); + SrsAutoFree(SrsCommonMessage, msg); SrsAutoFree(SrsConnectAppPacket, pkt); ASSERT_TRUE(NULL != pkt); } diff --git a/trunk/src/utest/srs_utest_protocol.hpp b/trunk/src/utest/srs_utest_protocol.hpp index 8ffb69112a..ac97ea2d45 100644 --- a/trunk/src/utest/srs_utest_protocol.hpp +++ b/trunk/src/utest/srs_utest_protocol.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -30,17 +30,17 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include -#include +#include -#include -#include -#include +#include +#include +#include #ifdef SRS_AUTO_SSL using namespace _srs_internal; #endif -#include +#include class MockEmptyIO : public ISrsProtocolReaderWriter { @@ -78,9 +78,9 @@ class MockBufferIO : public ISrsProtocolReaderWriter int64_t recv_bytes; int64_t send_bytes; // data source for socket read. - SrsBuffer in_buffer; + SrsSimpleBuffer in_buffer; // data buffer for socket send. - SrsBuffer out_buffer; + SrsSimpleBuffer out_buffer; public: MockBufferIO(); virtual ~MockBufferIO(); diff --git a/trunk/src/utest/srs_utest_reload.cpp b/trunk/src/utest/srs_utest_reload.cpp index ae4c60e940..e964b1b7c8 100644 --- a/trunk/src/utest/srs_utest_reload.cpp +++ b/trunk/src/utest/srs_utest_reload.cpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -266,6 +266,12 @@ int MockReloadHandler::on_reload_vhost_hls(string /*vhost*/) return ERROR_SUCCESS; } +int MockReloadHandler::on_reload_vhost_hds(string /*vhost*/) +{ + vhost_hls_reloaded = true; + return ERROR_SUCCESS; +} + int MockReloadHandler::on_reload_vhost_dvr(string /*vhost*/) { vhost_dvr_reloaded = true; @@ -460,17 +466,17 @@ VOID TEST(ConfigReloadTest, ReloadPithyPrint) MockSrsReloadConfig conf; conf.subscribe(&handler); - EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_MIN_OK_CONF"pithy_print {publish 1000;}")); - EXPECT_TRUE(ERROR_SUCCESS == conf.reload(_MIN_OK_CONF"pithy_print {publish 1000;}")); + EXPECT_TRUE(ERROR_SUCCESS == conf.parse(_MIN_OK_CONF"pithy_print_ms 1000;")); + EXPECT_TRUE(ERROR_SUCCESS == conf.reload(_MIN_OK_CONF"pithy_print_ms 1000;")); EXPECT_TRUE(handler.all_false()); handler.reset(); - EXPECT_TRUE(ERROR_SUCCESS == conf.reload(_MIN_OK_CONF"pithy_print {publish 2000;}")); + EXPECT_TRUE(ERROR_SUCCESS == conf.reload(_MIN_OK_CONF"pithy_print_ms 2000;")); EXPECT_TRUE(handler.pithy_print_reloaded); EXPECT_EQ(1, handler.count_true()); handler.reset(); - EXPECT_TRUE(ERROR_SUCCESS == conf.reload(_MIN_OK_CONF"pithy_print {publish 1000;}")); + EXPECT_TRUE(ERROR_SUCCESS == conf.reload(_MIN_OK_CONF"pithy_print_ms 1000;")); EXPECT_EQ(1, handler.count_true()); handler.reset(); } diff --git a/trunk/src/utest/srs_utest_reload.hpp b/trunk/src/utest/srs_utest_reload.hpp index 9aa724ae83..3824546100 100644 --- a/trunk/src/utest/srs_utest_reload.hpp +++ b/trunk/src/utest/srs_utest_reload.hpp @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2013-2014 winlin +Copyright (c) 2013-2015 SRS(simple-rtmp-server) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -91,6 +91,7 @@ class MockReloadHandler : public ISrsReloadHandler virtual int on_reload_vhost_time_jitter(std::string vhost); virtual int on_reload_vhost_forward(std::string vhost); virtual int on_reload_vhost_hls(std::string vhost); + virtual int on_reload_vhost_hds(std::string vhost); virtual int on_reload_vhost_dvr(std::string vhost); virtual int on_reload_vhost_transcode(std::string vhost); virtual int on_reload_ingest_removed(std::string vhost, std::string ingest_id);