From 5d50bdc3fc21d613aa257667710b0861c7346d27 Mon Sep 17 00:00:00 2001 From: winlin Date: Tue, 10 Dec 2013 09:20:11 +0800 Subject: [PATCH 01/37] update build ffmpeg script bug for ubuntu, remove the opencl from x264 --- trunk/auto/build_ffmpeg.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trunk/auto/build_ffmpeg.sh b/trunk/auto/build_ffmpeg.sh index 04de4b01a..439f73ae9 100644 --- a/trunk/auto/build_ffmpeg.sh +++ b/trunk/auto/build_ffmpeg.sh @@ -59,7 +59,7 @@ else echo "build x264" 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 && ./configure --prefix=${ff_release_dir} --bit-depth=8 --enable-static && make && make install + cd x264-snapshot-20131129-2245-stable && ./configure --prefix=${ff_release_dir} --disable-opencl --bit-depth=8 --enable-static && make && make install ret=$?; if [[ 0 -ne ${ret} ]]; then echo "build x264 failed"; exit 1; fi fi From bb908814893d84bb946ceaacd43b4a1870460c3b Mon Sep 17 00:00:00 2001 From: winlin Date: Tue, 10 Dec 2013 10:09:29 +0800 Subject: [PATCH 02/37] auto install depends software for ubuntu or centos. --- README.md | 6 ++ trunk/auto/depends.sh | 163 ++++++++++++++++++++++++++++++++++++++++++ trunk/conf/srs.conf | 6 +- trunk/configure | 11 +-- 4 files changed, 178 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 6f67e4d69..08b2917c8 100755 --- a/README.md +++ b/README.md @@ -59,22 +59,28 @@ step 8: play live stream.
 rtmp url: rtmp://demo:1935/live/livestream
 m3u8 url: http://demo:80/live/livestream.m3u8
+for android: http://demo:80/live/livestream.html
 
step 9: play live stream auto transcoded
 rtmp url: rtmp://demo:1935/live/livestream_ld
 m3u8 url: http://demo:80/live/livestream_ld.m3u8
+for android: http://demo:80/live/livestream_ld.html
 rtmp url: rtmp://demo:1935/live/livestream_sd
 m3u8 url: http://demo:80/live/livestream_sd.m3u8
+for android: http://demo:80/live/livestream_sd.html
 
step 10: play live stream auto forwarded, the hls dir change to /forward
 rtmp url: rtmp://demo:19350/live/livestream
 m3u8 url: http://demo:80/forward/live/livestream.m3u8
+for android: http://demo:80/forward/live/livestream.html
 rtmp url: rtmp://demo:19350/live/livestream_ld
 m3u8 url: http://demo:80/forward/live/livestream_ld.m3u8
+for android: http://demo:80/forward/live/livestream_ld.html
 rtmp url: rtmp://demo:19350/live/livestream_sd
 m3u8 url: http://demo:80/forward/live/livestream_sd.m3u8
+for android: http://demo:80/forward/live/livestream_sd.html
 
### Architecture diff --git a/trunk/auto/depends.sh b/trunk/auto/depends.sh index 1024bdea7..cc873a377 100755 --- a/trunk/auto/depends.sh +++ b/trunk/auto/depends.sh @@ -3,6 +3,150 @@ # TODO: check gcc/g++ echo "check gcc/g++/gdb/make/openssl-devel" echo "depends tools are ok" +##################################################################################### +# for Ubuntu +##################################################################################### +function Ubuntu_prepare() +{ + uname -v|grep Ubuntu >/dev/null 2>&1 + ret=$?; if [[ 0 -ne $ret ]]; then + return; + fi + + echo "Ubuntu detected, install tools if needed" + + apt-cache show libpcre3; ret=$?; if [[ 0 -ne $ret ]]; then + echo "install libpcre3" + require_sudoer "sudo apt-get install -y libpcre3" + sudo apt-get install -y libpcre3 + echo "install libpcre3 success" + fi + + apt-cache show libpcre3-dev; ret=$?; if [[ 0 -ne $ret ]]; then + echo "install libpcre3-dev" + require_sudoer "sudo apt-get install -y libpcre3-dev" + sudo apt-get install -y libpcre3-dev + echo "install libpcre3-dev success" + fi + + apt-cache show zlib1g-dev; ret=$?; if [[ 0 -ne $ret ]]; then + echo "install zlib1g-dev" + require_sudoer "sudo apt-get install -y zlib1g-dev" + sudo apt-get install -y zlib1g-dev + echo "install zlib1g-dev success" + fi + + apt-cache show libfreetype6-dev; ret=$?; if [[ 0 -ne $ret ]]; then + echo "install libfreetype6-dev" + require_sudoer "sudo apt-get install -y libfreetype6-dev" + sudo apt-get install -y libfreetype6-dev + echo "install libfreetype6-dev success" + fi + + apt-cache show gcc; ret=$?; if [[ 0 -ne $ret ]]; then + echo "install gcc" + require_sudoer "sudo apt-get install -y gcc" + sudo apt-get install -y gcc + echo "install gcc success" + fi + + apt-cache show g++; ret=$?; if [[ 0 -ne $ret ]]; then + echo "install g++" + require_sudoer "sudo apt-get install -y g++" + sudo apt-get install -y g++ + echo "install g++ success" + fi + + apt-cache show make; ret=$?; if [[ 0 -ne $ret ]]; then + echo "install make" + require_sudoer "sudo apt-get install -y make" + sudo apt-get install -y make + echo "install make success" + fi + + apt-cache show autoconf; ret=$?; if [[ 0 -ne $ret ]]; then + echo "install autoconf" + require_sudoer "sudo apt-get install -y autoconf" + sudo apt-get install -y autoconf + echo "install autoconf success" + fi + + apt-cache show libtool; ret=$?; if [[ 0 -ne $ret ]]; then + echo "install libtool" + require_sudoer "sudo apt-get install -y libtool" + sudo apt-get install -y libtool + echo "install libtool success" + fi + + apt-cache show libssl-dev; ret=$?; if [[ 0 -ne $ret ]]; then + echo "install libssl-dev" + require_sudoer "sudo apt-get install -y libssl-dev" + sudo apt-get install -y libssl-dev + echo "install libssl-dev success" + fi +} +Ubuntu_prepare +##################################################################################### +# for Centos +##################################################################################### +function Centos_prepare() +{ + if [[ ! -f /etc/redhat-release ]]; then + return; + fi + + echo "Centos detected, install tools if needed" + + gcc --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then + echo "install gcc" + require_sudoer "sudo yum install -y gcc" + sudo yum install -y gcc + echo "install gcc success" + fi + + g++ --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then + echo "install gcc-c++" + require_sudoer "sudo yum install -y gcc-c++" + sudo yum install -y gcc-c++ + echo "install gcc-c++ success" + fi + + make --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then + echo "install make" + require_sudoer "sudo yum install -y make" + sudo yum install -y make + echo "install make success" + fi + + automake --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then + echo "install automake" + require_sudoer "sudo yum install -y automake" + sudo yum install -y automake + echo "install automake success" + fi + + autoconf --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then + echo "install autoconf" + require_sudoer "sudo yum install -y autoconf" + sudo yum install -y autoconf + echo "install autoconf success" + fi + + libtool --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then + echo "install libtool" + require_sudoer "sudo yum install -y libtool" + sudo yum install -y libtool + echo "install libtool success" + fi + + if [[ ! -d /usr/include/openssl ]]; then + echo "install openssl-devel" + require_sudoer "sudo yum install -y openssl-devel" + sudo yum install -y openssl-devel + echo "install openssl-devels success" + fi +} +Centos_prepare ##################################################################################### # st-1.9 @@ -48,6 +192,16 @@ fi ##################################################################################### # nginx for HLS, nginx-1.5.0 ##################################################################################### +function write_nginx_html5() +{ + cat<> ${html_file} + +END +} if [ $SRS_HLS = YES ]; then if [[ -f ${SRS_OBJS}/nginx/sbin/nginx ]]; then echo "nginx-1.5.7 is ok."; @@ -72,6 +226,14 @@ if [ $SRS_HLS = YES ]; then # create forward dir mkdir -p ${SRS_OBJS}/nginx/html/forward + + # generate default html pages for android. + 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 fi if [ $SRS_HLS = YES ]; then @@ -87,6 +249,7 @@ if [ $SRS_HTTP = 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" echo "install CherryPy-3.2.4"; ( sudo rm -rf ${SRS_OBJS}/CherryPy-3.2.4 && cd ${SRS_OBJS} && diff --git a/trunk/conf/srs.conf b/trunk/conf/srs.conf index fdfe601f3..65411f6b0 100755 --- a/trunk/conf/srs.conf +++ b/trunk/conf/srs.conf @@ -85,9 +85,9 @@ vhost dev { hls_path ./objs/nginx/html; hls_fragment 5; hls_window 30; - forward 127.0.0.1:19350; + #forward 127.0.0.1:19350; http_hooks { - enabled on; + enabled off; on_connect http://127.0.0.1:8085/api/v1/clients; on_close http://127.0.0.1:8085/api/v1/clients; on_publish http://127.0.0.1:8085/api/v1/streams; @@ -96,7 +96,7 @@ vhost dev { on_stop http://127.0.0.1:8085/api/v1/sessions; } transcode { - enabled on; + enabled off; ffmpeg ./objs/ffmpeg/bin/ffmpeg; engine dev { enabled on; diff --git a/trunk/configure b/trunk/configure index 652d2fff1..85493c767 100755 --- a/trunk/configure +++ b/trunk/configure @@ -15,14 +15,15 @@ BLACK="\\e[0m" # parse user options. . auto/options.sh -# if specifies http, requires sudo to install the CherryPy. -if [ $SRS_HTTP = YES ]; then +function require_sudoer() +{ sudo echo "" >/dev/null 2>&1 - ret=$?; if [[ 0 -ne $ret ]]; then echo - "--with-http requires sudoer, ret=$ret"; + + ret=$?; if [[ 0 -ne $ret ]]; then + echo "\"$1\" require sudoer failed. ret=$ret"; exit $ret; fi -fi +} # clean the exists if [[ -f Makefile ]]; then From 86e30ec2c99ecd1482c1d3898eb3c7f71982507f Mon Sep 17 00:00:00 2001 From: winlin Date: Tue, 10 Dec 2013 10:34:33 +0800 Subject: [PATCH 03/37] update build script for ubuntu --- trunk/auto/depends.sh | 61 +++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 34 deletions(-) diff --git a/trunk/auto/depends.sh b/trunk/auto/depends.sh index cc873a377..1c9bff9e6 100755 --- a/trunk/auto/depends.sh +++ b/trunk/auto/depends.sh @@ -15,70 +15,63 @@ function Ubuntu_prepare() echo "Ubuntu detected, install tools if needed" - apt-cache show libpcre3; ret=$?; if [[ 0 -ne $ret ]]; then - echo "install libpcre3" - require_sudoer "sudo apt-get install -y libpcre3" - sudo apt-get install -y libpcre3 - echo "install libpcre3 success" - fi - - apt-cache show libpcre3-dev; ret=$?; if [[ 0 -ne $ret ]]; then - echo "install libpcre3-dev" - require_sudoer "sudo apt-get install -y libpcre3-dev" - sudo apt-get install -y libpcre3-dev - echo "install libpcre3-dev success" - fi - - apt-cache show zlib1g-dev; ret=$?; if [[ 0 -ne $ret ]]; then - echo "install zlib1g-dev" - require_sudoer "sudo apt-get install -y zlib1g-dev" - sudo apt-get install -y zlib1g-dev - echo "install zlib1g-dev success" - fi - - apt-cache show libfreetype6-dev; ret=$?; if [[ 0 -ne $ret ]]; then - echo "install libfreetype6-dev" - require_sudoer "sudo apt-get install -y libfreetype6-dev" - sudo apt-get install -y libfreetype6-dev - echo "install libfreetype6-dev success" - fi - - apt-cache show gcc; ret=$?; if [[ 0 -ne $ret ]]; then + gcc --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then echo "install gcc" require_sudoer "sudo apt-get install -y gcc" sudo apt-get install -y gcc echo "install gcc success" fi - apt-cache show g++; ret=$?; if [[ 0 -ne $ret ]]; then + g++ --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then echo "install g++" require_sudoer "sudo apt-get install -y g++" sudo apt-get install -y g++ echo "install g++ success" fi - apt-cache show make; ret=$?; if [[ 0 -ne $ret ]]; then + make --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then echo "install make" require_sudoer "sudo apt-get install -y make" sudo apt-get install -y make echo "install make success" fi - apt-cache show autoconf; ret=$?; if [[ 0 -ne $ret ]]; then + autoconf --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then echo "install autoconf" require_sudoer "sudo apt-get install -y autoconf" sudo apt-get install -y autoconf echo "install autoconf success" fi - apt-cache show libtool; ret=$?; if [[ 0 -ne $ret ]]; then + libtool --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then echo "install libtool" require_sudoer "sudo apt-get install -y libtool" sudo apt-get install -y libtool echo "install libtool success" fi - apt-cache show libssl-dev; ret=$?; if [[ 0 -ne $ret ]]; then + if [[ ! -f /usr/include/pcre.h ]]; then + echo "install libpcre3-dev" + require_sudoer "sudo apt-get install -y libpcre3-dev" + sudo apt-get install -y libpcre3-dev + echo "install libpcre3-dev success" + fi + + if [[ ! -f /usr/include/zlib.h ]]; then + echo "install zlib1g-dev" + require_sudoer "sudo apt-get install -y zlib1g-dev" + sudo apt-get install -y zlib1g-dev + echo "install zlib1g-dev success" + fi + + if [[ ! -d /usr/include/freetype2 ]]; then + echo "install libfreetype6-dev" + require_sudoer "sudo apt-get install -y libfreetype6-dev" + sudo apt-get install -y libfreetype6-dev + echo "install libfreetype6-dev success" + fi + + if [[ ! -d /usr/include/openssl ]]; then echo "install libssl-dev" require_sudoer "sudo apt-get install -y libssl-dev" sudo apt-get install -y libssl-dev From 5d349882bb888f49957cb4b1308a021320ac35c4 Mon Sep 17 00:00:00 2001 From: winlin Date: Tue, 10 Dec 2013 10:37:18 +0800 Subject: [PATCH 04/37] update build script for ubuntu --- trunk/auto/depends.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/trunk/auto/depends.sh b/trunk/auto/depends.sh index 1c9bff9e6..e181dfe88 100755 --- a/trunk/auto/depends.sh +++ b/trunk/auto/depends.sh @@ -77,6 +77,8 @@ function Ubuntu_prepare() sudo apt-get install -y libssl-dev echo "install libssl-dev success" fi + + echo "Ubuntu install tools success" } Ubuntu_prepare ##################################################################################### @@ -138,6 +140,8 @@ function Centos_prepare() sudo yum install -y openssl-devel echo "install openssl-devels success" fi + + echo "Centos install tools success" } Centos_prepare From 55a81fd01f5098fbc2548c9ece3f9d6af76ecd96 Mon Sep 17 00:00:00 2001 From: winlin Date: Tue, 10 Dec 2013 10:38:19 +0800 Subject: [PATCH 05/37] update build script for ubuntu --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 08b2917c8..83ba3905d 100755 --- a/README.md +++ b/README.md @@ -198,6 +198,7 @@ usr sys idl wai hiq siq| read writ| recv send| in out | int csw * nginx v1.5.0: 139524 lines
### History +* v0.9, 2013-12-10, auto install depends tools/libs on centos/ubuntu. * v0.8, 2013-12-08, v0.8 released. 19186 lines. * v0.8, 2013-12-08, support http hooks: on_connect/close/publish/unpublish/play/stop. * v0.8, 2013-12-08, support multiple http hooks for a event. From ae18c388c1526ada28d295303b57a04c1556500b Mon Sep 17 00:00:00 2001 From: winlin Date: Tue, 10 Dec 2013 11:37:59 +0800 Subject: [PATCH 06/37] fix bug of build script on centos --- trunk/auto/depends.sh | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/trunk/auto/depends.sh b/trunk/auto/depends.sh index e181dfe88..a52470848 100755 --- a/trunk/auto/depends.sh +++ b/trunk/auto/depends.sh @@ -134,11 +134,32 @@ 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 + 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" + sudo yum install -y zlib-devel + echo "install zlib-devel success" + fi + + if [[ ! -d /usr/include/freetype2 ]]; then + echo "install freetype-devel" + require_sudoer "sudo yum install -y freetype-devel" + sudo yum install -y freetype-devel + echo "install freetype-devel success" + fi + if [[ ! -d /usr/include/openssl ]]; then echo "install openssl-devel" require_sudoer "sudo yum install -y openssl-devel" sudo yum install -y openssl-devel - echo "install openssl-devels success" + echo "install openssl-devel success" fi echo "Centos install tools success" From 66048cebbb095a76c30de464bab8f87f06b42029 Mon Sep 17 00:00:00 2001 From: winlin Date: Tue, 10 Dec 2013 14:49:35 +0800 Subject: [PATCH 07/37] add todo for source --- trunk/src/core/srs_core_source.cpp | 1499 ++++++++++++++-------------- 1 file changed, 750 insertions(+), 749 deletions(-) mode change 100644 => 100755 trunk/src/core/srs_core_source.cpp diff --git a/trunk/src/core/srs_core_source.cpp b/trunk/src/core/srs_core_source.cpp old mode 100644 new mode 100755 index 3277ebb85..8071cd6e9 --- a/trunk/src/core/srs_core_source.cpp +++ b/trunk/src/core/srs_core_source.cpp @@ -1,749 +1,750 @@ -/* -The MIT License (MIT) - -Copyright (c) 2013 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 -#include -#include -#include -#include -#include -#include -#include - -#define CONST_MAX_JITTER_MS 500 -#define DEFAULT_FRAME_TIME_MS 10 -#define PAUSED_SHRINK_SIZE 250 - -SrsRtmpJitter::SrsRtmpJitter() -{ - last_pkt_correct_time = last_pkt_time = 0; -} - -SrsRtmpJitter::~SrsRtmpJitter() -{ -} - -int SrsRtmpJitter::correct(SrsSharedPtrMessage* msg, int tba, int tbv, int64_t* corrected_time) -{ - int ret = ERROR_SUCCESS; - - int sample_rate = tba; - int frame_rate = tbv; - - /** - * we use a very simple time jitter detect/correct algorithm: - * 1. delta: ensure the delta is positive and valid, - * we set the delta to DEFAULT_FRAME_TIME_MS, - * if the delta of time is nagative or greater than CONST_MAX_JITTER_MS. - * 2. last_pkt_time: specifies the original packet time, - * is used to detect next jitter. - * 3. last_pkt_correct_time: simply add the positive delta, - * and enforce the time monotonically. - */ - u_int32_t time = msg->header.timestamp; - int32_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) { - delta = (int32_t)(delta * 1000.0 / sample_rate); - } else if (msg->header.is_video() && frame_rate > 0) { - delta = (int32_t)(delta * 1.0 / frame_rate); - } else { - delta = DEFAULT_FRAME_TIME_MS; - } - - // sometimes, the time is absolute time, so correct it again. - if (delta < 0 || delta > CONST_MAX_JITTER_MS) { - delta = DEFAULT_FRAME_TIME_MS; - } - - srs_info("jitter detected, last_pts=%d, pts=%d, diff=%d, last_time=%d, time=%d, diff=%d", - last_pkt_time, time, time - last_pkt_time, last_pkt_correct_time, last_pkt_correct_time + delta, delta); - } else { - srs_verbose("timestamp no jitter. time=%d, last_pkt=%d, correct_to=%d", - time, last_pkt_time, last_pkt_correct_time + delta); - } - - last_pkt_correct_time = srs_max(0, last_pkt_correct_time + delta); - - if (corrected_time) { - *corrected_time = last_pkt_correct_time; - } - msg->header.timestamp = last_pkt_correct_time; - - last_pkt_time = time; - - return ret; -} - -int SrsRtmpJitter::get_time() -{ - return (int)last_pkt_correct_time; -} - -SrsConsumer::SrsConsumer(SrsSource* _source) -{ - source = _source; - paused = false; - jitter = new SrsRtmpJitter(); -} - -SrsConsumer::~SrsConsumer() -{ - clear(); - - source->on_consumer_destroy(this); - srs_freep(jitter); -} - -int SrsConsumer::get_time() -{ - return jitter->get_time(); -} - -int SrsConsumer::enqueue(SrsSharedPtrMessage* msg, int tba, int tbv) -{ - int ret = ERROR_SUCCESS; - - if ((ret = jitter->correct(msg, tba, tbv)) != ERROR_SUCCESS) { - srs_freep(msg); - return ret; - } - - // TODO: check the queue size and drop packets if overflow. - msgs.push_back(msg); - - return ret; -} - -int SrsConsumer::get_packets(int max_count, SrsSharedPtrMessage**& pmsgs, int& count) -{ - int ret = ERROR_SUCCESS; - - if (msgs.empty()) { - return ret; - } - - if (paused) { - if ((int)msgs.size() >= PAUSED_SHRINK_SIZE) { - shrink(); - } - return ret; - } - - if (max_count == 0) { - count = (int)msgs.size(); - } else { - count = srs_min(max_count, (int)msgs.size()); - } - - pmsgs = new SrsSharedPtrMessage*[count]; - - for (int i = 0; i < count; i++) { - pmsgs[i] = msgs[i]; - } - - if (count == (int)msgs.size()) { - msgs.clear(); - } else { - msgs.erase(msgs.begin(), msgs.begin() + count); - } - - return ret; -} - -int SrsConsumer::on_play_client_pause(bool is_pause) -{ - int ret = ERROR_SUCCESS; - - srs_trace("stream consumer change pause state %d=>%d", paused, is_pause); - paused = is_pause; - - return ret; -} - -void SrsConsumer::shrink() -{ - int i = 0; - std::vector::iterator it; - - // issue the last video iframe. - bool has_video = false; - int frame_to_remove = 0; - std::vector::iterator iframe = msgs.end(); - for (i = 0, it = msgs.begin(); it != msgs.end(); ++it, i++) { - SrsSharedPtrMessage* msg = *it; - if (msg->header.is_video()) { - has_video = true; - if (SrsCodec::video_is_keyframe(msg->payload, msg->size)) { - iframe = it; - frame_to_remove = i + 1; - } - } - } - - // last iframe is the first elem, ignore it. - if (iframe == msgs.begin()) { - return; - } - - // recalc the frame to remove - if (iframe == msgs.end()) { - frame_to_remove = 0; - } - if (!has_video) { - frame_to_remove = (int)msgs.size(); - } - - srs_trace("shrink the cache queue, has_video=%d, has_iframe=%d, size=%d, removed=%d", - has_video, iframe != msgs.end(), (int)msgs.size(), frame_to_remove); - - // if no video, remove all audio. - if (!has_video) { - clear(); - return; - } - - // if exists video Iframe, remove the frames before it. - if (iframe != msgs.end()) { - for (it = msgs.begin(); it != iframe; ++it) { - SrsSharedPtrMessage* msg = *it; - srs_freep(msg); - } - msgs.erase(msgs.begin(), iframe); - } -} - -void SrsConsumer::clear() -{ - std::vector::iterator it; - for (it = msgs.begin(); it != msgs.end(); ++it) { - SrsSharedPtrMessage* msg = *it; - srs_freep(msg); - } - msgs.clear(); -} - -SrsGopCache::SrsGopCache() -{ - cached_video_count = 0; - enable_gop_cache = true; -} - -SrsGopCache::~SrsGopCache() -{ - clear(); -} - -void SrsGopCache::set(bool enabled) -{ - enable_gop_cache = enabled; - - if (!enabled) { - srs_info("disable gop cache, clear %d packets.", (int)gop_cache.size()); - clear(); - return; - } - - srs_info("enable gop cache"); -} - -int SrsGopCache::cache(SrsSharedPtrMessage* msg) -{ - int ret = ERROR_SUCCESS; - - if (!enable_gop_cache) { - srs_verbose("gop cache is disabled."); - return ret; - } - - // got video, update the video count if acceptable - if (msg->header.is_video()) { - cached_video_count++; - } - - // no acceptable video or pure audio, disable the cache. - if (cached_video_count == 0) { - srs_verbose("ignore any frame util got a h264 video frame."); - return ret; - } - - // clear gop cache when got key frame - if (msg->header.is_video() && SrsCodec::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()); - - clear(); - - // curent msg is video frame, so we set to 1. - cached_video_count = 1; - } - - // cache the frame. - gop_cache.push_back(msg->copy()); - - return ret; -} - -void SrsGopCache::clear() -{ - std::vector::iterator it; - for (it = gop_cache.begin(); it != gop_cache.end(); ++it) { - SrsSharedPtrMessage* msg = *it; - srs_freep(msg); - } - gop_cache.clear(); - - cached_video_count = 0; -} - -int SrsGopCache::dump(SrsConsumer* consumer, int tba, int tbv) -{ - int ret = ERROR_SUCCESS; - - std::vector::iterator it; - for (it = gop_cache.begin(); it != gop_cache.end(); ++it) { - SrsSharedPtrMessage* msg = *it; - if ((ret = consumer->enqueue(msg->copy(), tba, tbv)) != ERROR_SUCCESS) { - srs_error("dispatch cached gop failed. ret=%d", ret); - return ret; - } - } - srs_trace("dispatch cached gop success. count=%d, duration=%d", (int)gop_cache.size(), consumer->get_time()); - - return ret; -} - -std::map SrsSource::pool; - -SrsSource* SrsSource::find(std::string stream_url) -{ - if (pool.find(stream_url) == pool.end()) { - pool[stream_url] = new SrsSource(stream_url); - srs_verbose("create new source for url=%s", stream_url.c_str()); - } - - return pool[stream_url]; -} - -SrsSource::SrsSource(std::string _stream_url) -{ - stream_url = _stream_url; - -#ifdef SRS_HLS - hls = new SrsHls(); -#endif -#ifdef SRS_FFMPEG - encoder = new SrsEncoder(); -#endif - - cache_metadata = cache_sh_video = cache_sh_audio = NULL; - - frame_rate = sample_rate = 0; - _can_publish = true; - - gop_cache = new SrsGopCache(); -} - -SrsSource::~SrsSource() -{ - if (true) { - std::vector::iterator it; - for (it = consumers.begin(); it != consumers.end(); ++it) { - SrsConsumer* consumer = *it; - srs_freep(consumer); - } - consumers.clear(); - } - - if (true) { - std::vector::iterator it; - for (it = forwarders.begin(); it != forwarders.end(); ++it) { - SrsForwarder* forwarder = *it; - srs_freep(forwarder); - } - forwarders.clear(); - } - - srs_freep(cache_metadata); - srs_freep(cache_sh_video); - srs_freep(cache_sh_audio); - - srs_freep(gop_cache); - -#ifdef SRS_HLS - srs_freep(hls); -#endif -#ifdef SRS_FFMPEG - srs_freep(encoder); -#endif -} - -bool SrsSource::can_publish() -{ - return _can_publish; -} - -int SrsSource::on_meta_data(SrsCommonMessage* msg, SrsOnMetaDataPacket* metadata) -{ - int ret = ERROR_SUCCESS; - -#ifdef SRS_HLS - if ((ret = hls->on_meta_data(metadata)) != ERROR_SUCCESS) { - srs_error("hls process onMetaData message failed. ret=%d", ret); - return ret; - } -#endif - - metadata->metadata->set("server", new SrsAmf0String( - RTMP_SIG_SRS_KEY" "RTMP_SIG_SRS_VERSION" ("RTMP_SIG_SRS_URL_SHORT")")); - - SrsAmf0Any* prop = NULL; - if ((prop = metadata->metadata->get_property("audiosamplerate")) != NULL) { - if (prop->is_number()) { - sample_rate = (int)(srs_amf0_convert(prop)->value); - } - } - if ((prop = metadata->metadata->get_property("framerate")) != NULL) { - if (prop->is_number()) { - frame_rate = (int)(srs_amf0_convert(prop)->value); - } - } - - // encode the metadata to payload - int size = metadata->get_payload_length(); - if (size <= 0) { - srs_warn("ignore the invalid metadata. size=%d", size); - return ret; - } - srs_verbose("get metadata size success."); - - char* payload = new char[size]; - memset(payload, 0, size); - if ((ret = metadata->encode(size, payload)) != ERROR_SUCCESS) { - srs_error("encode metadata error. ret=%d", ret); - srs_freepa(payload); - return ret; - } - srs_verbose("encode metadata success."); - - // create a shared ptr message. - srs_freep(cache_metadata); - cache_metadata = new SrsSharedPtrMessage(); - - // dump message to shared ptr message. - if ((ret = cache_metadata->initialize(msg, payload, size)) != ERROR_SUCCESS) { - srs_error("initialize the cache metadata failed. ret=%d", ret); - return ret; - } - srs_verbose("initialize shared ptr metadata success."); - - // copy to all consumer - if (true) { - std::vector::iterator it; - for (it = consumers.begin(); it != consumers.end(); ++it) { - SrsConsumer* consumer = *it; - if ((ret = consumer->enqueue(cache_metadata->copy(), sample_rate, frame_rate)) != ERROR_SUCCESS) { - srs_error("dispatch the metadata failed. ret=%d", ret); - return ret; - } - } - srs_trace("dispatch metadata success."); - } - - // copy to all forwarders - if (true) { - 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) { - srs_error("forwarder process onMetaData message failed. ret=%d", ret); - return ret; - } - } - } - - return ret; -} - -int SrsSource::on_audio(SrsCommonMessage* audio) -{ - int ret = ERROR_SUCCESS; - - SrsSharedPtrMessage* msg = new SrsSharedPtrMessage(); - SrsAutoFree(SrsSharedPtrMessage, msg, false); - if ((ret = msg->initialize(audio)) != ERROR_SUCCESS) { - srs_error("initialize the audio failed. ret=%d", ret); - return ret; - } - srs_verbose("initialize shared ptr audio success."); - -#ifdef SRS_HLS - if ((ret = hls->on_audio(msg->copy())) != ERROR_SUCCESS) { - srs_warn("hls process audio message failed, ignore and disable hls. ret=%d", ret); - - // unpublish, ignore ret. - hls->on_unpublish(); - - // ignore. - ret = ERROR_SUCCESS; - } -#endif - - // copy to all consumer - if (true) { - std::vector::iterator it; - for (it = consumers.begin(); it != consumers.end(); ++it) { - SrsConsumer* consumer = *it; - if ((ret = consumer->enqueue(msg->copy(), sample_rate, frame_rate)) != ERROR_SUCCESS) { - srs_error("dispatch the audio failed. ret=%d", ret); - return ret; - } - } - srs_info("dispatch audio success."); - } - - // copy to all forwarders. - if (true) { - std::vector::iterator it; - for (it = forwarders.begin(); it != forwarders.end(); ++it) { - SrsForwarder* forwarder = *it; - if ((ret = forwarder->on_audio(msg->copy())) != ERROR_SUCCESS) { - srs_error("forwarder process audio message failed. ret=%d", ret); - return ret; - } - } - } - - // cache the sequence header if h264 - if (SrsCodec::audio_is_sequence_header(msg->payload, msg->size)) { - srs_freep(cache_sh_audio); - cache_sh_audio = msg->copy(); - srs_trace("update audio sequence header success. size=%d", msg->header.payload_length); - return ret; - } - - // cache the last gop packets - if ((ret = gop_cache->cache(msg)) != ERROR_SUCCESS) { - srs_error("shrink gop cache failed. ret=%d", ret); - return ret; - } - srs_verbose("cache gop success."); - - return ret; -} - -int SrsSource::on_video(SrsCommonMessage* video) -{ - int ret = ERROR_SUCCESS; - - SrsSharedPtrMessage* msg = new SrsSharedPtrMessage(); - SrsAutoFree(SrsSharedPtrMessage, msg, false); - if ((ret = msg->initialize(video)) != ERROR_SUCCESS) { - srs_error("initialize the video failed. ret=%d", ret); - return ret; - } - srs_verbose("initialize shared ptr video success."); - -#ifdef SRS_HLS - if ((ret = hls->on_video(msg->copy())) != ERROR_SUCCESS) { - srs_warn("hls process video message failed, ignore and disable hls. ret=%d", ret); - - // unpublish, ignore ret. - hls->on_unpublish(); - - // ignore. - ret = ERROR_SUCCESS; - } -#endif - - // copy to all consumer - if (true) { - std::vector::iterator it; - for (it = consumers.begin(); it != consumers.end(); ++it) { - SrsConsumer* consumer = *it; - if ((ret = consumer->enqueue(msg->copy(), sample_rate, frame_rate)) != ERROR_SUCCESS) { - srs_error("dispatch the video failed. ret=%d", ret); - return ret; - } - } - srs_info("dispatch video success."); - } - - // copy to all forwarders. - if (true) { - std::vector::iterator it; - for (it = forwarders.begin(); it != forwarders.end(); ++it) { - SrsForwarder* forwarder = *it; - if ((ret = forwarder->on_video(msg->copy())) != ERROR_SUCCESS) { - srs_error("forwarder process video message failed. ret=%d", ret); - return ret; - } - } - } - - // cache the sequence header if h264 - if (SrsCodec::video_is_sequence_header(msg->payload, msg->size)) { - srs_freep(cache_sh_video); - cache_sh_video = msg->copy(); - srs_trace("update video sequence header success. size=%d", msg->header.payload_length); - return ret; - } - - // cache the last gop packets - if ((ret = gop_cache->cache(msg)) != ERROR_SUCCESS) { - srs_error("shrink gop cache failed. ret=%d", ret); - return ret; - } - srs_verbose("cache gop success."); - - return ret; -} - -int SrsSource::on_publish(SrsRequest* req) -{ - int ret = ERROR_SUCCESS; - - _can_publish = false; - - // TODO: support reload. - - // create forwarders - SrsConfDirective* conf = config->get_forward(req->vhost); - for (int i = 0; conf && i < (int)conf->args.size(); i++) { - std::string forward_server = conf->args.at(i); - - SrsForwarder* forwarder = new SrsForwarder(); - forwarders.push_back(forwarder); - - if ((ret = forwarder->on_publish(req, forward_server)) != ERROR_SUCCESS) { - srs_error("start forwarder failed. " - "vhost=%s, app=%s, stream=%s, forward-to=%s", - req->vhost.c_str(), req->app.c_str(), req->stream.c_str(), - forward_server.c_str()); - return ret; - } - } - -#ifdef SRS_FFMPEG - if ((ret = encoder->on_publish(req)) != ERROR_SUCCESS) { - return ret; - } -#endif - -#ifdef SRS_HLS - if ((ret = hls->on_publish(req)) != ERROR_SUCCESS) { - return ret; - } -#endif - - return ret; -} - -void SrsSource::on_unpublish() -{ - // close all forwarders - std::vector::iterator it; - for (it = forwarders.begin(); it != forwarders.end(); ++it) { - SrsForwarder* forwarder = *it; - forwarder->on_unpublish(); - srs_freep(forwarder); - } - forwarders.clear(); - -#ifdef SRS_FFMPEG - encoder->on_unpublish(); -#endif - -#ifdef SRS_HLS - hls->on_unpublish(); -#endif - - gop_cache->clear(); - - srs_freep(cache_metadata); - frame_rate = sample_rate = 0; - - srs_freep(cache_sh_video); - srs_freep(cache_sh_audio); - - srs_trace("clear cache/metadata/sequence-headers when unpublish."); - - _can_publish = true; -} - - int SrsSource::create_consumer(SrsConsumer*& consumer) -{ - int ret = ERROR_SUCCESS; - - consumer = new SrsConsumer(this); - consumers.push_back(consumer); - - if (cache_metadata && (ret = consumer->enqueue(cache_metadata->copy(), sample_rate, frame_rate)) != ERROR_SUCCESS) { - srs_error("dispatch metadata failed. ret=%d", ret); - return ret; - } - srs_info("dispatch metadata success"); - - if (cache_sh_video && (ret = consumer->enqueue(cache_sh_video->copy(), sample_rate, frame_rate)) != 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(), sample_rate, frame_rate)) != ERROR_SUCCESS) { - srs_error("dispatch audio sequence header failed. ret=%d", ret); - return ret; - } - srs_info("dispatch audio sequence header success"); - - if ((ret = gop_cache->dump(consumer, sample_rate, frame_rate)) != ERROR_SUCCESS) { - return ret; - } - - return ret; -} - -void SrsSource::on_consumer_destroy(SrsConsumer* consumer) -{ - std::vector::iterator it; - it = std::find(consumers.begin(), consumers.end(), consumer); - if (it != consumers.end()) { - consumers.erase(it); - } - srs_info("handle consumer destroy success."); -} - -void SrsSource::set_cache(bool enabled) -{ - gop_cache->set(enabled); -} - +/* +The MIT License (MIT) + +Copyright (c) 2013 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 +#include +#include +#include +#include +#include +#include +#include + +#define CONST_MAX_JITTER_MS 500 +#define DEFAULT_FRAME_TIME_MS 10 +#define PAUSED_SHRINK_SIZE 250 + +SrsRtmpJitter::SrsRtmpJitter() +{ + last_pkt_correct_time = last_pkt_time = 0; +} + +SrsRtmpJitter::~SrsRtmpJitter() +{ +} + +int SrsRtmpJitter::correct(SrsSharedPtrMessage* msg, int tba, int tbv, int64_t* corrected_time) +{ + int ret = ERROR_SUCCESS; + + int sample_rate = tba; + int frame_rate = tbv; + + /** + * we use a very simple time jitter detect/correct algorithm: + * 1. delta: ensure the delta is positive and valid, + * we set the delta to DEFAULT_FRAME_TIME_MS, + * if the delta of time is nagative or greater than CONST_MAX_JITTER_MS. + * 2. last_pkt_time: specifies the original packet time, + * is used to detect next jitter. + * 3. last_pkt_correct_time: simply add the positive delta, + * and enforce the time monotonically. + */ + u_int32_t time = msg->header.timestamp; + int32_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) { + delta = (int32_t)(delta * 1000.0 / sample_rate); + } else if (msg->header.is_video() && frame_rate > 0) { + delta = (int32_t)(delta * 1.0 / frame_rate); + } else { + delta = DEFAULT_FRAME_TIME_MS; + } + + // sometimes, the time is absolute time, so correct it again. + if (delta < 0 || delta > CONST_MAX_JITTER_MS) { + delta = DEFAULT_FRAME_TIME_MS; + } + + srs_info("jitter detected, last_pts=%d, pts=%d, diff=%d, last_time=%d, time=%d, diff=%d", + last_pkt_time, time, time - last_pkt_time, last_pkt_correct_time, last_pkt_correct_time + delta, delta); + } else { + srs_verbose("timestamp no jitter. time=%d, last_pkt=%d, correct_to=%d", + time, last_pkt_time, last_pkt_correct_time + delta); + } + + last_pkt_correct_time = srs_max(0, last_pkt_correct_time + delta); + + if (corrected_time) { + *corrected_time = last_pkt_correct_time; + } + msg->header.timestamp = last_pkt_correct_time; + + last_pkt_time = time; + + return ret; +} + +int SrsRtmpJitter::get_time() +{ + return (int)last_pkt_correct_time; +} + +SrsConsumer::SrsConsumer(SrsSource* _source) +{ + source = _source; + paused = false; + jitter = new SrsRtmpJitter(); +} + +SrsConsumer::~SrsConsumer() +{ + clear(); + + source->on_consumer_destroy(this); + srs_freep(jitter); +} + +int SrsConsumer::get_time() +{ + return jitter->get_time(); +} + +int SrsConsumer::enqueue(SrsSharedPtrMessage* msg, int tba, int tbv) +{ + int ret = ERROR_SUCCESS; + + if ((ret = jitter->correct(msg, tba, tbv)) != ERROR_SUCCESS) { + srs_freep(msg); + return ret; + } + + // TODO: check the queue size and drop packets if overflow. + msgs.push_back(msg); + + return ret; +} + +int SrsConsumer::get_packets(int max_count, SrsSharedPtrMessage**& pmsgs, int& count) +{ + int ret = ERROR_SUCCESS; + + if (msgs.empty()) { + return ret; + } + + if (paused) { + if ((int)msgs.size() >= PAUSED_SHRINK_SIZE) { + shrink(); + } + return ret; + } + + if (max_count == 0) { + count = (int)msgs.size(); + } else { + count = srs_min(max_count, (int)msgs.size()); + } + + pmsgs = new SrsSharedPtrMessage*[count]; + + for (int i = 0; i < count; i++) { + pmsgs[i] = msgs[i]; + } + + if (count == (int)msgs.size()) { + msgs.clear(); + } else { + msgs.erase(msgs.begin(), msgs.begin() + count); + } + + return ret; +} + +int SrsConsumer::on_play_client_pause(bool is_pause) +{ + int ret = ERROR_SUCCESS; + + srs_trace("stream consumer change pause state %d=>%d", paused, is_pause); + paused = is_pause; + + return ret; +} + +void SrsConsumer::shrink() +{ + int i = 0; + std::vector::iterator it; + + // issue the last video iframe. + bool has_video = false; + int frame_to_remove = 0; + std::vector::iterator iframe = msgs.end(); + for (i = 0, it = msgs.begin(); it != msgs.end(); ++it, i++) { + SrsSharedPtrMessage* msg = *it; + if (msg->header.is_video()) { + has_video = true; + if (SrsCodec::video_is_keyframe(msg->payload, msg->size)) { + iframe = it; + frame_to_remove = i + 1; + } + } + } + + // last iframe is the first elem, ignore it. + if (iframe == msgs.begin()) { + return; + } + + // recalc the frame to remove + if (iframe == msgs.end()) { + frame_to_remove = 0; + } + if (!has_video) { + frame_to_remove = (int)msgs.size(); + } + + srs_trace("shrink the cache queue, has_video=%d, has_iframe=%d, size=%d, removed=%d", + has_video, iframe != msgs.end(), (int)msgs.size(), frame_to_remove); + + // if no video, remove all audio. + if (!has_video) { + clear(); + return; + } + + // if exists video Iframe, remove the frames before it. + if (iframe != msgs.end()) { + for (it = msgs.begin(); it != iframe; ++it) { + SrsSharedPtrMessage* msg = *it; + srs_freep(msg); + } + msgs.erase(msgs.begin(), iframe); + } +} + +void SrsConsumer::clear() +{ + std::vector::iterator it; + for (it = msgs.begin(); it != msgs.end(); ++it) { + SrsSharedPtrMessage* msg = *it; + srs_freep(msg); + } + msgs.clear(); +} + +SrsGopCache::SrsGopCache() +{ + cached_video_count = 0; + enable_gop_cache = true; +} + +SrsGopCache::~SrsGopCache() +{ + clear(); +} + +void SrsGopCache::set(bool enabled) +{ + enable_gop_cache = enabled; + + if (!enabled) { + srs_info("disable gop cache, clear %d packets.", (int)gop_cache.size()); + clear(); + return; + } + + srs_info("enable gop cache"); +} + +int SrsGopCache::cache(SrsSharedPtrMessage* msg) +{ + int ret = ERROR_SUCCESS; + + if (!enable_gop_cache) { + srs_verbose("gop cache is disabled."); + return ret; + } + + // got video, update the video count if acceptable + if (msg->header.is_video()) { + cached_video_count++; + } + + // no acceptable video or pure audio, disable the cache. + if (cached_video_count == 0) { + srs_verbose("ignore any frame util got a h264 video frame."); + return ret; + } + + // clear gop cache when got key frame + if (msg->header.is_video() && SrsCodec::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()); + + clear(); + + // curent msg is video frame, so we set to 1. + cached_video_count = 1; + } + + // cache the frame. + gop_cache.push_back(msg->copy()); + + return ret; +} + +void SrsGopCache::clear() +{ + std::vector::iterator it; + for (it = gop_cache.begin(); it != gop_cache.end(); ++it) { + SrsSharedPtrMessage* msg = *it; + srs_freep(msg); + } + gop_cache.clear(); + + cached_video_count = 0; +} + +int SrsGopCache::dump(SrsConsumer* consumer, int tba, int tbv) +{ + int ret = ERROR_SUCCESS; + + std::vector::iterator it; + for (it = gop_cache.begin(); it != gop_cache.end(); ++it) { + SrsSharedPtrMessage* msg = *it; + if ((ret = consumer->enqueue(msg->copy(), tba, tbv)) != ERROR_SUCCESS) { + srs_error("dispatch cached gop failed. ret=%d", ret); + return ret; + } + } + srs_trace("dispatch cached gop success. count=%d, duration=%d", (int)gop_cache.size(), consumer->get_time()); + + return ret; +} + +std::map SrsSource::pool; + +SrsSource* SrsSource::find(std::string stream_url) +{ + if (pool.find(stream_url) == pool.end()) { + pool[stream_url] = new SrsSource(stream_url); + srs_verbose("create new source for url=%s", stream_url.c_str()); + } + + return pool[stream_url]; +} + +SrsSource::SrsSource(std::string _stream_url) +{ + stream_url = _stream_url; + +#ifdef SRS_HLS + hls = new SrsHls(); +#endif +#ifdef SRS_FFMPEG + encoder = new SrsEncoder(); +#endif + + cache_metadata = cache_sh_video = cache_sh_audio = NULL; + + frame_rate = sample_rate = 0; + _can_publish = true; + + gop_cache = new SrsGopCache(); +} + +SrsSource::~SrsSource() +{ + if (true) { + std::vector::iterator it; + for (it = consumers.begin(); it != consumers.end(); ++it) { + SrsConsumer* consumer = *it; + srs_freep(consumer); + } + consumers.clear(); + } + + if (true) { + std::vector::iterator it; + for (it = forwarders.begin(); it != forwarders.end(); ++it) { + SrsForwarder* forwarder = *it; + srs_freep(forwarder); + } + forwarders.clear(); + } + + srs_freep(cache_metadata); + srs_freep(cache_sh_video); + srs_freep(cache_sh_audio); + + srs_freep(gop_cache); + +#ifdef SRS_HLS + srs_freep(hls); +#endif +#ifdef SRS_FFMPEG + srs_freep(encoder); +#endif +} + +bool SrsSource::can_publish() +{ + return _can_publish; +} + +int SrsSource::on_meta_data(SrsCommonMessage* msg, SrsOnMetaDataPacket* metadata) +{ + int ret = ERROR_SUCCESS; + +#ifdef SRS_HLS + if ((ret = hls->on_meta_data(metadata)) != ERROR_SUCCESS) { + srs_error("hls process onMetaData message failed. ret=%d", ret); + return ret; + } +#endif + + metadata->metadata->set("server", new SrsAmf0String( + RTMP_SIG_SRS_KEY" "RTMP_SIG_SRS_VERSION" ("RTMP_SIG_SRS_URL_SHORT")")); + + SrsAmf0Any* prop = NULL; + if ((prop = metadata->metadata->get_property("audiosamplerate")) != NULL) { + if (prop->is_number()) { + sample_rate = (int)(srs_amf0_convert(prop)->value); + } + } + if ((prop = metadata->metadata->get_property("framerate")) != NULL) { + if (prop->is_number()) { + frame_rate = (int)(srs_amf0_convert(prop)->value); + } + } + + // encode the metadata to payload + int size = metadata->get_payload_length(); + if (size <= 0) { + srs_warn("ignore the invalid metadata. size=%d", size); + return ret; + } + srs_verbose("get metadata size success."); + + char* payload = new char[size]; + memset(payload, 0, size); + if ((ret = metadata->encode(size, payload)) != ERROR_SUCCESS) { + srs_error("encode metadata error. ret=%d", ret); + srs_freepa(payload); + return ret; + } + srs_verbose("encode metadata success."); + + // create a shared ptr message. + srs_freep(cache_metadata); + cache_metadata = new SrsSharedPtrMessage(); + + // dump message to shared ptr message. + if ((ret = cache_metadata->initialize(msg, payload, size)) != ERROR_SUCCESS) { + srs_error("initialize the cache metadata failed. ret=%d", ret); + return ret; + } + srs_verbose("initialize shared ptr metadata success."); + + // copy to all consumer + if (true) { + std::vector::iterator it; + for (it = consumers.begin(); it != consumers.end(); ++it) { + SrsConsumer* consumer = *it; + if ((ret = consumer->enqueue(cache_metadata->copy(), sample_rate, frame_rate)) != ERROR_SUCCESS) { + srs_error("dispatch the metadata failed. ret=%d", ret); + return ret; + } + } + srs_trace("dispatch metadata success."); + } + + // copy to all forwarders + if (true) { + 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) { + srs_error("forwarder process onMetaData message failed. ret=%d", ret); + return ret; + } + } + } + + return ret; +} + +int SrsSource::on_audio(SrsCommonMessage* audio) +{ + int ret = ERROR_SUCCESS; + + SrsSharedPtrMessage* msg = new SrsSharedPtrMessage(); + SrsAutoFree(SrsSharedPtrMessage, msg, false); + if ((ret = msg->initialize(audio)) != ERROR_SUCCESS) { + srs_error("initialize the audio failed. ret=%d", ret); + return ret; + } + srs_verbose("initialize shared ptr audio success."); + +#ifdef SRS_HLS + if ((ret = hls->on_audio(msg->copy())) != ERROR_SUCCESS) { + srs_warn("hls process audio message failed, ignore and disable hls. ret=%d", ret); + + // unpublish, ignore ret. + hls->on_unpublish(); + + // ignore. + ret = ERROR_SUCCESS; + } +#endif + + // copy to all consumer + if (true) { + std::vector::iterator it; + for (it = consumers.begin(); it != consumers.end(); ++it) { + SrsConsumer* consumer = *it; + if ((ret = consumer->enqueue(msg->copy(), sample_rate, frame_rate)) != ERROR_SUCCESS) { + srs_error("dispatch the audio failed. ret=%d", ret); + return ret; + } + } + srs_info("dispatch audio success."); + } + + // copy to all forwarders. + if (true) { + std::vector::iterator it; + for (it = forwarders.begin(); it != forwarders.end(); ++it) { + SrsForwarder* forwarder = *it; + if ((ret = forwarder->on_audio(msg->copy())) != ERROR_SUCCESS) { + srs_error("forwarder process audio message failed. ret=%d", ret); + return ret; + } + } + } + + // cache the sequence header if h264 + if (SrsCodec::audio_is_sequence_header(msg->payload, msg->size)) { + srs_freep(cache_sh_audio); + cache_sh_audio = msg->copy(); + srs_trace("update audio sequence header success. size=%d", msg->header.payload_length); + return ret; + } + + // cache the last gop packets + if ((ret = gop_cache->cache(msg)) != ERROR_SUCCESS) { + srs_error("shrink gop cache failed. ret=%d", ret); + return ret; + } + srs_verbose("cache gop success."); + + return ret; +} + +int SrsSource::on_video(SrsCommonMessage* video) +{ + int ret = ERROR_SUCCESS; + + SrsSharedPtrMessage* msg = new SrsSharedPtrMessage(); + SrsAutoFree(SrsSharedPtrMessage, msg, false); + if ((ret = msg->initialize(video)) != ERROR_SUCCESS) { + srs_error("initialize the video failed. ret=%d", ret); + return ret; + } + srs_verbose("initialize shared ptr video success."); + +#ifdef SRS_HLS + if ((ret = hls->on_video(msg->copy())) != ERROR_SUCCESS) { + srs_warn("hls process video message failed, ignore and disable hls. ret=%d", ret); + + // unpublish, ignore ret. + hls->on_unpublish(); + + // ignore. + ret = ERROR_SUCCESS; + } +#endif + + // copy to all consumer + if (true) { + std::vector::iterator it; + for (it = consumers.begin(); it != consumers.end(); ++it) { + SrsConsumer* consumer = *it; + if ((ret = consumer->enqueue(msg->copy(), sample_rate, frame_rate)) != ERROR_SUCCESS) { + srs_error("dispatch the video failed. ret=%d", ret); + return ret; + } + } + srs_info("dispatch video success."); + } + + // copy to all forwarders. + if (true) { + std::vector::iterator it; + for (it = forwarders.begin(); it != forwarders.end(); ++it) { + SrsForwarder* forwarder = *it; + if ((ret = forwarder->on_video(msg->copy())) != ERROR_SUCCESS) { + srs_error("forwarder process video message failed. ret=%d", ret); + return ret; + } + } + } + + // cache the sequence header if h264 + if (SrsCodec::video_is_sequence_header(msg->payload, msg->size)) { + srs_freep(cache_sh_video); + cache_sh_video = msg->copy(); + srs_trace("update video sequence header success. size=%d", msg->header.payload_length); + return ret; + } + + // cache the last gop packets + if ((ret = gop_cache->cache(msg)) != ERROR_SUCCESS) { + srs_error("shrink gop cache failed. ret=%d", ret); + return ret; + } + srs_verbose("cache gop success."); + + return ret; +} + +int SrsSource::on_publish(SrsRequest* req) +{ + int ret = ERROR_SUCCESS; + + _can_publish = false; + + // TODO: support reload. + + // create forwarders + SrsConfDirective* conf = config->get_forward(req->vhost); + for (int i = 0; conf && i < (int)conf->args.size(); i++) { + std::string forward_server = conf->args.at(i); + + SrsForwarder* forwarder = new SrsForwarder(); + forwarders.push_back(forwarder); + + if ((ret = forwarder->on_publish(req, forward_server)) != ERROR_SUCCESS) { + srs_error("start forwarder failed. " + "vhost=%s, app=%s, stream=%s, forward-to=%s", + req->vhost.c_str(), req->app.c_str(), req->stream.c_str(), + forward_server.c_str()); + return ret; + } + } + +#ifdef SRS_FFMPEG + if ((ret = encoder->on_publish(req)) != ERROR_SUCCESS) { + return ret; + } +#endif + +#ifdef SRS_HLS + if ((ret = hls->on_publish(req)) != ERROR_SUCCESS) { + return ret; + } +#endif + + return ret; +} + +void SrsSource::on_unpublish() +{ + // close all forwarders + std::vector::iterator it; + for (it = forwarders.begin(); it != forwarders.end(); ++it) { + SrsForwarder* forwarder = *it; + forwarder->on_unpublish(); + srs_freep(forwarder); + } + forwarders.clear(); + +#ifdef SRS_FFMPEG + encoder->on_unpublish(); +#endif + + // TODO: HLS should continue previous sequence and stream. +#ifdef SRS_HLS + hls->on_unpublish(); +#endif + + gop_cache->clear(); + + srs_freep(cache_metadata); + frame_rate = sample_rate = 0; + + srs_freep(cache_sh_video); + srs_freep(cache_sh_audio); + + srs_trace("clear cache/metadata/sequence-headers when unpublish."); + + _can_publish = true; +} + + int SrsSource::create_consumer(SrsConsumer*& consumer) +{ + int ret = ERROR_SUCCESS; + + consumer = new SrsConsumer(this); + consumers.push_back(consumer); + + if (cache_metadata && (ret = consumer->enqueue(cache_metadata->copy(), sample_rate, frame_rate)) != ERROR_SUCCESS) { + srs_error("dispatch metadata failed. ret=%d", ret); + return ret; + } + srs_info("dispatch metadata success"); + + if (cache_sh_video && (ret = consumer->enqueue(cache_sh_video->copy(), sample_rate, frame_rate)) != 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(), sample_rate, frame_rate)) != ERROR_SUCCESS) { + srs_error("dispatch audio sequence header failed. ret=%d", ret); + return ret; + } + srs_info("dispatch audio sequence header success"); + + if ((ret = gop_cache->dump(consumer, sample_rate, frame_rate)) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +void SrsSource::on_consumer_destroy(SrsConsumer* consumer) +{ + std::vector::iterator it; + it = std::find(consumers.begin(), consumers.end(), consumer); + if (it != consumers.end()) { + consumers.erase(it); + } + srs_info("handle consumer destroy success."); +} + +void SrsSource::set_cache(bool enabled) +{ + gop_cache->set(enabled); +} + From ff9c434eb9afd892709bbe4f1e8edda2927e75a3 Mon Sep 17 00:00:00 2001 From: winlin Date: Thu, 12 Dec 2013 10:41:27 +0800 Subject: [PATCH 08/37] add todo for memory increase when forwarder server failed. --- trunk/src/core/srs_core_forward.cpp | 766 ++++++++++++++-------------- 1 file changed, 384 insertions(+), 382 deletions(-) mode change 100644 => 100755 trunk/src/core/srs_core_forward.cpp diff --git a/trunk/src/core/srs_core_forward.cpp b/trunk/src/core/srs_core_forward.cpp old mode 100644 new mode 100755 index 85103f69b..577f8e495 --- a/trunk/src/core/srs_core_forward.cpp +++ b/trunk/src/core/srs_core_forward.cpp @@ -1,382 +1,384 @@ -/* -The MIT License (MIT) - -Copyright (c) 2013 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 - -#include -#include -#include -#include -#include -#include -#include - -#define SRS_PULSE_TIMEOUT_MS 100 -#define SRS_FORWARDER_SLEEP_MS 2000 -#define SRS_SEND_TIMEOUT_US 3000000L -#define SRS_RECV_TIMEOUT_US SRS_SEND_TIMEOUT_US - -SrsForwarder::SrsForwarder() -{ - client = NULL; - stfd = NULL; - stream_id = 0; - - tid = NULL; - loop = false; -} - -SrsForwarder::~SrsForwarder() -{ - on_unpublish(); - - std::vector::iterator it; - for (it = msgs.begin(); it != msgs.end(); ++it) { - SrsSharedPtrMessage* msg = *it; - srs_freep(msg); - } - msgs.clear(); -} - -int SrsForwarder::on_publish(SrsRequest* req, std::string forward_server) -{ - int ret = ERROR_SUCCESS; - - // forward app - app = req->app; - - stream_name = req->stream; - server = forward_server; - std::string s_port = RTMP_DEFAULT_PORTS; - port = RTMP_DEFAULT_PORT; - - size_t pos = forward_server.find(":"); - if (pos != std::string::npos) { - s_port = forward_server.substr(pos + 1); - server = forward_server.substr(0, pos); - } - // discovery vhost - std::string vhost = req->vhost; - srs_vhost_resolve(vhost, s_port); - port = ::atoi(s_port.c_str()); - - // generate tcUrl - tc_url = "rtmp://"; - tc_url += vhost; - tc_url += "/"; - tc_url += req->app; - - // dead loop check - std::string source_ep = req->vhost; - source_ep += ":"; - source_ep += req->port; - - std::string dest_ep = vhost; - dest_ep += ":"; - dest_ep += s_port; - - if (source_ep == dest_ep) { - ret = ERROR_SYSTEM_FORWARD_LOOP; - srs_warn("farder loop detected. src=%s, dest=%s, ret=%d", - source_ep.c_str(), dest_ep.c_str(), ret); - return ret; - } - srs_trace("start forward %s to %s, stream: %s/%s", - source_ep.c_str(), dest_ep.c_str(), tc_url.c_str(), - stream_name.c_str()); - - // TODO: seems bug when republish and reforward. - - // start forward - if ((ret = open_socket()) != ERROR_SUCCESS) { - return ret; - } - - srs_assert(!tid); - if((tid = st_thread_create(forward_thread, this, 1, 0)) == NULL){ - ret = ERROR_ST_CREATE_FORWARD_THREAD; - srs_error("st_thread_create failed. ret=%d", ret); - return ret; - } - - return ret; -} - -void SrsForwarder::on_unpublish() -{ - if (tid) { - loop = false; - st_thread_interrupt(tid); - st_thread_join(tid, NULL); - tid = NULL; - } - - if (stfd) { - int fd = st_netfd_fileno(stfd); - st_netfd_close(stfd); - stfd = NULL; - - // st does not close it sometimes, - // close it manually. - close(fd); - } - - srs_freep(client); -} - -int SrsForwarder::on_meta_data(SrsSharedPtrMessage* metadata) -{ - int ret = ERROR_SUCCESS; - - msgs.push_back(metadata); - - return ret; -} - -int SrsForwarder::on_audio(SrsSharedPtrMessage* msg) -{ - int ret = ERROR_SUCCESS; - - msgs.push_back(msg); - - return ret; -} - -int SrsForwarder::on_video(SrsSharedPtrMessage* msg) -{ - int ret = ERROR_SUCCESS; - - msgs.push_back(msg); - - return ret; -} - -int SrsForwarder::open_socket() -{ - int ret = ERROR_SUCCESS; - - srs_trace("forward stream=%s, tcUrl=%s to server=%s, port=%d", - stream_name.c_str(), tc_url.c_str(), server.c_str(), port); - - int sock = socket(AF_INET, SOCK_STREAM, 0); - if(sock == -1){ - ret = ERROR_SOCKET_CREATE; - srs_error("create socket error. ret=%d", ret); - return ret; - } - - stfd = st_netfd_open_socket(sock); - if(stfd == NULL){ - ret = ERROR_ST_OPEN_SOCKET; - srs_error("st_netfd_open_socket failed. ret=%d", ret); - return ret; - } - - srs_freep(client); - client = new SrsRtmpClient(stfd); - - return ret; -} - -int SrsForwarder::connect_server() -{ - int ret = ERROR_SUCCESS; - - std::string ip = srs_dns_resolve(server); - if (ip.empty()) { - ret = ERROR_SYSTEM_IP_INVALID; - srs_error("dns resolve server error, ip empty. ret=%d", ret); - return ret; - } - - sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - addr.sin_addr.s_addr = inet_addr(ip.c_str()); - - if (st_connect(stfd, (const struct sockaddr*)&addr, sizeof(sockaddr_in), ST_UTIME_NO_TIMEOUT) == -1){ - ret = ERROR_ST_CONNECT; - srs_error("connect to server error. ip=%s, port=%d, ret=%d", ip.c_str(), port, ret); - return ret; - } - srs_trace("connect to server success. server=%s, ip=%s, port=%d", server.c_str(), ip.c_str(), port); - - return ret; -} - -int SrsForwarder::cycle() -{ - int ret = ERROR_SUCCESS; - - client->set_recv_timeout(SRS_RECV_TIMEOUT_US); - client->set_send_timeout(SRS_SEND_TIMEOUT_US); - - if ((ret = connect_server()) != ERROR_SUCCESS) { - return ret; - } - srs_assert(client); - - if ((ret = client->handshake()) != ERROR_SUCCESS) { - srs_error("handshake with server failed. ret=%d", ret); - return ret; - } - if ((ret = client->connect_app(app, tc_url)) != ERROR_SUCCESS) { - srs_error("connect with server failed, tcUrl=%s. ret=%d", tc_url.c_str(), ret); - return ret; - } - if ((ret = client->create_stream(stream_id)) != ERROR_SUCCESS) { - srs_error("connect with server failed, stream_id=%d. ret=%d", stream_id, ret); - return ret; - } - if ((ret = client->publish(stream_name, stream_id)) != ERROR_SUCCESS) { - srs_error("connect with server failed, stream_name=%s, stream_id=%d. ret=%d", - stream_name.c_str(), stream_id, ret); - return ret; - } - - if ((ret = forward()) != ERROR_SUCCESS) { - return ret; - } - - return ret; -} - -int SrsForwarder::forward() -{ - int ret = ERROR_SUCCESS; - - client->set_recv_timeout(SRS_PULSE_TIMEOUT_MS * 1000); - - SrsPithyPrint pithy_print(SRS_STAGE_FORWARDER); - - while (loop) { - pithy_print.elapse(SRS_PULSE_TIMEOUT_MS); - - // switch to other st-threads. - st_usleep(0); - - // read from client. - if (true) { - SrsCommonMessage* msg = NULL; - ret = client->recv_message(&msg); - - srs_verbose("play loop recv message. ret=%d", ret); - if (ret != ERROR_SUCCESS && ret != ERROR_SOCKET_TIMEOUT) { - srs_error("recv server control message failed. ret=%d", ret); - return ret; - } - } - - // ignore when no messages. - int count = (int)msgs.size(); - if (msgs.empty()) { - continue; - } - - // reportable - if (pithy_print.can_print()) { - srs_trace("-> time=%"PRId64", msgs=%d, obytes=%"PRId64", ibytes=%"PRId64", okbps=%d, ikbps=%d", - pithy_print.get_age(), count, client->get_send_bytes(), client->get_recv_bytes(), client->get_send_kbps(), client->get_recv_kbps()); - } - - // all msgs to forward. - int i = 0; - for (i = 0; i < count; i++) { - SrsSharedPtrMessage* msg = msgs[i]; - msgs[i] = NULL; - - // we erased the sendout messages, the msg must not be NULL. - srs_assert(msg); - - ret = client->send_message(msg); - if (ret != ERROR_SUCCESS) { - srs_error("forwarder send message to server failed. ret=%d", ret); - - // convert the index to count when error. - i++; - - break; - } - } - - // clear sendout mesages. - if (i < count) { - srs_warn("clear forwarded msg, total=%d, forwarded=%d, ret=%d", count, i, ret); - } else { - srs_info("clear forwarded msg, total=%d, forwarded=%d, ret=%d", count, i, ret); - } - msgs.erase(msgs.begin(), msgs.begin() + i); - - if (ret != ERROR_SUCCESS) { - break; - } - } - - return ret; -} - -void SrsForwarder::forward_cycle() -{ - int ret = ERROR_SUCCESS; - - log_context->generate_id(); - srs_trace("forward cycle start"); - - while (loop) { - if ((ret = cycle()) != ERROR_SUCCESS) { - srs_warn("forward cycle failed, ignored and retry, ret=%d", ret); - } else { - srs_info("forward cycle success, retry"); - } - - if (!loop) { - break; - } - - st_usleep(SRS_FORWARDER_SLEEP_MS * 1000); - - if ((ret = open_socket()) != ERROR_SUCCESS) { - srs_warn("forward cycle reopen failed, ignored and retry, ret=%d", ret); - } else { - srs_info("forward cycle reopen success"); - } - } - srs_trace("forward cycle finished"); -} - -void* SrsForwarder::forward_thread(void* arg) -{ - SrsForwarder* obj = (SrsForwarder*)arg; - srs_assert(obj != NULL); - - obj->loop = true; - obj->forward_cycle(); - - return NULL; -} - +/* +The MIT License (MIT) + +Copyright (c) 2013 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 + +#include +#include +#include +#include +#include +#include +#include + +#define SRS_PULSE_TIMEOUT_MS 100 +#define SRS_FORWARDER_SLEEP_MS 2000 +#define SRS_SEND_TIMEOUT_US 3000000L +#define SRS_RECV_TIMEOUT_US SRS_SEND_TIMEOUT_US + +SrsForwarder::SrsForwarder() +{ + client = NULL; + stfd = NULL; + stream_id = 0; + + tid = NULL; + loop = false; +} + +SrsForwarder::~SrsForwarder() +{ + on_unpublish(); + + std::vector::iterator it; + for (it = msgs.begin(); it != msgs.end(); ++it) { + SrsSharedPtrMessage* msg = *it; + srs_freep(msg); + } + msgs.clear(); +} + +int SrsForwarder::on_publish(SrsRequest* req, std::string forward_server) +{ + int ret = ERROR_SUCCESS; + + // forward app + app = req->app; + + stream_name = req->stream; + server = forward_server; + std::string s_port = RTMP_DEFAULT_PORTS; + port = RTMP_DEFAULT_PORT; + + size_t pos = forward_server.find(":"); + if (pos != std::string::npos) { + s_port = forward_server.substr(pos + 1); + server = forward_server.substr(0, pos); + } + // discovery vhost + std::string vhost = req->vhost; + srs_vhost_resolve(vhost, s_port); + port = ::atoi(s_port.c_str()); + + // generate tcUrl + tc_url = "rtmp://"; + tc_url += vhost; + tc_url += "/"; + tc_url += req->app; + + // dead loop check + std::string source_ep = req->vhost; + source_ep += ":"; + source_ep += req->port; + + std::string dest_ep = vhost; + dest_ep += ":"; + dest_ep += s_port; + + if (source_ep == dest_ep) { + ret = ERROR_SYSTEM_FORWARD_LOOP; + srs_warn("farder loop detected. src=%s, dest=%s, ret=%d", + source_ep.c_str(), dest_ep.c_str(), ret); + return ret; + } + srs_trace("start forward %s to %s, stream: %s/%s", + source_ep.c_str(), dest_ep.c_str(), tc_url.c_str(), + stream_name.c_str()); + + // TODO: seems bug when republish and reforward. + + // start forward + if ((ret = open_socket()) != ERROR_SUCCESS) { + return ret; + } + + srs_assert(!tid); + if((tid = st_thread_create(forward_thread, this, 1, 0)) == NULL){ + ret = ERROR_ST_CREATE_FORWARD_THREAD; + srs_error("st_thread_create failed. ret=%d", ret); + return ret; + } + + return ret; +} + +void SrsForwarder::on_unpublish() +{ + if (tid) { + loop = false; + st_thread_interrupt(tid); + st_thread_join(tid, NULL); + tid = NULL; + } + + if (stfd) { + int fd = st_netfd_fileno(stfd); + st_netfd_close(stfd); + stfd = NULL; + + // st does not close it sometimes, + // close it manually. + close(fd); + } + + srs_freep(client); +} + +int SrsForwarder::on_meta_data(SrsSharedPtrMessage* metadata) +{ + int ret = ERROR_SUCCESS; + + msgs.push_back(metadata); + + return ret; +} + +int SrsForwarder::on_audio(SrsSharedPtrMessage* msg) +{ + int ret = ERROR_SUCCESS; + + msgs.push_back(ms + // TODO: FIXME: must drop the msgs when server failed.g); + + return ret; +} + +int SrsForwarder::on_video(SrsSharedPtrMessage* msg) +{ + int ret = ERROR_SUCCESS; + + // TODO: FIXME: must drop the msgs when server failed. + msgs.push_back(msg); + + return ret; +} + +int SrsForwarder::open_socket() +{ + int ret = ERROR_SUCCESS; + + srs_trace("forward stream=%s, tcUrl=%s to server=%s, port=%d", + stream_name.c_str(), tc_url.c_str(), server.c_str(), port); + + int sock = socket(AF_INET, SOCK_STREAM, 0); + if(sock == -1){ + ret = ERROR_SOCKET_CREATE; + srs_error("create socket error. ret=%d", ret); + return ret; + } + + stfd = st_netfd_open_socket(sock); + if(stfd == NULL){ + ret = ERROR_ST_OPEN_SOCKET; + srs_error("st_netfd_open_socket failed. ret=%d", ret); + return ret; + } + + srs_freep(client); + client = new SrsRtmpClient(stfd); + + return ret; +} + +int SrsForwarder::connect_server() +{ + int ret = ERROR_SUCCESS; + + std::string ip = srs_dns_resolve(server); + if (ip.empty()) { + ret = ERROR_SYSTEM_IP_INVALID; + srs_error("dns resolve server error, ip empty. ret=%d", ret); + return ret; + } + + sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = inet_addr(ip.c_str()); + + if (st_connect(stfd, (const struct sockaddr*)&addr, sizeof(sockaddr_in), ST_UTIME_NO_TIMEOUT) == -1){ + ret = ERROR_ST_CONNECT; + srs_error("connect to server error. ip=%s, port=%d, ret=%d", ip.c_str(), port, ret); + return ret; + } + srs_trace("connect to server success. server=%s, ip=%s, port=%d", server.c_str(), ip.c_str(), port); + + return ret; +} + +int SrsForwarder::cycle() +{ + int ret = ERROR_SUCCESS; + + client->set_recv_timeout(SRS_RECV_TIMEOUT_US); + client->set_send_timeout(SRS_SEND_TIMEOUT_US); + + if ((ret = connect_server()) != ERROR_SUCCESS) { + return ret; + } + srs_assert(client); + + if ((ret = client->handshake()) != ERROR_SUCCESS) { + srs_error("handshake with server failed. ret=%d", ret); + return ret; + } + if ((ret = client->connect_app(app, tc_url)) != ERROR_SUCCESS) { + srs_error("connect with server failed, tcUrl=%s. ret=%d", tc_url.c_str(), ret); + return ret; + } + if ((ret = client->create_stream(stream_id)) != ERROR_SUCCESS) { + srs_error("connect with server failed, stream_id=%d. ret=%d", stream_id, ret); + return ret; + } + if ((ret = client->publish(stream_name, stream_id)) != ERROR_SUCCESS) { + srs_error("connect with server failed, stream_name=%s, stream_id=%d. ret=%d", + stream_name.c_str(), stream_id, ret); + return ret; + } + + if ((ret = forward()) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +int SrsForwarder::forward() +{ + int ret = ERROR_SUCCESS; + + client->set_recv_timeout(SRS_PULSE_TIMEOUT_MS * 1000); + + SrsPithyPrint pithy_print(SRS_STAGE_FORWARDER); + + while (loop) { + pithy_print.elapse(SRS_PULSE_TIMEOUT_MS); + + // switch to other st-threads. + st_usleep(0); + + // read from client. + if (true) { + SrsCommonMessage* msg = NULL; + ret = client->recv_message(&msg); + + srs_verbose("play loop recv message. ret=%d", ret); + if (ret != ERROR_SUCCESS && ret != ERROR_SOCKET_TIMEOUT) { + srs_error("recv server control message failed. ret=%d", ret); + return ret; + } + } + + // ignore when no messages. + int count = (int)msgs.size(); + if (msgs.empty()) { + continue; + } + + // reportable + if (pithy_print.can_print()) { + srs_trace("-> time=%"PRId64", msgs=%d, obytes=%"PRId64", ibytes=%"PRId64", okbps=%d, ikbps=%d", + pithy_print.get_age(), count, client->get_send_bytes(), client->get_recv_bytes(), client->get_send_kbps(), client->get_recv_kbps()); + } + + // all msgs to forward. + int i = 0; + for (i = 0; i < count; i++) { + SrsSharedPtrMessage* msg = msgs[i]; + msgs[i] = NULL; + + // we erased the sendout messages, the msg must not be NULL. + srs_assert(msg); + + ret = client->send_message(msg); + if (ret != ERROR_SUCCESS) { + srs_error("forwarder send message to server failed. ret=%d", ret); + + // convert the index to count when error. + i++; + + break; + } + } + + // clear sendout mesages. + if (i < count) { + srs_warn("clear forwarded msg, total=%d, forwarded=%d, ret=%d", count, i, ret); + } else { + srs_info("clear forwarded msg, total=%d, forwarded=%d, ret=%d", count, i, ret); + } + msgs.erase(msgs.begin(), msgs.begin() + i); + + if (ret != ERROR_SUCCESS) { + break; + } + } + + return ret; +} + +void SrsForwarder::forward_cycle() +{ + int ret = ERROR_SUCCESS; + + log_context->generate_id(); + srs_trace("forward cycle start"); + + while (loop) { + if ((ret = cycle()) != ERROR_SUCCESS) { + srs_warn("forward cycle failed, ignored and retry, ret=%d", ret); + } else { + srs_info("forward cycle success, retry"); + } + + if (!loop) { + break; + } + + st_usleep(SRS_FORWARDER_SLEEP_MS * 1000); + + if ((ret = open_socket()) != ERROR_SUCCESS) { + srs_warn("forward cycle reopen failed, ignored and retry, ret=%d", ret); + } else { + srs_info("forward cycle reopen success"); + } + } + srs_trace("forward cycle finished"); +} + +void* SrsForwarder::forward_thread(void* arg) +{ + SrsForwarder* obj = (SrsForwarder*)arg; + srs_assert(obj != NULL); + + obj->loop = true; + obj->forward_cycle(); + + return NULL; +} + From 7baa53a8d6eaf3d849bd2aaccdfc155261dd87f4 Mon Sep 17 00:00:00 2001 From: winlin Date: Thu, 12 Dec 2013 12:42:53 +0800 Subject: [PATCH 09/37] add todo for memory increase when forwarder server failed. --- trunk/src/core/srs_core_forward.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/trunk/src/core/srs_core_forward.cpp b/trunk/src/core/srs_core_forward.cpp index 577f8e495..43c45fbea 100755 --- a/trunk/src/core/srs_core_forward.cpp +++ b/trunk/src/core/srs_core_forward.cpp @@ -162,8 +162,8 @@ int SrsForwarder::on_audio(SrsSharedPtrMessage* msg) { int ret = ERROR_SUCCESS; - msgs.push_back(ms - // TODO: FIXME: must drop the msgs when server failed.g); + // TODO: FIXME: must drop the msgs when server failed. + msgs.push_back(msg); return ret; } From 6329284b392a6754c54b74e94acd08836a8a25b4 Mon Sep 17 00:00:00 2001 From: winlin Date: Thu, 12 Dec 2013 16:18:32 +0800 Subject: [PATCH 10/37] add todo for the bug of forwarder --- trunk/src/core/srs_core_forward.cpp | 3 + trunk/src/core/srs_core_rtmp.cpp | 2252 ++++++++++++++------------- 2 files changed, 1130 insertions(+), 1125 deletions(-) mode change 100644 => 100755 trunk/src/core/srs_core_rtmp.cpp diff --git a/trunk/src/core/srs_core_forward.cpp b/trunk/src/core/srs_core_forward.cpp index 43c45fbea..ebca3f667 100755 --- a/trunk/src/core/srs_core_forward.cpp +++ b/trunk/src/core/srs_core_forward.cpp @@ -255,6 +255,9 @@ int SrsForwarder::cycle() srs_error("connect with server failed, stream_id=%d. ret=%d", stream_id, ret); return ret; } + + // TODO: FIXME: need to cache the metadata and sequence header when reconnect. + if ((ret = client->publish(stream_name, stream_id)) != ERROR_SUCCESS) { srs_error("connect with server failed, stream_name=%s, stream_id=%d. ret=%d", stream_name.c_str(), stream_id, ret); diff --git a/trunk/src/core/srs_core_rtmp.cpp b/trunk/src/core/srs_core_rtmp.cpp old mode 100644 new mode 100755 index f79ffbe83..9c5e63d50 --- a/trunk/src/core/srs_core_rtmp.cpp +++ b/trunk/src/core/srs_core_rtmp.cpp @@ -1,1125 +1,1127 @@ -/* -The MIT License (MIT) - -Copyright (c) 2013 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 -#include -#include -#include -#include - -/** -* the signature for packets to client. -*/ -#define RTMP_SIG_FMS_VER "3,5,3,888" -#define RTMP_SIG_AMF0_VER 0 -#define RTMP_SIG_CLIENT_ID "ASAICiss" - -/** -* onStatus consts. -*/ -#define StatusLevel "level" -#define StatusCode "code" -#define StatusDescription "description" -#define StatusDetails "details" -#define StatusClientId "clientid" -// status value -#define StatusLevelStatus "status" -// code value -#define StatusCodeConnectSuccess "NetConnection.Connect.Success" -#define StatusCodeStreamReset "NetStream.Play.Reset" -#define StatusCodeStreamStart "NetStream.Play.Start" -#define StatusCodeStreamPause "NetStream.Pause.Notify" -#define StatusCodeStreamUnpause "NetStream.Unpause.Notify" -#define StatusCodePublishStart "NetStream.Publish.Start" -#define StatusCodeDataStart "NetStream.Data.Start" -#define StatusCodeUnpublishSuccess "NetStream.Unpublish.Success" - -// FMLE -#define RTMP_AMF0_COMMAND_ON_FC_PUBLISH "onFCPublish" -#define RTMP_AMF0_COMMAND_ON_FC_UNPUBLISH "onFCUnpublish" - -// default stream id for response the createStream request. -#define SRS_DEFAULT_SID 1 - -SrsRequest::SrsRequest() -{ - objectEncoding = RTMP_SIG_AMF0_VER; -} - -SrsRequest::~SrsRequest() -{ -} - -int SrsRequest::discovery_app() -{ - int ret = ERROR_SUCCESS; - - 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_verbose("discovery schema=%s", schema.c_str()); - } - - if ((pos = url.find("/")) != std::string::npos) { - vhost = url.substr(0, pos); - url = url.substr(vhost.length() + 1); - srs_verbose("discovery vhost=%s", vhost.c_str()); - } - - port = RTMP_DEFAULT_PORTS; - if ((pos = vhost.find(":")) != std::string::npos) { - port = vhost.substr(pos + 1); - vhost = vhost.substr(0, pos); - srs_verbose("discovery vhost=%s, port=%s", vhost.c_str(), port.c_str()); - } - - app = url; - srs_vhost_resolve(vhost, app); - - // resolve the vhost from config - SrsConfDirective* parsed_vhost = config->get_vhost(vhost); - if (parsed_vhost) { - vhost = parsed_vhost->arg0(); - } - - srs_info("discovery app success. schema=%s, vhost=%s, port=%s, app=%s", - schema.c_str(), vhost.c_str(), port.c_str(), app.c_str()); - - if (schema.empty() || vhost.empty() || port.empty() || app.empty()) { - ret = ERROR_RTMP_REQ_TCURL; - srs_error("discovery tcUrl failed. " - "tcUrl=%s, schema=%s, vhost=%s, port=%s, app=%s, ret=%d", - tcUrl.c_str(), schema.c_str(), vhost.c_str(), port.c_str(), app.c_str(), ret); - return ret; - } - - strip(); - - return ret; -} - -std::string SrsRequest::get_stream_url() -{ - std::string url = ""; - - url += vhost; - url += "/"; - url += app; - url += "/"; - url += stream; - - return url; -} - -void SrsRequest::strip() -{ - trim(vhost, "/ \n\r\t"); - trim(app, "/ \n\r\t"); - trim(stream, "/ \n\r\t"); -} - -std::string& SrsRequest::trim(std::string& str, std::string chs) -{ - for (int i = 0; i < (int)chs.length(); i++) { - char ch = chs.at(i); - - for (std::string::iterator it = str.begin(); it != str.end();) { - if (ch == *it) { - it = str.erase(it); - } else { - ++it; - } - } - } - - return str; -} - -SrsResponse::SrsResponse() -{ - stream_id = SRS_DEFAULT_SID; -} - -SrsResponse::~SrsResponse() -{ -} - -SrsRtmpClient::SrsRtmpClient(st_netfd_t _stfd) -{ - stfd = _stfd; - protocol = new SrsProtocol(stfd); -} - -SrsRtmpClient::~SrsRtmpClient() -{ - srs_freep(protocol); -} - -void SrsRtmpClient::set_recv_timeout(int64_t timeout_us) -{ - protocol->set_recv_timeout(timeout_us); -} - -void SrsRtmpClient::set_send_timeout(int64_t timeout_us) -{ - protocol->set_send_timeout(timeout_us); -} - -int64_t SrsRtmpClient::get_recv_bytes() -{ - return protocol->get_recv_bytes(); -} - -int64_t SrsRtmpClient::get_send_bytes() -{ - return protocol->get_send_bytes(); -} - -int SrsRtmpClient::get_recv_kbps() -{ - return protocol->get_recv_kbps(); -} - -int SrsRtmpClient::get_send_kbps() -{ - return protocol->get_send_kbps(); -} - -int SrsRtmpClient::recv_message(SrsCommonMessage** pmsg) -{ - return protocol->recv_message(pmsg); -} - -int SrsRtmpClient::send_message(ISrsMessage* msg) -{ - return protocol->send_message(msg); -} - -int SrsRtmpClient::handshake() -{ - int ret = ERROR_SUCCESS; - - SrsSocket skt(stfd); - - SrsComplexHandshake complex_hs; - SrsSimpleHandshake simple_hs; - if ((ret = simple_hs.handshake_with_server(skt, complex_hs)) != ERROR_SUCCESS) { - return ret; - } - - return ret; -} - -int SrsRtmpClient::connect_app(std::string app, std::string tc_url) -{ - int ret = ERROR_SUCCESS; - - // Connect(vhost, app) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsConnectAppPacket* pkt = new SrsConnectAppPacket(); - msg->set_packet(pkt, 0); - - pkt->command_object = new SrsAmf0Object(); - pkt->command_object->set("app", new SrsAmf0String(app.c_str())); - pkt->command_object->set("swfUrl", new SrsAmf0String()); - pkt->command_object->set("tcUrl", new SrsAmf0String(tc_url.c_str())); - pkt->command_object->set("fpad", new SrsAmf0Boolean(false)); - pkt->command_object->set("capabilities", new SrsAmf0Number(239)); - pkt->command_object->set("audioCodecs", new SrsAmf0Number(3575)); - pkt->command_object->set("videoCodecs", new SrsAmf0Number(252)); - pkt->command_object->set("videoFunction", new SrsAmf0Number(1)); - pkt->command_object->set("pageUrl", new SrsAmf0String()); - pkt->command_object->set("objectEncoding", new SrsAmf0Number(0)); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - return ret; - } - } - - // Set Window Acknowledgement size(2500000) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsSetWindowAckSizePacket* pkt = new SrsSetWindowAckSizePacket(); - - pkt->ackowledgement_window_size = 2500000; - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - return ret; - } - } - - // expect connect _result - SrsCommonMessage* msg = NULL; - SrsConnectAppResPacket* pkt = NULL; - if ((ret = srs_rtmp_expect_message(protocol, &msg, &pkt)) != ERROR_SUCCESS) { - srs_error("expect connect app response message failed. ret=%d", ret); - return ret; - } - SrsAutoFree(SrsCommonMessage, msg, false); - srs_info("get connect app response message"); - - return ret; -} - -int SrsRtmpClient::create_stream(int& stream_id) -{ - int ret = ERROR_SUCCESS; - - // CreateStream - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsCreateStreamPacket* pkt = new SrsCreateStreamPacket(); - - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - return ret; - } - } - - // CreateStream _result. - if (true) { - SrsCommonMessage* msg = NULL; - SrsCreateStreamResPacket* pkt = NULL; - if ((ret = srs_rtmp_expect_message(protocol, &msg, &pkt)) != ERROR_SUCCESS) { - srs_error("expect create stream response message failed. ret=%d", ret); - return ret; - } - SrsAutoFree(SrsCommonMessage, msg, false); - srs_info("get create stream response message"); - - stream_id = (int)pkt->stream_id; - } - - return ret; -} - -int SrsRtmpClient::play(std::string stream, int stream_id) -{ - int ret = ERROR_SUCCESS; - - // Play(stream) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsPlayPacket* pkt = new SrsPlayPacket(); - - pkt->stream_name = stream; - msg->set_packet(pkt, stream_id); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send play stream failed. " - "stream=%s, stream_id=%d, ret=%d", - stream.c_str(), stream_id, ret); - return ret; - } - } - - // SetBufferLength(1000ms) - int buffer_length_ms = 1000; - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsUserControlPacket* pkt = new SrsUserControlPacket(); - - pkt->event_type = SrcPCUCSetBufferLength; - pkt->event_data = stream_id; - pkt->extra_data = buffer_length_ms; - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send set buffer length failed. " - "stream=%s, stream_id=%d, bufferLength=%d, ret=%d", - stream.c_str(), stream_id, buffer_length_ms, ret); - return ret; - } - } - - return ret; -} - -int SrsRtmpClient::publish(std::string stream, int stream_id) -{ - int ret = ERROR_SUCCESS; - - // publish(stream) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsPublishPacket* pkt = new SrsPublishPacket(); - - pkt->stream_name = stream; - msg->set_packet(pkt, stream_id); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send publish message failed. " - "stream=%s, stream_id=%d, ret=%d", - stream.c_str(), stream_id, ret); - return ret; - } - } - - return ret; -} - -SrsRtmp::SrsRtmp(st_netfd_t client_stfd) -{ - protocol = new SrsProtocol(client_stfd); - stfd = client_stfd; -} - -SrsRtmp::~SrsRtmp() -{ - srs_freep(protocol); -} - -SrsProtocol* SrsRtmp::get_protocol() -{ - return protocol; -} - -void SrsRtmp::set_recv_timeout(int64_t timeout_us) -{ - protocol->set_recv_timeout(timeout_us); -} - -int64_t SrsRtmp::get_recv_timeout() -{ - return protocol->get_recv_timeout(); -} - -void SrsRtmp::set_send_timeout(int64_t timeout_us) -{ - protocol->set_send_timeout(timeout_us); -} - -int64_t SrsRtmp::get_recv_bytes() -{ - return protocol->get_recv_bytes(); -} - -int64_t SrsRtmp::get_send_bytes() -{ - return protocol->get_send_bytes(); -} - -int SrsRtmp::get_recv_kbps() -{ - return protocol->get_recv_kbps(); -} - -int SrsRtmp::get_send_kbps() -{ - return protocol->get_send_kbps(); -} - -int SrsRtmp::recv_message(SrsCommonMessage** pmsg) -{ - return protocol->recv_message(pmsg); -} - -int SrsRtmp::send_message(ISrsMessage* msg) -{ - return protocol->send_message(msg); -} - -int SrsRtmp::handshake() -{ - int ret = ERROR_SUCCESS; - - SrsSocket skt(stfd); - - SrsComplexHandshake complex_hs; - SrsSimpleHandshake simple_hs; - if ((ret = simple_hs.handshake_with_client(skt, complex_hs)) != ERROR_SUCCESS) { - return ret; - } - - return ret; -} - -int SrsRtmp::connect_app(SrsRequest* req) -{ - int ret = ERROR_SUCCESS; - - SrsCommonMessage* msg = NULL; - SrsConnectAppPacket* pkt = NULL; - if ((ret = srs_rtmp_expect_message(protocol, &msg, &pkt)) != ERROR_SUCCESS) { - srs_error("expect connect app message failed. ret=%d", ret); - return ret; - } - SrsAutoFree(SrsCommonMessage, msg, false); - srs_info("get connect app message"); - - SrsAmf0Any* prop = NULL; - - if ((prop = pkt->command_object->ensure_property_string("tcUrl")) == NULL) { - ret = ERROR_RTMP_REQ_CONNECT; - srs_error("invalid request, must specifies the tcUrl. ret=%d", ret); - return ret; - } - req->tcUrl = srs_amf0_convert(prop)->value; - - if ((prop = pkt->command_object->ensure_property_string("pageUrl")) != NULL) { - req->pageUrl = srs_amf0_convert(prop)->value; - } - - if ((prop = pkt->command_object->ensure_property_string("swfUrl")) != NULL) { - req->swfUrl = srs_amf0_convert(prop)->value; - } - - if ((prop = pkt->command_object->ensure_property_number("objectEncoding")) != NULL) { - req->objectEncoding = srs_amf0_convert(prop)->value; - } - - srs_info("get connect app message params success."); - - return req->discovery_app(); -} - -int SrsRtmp::set_window_ack_size(int ack_size) -{ - int ret = ERROR_SUCCESS; - - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsSetWindowAckSizePacket* pkt = new SrsSetWindowAckSizePacket(); - - pkt->ackowledgement_window_size = ack_size; - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send ack size message failed. ret=%d", ret); - return ret; - } - srs_info("send ack size message success. ack_size=%d", ack_size); - - return ret; -} - -int SrsRtmp::set_peer_bandwidth(int bandwidth, int type) -{ - int ret = ERROR_SUCCESS; - - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsSetPeerBandwidthPacket* pkt = new SrsSetPeerBandwidthPacket(); - - pkt->bandwidth = bandwidth; - pkt->type = type; - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send set bandwidth message failed. ret=%d", ret); - return ret; - } - srs_info("send set bandwidth message " - "success. bandwidth=%d, type=%d", bandwidth, type); - - return ret; -} - -int SrsRtmp::response_connect_app(SrsRequest* req) -{ - int ret = ERROR_SUCCESS; - - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsConnectAppResPacket* pkt = new SrsConnectAppResPacket(); - - pkt->props->set("fmsVer", new SrsAmf0String("FMS/"RTMP_SIG_FMS_VER)); - pkt->props->set("capabilities", new SrsAmf0Number(127)); - pkt->props->set("mode", new SrsAmf0Number(1)); - - pkt->info->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); - pkt->info->set(StatusCode, new SrsAmf0String(StatusCodeConnectSuccess)); - pkt->info->set(StatusDescription, new SrsAmf0String("Connection succeeded")); - pkt->info->set("objectEncoding", new SrsAmf0Number(req->objectEncoding)); - SrsASrsAmf0EcmaArray* data = new SrsASrsAmf0EcmaArray(); - pkt->info->set("data", data); - - data->set("srs_version", new SrsAmf0String(RTMP_SIG_FMS_VER)); - data->set("srs_server", new SrsAmf0String(RTMP_SIG_SRS_NAME)); - data->set("srs_license", new SrsAmf0String(RTMP_SIG_SRS_LICENSE)); - data->set("srs_role", new SrsAmf0String(RTMP_SIG_SRS_ROLE)); - data->set("srs_url", new SrsAmf0String(RTMP_SIG_SRS_URL)); - data->set("srs_version", new SrsAmf0String(RTMP_SIG_SRS_VERSION)); - data->set("srs_site", new SrsAmf0String(RTMP_SIG_SRS_WEB)); - data->set("srs_email", new SrsAmf0String(RTMP_SIG_SRS_EMAIL)); - data->set("srs_copyright", new SrsAmf0String(RTMP_SIG_SRS_COPYRIGHT)); - - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send connect app response message failed. ret=%d", ret); - return ret; - } - srs_info("send connect app response message success."); - - return ret; -} - -int SrsRtmp::on_bw_done() -{ - int ret = ERROR_SUCCESS; - - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsOnBWDonePacket* pkt = new SrsOnBWDonePacket(); - - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send onBWDone message failed. ret=%d", ret); - return ret; - } - srs_info("send onBWDone message success."); - - return ret; -} - -int SrsRtmp::identify_client(int stream_id, SrsClientType& type, std::string& stream_name) -{ - type = SrsClientUnknown; - int ret = ERROR_SUCCESS; - - while (true) { - SrsCommonMessage* msg = NULL; - if ((ret = protocol->recv_message(&msg)) != ERROR_SUCCESS) { - srs_error("recv identify client message failed. ret=%d", ret); - return ret; - } - - SrsAutoFree(SrsCommonMessage, msg, false); - - if (!msg->header.is_amf0_command() && !msg->header.is_amf3_command()) { - srs_trace("identify ignore messages except " - "AMF0/AMF3 command message. type=%#x", msg->header.message_type); - continue; - } - - if ((ret = msg->decode_packet(protocol)) != ERROR_SUCCESS) { - srs_error("identify decode message failed. ret=%d", ret); - return ret; - } - - SrsPacket* pkt = msg->get_packet(); - if (dynamic_cast(pkt)) { - srs_info("identify client by create stream, play or flash publish."); - return identify_create_stream_client( - dynamic_cast(pkt), stream_id, type, stream_name); - } - if (dynamic_cast(pkt)) { - srs_info("identify client by releaseStream, fmle publish."); - return identify_fmle_publish_client( - dynamic_cast(pkt), type, stream_name); - } - - srs_trace("ignore AMF0/AMF3 command message."); - } - - return ret; -} - -int SrsRtmp::set_chunk_size(int chunk_size) -{ - int ret = ERROR_SUCCESS; - - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsSetChunkSizePacket* pkt = new SrsSetChunkSizePacket(); - - pkt->chunk_size = chunk_size; - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send set chunk size message failed. ret=%d", ret); - return ret; - } - srs_info("send set chunk size message success. chunk_size=%d", chunk_size); - - return ret; -} - -int SrsRtmp::start_play(int stream_id) -{ - int ret = ERROR_SUCCESS; - - // StreamBegin - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsUserControlPacket* pkt = new SrsUserControlPacket(); - - pkt->event_type = SrcPCUCStreamBegin; - pkt->event_data = stream_id; - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send PCUC(StreamBegin) message failed. ret=%d", ret); - return ret; - } - srs_info("send PCUC(StreamBegin) message success."); - } - - // onStatus(NetStream.Play.Reset) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); - - pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); - pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeStreamReset)); - pkt->data->set(StatusDescription, new SrsAmf0String("Playing and resetting stream.")); - pkt->data->set(StatusDetails, new SrsAmf0String("stream")); - pkt->data->set(StatusClientId, new SrsAmf0String(RTMP_SIG_CLIENT_ID)); - - msg->set_packet(pkt, stream_id); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send onStatus(NetStream.Play.Reset) message failed. ret=%d", ret); - return ret; - } - srs_info("send onStatus(NetStream.Play.Reset) message success."); - } - - // onStatus(NetStream.Play.Start) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); - - pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); - pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeStreamStart)); - pkt->data->set(StatusDescription, new SrsAmf0String("Started playing stream.")); - pkt->data->set(StatusDetails, new SrsAmf0String("stream")); - pkt->data->set(StatusClientId, new SrsAmf0String(RTMP_SIG_CLIENT_ID)); - - msg->set_packet(pkt, stream_id); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send onStatus(NetStream.Play.Reset) message failed. ret=%d", ret); - return ret; - } - srs_info("send onStatus(NetStream.Play.Reset) message success."); - } - - // |RtmpSampleAccess(false, false) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsSampleAccessPacket* pkt = new SrsSampleAccessPacket(); - - msg->set_packet(pkt, stream_id); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send |RtmpSampleAccess(false, false) message failed. ret=%d", ret); - return ret; - } - srs_info("send |RtmpSampleAccess(false, false) message success."); - } - - // onStatus(NetStream.Data.Start) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsOnStatusDataPacket* pkt = new SrsOnStatusDataPacket(); - - pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeDataStart)); - - msg->set_packet(pkt, stream_id); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send onStatus(NetStream.Data.Start) message failed. ret=%d", ret); - return ret; - } - srs_info("send onStatus(NetStream.Data.Start) message success."); - } - - srs_info("start play success."); - - return ret; -} - -int SrsRtmp::on_play_client_pause(int stream_id, bool is_pause) -{ - int ret = ERROR_SUCCESS; - - if (is_pause) { - // onStatus(NetStream.Pause.Notify) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); - - pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); - pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeStreamPause)); - pkt->data->set(StatusDescription, new SrsAmf0String("Paused stream.")); - - msg->set_packet(pkt, stream_id); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send onStatus(NetStream.Pause.Notify) message failed. ret=%d", ret); - return ret; - } - srs_info("send onStatus(NetStream.Pause.Notify) message success."); - } - // StreamEOF - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsUserControlPacket* pkt = new SrsUserControlPacket(); - - pkt->event_type = SrcPCUCStreamEOF; - pkt->event_data = stream_id; - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send PCUC(StreamEOF) message failed. ret=%d", ret); - return ret; - } - srs_info("send PCUC(StreamEOF) message success."); - } - } else { - // onStatus(NetStream.Unpause.Notify) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); - - pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); - pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeStreamUnpause)); - pkt->data->set(StatusDescription, new SrsAmf0String("Unpaused stream.")); - - msg->set_packet(pkt, stream_id); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send onStatus(NetStream.Unpause.Notify) message failed. ret=%d", ret); - return ret; - } - srs_info("send onStatus(NetStream.Unpause.Notify) message success."); - } - // StreanBegin - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsUserControlPacket* pkt = new SrsUserControlPacket(); - - pkt->event_type = SrcPCUCStreamBegin; - pkt->event_data = stream_id; - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send PCUC(StreanBegin) message failed. ret=%d", ret); - return ret; - } - srs_info("send PCUC(StreanBegin) message success."); - } - } - - return ret; -} - -int SrsRtmp::start_fmle_publish(int stream_id) -{ - int ret = ERROR_SUCCESS; - - // FCPublish - double fc_publish_tid = 0; - if (true) { - SrsCommonMessage* msg = NULL; - SrsFMLEStartPacket* pkt = NULL; - if ((ret = srs_rtmp_expect_message(protocol, &msg, &pkt)) != ERROR_SUCCESS) { - srs_error("recv FCPublish message failed. ret=%d", ret); - return ret; - } - srs_info("recv FCPublish request message success."); - - SrsAutoFree(SrsCommonMessage, msg, false); - fc_publish_tid = pkt->transaction_id; - } - // FCPublish response - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsFMLEStartResPacket* pkt = new SrsFMLEStartResPacket(fc_publish_tid); - - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send FCPublish response message failed. ret=%d", ret); - return ret; - } - srs_info("send FCPublish response message success."); - } - - // createStream - double create_stream_tid = 0; - if (true) { - SrsCommonMessage* msg = NULL; - SrsCreateStreamPacket* pkt = NULL; - if ((ret = srs_rtmp_expect_message(protocol, &msg, &pkt)) != ERROR_SUCCESS) { - srs_error("recv createStream message failed. ret=%d", ret); - return ret; - } - srs_info("recv createStream request message success."); - - SrsAutoFree(SrsCommonMessage, msg, false); - create_stream_tid = pkt->transaction_id; - } - // createStream response - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsCreateStreamResPacket* pkt = new SrsCreateStreamResPacket(create_stream_tid, stream_id); - - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send createStream response message failed. ret=%d", ret); - return ret; - } - srs_info("send createStream response message success."); - } - - // publish - if (true) { - SrsCommonMessage* msg = NULL; - SrsPublishPacket* pkt = NULL; - if ((ret = srs_rtmp_expect_message(protocol, &msg, &pkt)) != ERROR_SUCCESS) { - srs_error("recv publish message failed. ret=%d", ret); - return ret; - } - srs_info("recv publish request message success."); - - SrsAutoFree(SrsCommonMessage, msg, false); - } - // publish response onFCPublish(NetStream.Publish.Start) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); - - pkt->command_name = RTMP_AMF0_COMMAND_ON_FC_PUBLISH; - pkt->data->set(StatusCode, new SrsAmf0String(StatusCodePublishStart)); - pkt->data->set(StatusDescription, new SrsAmf0String("Started publishing stream.")); - - msg->set_packet(pkt, stream_id); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send onFCPublish(NetStream.Publish.Start) message failed. ret=%d", ret); - return ret; - } - srs_info("send onFCPublish(NetStream.Publish.Start) message success."); - } - // publish response onStatus(NetStream.Publish.Start) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); - - pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); - pkt->data->set(StatusCode, new SrsAmf0String(StatusCodePublishStart)); - pkt->data->set(StatusDescription, new SrsAmf0String("Started publishing stream.")); - pkt->data->set(StatusClientId, new SrsAmf0String(RTMP_SIG_CLIENT_ID)); - - msg->set_packet(pkt, stream_id); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send onStatus(NetStream.Publish.Start) message failed. ret=%d", ret); - return ret; - } - srs_info("send onStatus(NetStream.Publish.Start) message success."); - } - - srs_info("FMLE publish success."); - - return ret; -} - -int SrsRtmp::fmle_unpublish(int stream_id, double unpublish_tid) -{ - int ret = ERROR_SUCCESS; - - // publish response onFCUnpublish(NetStream.unpublish.Success) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); - - pkt->command_name = RTMP_AMF0_COMMAND_ON_FC_UNPUBLISH; - pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeUnpublishSuccess)); - pkt->data->set(StatusDescription, new SrsAmf0String("Stop publishing stream.")); - - msg->set_packet(pkt, stream_id); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send onFCUnpublish(NetStream.unpublish.Success) message failed. ret=%d", ret); - return ret; - } - srs_info("send onFCUnpublish(NetStream.unpublish.Success) message success."); - } - // FCUnpublish response - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsFMLEStartResPacket* pkt = new SrsFMLEStartResPacket(unpublish_tid); - - msg->set_packet(pkt, stream_id); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send FCUnpublish response message failed. ret=%d", ret); - return ret; - } - srs_info("send FCUnpublish response message success."); - } - // publish response onStatus(NetStream.Unpublish.Success) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); - - pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); - pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeUnpublishSuccess)); - pkt->data->set(StatusDescription, new SrsAmf0String("Stream is now unpublished")); - pkt->data->set(StatusClientId, new SrsAmf0String(RTMP_SIG_CLIENT_ID)); - - msg->set_packet(pkt, stream_id); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send onStatus(NetStream.Unpublish.Success) message failed. ret=%d", ret); - return ret; - } - srs_info("send onStatus(NetStream.Unpublish.Success) message success."); - } - - srs_info("FMLE unpublish success."); - - return ret; -} - -int SrsRtmp::start_flash_publish(int stream_id) -{ - int ret = ERROR_SUCCESS; - - // publish response onStatus(NetStream.Publish.Start) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); - - pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); - pkt->data->set(StatusCode, new SrsAmf0String(StatusCodePublishStart)); - pkt->data->set(StatusDescription, new SrsAmf0String("Started publishing stream.")); - pkt->data->set(StatusClientId, new SrsAmf0String(RTMP_SIG_CLIENT_ID)); - - msg->set_packet(pkt, stream_id); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send onStatus(NetStream.Publish.Start) message failed. ret=%d", ret); - return ret; - } - srs_info("send onStatus(NetStream.Publish.Start) message success."); - } - - srs_info("flash publish success."); - - return ret; -} - -int SrsRtmp::identify_create_stream_client(SrsCreateStreamPacket* req, int stream_id, SrsClientType& type, std::string& stream_name) -{ - int ret = ERROR_SUCCESS; - - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsCreateStreamResPacket* pkt = new SrsCreateStreamResPacket(req->transaction_id, stream_id); - - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send createStream response message failed. ret=%d", ret); - return ret; - } - srs_info("send createStream response message success."); - } - - while (true) { - SrsCommonMessage* msg = NULL; - if ((ret = protocol->recv_message(&msg)) != ERROR_SUCCESS) { - srs_error("recv identify client message failed. ret=%d", ret); - return ret; - } - - SrsAutoFree(SrsCommonMessage, msg, false); - - if (!msg->header.is_amf0_command() && !msg->header.is_amf3_command()) { - srs_trace("identify ignore messages except " - "AMF0/AMF3 command message. type=%#x", msg->header.message_type); - continue; - } - - if ((ret = msg->decode_packet(protocol)) != ERROR_SUCCESS) { - srs_error("identify decode message failed. ret=%d", ret); - return ret; - } - - SrsPacket* pkt = msg->get_packet(); - if (dynamic_cast(pkt)) { - SrsPlayPacket* play = dynamic_cast(pkt); - type = SrsClientPlay; - stream_name = play->stream_name; - srs_trace("identity client type=play, stream_name=%s", stream_name.c_str()); - return ret; - } - if (dynamic_cast(pkt)) { - srs_info("identify client by publish, falsh publish."); - return identify_flash_publish_client( - dynamic_cast(pkt), type, stream_name); - } - - srs_trace("ignore AMF0/AMF3 command message."); - } - - return ret; -} - -int SrsRtmp::identify_fmle_publish_client(SrsFMLEStartPacket* req, SrsClientType& type, std::string& stream_name) -{ - int ret = ERROR_SUCCESS; - - type = SrsClientFMLEPublish; - stream_name = req->stream_name; - - // releaseStream response - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsFMLEStartResPacket* pkt = new SrsFMLEStartResPacket(req->transaction_id); - - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send releaseStream response message failed. ret=%d", ret); - return ret; - } - srs_info("send releaseStream response message success."); - } - - return ret; -} - -int SrsRtmp::identify_flash_publish_client(SrsPublishPacket* req, SrsClientType& type, std::string& stream_name) -{ - int ret = ERROR_SUCCESS; - - type = SrsClientFlashPublish; - stream_name = req->stream_name; - - return ret; -} - +/* +The MIT License (MIT) + +Copyright (c) 2013 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 +#include +#include +#include +#include + +/** +* the signature for packets to client. +*/ +#define RTMP_SIG_FMS_VER "3,5,3,888" +#define RTMP_SIG_AMF0_VER 0 +#define RTMP_SIG_CLIENT_ID "ASAICiss" + +/** +* onStatus consts. +*/ +#define StatusLevel "level" +#define StatusCode "code" +#define StatusDescription "description" +#define StatusDetails "details" +#define StatusClientId "clientid" +// status value +#define StatusLevelStatus "status" +// code value +#define StatusCodeConnectSuccess "NetConnection.Connect.Success" +#define StatusCodeStreamReset "NetStream.Play.Reset" +#define StatusCodeStreamStart "NetStream.Play.Start" +#define StatusCodeStreamPause "NetStream.Pause.Notify" +#define StatusCodeStreamUnpause "NetStream.Unpause.Notify" +#define StatusCodePublishStart "NetStream.Publish.Start" +#define StatusCodeDataStart "NetStream.Data.Start" +#define StatusCodeUnpublishSuccess "NetStream.Unpublish.Success" + +// FMLE +#define RTMP_AMF0_COMMAND_ON_FC_PUBLISH "onFCPublish" +#define RTMP_AMF0_COMMAND_ON_FC_UNPUBLISH "onFCUnpublish" + +// default stream id for response the createStream request. +#define SRS_DEFAULT_SID 1 + +SrsRequest::SrsRequest() +{ + objectEncoding = RTMP_SIG_AMF0_VER; +} + +SrsRequest::~SrsRequest() +{ +} + +int SrsRequest::discovery_app() +{ + int ret = ERROR_SUCCESS; + + 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_verbose("discovery schema=%s", schema.c_str()); + } + + if ((pos = url.find("/")) != std::string::npos) { + vhost = url.substr(0, pos); + url = url.substr(vhost.length() + 1); + srs_verbose("discovery vhost=%s", vhost.c_str()); + } + + port = RTMP_DEFAULT_PORTS; + if ((pos = vhost.find(":")) != std::string::npos) { + port = vhost.substr(pos + 1); + vhost = vhost.substr(0, pos); + srs_verbose("discovery vhost=%s, port=%s", vhost.c_str(), port.c_str()); + } + + app = url; + srs_vhost_resolve(vhost, app); + + // resolve the vhost from config + SrsConfDirective* parsed_vhost = config->get_vhost(vhost); + if (parsed_vhost) { + vhost = parsed_vhost->arg0(); + } + + // TODO: discovery the params of vhost. + + srs_info("discovery app success. schema=%s, vhost=%s, port=%s, app=%s", + schema.c_str(), vhost.c_str(), port.c_str(), app.c_str()); + + if (schema.empty() || vhost.empty() || port.empty() || app.empty()) { + ret = ERROR_RTMP_REQ_TCURL; + srs_error("discovery tcUrl failed. " + "tcUrl=%s, schema=%s, vhost=%s, port=%s, app=%s, ret=%d", + tcUrl.c_str(), schema.c_str(), vhost.c_str(), port.c_str(), app.c_str(), ret); + return ret; + } + + strip(); + + return ret; +} + +std::string SrsRequest::get_stream_url() +{ + std::string url = ""; + + url += vhost; + url += "/"; + url += app; + url += "/"; + url += stream; + + return url; +} + +void SrsRequest::strip() +{ + trim(vhost, "/ \n\r\t"); + trim(app, "/ \n\r\t"); + trim(stream, "/ \n\r\t"); +} + +std::string& SrsRequest::trim(std::string& str, std::string chs) +{ + for (int i = 0; i < (int)chs.length(); i++) { + char ch = chs.at(i); + + for (std::string::iterator it = str.begin(); it != str.end();) { + if (ch == *it) { + it = str.erase(it); + } else { + ++it; + } + } + } + + return str; +} + +SrsResponse::SrsResponse() +{ + stream_id = SRS_DEFAULT_SID; +} + +SrsResponse::~SrsResponse() +{ +} + +SrsRtmpClient::SrsRtmpClient(st_netfd_t _stfd) +{ + stfd = _stfd; + protocol = new SrsProtocol(stfd); +} + +SrsRtmpClient::~SrsRtmpClient() +{ + srs_freep(protocol); +} + +void SrsRtmpClient::set_recv_timeout(int64_t timeout_us) +{ + protocol->set_recv_timeout(timeout_us); +} + +void SrsRtmpClient::set_send_timeout(int64_t timeout_us) +{ + protocol->set_send_timeout(timeout_us); +} + +int64_t SrsRtmpClient::get_recv_bytes() +{ + return protocol->get_recv_bytes(); +} + +int64_t SrsRtmpClient::get_send_bytes() +{ + return protocol->get_send_bytes(); +} + +int SrsRtmpClient::get_recv_kbps() +{ + return protocol->get_recv_kbps(); +} + +int SrsRtmpClient::get_send_kbps() +{ + return protocol->get_send_kbps(); +} + +int SrsRtmpClient::recv_message(SrsCommonMessage** pmsg) +{ + return protocol->recv_message(pmsg); +} + +int SrsRtmpClient::send_message(ISrsMessage* msg) +{ + return protocol->send_message(msg); +} + +int SrsRtmpClient::handshake() +{ + int ret = ERROR_SUCCESS; + + SrsSocket skt(stfd); + + SrsComplexHandshake complex_hs; + SrsSimpleHandshake simple_hs; + if ((ret = simple_hs.handshake_with_server(skt, complex_hs)) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +int SrsRtmpClient::connect_app(std::string app, std::string tc_url) +{ + int ret = ERROR_SUCCESS; + + // Connect(vhost, app) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsConnectAppPacket* pkt = new SrsConnectAppPacket(); + msg->set_packet(pkt, 0); + + pkt->command_object = new SrsAmf0Object(); + pkt->command_object->set("app", new SrsAmf0String(app.c_str())); + pkt->command_object->set("swfUrl", new SrsAmf0String()); + pkt->command_object->set("tcUrl", new SrsAmf0String(tc_url.c_str())); + pkt->command_object->set("fpad", new SrsAmf0Boolean(false)); + pkt->command_object->set("capabilities", new SrsAmf0Number(239)); + pkt->command_object->set("audioCodecs", new SrsAmf0Number(3575)); + pkt->command_object->set("videoCodecs", new SrsAmf0Number(252)); + pkt->command_object->set("videoFunction", new SrsAmf0Number(1)); + pkt->command_object->set("pageUrl", new SrsAmf0String()); + pkt->command_object->set("objectEncoding", new SrsAmf0Number(0)); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + return ret; + } + } + + // Set Window Acknowledgement size(2500000) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsSetWindowAckSizePacket* pkt = new SrsSetWindowAckSizePacket(); + + pkt->ackowledgement_window_size = 2500000; + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + return ret; + } + } + + // expect connect _result + SrsCommonMessage* msg = NULL; + SrsConnectAppResPacket* pkt = NULL; + if ((ret = srs_rtmp_expect_message(protocol, &msg, &pkt)) != ERROR_SUCCESS) { + srs_error("expect connect app response message failed. ret=%d", ret); + return ret; + } + SrsAutoFree(SrsCommonMessage, msg, false); + srs_info("get connect app response message"); + + return ret; +} + +int SrsRtmpClient::create_stream(int& stream_id) +{ + int ret = ERROR_SUCCESS; + + // CreateStream + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsCreateStreamPacket* pkt = new SrsCreateStreamPacket(); + + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + return ret; + } + } + + // CreateStream _result. + if (true) { + SrsCommonMessage* msg = NULL; + SrsCreateStreamResPacket* pkt = NULL; + if ((ret = srs_rtmp_expect_message(protocol, &msg, &pkt)) != ERROR_SUCCESS) { + srs_error("expect create stream response message failed. ret=%d", ret); + return ret; + } + SrsAutoFree(SrsCommonMessage, msg, false); + srs_info("get create stream response message"); + + stream_id = (int)pkt->stream_id; + } + + return ret; +} + +int SrsRtmpClient::play(std::string stream, int stream_id) +{ + int ret = ERROR_SUCCESS; + + // Play(stream) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsPlayPacket* pkt = new SrsPlayPacket(); + + pkt->stream_name = stream; + msg->set_packet(pkt, stream_id); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send play stream failed. " + "stream=%s, stream_id=%d, ret=%d", + stream.c_str(), stream_id, ret); + return ret; + } + } + + // SetBufferLength(1000ms) + int buffer_length_ms = 1000; + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsUserControlPacket* pkt = new SrsUserControlPacket(); + + pkt->event_type = SrcPCUCSetBufferLength; + pkt->event_data = stream_id; + pkt->extra_data = buffer_length_ms; + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send set buffer length failed. " + "stream=%s, stream_id=%d, bufferLength=%d, ret=%d", + stream.c_str(), stream_id, buffer_length_ms, ret); + return ret; + } + } + + return ret; +} + +int SrsRtmpClient::publish(std::string stream, int stream_id) +{ + int ret = ERROR_SUCCESS; + + // publish(stream) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsPublishPacket* pkt = new SrsPublishPacket(); + + pkt->stream_name = stream; + msg->set_packet(pkt, stream_id); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send publish message failed. " + "stream=%s, stream_id=%d, ret=%d", + stream.c_str(), stream_id, ret); + return ret; + } + } + + return ret; +} + +SrsRtmp::SrsRtmp(st_netfd_t client_stfd) +{ + protocol = new SrsProtocol(client_stfd); + stfd = client_stfd; +} + +SrsRtmp::~SrsRtmp() +{ + srs_freep(protocol); +} + +SrsProtocol* SrsRtmp::get_protocol() +{ + return protocol; +} + +void SrsRtmp::set_recv_timeout(int64_t timeout_us) +{ + protocol->set_recv_timeout(timeout_us); +} + +int64_t SrsRtmp::get_recv_timeout() +{ + return protocol->get_recv_timeout(); +} + +void SrsRtmp::set_send_timeout(int64_t timeout_us) +{ + protocol->set_send_timeout(timeout_us); +} + +int64_t SrsRtmp::get_recv_bytes() +{ + return protocol->get_recv_bytes(); +} + +int64_t SrsRtmp::get_send_bytes() +{ + return protocol->get_send_bytes(); +} + +int SrsRtmp::get_recv_kbps() +{ + return protocol->get_recv_kbps(); +} + +int SrsRtmp::get_send_kbps() +{ + return protocol->get_send_kbps(); +} + +int SrsRtmp::recv_message(SrsCommonMessage** pmsg) +{ + return protocol->recv_message(pmsg); +} + +int SrsRtmp::send_message(ISrsMessage* msg) +{ + return protocol->send_message(msg); +} + +int SrsRtmp::handshake() +{ + int ret = ERROR_SUCCESS; + + SrsSocket skt(stfd); + + SrsComplexHandshake complex_hs; + SrsSimpleHandshake simple_hs; + if ((ret = simple_hs.handshake_with_client(skt, complex_hs)) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +int SrsRtmp::connect_app(SrsRequest* req) +{ + int ret = ERROR_SUCCESS; + + SrsCommonMessage* msg = NULL; + SrsConnectAppPacket* pkt = NULL; + if ((ret = srs_rtmp_expect_message(protocol, &msg, &pkt)) != ERROR_SUCCESS) { + srs_error("expect connect app message failed. ret=%d", ret); + return ret; + } + SrsAutoFree(SrsCommonMessage, msg, false); + srs_info("get connect app message"); + + SrsAmf0Any* prop = NULL; + + if ((prop = pkt->command_object->ensure_property_string("tcUrl")) == NULL) { + ret = ERROR_RTMP_REQ_CONNECT; + srs_error("invalid request, must specifies the tcUrl. ret=%d", ret); + return ret; + } + req->tcUrl = srs_amf0_convert(prop)->value; + + if ((prop = pkt->command_object->ensure_property_string("pageUrl")) != NULL) { + req->pageUrl = srs_amf0_convert(prop)->value; + } + + if ((prop = pkt->command_object->ensure_property_string("swfUrl")) != NULL) { + req->swfUrl = srs_amf0_convert(prop)->value; + } + + if ((prop = pkt->command_object->ensure_property_number("objectEncoding")) != NULL) { + req->objectEncoding = srs_amf0_convert(prop)->value; + } + + srs_info("get connect app message params success."); + + return req->discovery_app(); +} + +int SrsRtmp::set_window_ack_size(int ack_size) +{ + int ret = ERROR_SUCCESS; + + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsSetWindowAckSizePacket* pkt = new SrsSetWindowAckSizePacket(); + + pkt->ackowledgement_window_size = ack_size; + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send ack size message failed. ret=%d", ret); + return ret; + } + srs_info("send ack size message success. ack_size=%d", ack_size); + + return ret; +} + +int SrsRtmp::set_peer_bandwidth(int bandwidth, int type) +{ + int ret = ERROR_SUCCESS; + + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsSetPeerBandwidthPacket* pkt = new SrsSetPeerBandwidthPacket(); + + pkt->bandwidth = bandwidth; + pkt->type = type; + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send set bandwidth message failed. ret=%d", ret); + return ret; + } + srs_info("send set bandwidth message " + "success. bandwidth=%d, type=%d", bandwidth, type); + + return ret; +} + +int SrsRtmp::response_connect_app(SrsRequest* req) +{ + int ret = ERROR_SUCCESS; + + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsConnectAppResPacket* pkt = new SrsConnectAppResPacket(); + + pkt->props->set("fmsVer", new SrsAmf0String("FMS/"RTMP_SIG_FMS_VER)); + pkt->props->set("capabilities", new SrsAmf0Number(127)); + pkt->props->set("mode", new SrsAmf0Number(1)); + + pkt->info->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); + pkt->info->set(StatusCode, new SrsAmf0String(StatusCodeConnectSuccess)); + pkt->info->set(StatusDescription, new SrsAmf0String("Connection succeeded")); + pkt->info->set("objectEncoding", new SrsAmf0Number(req->objectEncoding)); + SrsASrsAmf0EcmaArray* data = new SrsASrsAmf0EcmaArray(); + pkt->info->set("data", data); + + data->set("srs_version", new SrsAmf0String(RTMP_SIG_FMS_VER)); + data->set("srs_server", new SrsAmf0String(RTMP_SIG_SRS_NAME)); + data->set("srs_license", new SrsAmf0String(RTMP_SIG_SRS_LICENSE)); + data->set("srs_role", new SrsAmf0String(RTMP_SIG_SRS_ROLE)); + data->set("srs_url", new SrsAmf0String(RTMP_SIG_SRS_URL)); + data->set("srs_version", new SrsAmf0String(RTMP_SIG_SRS_VERSION)); + data->set("srs_site", new SrsAmf0String(RTMP_SIG_SRS_WEB)); + data->set("srs_email", new SrsAmf0String(RTMP_SIG_SRS_EMAIL)); + data->set("srs_copyright", new SrsAmf0String(RTMP_SIG_SRS_COPYRIGHT)); + + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send connect app response message failed. ret=%d", ret); + return ret; + } + srs_info("send connect app response message success."); + + return ret; +} + +int SrsRtmp::on_bw_done() +{ + int ret = ERROR_SUCCESS; + + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsOnBWDonePacket* pkt = new SrsOnBWDonePacket(); + + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send onBWDone message failed. ret=%d", ret); + return ret; + } + srs_info("send onBWDone message success."); + + return ret; +} + +int SrsRtmp::identify_client(int stream_id, SrsClientType& type, std::string& stream_name) +{ + type = SrsClientUnknown; + int ret = ERROR_SUCCESS; + + while (true) { + SrsCommonMessage* msg = NULL; + if ((ret = protocol->recv_message(&msg)) != ERROR_SUCCESS) { + srs_error("recv identify client message failed. ret=%d", ret); + return ret; + } + + SrsAutoFree(SrsCommonMessage, msg, false); + + if (!msg->header.is_amf0_command() && !msg->header.is_amf3_command()) { + srs_trace("identify ignore messages except " + "AMF0/AMF3 command message. type=%#x", msg->header.message_type); + continue; + } + + if ((ret = msg->decode_packet(protocol)) != ERROR_SUCCESS) { + srs_error("identify decode message failed. ret=%d", ret); + return ret; + } + + SrsPacket* pkt = msg->get_packet(); + if (dynamic_cast(pkt)) { + srs_info("identify client by create stream, play or flash publish."); + return identify_create_stream_client( + dynamic_cast(pkt), stream_id, type, stream_name); + } + if (dynamic_cast(pkt)) { + srs_info("identify client by releaseStream, fmle publish."); + return identify_fmle_publish_client( + dynamic_cast(pkt), type, stream_name); + } + + srs_trace("ignore AMF0/AMF3 command message."); + } + + return ret; +} + +int SrsRtmp::set_chunk_size(int chunk_size) +{ + int ret = ERROR_SUCCESS; + + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsSetChunkSizePacket* pkt = new SrsSetChunkSizePacket(); + + pkt->chunk_size = chunk_size; + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send set chunk size message failed. ret=%d", ret); + return ret; + } + srs_info("send set chunk size message success. chunk_size=%d", chunk_size); + + return ret; +} + +int SrsRtmp::start_play(int stream_id) +{ + int ret = ERROR_SUCCESS; + + // StreamBegin + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsUserControlPacket* pkt = new SrsUserControlPacket(); + + pkt->event_type = SrcPCUCStreamBegin; + pkt->event_data = stream_id; + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send PCUC(StreamBegin) message failed. ret=%d", ret); + return ret; + } + srs_info("send PCUC(StreamBegin) message success."); + } + + // onStatus(NetStream.Play.Reset) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); + + pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); + pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeStreamReset)); + pkt->data->set(StatusDescription, new SrsAmf0String("Playing and resetting stream.")); + pkt->data->set(StatusDetails, new SrsAmf0String("stream")); + pkt->data->set(StatusClientId, new SrsAmf0String(RTMP_SIG_CLIENT_ID)); + + msg->set_packet(pkt, stream_id); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send onStatus(NetStream.Play.Reset) message failed. ret=%d", ret); + return ret; + } + srs_info("send onStatus(NetStream.Play.Reset) message success."); + } + + // onStatus(NetStream.Play.Start) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); + + pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); + pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeStreamStart)); + pkt->data->set(StatusDescription, new SrsAmf0String("Started playing stream.")); + pkt->data->set(StatusDetails, new SrsAmf0String("stream")); + pkt->data->set(StatusClientId, new SrsAmf0String(RTMP_SIG_CLIENT_ID)); + + msg->set_packet(pkt, stream_id); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send onStatus(NetStream.Play.Reset) message failed. ret=%d", ret); + return ret; + } + srs_info("send onStatus(NetStream.Play.Reset) message success."); + } + + // |RtmpSampleAccess(false, false) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsSampleAccessPacket* pkt = new SrsSampleAccessPacket(); + + msg->set_packet(pkt, stream_id); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send |RtmpSampleAccess(false, false) message failed. ret=%d", ret); + return ret; + } + srs_info("send |RtmpSampleAccess(false, false) message success."); + } + + // onStatus(NetStream.Data.Start) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsOnStatusDataPacket* pkt = new SrsOnStatusDataPacket(); + + pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeDataStart)); + + msg->set_packet(pkt, stream_id); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send onStatus(NetStream.Data.Start) message failed. ret=%d", ret); + return ret; + } + srs_info("send onStatus(NetStream.Data.Start) message success."); + } + + srs_info("start play success."); + + return ret; +} + +int SrsRtmp::on_play_client_pause(int stream_id, bool is_pause) +{ + int ret = ERROR_SUCCESS; + + if (is_pause) { + // onStatus(NetStream.Pause.Notify) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); + + pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); + pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeStreamPause)); + pkt->data->set(StatusDescription, new SrsAmf0String("Paused stream.")); + + msg->set_packet(pkt, stream_id); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send onStatus(NetStream.Pause.Notify) message failed. ret=%d", ret); + return ret; + } + srs_info("send onStatus(NetStream.Pause.Notify) message success."); + } + // StreamEOF + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsUserControlPacket* pkt = new SrsUserControlPacket(); + + pkt->event_type = SrcPCUCStreamEOF; + pkt->event_data = stream_id; + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send PCUC(StreamEOF) message failed. ret=%d", ret); + return ret; + } + srs_info("send PCUC(StreamEOF) message success."); + } + } else { + // onStatus(NetStream.Unpause.Notify) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); + + pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); + pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeStreamUnpause)); + pkt->data->set(StatusDescription, new SrsAmf0String("Unpaused stream.")); + + msg->set_packet(pkt, stream_id); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send onStatus(NetStream.Unpause.Notify) message failed. ret=%d", ret); + return ret; + } + srs_info("send onStatus(NetStream.Unpause.Notify) message success."); + } + // StreanBegin + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsUserControlPacket* pkt = new SrsUserControlPacket(); + + pkt->event_type = SrcPCUCStreamBegin; + pkt->event_data = stream_id; + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send PCUC(StreanBegin) message failed. ret=%d", ret); + return ret; + } + srs_info("send PCUC(StreanBegin) message success."); + } + } + + return ret; +} + +int SrsRtmp::start_fmle_publish(int stream_id) +{ + int ret = ERROR_SUCCESS; + + // FCPublish + double fc_publish_tid = 0; + if (true) { + SrsCommonMessage* msg = NULL; + SrsFMLEStartPacket* pkt = NULL; + if ((ret = srs_rtmp_expect_message(protocol, &msg, &pkt)) != ERROR_SUCCESS) { + srs_error("recv FCPublish message failed. ret=%d", ret); + return ret; + } + srs_info("recv FCPublish request message success."); + + SrsAutoFree(SrsCommonMessage, msg, false); + fc_publish_tid = pkt->transaction_id; + } + // FCPublish response + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsFMLEStartResPacket* pkt = new SrsFMLEStartResPacket(fc_publish_tid); + + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send FCPublish response message failed. ret=%d", ret); + return ret; + } + srs_info("send FCPublish response message success."); + } + + // createStream + double create_stream_tid = 0; + if (true) { + SrsCommonMessage* msg = NULL; + SrsCreateStreamPacket* pkt = NULL; + if ((ret = srs_rtmp_expect_message(protocol, &msg, &pkt)) != ERROR_SUCCESS) { + srs_error("recv createStream message failed. ret=%d", ret); + return ret; + } + srs_info("recv createStream request message success."); + + SrsAutoFree(SrsCommonMessage, msg, false); + create_stream_tid = pkt->transaction_id; + } + // createStream response + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsCreateStreamResPacket* pkt = new SrsCreateStreamResPacket(create_stream_tid, stream_id); + + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send createStream response message failed. ret=%d", ret); + return ret; + } + srs_info("send createStream response message success."); + } + + // publish + if (true) { + SrsCommonMessage* msg = NULL; + SrsPublishPacket* pkt = NULL; + if ((ret = srs_rtmp_expect_message(protocol, &msg, &pkt)) != ERROR_SUCCESS) { + srs_error("recv publish message failed. ret=%d", ret); + return ret; + } + srs_info("recv publish request message success."); + + SrsAutoFree(SrsCommonMessage, msg, false); + } + // publish response onFCPublish(NetStream.Publish.Start) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); + + pkt->command_name = RTMP_AMF0_COMMAND_ON_FC_PUBLISH; + pkt->data->set(StatusCode, new SrsAmf0String(StatusCodePublishStart)); + pkt->data->set(StatusDescription, new SrsAmf0String("Started publishing stream.")); + + msg->set_packet(pkt, stream_id); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send onFCPublish(NetStream.Publish.Start) message failed. ret=%d", ret); + return ret; + } + srs_info("send onFCPublish(NetStream.Publish.Start) message success."); + } + // publish response onStatus(NetStream.Publish.Start) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); + + pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); + pkt->data->set(StatusCode, new SrsAmf0String(StatusCodePublishStart)); + pkt->data->set(StatusDescription, new SrsAmf0String("Started publishing stream.")); + pkt->data->set(StatusClientId, new SrsAmf0String(RTMP_SIG_CLIENT_ID)); + + msg->set_packet(pkt, stream_id); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send onStatus(NetStream.Publish.Start) message failed. ret=%d", ret); + return ret; + } + srs_info("send onStatus(NetStream.Publish.Start) message success."); + } + + srs_info("FMLE publish success."); + + return ret; +} + +int SrsRtmp::fmle_unpublish(int stream_id, double unpublish_tid) +{ + int ret = ERROR_SUCCESS; + + // publish response onFCUnpublish(NetStream.unpublish.Success) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); + + pkt->command_name = RTMP_AMF0_COMMAND_ON_FC_UNPUBLISH; + pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeUnpublishSuccess)); + pkt->data->set(StatusDescription, new SrsAmf0String("Stop publishing stream.")); + + msg->set_packet(pkt, stream_id); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send onFCUnpublish(NetStream.unpublish.Success) message failed. ret=%d", ret); + return ret; + } + srs_info("send onFCUnpublish(NetStream.unpublish.Success) message success."); + } + // FCUnpublish response + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsFMLEStartResPacket* pkt = new SrsFMLEStartResPacket(unpublish_tid); + + msg->set_packet(pkt, stream_id); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send FCUnpublish response message failed. ret=%d", ret); + return ret; + } + srs_info("send FCUnpublish response message success."); + } + // publish response onStatus(NetStream.Unpublish.Success) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); + + pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); + pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeUnpublishSuccess)); + pkt->data->set(StatusDescription, new SrsAmf0String("Stream is now unpublished")); + pkt->data->set(StatusClientId, new SrsAmf0String(RTMP_SIG_CLIENT_ID)); + + msg->set_packet(pkt, stream_id); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send onStatus(NetStream.Unpublish.Success) message failed. ret=%d", ret); + return ret; + } + srs_info("send onStatus(NetStream.Unpublish.Success) message success."); + } + + srs_info("FMLE unpublish success."); + + return ret; +} + +int SrsRtmp::start_flash_publish(int stream_id) +{ + int ret = ERROR_SUCCESS; + + // publish response onStatus(NetStream.Publish.Start) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); + + pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); + pkt->data->set(StatusCode, new SrsAmf0String(StatusCodePublishStart)); + pkt->data->set(StatusDescription, new SrsAmf0String("Started publishing stream.")); + pkt->data->set(StatusClientId, new SrsAmf0String(RTMP_SIG_CLIENT_ID)); + + msg->set_packet(pkt, stream_id); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send onStatus(NetStream.Publish.Start) message failed. ret=%d", ret); + return ret; + } + srs_info("send onStatus(NetStream.Publish.Start) message success."); + } + + srs_info("flash publish success."); + + return ret; +} + +int SrsRtmp::identify_create_stream_client(SrsCreateStreamPacket* req, int stream_id, SrsClientType& type, std::string& stream_name) +{ + int ret = ERROR_SUCCESS; + + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsCreateStreamResPacket* pkt = new SrsCreateStreamResPacket(req->transaction_id, stream_id); + + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send createStream response message failed. ret=%d", ret); + return ret; + } + srs_info("send createStream response message success."); + } + + while (true) { + SrsCommonMessage* msg = NULL; + if ((ret = protocol->recv_message(&msg)) != ERROR_SUCCESS) { + srs_error("recv identify client message failed. ret=%d", ret); + return ret; + } + + SrsAutoFree(SrsCommonMessage, msg, false); + + if (!msg->header.is_amf0_command() && !msg->header.is_amf3_command()) { + srs_trace("identify ignore messages except " + "AMF0/AMF3 command message. type=%#x", msg->header.message_type); + continue; + } + + if ((ret = msg->decode_packet(protocol)) != ERROR_SUCCESS) { + srs_error("identify decode message failed. ret=%d", ret); + return ret; + } + + SrsPacket* pkt = msg->get_packet(); + if (dynamic_cast(pkt)) { + SrsPlayPacket* play = dynamic_cast(pkt); + type = SrsClientPlay; + stream_name = play->stream_name; + srs_trace("identity client type=play, stream_name=%s", stream_name.c_str()); + return ret; + } + if (dynamic_cast(pkt)) { + srs_info("identify client by publish, falsh publish."); + return identify_flash_publish_client( + dynamic_cast(pkt), type, stream_name); + } + + srs_trace("ignore AMF0/AMF3 command message."); + } + + return ret; +} + +int SrsRtmp::identify_fmle_publish_client(SrsFMLEStartPacket* req, SrsClientType& type, std::string& stream_name) +{ + int ret = ERROR_SUCCESS; + + type = SrsClientFMLEPublish; + stream_name = req->stream_name; + + // releaseStream response + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsFMLEStartResPacket* pkt = new SrsFMLEStartResPacket(req->transaction_id); + + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send releaseStream response message failed. ret=%d", ret); + return ret; + } + srs_info("send releaseStream response message success."); + } + + return ret; +} + +int SrsRtmp::identify_flash_publish_client(SrsPublishPacket* req, SrsClientType& type, std::string& stream_name) +{ + int ret = ERROR_SUCCESS; + + type = SrsClientFlashPublish; + stream_name = req->stream_name; + + return ret; +} + From ac5ccbc77e3dcb5af2a6ca043fc3fbda3227cf4e Mon Sep 17 00:00:00 2001 From: winlin Date: Sat, 14 Dec 2013 14:06:32 +0800 Subject: [PATCH 11/37] refine the thread model for the retry threads --- trunk/conf/srs.conf | 6 +- trunk/configure | 2 +- trunk/research/players/rtmp/RtmpPlayer.swf | Bin 107101 -> 107368 bytes trunk/src/core/srs_core.cpp | 14 + trunk/src/core/srs_core.hpp | 5 + trunk/src/core/srs_core_conn.cpp | 10 +- trunk/src/core/srs_core_conn.hpp | 2 - trunk/src/core/srs_core_encoder.cpp | 226 +- trunk/src/core/srs_core_encoder.hpp | 19 +- trunk/src/core/srs_core_error.hpp | 3 +- trunk/src/core/srs_core_forward.cpp | 716 +++---- trunk/src/core/srs_core_forward.hpp | 18 +- trunk/src/core/srs_core_http.cpp | 10 +- trunk/src/core/srs_core_http.hpp | 1 - trunk/src/core/srs_core_log.cpp | 2 - trunk/src/core/srs_core_protocol.hpp | 2 - trunk/src/core/srs_core_rtmp.cpp | 2254 ++++++++++---------- trunk/src/core/srs_core_rtmp.hpp | 2 - trunk/src/core/srs_core_server.cpp | 73 +- trunk/src/core/srs_core_server.hpp | 15 +- trunk/src/core/srs_core_socket.hpp | 2 - trunk/src/core/srs_core_source.cpp | 1502 ++++++------- trunk/src/core/srs_core_thread.cpp | 156 ++ trunk/src/core/srs_core_thread.hpp | 98 + trunk/src/srs/srs.upp | 114 +- 25 files changed, 2692 insertions(+), 2560 deletions(-) mode change 100644 => 100755 trunk/research/players/rtmp/RtmpPlayer.swf mode change 100755 => 100644 trunk/src/core/srs_core_forward.cpp mode change 100755 => 100644 trunk/src/core/srs_core_rtmp.cpp mode change 100755 => 100644 trunk/src/core/srs_core_source.cpp create mode 100644 trunk/src/core/srs_core_thread.cpp create mode 100644 trunk/src/core/srs_core_thread.hpp diff --git a/trunk/conf/srs.conf b/trunk/conf/srs.conf index 65411f6b0..a76f811e7 100755 --- a/trunk/conf/srs.conf +++ b/trunk/conf/srs.conf @@ -81,11 +81,11 @@ vhost __defaultVhost__ { vhost dev { enabled on; gop_cache on; - hls on; + hls off; hls_path ./objs/nginx/html; hls_fragment 5; hls_window 30; - #forward 127.0.0.1:19350; + forward 127.0.0.1:19350; http_hooks { enabled off; on_connect http://127.0.0.1:8085/api/v1/clients; @@ -96,7 +96,7 @@ vhost dev { on_stop http://127.0.0.1:8085/api/v1/sessions; } transcode { - enabled off; + enabled on; ffmpeg ./objs/ffmpeg/bin/ffmpeg; engine dev { enabled on; diff --git a/trunk/configure b/trunk/configure index 85493c767..40455a1de 100755 --- a/trunk/configure +++ b/trunk/configure @@ -116,7 +116,7 @@ MODULE_FILES=("srs_core" "srs_core_log" "srs_core_server" "srs_core_handshake" "srs_core_pithy_print" "srs_core_config" "srs_core_refer" "srs_core_reload" "srs_core_hls" "srs_core_forward" "srs_core_encoder" - "srs_core_http") + "srs_core_http" "srs_core_thread") MODULE_DIR="src/core" . auto/modules.sh CORE_OBJS="${MODULE_OBJS[@]}" diff --git a/trunk/research/players/rtmp/RtmpPlayer.swf b/trunk/research/players/rtmp/RtmpPlayer.swf old mode 100644 new mode 100755 index 467de310a34b4afa180dba2cf6b8d3f37addb0d3..50ff1af0085b83e3c0b44917c29c6eb2a332f9e9 GIT binary patch literal 107368 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 z z9uD?$u#baRIoQv^0S*pwaEOD$930``ChaG8TE99-q#0}ejq;2HvOAc;v@D&HQIk>~YT@JqH;2sCxa3FK=EeGFm z@I41VaPT7sKXLFg2fuLeD+j-E@J|kY=imDfMDz{P@t3TK)Dpjkmjpy*2wGz3m?7wh(VQXZNnrB~K_WrVkqjY#1aBoh1p^6L zF7_0Rq~6N_fh2StKoAM50thDIO8`Ph{S^T9NW@ZrP|{#CKp2TU01!?Z9t5aQ8V$%0 zBFH0g8A1cnI6gy&Bu#cfd_&SSAp?IwxmjX{@Ca#sjHC+)pfu#s7fes`#Cqw8+;7Qb_kt z0a8hi@C+f1q<@9VT}Z~T452IOnVlhYBfW-a2;E8Mhzy|z$r_m{R8Ut zBz1n?Lc z(mzA!Pli4XWB|$j5hfc*hAjjbM21^3gu!IQ&(LNF8QD8S7)l=RlObf2zxB%yhLKTy zGlbz}bk_`F1Q{cv#z>NL58!b!wr7U$H!|)tkWpm(I{>4J^&G$$lIsS@A$b=8#*+N^ z0mczqCVr_kv2TD5Rx)8DKrShG0bR-?h3OfBolKmCE=?el)&LZcqG`~mkQ7$|n@CEY z$FR;tEa6xWd=e>r1)zvL@hZHZ`Ape<OcWiHv;r+%-E66=_h@DtO!EM*L?OKaYjFL0mIr@Hjp+J(e+;+)r z53n07Iua%KcIO!T8S!^dVuq##6o%$-xu-LD=g2$lQ0bHdzi zdw9Pvv7o*@z?~D(FG472AP;uuMD~jm3L45o-1df+MnaT46q%ey%#Zeagp458#_}+? zy-7|}dsBNex4pULQK&N<6cheJmLQLC9V)``>&K%g-j3qo zkr1B?lr`GkZIALaYD1$@XmkP^)o4Vc@lo2`OVqHdNKONLgPe}`jyYz#+0qFeSWUpD z4b~Wg78<}&=#CxP;Sv-~C51LpQ%q>xLV2Q%zj9^kOo|vl!DX%4Cpd1LmVA5dtUn1!m%TO>@c`??L*D%I4l(C-Mo-MBhj=2EmAK|u-blV@d{0;i9 z_o@>rZ=iKhA=EMo&hZNIOyv;!Xv-LoHW9Za2a{|o6TXcK*CFYVB%~mu0C_tl8<31h zfk^d`LXoKGV9VH`D0!DE+C0fzB=06wIpgf(?BlDWIjYQyhxzA+awa;lWVjI@PJSqSFmULHSd%PTJ5H%!>{uW ze+4;B?9Y&#XNevE7n6v#kawF>6;dz8=ca41su{=jGaz9Y60J`0b1lNy@3pWuz68R^Oz;{UvALd_3 zO*nq9tNbfk$0E?gPM4M@_GQGfoM0ELhoawk(euQzf{Oo>iZ>A0Aq3(pMRzP}+EQoI$fc@M5%!)^=XB=no@&Q>I{WiMXA9mb*4gHL#ZJub(TV1OR4o#>THF&j#5Ka z>Kui-o>Ie9>Rg4ofl|X&>O6(|3Z>Rpsq+=;CQ6NDcxa| zXG9pK0Rc;Uuq7QJPZKaz^f0RQs}|MQ}}g7UlV zTZw%ev1})V4bBT{wHJ}uLF>PyHoTe&YN%>lrD(j9YTQVpH5jMUeSeK2e~tJJ`6t2k z^f%;pB5oArwP>=XJOY%DLynQceEd=9UL0&KRmN&9K>?1t19oL3O+(rSx!~irKqwCI?NKuZm!B6 zQQnQptk=+Bk57a9JPp1oUTAAQNaO>w{DS2WA_`ZZO9 zYEjvH%otGK1{PS6`~L~5USrjMrxg1grS@y0LHODYI-eJFUL!fji2WF`yhb~I(IfFW zG3Pk3W8pS0Qv|**<|LXdiQu|KMK{%n#vmJnya~}GJG>9qSuP51it<~~rJC3eDqT2@ zF1!QMF&I9C&boJLqZH>&x0 zs$%7<+{zz_@&{_yVcu)VOXn@G)2@7^deq0NN8O;3nyVK1RI$h#)FO|n)Xx;^NlG=T z)SC+RO-gM+CqrZC@};87m!f9PQ5%oU+Y!#&+0k(+&KLKnP4L<|y5RJb8>=Xlk1=uA9{RXgG z44$iz@1ozQ>BKVc6(gv_e(f2@eIncA?qL+Z^>Do^Mugzkm%mNy_3W`pTr#On)ZuqiNLYEJkKEi%qzdoXpGM&|01>??#v66e-&E? zIP-$!-^A9D&b(mxcTcZRisnl&LXS zlXHpm!+m(=C6X5+&)4LJ}>?HCR3% z2uimDHBzU~4YYhn>+I58Fh$S>V7I2Xd_Z#{H^_Po0T(jC*6WD1H1M)k8f{?U774mxdD!tD&3}$O?D7rGe}O#gCe!?v$YU;iOY^so|4Nj< zqxr9pzaz@u)BJ7ZkBP|K;abT*XhN;nb>*KlIfw<=9Qs)kIuNALzUco~8s3G==6j-i zTXUhYp8nC#(B^_*{zjC4R0t##(M`K>S|*0#Z9p3_K+~kUwpMIXTf4os7CHG>YO>Z^ zZD7zg9ZdF~hMxw%70G)V{PmQ|8>CpI@MBU8Tsa@UhgHdAlJ8JnM7#|8CxPA-y{6cHjvZhU2 zm*XlSoL(hRP0-Kf72(~{(@L#68)i`9p3#^JgK^=aNBA_Yr$3_QS1NplHgpR0&e>Xz zch1p<+NgJqhw$^XZSE46&9gM}1&@Qqz(Hqg&XeMCntgbLE2ey_&SB`pQJ6#zNJCLmyABYt#Rl4 zPWs_$v!Is9XCTsTuO&>X{0^`pakyZu6yC-SuleL2f>`UHvqpZmOIs z=4rnnFKL9s0IM|Q2*7HMSgn;0qvfrn@AkP`BCwaS2-dKZJdW{OX(0YSs8x)*M#R#rk}rW=jb*#WzSf(38RT`0d%ZXL3dkGK z>?^FHtH3q_+r+RBfNci0g<;o#orX$VMeBP+z7FgiVA~k>5wLT>wlnNwU~XVL7>P`iMOhP`;u4rLIiE!5QwDb8a!1;~r27!30^Vny8hEEv>rQWzFf2GGEO zuM{hM*qUc~O2r zharp61{#bfXmGY##~0((x^Ku?4S5;h9gX-3z&4F$H^3H+b|1iYjqWhO3XT2TaN>jSS^A9~knh{BJ(3U9E&3sm||WS5A!I-36+`4wWWp60(m{zsB4 z(fpS>x8*Y3FmEA)KU5nf-$v#t&D^1$9E%U`jAl5p3`dr~Ma>UH_?@!!RAHQ|l%=MM ziu<0{iPNFZUV57Qg;x3vmDoh5E_+>rXG9#7{wK&!KPZbWVpZ@C$YqNbl^c2k6>3p| z+p`$hG`&(@=e}(~p3cZh5k1K>843?|5US-&Feyzl#guONZyFJ@JnwGkCEdYEnn>R(X9O>PsmJTu`hQ2qpZgOddQ zHQ=XF-x|HVRR^lABSP29nokAep36wl-gOP zZdIrsQ);qG-KJ1)P-==w-L6nSq105Bxk-sULe`eE#j`n^|_yztrN3?$~Dt?3f7s&sU55hs()h^Vot-39VMMJZth6yXLWcB?@#^ zSYx?OW69TPgWYJqQX%A|BIKlAKBWk03L$SY71(7I)V;+*0#)PFR6uuCi!+LVGkW=~ zmlp3lOp7Y6#W^aZ2i1bQhg%Wi*2^`D5V|>>XL)*6rS^eWzDO&l>rlA>l`kokFX`pW zN@WDr@)hK9D~RM{NyY3}J)-Yppq8?Jf!j{T(0YpXt|(ul?PX|jGg^-JK0V-Cd zkPr+o8g($X3K}}XfNZdN!lUvS8G(ojHjbUh*u*7{drh&yPT~@$IEmAo#A%yeG-82l zHk-1^W>Yp{lFeo}o2>c%=ic|`jcCq(o8Pw|;=MWV_V(_(_uO;-=MS{}K{{()rtEJ; zI*^z2rGJO__TT9R{#S~-jy7_u5cl7l5dY>&|H0>e=AY07oAf_?)_ z(f_33`A14(y<3GpAqlGPpQs8OT-QH4u74)ijjro|Ij;XjuA4Y^KhM&(?JS(JmY~_^ ztnUmvuCqy=p5)eR>W0^_L3<4wwA*l8a?UrP79;3(j%7Jcw7y3j=`2f~^=RZ7Jsdx) z`f5X9eVk#R)YQ9pd+9XfUEJyAr{J6yKZqf{^cQ*h{fI+Fr-jD{1RcMNBD^GWx|_%kQ{3$o zcOBw>)QS61o_^VhOY`B!WL&|(_&DgdYLYqd2`-rfuZZ;Sq4YjY>Fsa__-CE;KFiau zqMG8v^f?OA2I76f5X#Xc0+uPGNeI$GmfdhPQkBs zJM^0fPCjp=4&CXxe#>#amt1#I(!@ylwv+U?dHOr3ia0NtqSUT&<4rs9?xT3ux~?Z2 z*ZawJx9j@1j_U*DYPqf_9oGlRwbyk$<+wgXu6tb9(~j%IvLWI!Et?*T>D+uA2_a$k?Vk_27~R`fH39P91Zdto(dN&MUS81 z*5jIu|4(SCIHb$zL!EXFLAi|8Z_9XKf^+OtCl|Er`KCD zO>Yx)7Sr^oaL5z~TqlhRl)gi_XBS5ZlHBc|iXW#*;T}JRNRl=UU!W$w*N-CHhird< zPr_R@aSj1?#QjJmNE254X$@bohwyl($DsuyLT2``aF-=~M9`vy@75CU(bA844)4_x z&uZz%{dljhMsiOOu@l5Q5Jd1W9xbG0+B-!IhbvO0RGqDkKd0%np(6M_mF)*-#6C_{ z8B$S|*{I1gRFil6-H*ceRI+=b2l z^MZa}vtH2b7qlW;2*2Q;j{F!*ryK3(wPVK+=0y?aec55&k1#(GVcsXhe9$e(v^YZ> zrmgKqPz86o{wSItfj)j2Zi1^LzDXZLFTF^;^a+3H1&Z=1IEZe0#UFZt96syBdzF6C zQJ+Kny!iVm&gcE1LkJRj1Iql8DB_RV>B!H(0r0!WUu|IX;JD$Fjst_(k2r4lwBw+< z#|@ux95nB^K^MmjI*uEjJbPAeK7021vuEEZ{^L3QMQZ1Lx@iVJOU$z`qn*RxFPwqq zqVi=lHgwp&f}fx3>R0je3tjs0=PgjYAI*oCW_0>G><^ z`8)lD=lN@-TL%6_WjYP22#j1U4voRu^lzyfhc#>swxSpRRlF_uApI+P>ECqrdw=@( z)O4~@|KP_5idy^M{j!--)JkaW|IF0w4=9mXAODHc-A}X5=V;bBOYiEWIc2}BfmcX- zqgzcO-0`v|g*%RzQ&mdxWgLW=?hE}#J^dEM5c@ZrS?bvTKgLjSY(4e#9VQ+>rpe6i za)XJji3mhHk`Iy0Fphd_DdZs`pL4f4Rg?UfmVT=#*L@-4{@iPlV9AvIqTJ~IOwCA@ zpgi}RsK^1`X9hk_ULCRP|vq@|vE0$V78IY+?-4+;F7d2w@3l z_JIL57sLD!bNU4Fbl^n?CC4IXbuqs@Y{m?|;1q_V;nzcdORxPC=2SHm^zCM7C7m9> z6IJx4jd!8=V%m7h49z0%*xzk9$!*yYyI=I|rq>s5SYG@u@?l#izbzLqnPwBI6 z!#wr%*Uo?OL)}OOI2_TgL z33=dwO_%n=PGh1UV8MgSM*+>boKRF?A`&o5n??X`KP!`>i=@{ttmqLAQZAW?-vw3b}WY4Fu<@F8vWC8w2u%~QuI)Jd8|k|~2k16H9+Gj3+~!%~HJPxdC_&u>PM={91IuSoCZyXd7b*iGvPyfN zJ4R&h6lUU~GLp`=no*qb7GQC*CNm6Qlw?8-pebC-iP=cOUK+?^CVa394(yFm1QDpg zK)@sXZ^z=n>`W#!$~$Kora0feMCHYU`+y`l+CK<@@PmiFf@K-TF#AP!0KzxlOGR|@ zrGRj>SYv}0YmL#1NN<_e&`_r}*41kOMbTr?h6c~vrXGs{jwS|}7Ci<)6+K39Ie;gr zyO7Zh0OvHd>h-a@mTco4Ixt(Bb%3nYUlM>stFMn~fTRfs_>ml=1gImW1Ugb0hF-j? z8$Rx5Y9m*n^(}BkfGDB?P9%>|L{y-N6q`OMh70@cV;MJqCL*9jR6vQ;bIP-stF2sJ#nshZy_~DwN<5H1KYR>*JfPZxVGZD64y3dJ8<>jx(e6T zxOU>Y2G_N?EL^>N*v9Mdn_%_a0S^XN6 zIX@Ia>YDB-hp9|ANa#HuOR3Nlbi)d!EVco{H)^^Niv=xRgs&4mbh?p)P$dXe3av3z zUQ^NEbKq7%d!c|*nXa@9+lb`GeQeS=nVywGAYJzvfqUsAH`m>RhuB=~xR26)J3Ptv zLp3El>E93S6tVj*Qs$=T4YHa;(1%)pt+RiFUxBxlUWjcow(7$yZnE0+BBJbo6|{p= z@(O#qT8mU$;2O_i@mwWR32iQ{)6&&bH3==f1f_OGw1}CMU!{ETP~ofaUs|P{CYR8W zem!&?3z$LHr=OUM5~d?aPzZ3@1;rRH>1Bhgno1T+$56IHyHLcVKz32Q7%bHNM!Mc9 zHCzppmFkvm!UO5wXm&}WB;A~ODovE8p=AYD#4rgd`Ql~Rdanv$6FU{1j$WfI={J@{ zm+oij)qZ1+-=O10{G^x0bgkejEbOq2N_QY4cn`F>U4x`(oog4sO%sW&gHyaxsZPv- zs$P6H_II1svhP5&_rq#=g>Md&WR@`#Ll5Ru0yGPEjmBOj3uApke)O`|%G1R!XGGuu>qdapc@nwh)r8Xom z2ih~~JFy=?`pcy{%w0~TyD8G!M5J65nRXx&G}jQROL+i_f^i02qz5P}dUDc#5(;e2 zdx2d6n@*u!#Y^!ho324WKhCC+eV&^=l+lpVI$$P13r%FdOlJQsC;KNU`{N?}e7xw| zEU}^uSPd4pd6aHgt|seIDB69!THJ)mQ!G@^$DwrBtjjJ0QmO3u>n&f;&l=0UnhPLf zuDF0(Kw<|HO0@*zCyHfkTfP30(Sh}Mj9{H>@qC^DonPr96bj6FGR)IA7(M_)ck_iU zP8N~K@jfihV47IkK3MC-6mG&|i$tua*!xFD$(m;3K)5bCpNwdR_aBz^SbQ)Fb1QX` z@bcx$LA2H`pL>ByXRY_#1-oMnll6PJqdJ!3!xa~ZPOIsrXypZbkTBV97K=!4%|Fwy zh6jy$$t6!+eVqn$vn(U=SOwKPxXT(K@SF|6rPnBRtu1P6%ru|?U8)+h%2csHnM(^4 zyj;LK(@F{K3`l2KnrH&)Oas!HfH~8OIDEA-u2o_=qRrv{dE6i7+9IyiaNrhcwOB(K zat)y?Y9JUanKXPV;M8VMmRFFwZ$4YF5LXqhYFvw#umwx;8(~FQ{9x(eZ&OUXKls=K z-hdUf(^npf^7zW*wbEG;eWP{5$o@vt>_s|ZLCjt`^o`D?Z?cZs+)Hc_e7estL&-k_ zAc1^*#8VND(^aUi&?nqhZ7@fQ>GM!z>7?wfMDp;7u1yzX)-0Qvj)Z`$mh`79ro%!V z+D|e`6bGMAY)R9VQrK{e(bEf2*?lNi+^Dpt9& z=nSi1i_+C8nLH+sTBMWxpvng{Yb}*Qd}M0Y(ifYuQ-pxtQpjtm)A1Y}2>^>Hy-fN^ zoyX-geR$&6wMGNxC)Zam6Xr)+s`=pjVEOh)$=L$orz z`Y(-6sTh=szc#cPsazpb;l*u8rR~3&3MJ;F#LQYOCV?j)6Y~p=fzJQC#3)TpX$ERT zFQQCcGEK7>${6YR|0qpL+D}ObYlSkx`oBhMl&DFGLRESr-bda0cKWOmtJp4%(v7~I zMR*%XD)iN2Z3dIa*}A%4%`0cJVzQVF3P6a(&VJWl?-MFErA`SjyTfqT1n^AB9?Y1- zka0X6&X4m%Tm^@T;6<)#u6zBj<~gZmxC6ui$!6(DE)3h}Y|-6PxLkt%>9N5 zc8pKDos+=~7K`)cU?UF4=zPA#B^fy$rn1A{a7#sY4bJ)W&ESHS$}PVeoH$6t)*|zI z4xhvIa`+U&;1L_jaDH5d#+;c6A#^sQh^#3&_LB&Qx#GOQPA+4)2L=ZRwhh_tbYnO(sR-M{B5W(i-dQX7bgvrq&k0(b8I@&60aZ(7if*uv2F%uiWn^|4g(3SmZrw@8Aaf`#9ohw($J*AU^($nG$T-^`GaDCQdyd{L?JGS z$^|ehu|;wn778mO2v&fv`YX5!<_S0|bHF(fd=s!s5VD%9wZteP<_I_=V2E5MxFKy^ zUBlHlR{@|3^wdpQ#^mE;tzcsX*d$-hqzUNI(4?typHVz%7P7qj*^{LT4jSOQVA52K z8mMaH1Id?R&*)n|nU8zje%b7>4klZk_Lh_JeocVMHE=wLOl+2KPN?OmOwTh|) zdRatIk{&*zz3=Sb8rggi93|0;sz-6TBDBRSMi_8K&M;Fc5v-AvtZb)z9_oCm@on3Kqd)B)XK+F*L3lHz1wm zqEz8Y^E8fEq54v)EJqR9iu4K`GC^h+#q*K$liBC%Gf=)Wz!ydx3$;30S83C>PQ0Ew z;sFIor0a+ea{_D^Qr)dj*}2omT2HTt`HX^BP!At?7L90B!4$|r_RUzx0$_$j(lE|- zqXKTLWYt#D`UYB;G6Mwgt3KU$BLZAOWj-06G*01yIjEOQEp(Rvi zgAT?{o?%sLJMfOY)DSJ*dBQFby6tODASRuVeM_E@0pEx?I>1FL0{`k%di`{Dq9DD2 z_R=PG<|Y(jv(uT2l`Yg28%xznXPh%*+T zE~RQ+?W7q|cBXgM96A~5L3B#}+Vr(vn)P0qyQgJ2i*R}+7%0=%1F5EW3hhZMC~A67 z^`VpL>x8_(Qt>jpVdzo;6r@e3 zFt8sXj>+TTm>_AY47AJWj1@3Y^pfSd3qd38x8_@>ZAR%v3&BE^k|a4_ExsRIXkwz+ z!z=bD4kU1#Hnw7Ye58M3Y_oiX(RmmXfuRp~by%rn);xzsh6ZQ_u>e-;-PkKH96QIR zG`kKY`e1qVV&7JsYzaTkI#=$-r+Y8>5#cx-#=?tlY-y_9-99kVHz@DnIE>#V!=2k3 zccXm!M-JeJ4g(!S59BBd9fq|m9E$927dBM4_m2)891qtokM1U;Okn|rZo`cDZhLsQ zTY=pdt^k&%!Y%+X=y7ZjZH*O>vDQJ+R8F<@hGC>C)V5R~# zE<_Z)si~#O92ia9ARn7!u^4Q0XPU9}!llrWMocedextMu--{gZ62M2`Oy{ake4YgZ z0U+u4JZt49F_gxH?8PdpXpIC4Amz-@-r}NKdu&`$tc9itPr80 z1D)U=ZGuhW(XBX|98}!%$nE2YF`RP6P~3*Y8ga|X&)74DGwFQKuYGuE?*V;NSA0j|z|fc&;htxKvt#mHDjXL8@1GM^mdtta z#sX`G#HdY-9?rJjULXqW1fpR)=IkjPHeg{^QxLbTF4oMQR(k!tQL(HhszGPtE4O2_ zGd@@~)A7Ov1Ln;Pbe^4oSu_1-Br;q2!I1WW@v)WX7%+fsBA7`z&_5`9FK0}g?aPNC zcz!TkKCljb6C#$O==9CPB+FQi2;($V_PYhUIeW2pBRfAbWq-ezJ$lxI<0sO?&|iShoVC>ew)oC#e#g1x(ZPz)B|wk`_on0F^boI&9yuYg5;DhsjJ6 zuoxHWo*UIYhhd3+JZX%QVHtKK!(;bVUDAlEPKJL2!a$F(Z4(rvv~1%W!&n>KC4YAE zLqlUj_^j#M*L7d5uUo&SOQ(6=%#24312cDz=tJkrP`Ky3k4Nr|_hY8jj$qa+ojc>h zKx})ACv#{_bm!neaAHVMG`-QVCTkvJL21TV7|Sikes#vn=e`8Akrgd-RbqHz&?jI0 z05LC;Fyh`mZlD474jQ5w=bI7n?|~T}G7_(?-`wR4?=fL=kvn}I+%qyf08a{(hmyh0 z7Fq3~$DlZG=uAwY_X`jNAM-3<-m_)n77TAGox%po5dy~$131ROdBoVnL9&}XIIuFD zJ2bWlMv%9T4jvjBydeZJpZc5P=qK?BIyk!0Rb#FOMy+$C9+JE-2pU9iq?lrsk$Py{Waa#ScT)2Wib>iiOM2;P8Onj6`(U zIcnBn38__YZmw@8!-4bxroJ6c?A2l6s8MgJt83C>QmRF7X=rZNTVRJuZy~E$t+0Hg zx5nxkU?HnDs<*=0mEPK1-wF#^Fu#PQEsRLPHkPo21@lRu!O6kBxv`<5-~9q$zLH;| z?StdvSg#p%jjb@-R0o4ih|$yp6HzcjRaYN$0@4}_+%v(p7E_x(J~}Z-P0x8(Ly@ zGd5;0euYZIycOzu{{99AtfIoO6)cO9F)K3I)q?zBt}52hn%UaGVpUUP6SazXMPan6 zAyywi-p=F?ho;sjr*9+`5Z0_3T41}X4o1(wJ%ur}2AJdmjS&mWR2qO0|pXoU^TbE23bQ3jIkLFtxe6amemS#YzFLm)z{(P zP}hh%*&u6#jWf8%$Z}RAdKbR1#|C$p*lIo3iq=J}WEr9$`T@+rCGz_{j#p;6x7PQO zjpxL;PnylvldxD{yspSh~h>H9#&aI*y>RgCsLsVEURQ6UWUkH|O&Lu!F0(DJ0^2LLv@a-U4uWmy&U=nPyjF+05I2+(>kQeXez9cUYmbJ`%+#KdA>}wt1<_I?r@?u!o8Wo1N#(D7sHxKa=VQ$O3 zi7alJH}eu0-FlO-yEO?zWrw+W1kH0Bn(%g9WPeQd6xThBmpbOS z%=_{D0Kz=T%!gohOMMvEBe)(#9GK_gkAr>!7H!^+-*@18Cob1i*Hg@V5^>luq!G2aQz(5u$^VX zZk7p~S*BwzOW4XfqM|;y-iqsOun%<~ejmW^+f_3OGg!~VT+|C{m*Cr*;N6=VHskoM zg(;T^Zh&Qym+PQL8XMt+i$$^tHL?=%S!5+{CTr>ZaFBar)k zm*PZePJ;)#(CMx=VL0n@*2}rw4Fd1 zS5|(U;ZySvYg5V~e13hKQVTmD=EHb?1We;VP8*$y26M$vPJSE2*jetQ@TFjR%kUcv zviv;Z9qMUQwkoV(hx9GnB7KXxMn-X;W6_ z|PNkN0O^_9x_|u5Al&yURKM)yyH-46}_3y#Ya<%~?eJj|;ePl&v z(=xo_UHgvjowgbp#>#lYH_gwj2L}+5$U^=LoZJ)$L3)@lE4(OepJMgfQ zU3E8pRq)+<~R-tC(VAK zsrCDyBW#{Z*@5xXRmwOHVG@V@t4Nw3u@>2j>}spVuBoX~s^b+# zEgBH=5@vuJaIw7@l`sgzB8-u+IF(#XZX_jYErCRRZ3rh#*3$S=g`(lG-wa446Nvn~ zq~A?!nY|2&nUok*LsAx=i*fX{ws%=7xy-5Za;h?t3J}Q9Qe?Rzm0ZyqolZub0Cg0= zidpqmyrENDLXj*B59TY4W@S5(roP`!2YGG2;b>M&?#)IQ=1Sa z4pClfRVo}`C9*G|W}ve;nI1||NI5j1%9o{*m-VimPOhG^F1IgF#jjvSn;@r&Y|m7_ zV?#Z2>Rv=x26K>Fr*#=D_jC$;#wopRB7|dla+*K}#Z(H2y;y5f)@ploe2pkbi5Ha| z-Kgy{Dx4^4X(lQqmU5!vT+|HYAVIW~)n#`*gOKGEvIuTX(XxelHJTVKRn56>_S zr6x*ez3f;du|7pgBVq_`I2z&(x$)@HQ`EZC)!wL@ub~vBsE_kX*2IhO@@$H45+i;A z^%>-StUNm}Rh8a8q;go~l|bIuT|a);{{ zdkZSHh-wU7_}(q4)>>DQL;Nash$JbjtHl!_JbHV=$(~f$sUo4 zd|Mmg=_AiZHgSsFr|(a+Ilt}nOT%uuN`3lRqD4GnSjop`>soREkk_?hhzp=&8f3fe z-G>Mdl8C85fHpj1pT4Yvse#+xvmg;77p^pd${5 zw;>9Ep8O7K(35CTXaq8AyS+VJ3=xHzbx@)kp~v_Lg9lU=(K0F0h&v@(W{zK_R)?vl z_pM`Pi)vA&g9=-z)RCc%E>wwLK|4rL4WeQ2C_kcbD?|p>>p~D(G%470{0&ORb=nS8 zDP{k3_ACkV#Au2_`^e8T#z#H9{+A*3QIS}kxVOef)j}8quy)ux=re#y)>>osm_05> z*F<%E0w1YE@k3&gXn@RT(KoWk5$Zl^5**Pmvu?0&_(&n%cY1(j!L9bzJJ1yH0QHeV zysPfR9aT!AE&HK8^FVC}+iuX5JgfkKfzl3joHMcz6CpN6G`35ilHxT-R?fgXz|-&KjZbXTbr_v%4kk`;%{+cg^Ph=a#p zkr&{Bby^3+OurD?H2uH{qSumYIE|Q8E`a=YVeub_@v~Y=#q!xcN@$UnkdNGGTR`Q8 z#T7E2B%P>>kT`7=kT|M*85!xULBk*h^$fnnWJ$+;wC-W#y5Szi#P@bJy<1q+q^917 zm*;X?SCDut>TA7?iP1$gCwn%DK&(axosnme8!OALSSm{l4nP0^xj;t0AQNzw6YxTQ zI8+>ltjXX&k0@ZtIYFgs$PGl|NtAWjIl)}_Ou%lZ0_O&F-CswD4-zj2A#ku}5K?TA zDj-@&q~T;#am3{UeWD*o{v=mOiHo^%Gp?{z zjwqxfKqOUt_PEV@DY-RQ_)p4O1@|N-#z(e^7AcTYNTMD9z}YuAP&jd5SP)(X10q`y z#u1?k2v1QZb6fYOO?x24u}z*93r9!fBe1!32a_Nbd~g z7$k8jM}WsCg~#&fu(~7Bzi+P)6%!I_0dm`vI6N{jF66!Z?yHqinevTI^o@;=zP{8Y zkbZg&9)!FcW(S>Kj{sK6PF|Oi3%jT+SC-gTw(Qpo=$}gpz})k{CIBPn!QI|G=*qw} zw>Dl#;srt_(MHIv#2WObmR3lmKo&s9Q@su;4BQ%_XVnZ*mA_mZBnlY_2%SKL#Hfow zo(6Xa)dalv20}JXBp(9dhgisaJ>)Ay@-~eS(`jgKgg8lK3{nksIH@Pmlo%uvAfQ5Y zbClehVh? z6a)43Cld$u4i-v*202|V7jO1OMHen9uAsLz#2{4E*wByzX@#*wd0Y>3V*!aBK>3~2%3QGnbqUvm*nv-o9cWR` z>NtyWO^6*BLhL{T7Eg=NK3hqMJlblm34sINE_bH0_U z+qk)%n>}2+ng@1qehpW%B@70*dWfrsxwe%DApqce3p0=6ZhM zg}sW4e)DmrQh?j}gm7jBmGCQ|K(kyTAPI8KTu5s19`!oR6yiQ8?zXt^75AjL4~hHr z;=WJZhxe-o4vxG*(4*o$Chp_nJ|XTmi2IG=ev`PrQQU79_glpMP2&D$ai3)3=T`Z1 zME=~y-paIl*s8a&(zbgubcvuL7O|?}J~$GQ?-IE#5p)UDCAdrAd_cz1?qm0h=nnvw zL{f_$Vh=NFlSgC;9%Zb(1LyQ=SyP+Rj01;?Hl-2=4&YVi<1|7qmA{6+jSjSYW%7}i zi%|Z&KB&)uIoS`^bFiMY8FK17!B5V;yaNXsd6#!W(=Y!DI#ddAI#ep~k)~ha9MbeF zT0ok9#W@6eRDu(ce6&FVXG&#&Qz~HPfJZ8170{T#0v3zJ)pY*BSP^Sq1Ez1~tl4ws z&I`|9u&`=Tbxkpjl*~X2w*L$V8#NQEd*QiMvmM`wZ9*alj z0*Rp(hZklQG?q4)YSK>|4l7^>Y{3l_+tysclt;M(LBZ9`L)y9uC<^di!OYC-4W*JH zEQAdTLD@s84MBVqgmzwdl$_7r!c?-*jUgBpfUt5}EP1vy$DZR>+HsqE0k?SuN^nxviuekY zO0v46lGQE4MS*!lU24_E>wxCb6%&aS=vpB7W+Vot8gaguvM=-dh{f**9$R? zMz*DJHpt=)YP?Z7mLiHBJ9;t|xe9ly$!?l#KGoY~wOET%RGSu-Y_eL(9el%9l>kBn zM-}Pgum#SSbt*$K;AyS0S0z>n3MUnb%g`S<9APH)M<*Q!;`qZ} z6QyH`D4kG5>C7Q&t+m%itq!|G+}z#eoMbzqzgm2>^!)ARrbOT1u-sa?`^#^zvaD6b z%B&DGH<bTzUD_xBAB#IZ#lQ?f;yvzJI&b-aLBw5uVtXP6qu*~gfaMO;WW6ZIEv9db6c zcXVyq)48sF%i6BLWZxw=3(h`5Y}@i>DYgzn@sPyP`JVBSy?ci<_@$Zs!+Bf6tew}) z&1C1M^_?5@WHQ^G{rH7M^W`4US?0+qn3+xFMfeaI<){noy*%mgfG7W&E976PVM%X_ zui3Swee?QGvE_ppFmVfrv*34COAOH|-k$56&6c0`Sz?3{A(EELdEWlZQ-ArZDqLJ@ z>*emwv$n^X4dDOXzK^OnbF=&3-QH#I6V6vVEX;cKrCwmm|A*RsSt^&8(38X--5Urw zcKuc94(d;qNsl6rPFqFZxu6;qtPW*&%~4<)V-Xo^6=n+z2nJ@ zhSyAOF<(bFxCooI{X++|{fQfe1}KNNT_^JPyDEBq`Cd#68QZtEcXs7M3Cd%x%;Y7V z%MUq6&Cg|w1hYEwf2im3w*T#IepXeSeLi;E5cwxPmZoS%*9ecz(bkKoTxP3wLNTTe ziWQ@zM+mw;hLo@gZ^pfecJ*~oMlnRD$R}D4U+7)eU6eO~Z9G(OaBrz^${VI{M`i#8 z2MBY^W*l6WaMr?tSYzic zKFBe{@6K?<9e#Oc-9n*)q6a$#cjKb04B$@>_3y))iPXIv=7-1dydZ%S z)XxH|BUD-hX2M>Vy|K(i2g6nNuZ|7zQ zF-(GBn8dlcmg^eN5eyUmMxMKw=XTG~%JB7=SAp+gnY~amfDXo0T)&R1Kqg3Vy^m|) znGAB(=IUOqCb=qxkYR9w;zK32&<0(`8}!36kNWdQijW95*k&Sw?vBpY41Gyy)=EIR={*6h6r zh(^yzCLkI;7x*7-009Lch~R1MX;ZILz~C)myfE+;AkX%82CG^yEtR7V!>9j!HmI zliXumSK=yRI$?_)%<&0U7q-k`RRwd15rv~|cVwKn>k0wh{v)R^cd$v8Ndc$m$s)lj}s9Zv>5SbC{1aiBF z$WT(vArv#HB8}NdV{R%rSCoei>nM|>gh*g2Vn;FpQxN~PmrG90it6|ZbaFHv6>OzET@U7v zXrWbS*GJRNTa zxEIN8kkmnQM01}>3Zb{oYB@n1r#i|?*jNPX2_#Yh>j?n18P-#ZoHMMal*4+;S5efP zQBts-@G<}eUvRx3kC3u2BPrWECRc}$QOdsDd74};q-IE#x7KPFp*eVDIkIB8M^QW{ zBnhsN02Mmw1cfGB>(i5~PYVZ#Lbk`-B}qNgUQY=OZs+0*K^@WzRH>8eDO!ONU^jjzV#MEF_+tv0)AoV9U ziJ(O!afSHSW_z=>!QMc$z2SJ3`b>NaOLS+v*4gWX*E-j0t0>&2nqnLY5eezJ4bJbn z(5pgCET1M};V0%D&3F+P>P}Sxi&cE_+v)3_0}fqS+(4%ep3VQ~Vmdde_FU z6$up4$EoeY36SX9HIxL!=-m}YbSrM*B?(w%@*A+BOnj^Gjuq2%OiXCVE0L5i%~v3& z;(Nrr3o&~ZSPkzXf>DfG*W1@et(YB)TKnvMQIfiilE|!>NTT#<6}Jp0dQP$zdAwTe zM^(;~bebXMv>m6(nWOEMk|8sb|C|TB34nD$_kDo&YOWm7OxFVM(cuj%*-S52)Fqok zl6|Ir0nTtXKiT0D2lsfnxWYG=V)SP7c0DfUg}9}H6)f1BU75;?J^sa5^dYY#9V4Sa zb|Hq5kW&(na@jyO*>WELeKw-QHe-j`jWdKqJQ$kuaMH4ohJ3O||A*YDLj?S{t$XdZ z_BX{`w7)go9bIue0xIFJ1M>$itB|zYEsN|*m&95DNK)U_?D3p{=GPeg3s_D%up?rZ zVig05HyR6gJ$Z3%lVA+#jbK>mjrEPqdSesNRGOl(CIQKEF&2*A+){rY2WQ6lz<*ar zBMLjECFK>RV2G4-ikwuP}(97jnIb`-+M2VU%%R&W%d0&*J)QuFv5?s1(32fzpVh4ueJ3Fv&n>wF%U?!V~9hja2?h?%`(uM=LvhhM8!0ppf zlSi-`Uwlf2qs2}!k+6SM+glJ$7Er>i6d~B zK6d0Z?aNNwkAleG3i>5!f)iDwcr;5qqCoPI3glPO1GEkh>X&jI0CFlG79C{LPMJ>k zYiPqIG%TZ}lNlvBfT*>^UJ|vI+DpZ-h|+%PLW_Yp$$7}>)#9hHfu+^JCY)+I<|9#d zCGOa#F$qUd06Hq${%TePWL}ue^CbmiFftDbMxQ?fM6~t;1MqAg9Zek05rA`>$M(`G zQmzw4{)i)5c4(I4NM}o z>$FRLcC?1NR<*GSvncS9a{aT7C1sjmrsGrNm6#xVw5AQUgGJn-ajs!W2J-0Y)Inwh?MQ2ZFuM{9yLM zb}_K)A?7rT5E6nJNwg9vi3aP5_*h^@H{qk{!m^L5@Lo36p}0b2aQ>~HhF)4aK%WaK zTqe!<>1iRKO3^FfEmqULO81?Ye$vDzH z_`&=a0fEmt!$NFUdNu-(<)7(kJzE)WD2^V2+2W)zn z2RcE#q;zyTGDqymu^6bGj?9%JSn0Yc+Xxq7^OKH&I+4gDQ#^X4)XLwGuf#*jvB-jB zCytQ@eYzQC_GzfvS`-;O!!xW#S$T#nW~(AvdgalRfSm_m0e#@cPK9tdVWuyGaTtBt zE{-fc28pEfs_D;!tBDJ#AQX zh{qSD8{vMd2(2N=tHr;Hp1)fQGA;QF#&Jy<_4O%dvlK4 z@?GC^6;xopSEjv(Wd3Iy*?C?hY(!o&4((u_#EqGyk@7m~AAh-QQLs%Qk?Z(7wHQxM?C;AiN3L{n zV#_BtZczBin5Ljfbftj4T>ECGAzU(P(36)2J;^k(PleApJQg6O1s(038`o~@?%A@& z>4h0mU^=yR_9yuQyL{)1VeT0m7!txq?!G=ZBU2>gzzERZ+=D(5-(-CP*!Gv&`MdlE zsSh%A;NXOmC-Xy#WPB9p-OdO1;^37!4Ki%%r3#hU)Dxh7ZW00vKow7pcMc8@`?q&( z>gwFlwMKm9{cars;y7Chu&rs|(Vi=x9eCEvk|PKN@BKSQHj(rKL}i?u;O2b&g$UPr zDtoS*vV_S5sAnScLet`)E?WQki;@fRJ^nwGGs>ab!OFlPATo|pKhkp!7El?-e0q?= z0z&!01Dy_1YdaEvuzgJcw_e!ug!UYM$`-s~0Mp$wFgkLOUSKifaLB-|M_2GY2Sb`D!qqu3d=V2JNKm&IqMt$2m zDb~3t^0YG$lZ1rs&O)IFvl3EjYcLowW`?nM7?76JGq~YcbE99+MW6S{u_~a`UD-E} z@9A(u1wO4bw~#HKHzv5B)EbUi1y6`m$azwOlqW|j-^Cq&(5y!IAw0B)@l1iXCRq=(FTvXDXx4(E7|+3Md#k zAV!nuSAnwY84D;r&ku$J&H>Oj1kyhrInYG{lKEp0T0^@YOzainZ2AZ$f{f0lGh%5v z={e|Nd*`mFcXb;i5(h^IN3*%t07}Q`CNy*y+Q4c6Q_*2Ls%ElC#p;S?O=Ad&+?Ypf zSx9R-Tz%d@K01uCd2wkFe6w*}k=lZw1OZ>?WAvm-5ipb#Ly{0FXXYAhU1? zO#%$!td@W_@Jx};MOjPd?50^cqXBc>Nm9(jLJjtR$V$KQ|0yfI(^;ZB%ybAf!eSkS z8tVbVT-OS$aL6zcs&@=pj}UppjqVLCP?Lo5I=Z(+5wfwh8KRI)KsUy{J_h$Da0PG& zs5kB{U;;pKG8)UJF-9v%(OhuDAMW)HF)l^x9W%yo1JrYIKc+{>oMWP(F$@%Vd=ZPc z4-p!C=L|1|Z)%FY4tpMgm=FdfLCl60)(9a?KxN|&6~`<{@faYf0r-v5!>_Rkm5Vh3 zYa7Crjdf^@CV;2|8k}T4(Ja8Fjy1PJE;ZIt2MBTk3x_Wdo)HVk-mRDz<&ptN-;D@R z4+wbNQGg}``s@>fpc@59wgNT0 zsi6+OO^AnkOAFzMHvn`SXyTBPt#1LqH=&7B+O0VJ0F*f3*9lM@BLEQK1oIt38krTd zSk)4Ecq>BHH#Xu9#arAPqY$n|A<)DP06-VO;B~mSK)e`t6d(5{fj)5hT$HAU?)L^BV;4Go$e4_!o3yf{6HwDSd=mDt<6p6O6E2~ z3>ndzn_F9E%I7w=piwS%xMMW7v@|#S-8U@zwDYecv@4{PvyXPN^npWScPBQNa;xSD z?dJ4n&3UkbLJJV!pS$9+az!E*Ef-KCHiS_AMdfeBc4Zq9#8-><5`)6d%ks=hDTiiV z;g1QxQB}SqClIp^-Yd*5Fe5PAfIMyKveGEza#tDK%A)@5l{+eXDzB=%TFBzYO0O;L zEj?7eqx2qD`U)%kJS+WMR(gt+{sZ&>hH3dEooi&_r+m3UjU_RU&Qq#Tz`Y>%ecORJka0T*YNvw z)bAUhzX|$V`29An@8Fuka|*xHV6~oLyy7G?PeTavyU+>OzK84Y8L#|4et&@LhY0&4 z{QejRlmCd{pWyl_u7ASyGhF|S>*u(Bfj0Ujet*UItY72z46ffYV9TrM3tV@pT)SK4 z>>iaD0IXemFRmAGy^QNC@TWdQdmCs|18rmc7;Zns^)r?0KZl*wU#Li5od12W>AWF#7)-VMG?3Rx@0j|p?BCe$*&jUe-#zj_ zJo1ko`JW#7Cy)HINB);bzUGlC#m+dc9f9{Em>e3wT)>5)%) zZy#|pVj|z++NF2 zXVpKdlbSQ_-l}EZt|J=z0{bGv@$gGb-65rGjBbHc?<;A8SyLzpwJEtO#FKEE3QS~X z0+XJ818V__Pjh=!n^FWBApqCoCwD;l<^jMS$SwIFW{9n@(DF9S{H)+3c>E|J%?n=! z@i7oZFd43?#U-;ylBe`2Fg6Xfto$ed-hHg1kQ6g2XBCoS#_ZA#C}zwl?SyRp+{z9m z$mUgcLN6mcy90U|^JjNLFJr;n4(MepoZAV#j4D_dFJO!2cR(+rdVVMLGHO1K)Qj2T z1s%YwuT6B|sCdaI;8ezzLN}ybjVyz1NCjK|Nw`(A6`w>IX0a$>N@laVp$=t^8mk}b z1ZaIjtOFRvjj>K8%$n-kmHDi>fgtN!8VRw!wW%Fw3M-r2l|^h-OS@9dE^BQETK#GO z>Mv%OKf)jt&aT+ot}J0~pF&PcS^Fz20i@fGPvd79>--FUma{cbF%e&ZOy)ooN%DdRL zpTk3(?fyJ|)-ns{=4}ke>53Xx@x!#Zz_Itysk!zc)~3(H!8==r!L^1d)xCQ>==$sT z4eviNa_|kKW8)KtZn*LAO>eyUmN&h5lHCdwvD?^sRLBgx0*N74Br6-`C8PHej^tNq zkx*56q#ztA3=_Z$Fz~fVF%V%OvJ@!=Dok0V9Dfz~tHj?d{LRMS9Q@72-#q+<@i!lT z3-GrPe^vNfguiP1)!=V2{%Y~J1b@KI2eM2-WNDLBR7*m^%zMy zRmNK(VY79T86$~e;K{^wM|4cW#H?2k5RE0#T_8R!MaKd`K!Er9Q&1HNz?i$EP9p;W zej0%UZVbtn<)o51Rvy8R+M#%!5MP2iO&~}B!VuFEATt6lhA>|MSWh|xbZnoBEW@!q zq}0Q}_dv#vN{CiONhJzVDAP>@8XN+2MgYe^-Gx*-Qi)QlJYEhlEtzwLlQZDmy__pi zOabqLVv;mgb`F)u0pRX3Fy$~Cf%6CphSY6D4zr!K=QwE#oD`}R1)q!Hp&7yFo)a9{ z?SUY2PKAq6$;h&GBGq`9Fn!cjfclF zO6+ohl6umrva7;H5E~$39pU^>a@pL}4}R(gdl4zHkfa;Pbyhef%UGVB3mH_e@FCe+ zREWU+ZRq&eON7GHQlnNjSoJwAP??=V1S!mNnxuDmD!JSV?2Z{gw+DiR#7}K66n$jR zx~&f^ci9FKi63C@0&xhVh|p7eqv2$fR8V@ODOdrJQR|$J1Jb)UC}Jo_m?9Ibz8GvV z42Ai?oDLHBDCIsKuZPg#%b#-#JwN$vZ6V^?pi#y|; zh>YChYw!y-7(vIy3%`VP{x+j<;+^bWTXiGlwuLRCQLOOxQjLvfBdG zeNEy@VAq2X613aUw?xbQ0VSTPVVvCKs1f8*7j9UnNO9CD0`R^g(L;lUQo@gD9icyx z6&LOzZi!`VFcc{Cp{ZofAV?q!szIXtoY*_MaliQ59fwQj6QH6x zEAt8p@e2CLX&Uin(~CkKKzdO$O!1tk4DA%5@@n8HUi~~OGrXbeHZFpNMSbUE+DGUm z=cI}0R-_3rZEGSv0cbP7j4ozB+4@o^P`sEQq<(@e2Qfpy>VOzxm~2QRR^%yM7Q_#s z8O6$Cs_O=^yr^@AsdZ!IB}VY{oP@(sEty0raTvoC$oqjH*_fc(p=1gAj01gNPR%pl z@oiKq_q{`~23-#X--;C-atI*qOMY3*X_N|N%%Obf_$usXL73&DIkFYx?k1(`GF=@CRVH=Jxg&e3Pb|RGk?!2Uc zsErp~j6>A7i6%svKB;K*Hi)54*!rTotL+8yjYu3NP{L-ylj}w3w-8i1S!vpE2UFv3 zVQlgaWJPa*eM{n&lzlT*`j|)pKbS@AJ6WFlqQDm+?VFA#ZYB#zGMbE$xEZ;$(0ly@ z>I0M*>(yoPy9nT(9?Yx z0O|0ji5O7|Nl%o5_uKvU+n9APv+tF__Oys`J?>-H{mi~U<3aC4#?y^OU#*Oz8-pBC z%~q-=;1GrX1I)(%2buK{vmeT&X^D9dVS#)f2>u`Dz63sw;>>rdkEyvNjjqwt<3fh}VWkHn0u_6TIj=r9-(VjLXEL4W`T!etv0k`RtS5+EcYfskW& zv#XI}JIQV~o9t$Hv&lX>d%f&?8_)ZHRo&AwvTVq1e*4yPcYpPDb#--hb#+gD|L^}T zo-?m_F^0q&oJbbeGUUYfXEO4D>^$nP_Imn=xDB#is3{l;TYN5+qu*NlL9`PnCeD{|YD%egHqz^|~H)uNO9yCDH z<9srSuHjV3Zc_2FW5336C|L1*{>Jp}OaBDwtD=5mlqahC@TD`yI~2!u(y_#-e+kB~ zAJKRT>O~A@;^|Am>*OT90yk>#=su3$4Sf_ZCTTp*3O<2fE$#SiB`=!Z1n=^u4)#-S zs|N*lA`#q5Uj|RG0eAFCX5jw`7I&B9p&1Bh=xt?Hf0Nutqz&SpQ`{L`K8ivk%O2&GBV}9z@S9GIGTjQw zq`J||lfj|8ik-m7NxObUr^BweHhmJVN;3Z$M|r~e^q}lt#hxk`NiR_zSso>yq9l!I z=&IsHE~zgH>W%8rR{GCf@?|l5Ig6 zbP3`!YCi3!35vc@T%Y?H?xHdGCil-NPOYaGYrZHbO9>tIlO?e8_`P8F{W#O z%_7e;_^#m>koftAyvfK5(9ls4`l^tJC$o>myLh_Rl*jgMN+u{c8Yo#LhcAVgLVA_laF{nd)VL873tMc+WwJTu}5U0I2fK?-P`x{cl7jl$LTns+P1wPu(+PC zvUFuKi(U-~q>ZI#H)Xkw2Z6K`Am_~^o#LYMp^%6?TVpwirWL{sTZN z5AGAk=Gtc!&S~Lyho2YN2dt(-MHws+kR{5KJFQANq|Oi0H=JE$4@lYCkpb4ENEQ1B zMV^J}vm6{7It1|Hn(Rwhbl(2ijj?#H9NDdNF?Tx^bbyY(U42uF+E`z&Ha2DX&^5L; zs?FhsK;{ioTbdfZ>88;@k=WpK+Cc;Mu1O=blUCn|I0|rWZ@MVBMg>Gqg?pa#vkT~p zN>tC{S$=klxFS$8X(v0_^3@#jvr~n4H(c#h0B8iWodRaNQqJaaCJ61=LT@GWE@kk! zS+9(|F{A69wzk1&1LXRqd0AbFXlIED1l#jMoug@2x4)wHgoSK_TD^r$E`-u zx~54dIBJgiXx#Y zJ%!Ii6NEkB7c#fe#%=DHL30?TdHBvUub817J4sQ3eH8f1+%0yK{z{_oao1LwSI70* zrE&Lenr4MrL({{k9Js>h?(H@AP8llP+RbRvD64#y=GT|rt4ew!d`*?1E)*ygbouhQ zetFz==5^L_v7}LM9rt-9eO$S9oDZEyg&!u)$T9aRT*I*xVvyI(kO7wsk#K3Igw1*5VPOec631$G^V2gJ>l;+$-V zjk5_hsUz6X;22xX_TsS(Ft6ueY;1fGk*z#K;}QBu7|a5@vp5@JWAJ*}SGf!RXe0Zj zH$1!6-G5U8nOW4`FA&7SQ=HX$DW`CdXE(4>b}M^7?{c9kE^**$o(Y|#U;;YtH+-MB31V*3UMhM=-u`%3s`wn`KweWO(P zWH1DDjBty_s7UrpZ9}k7p_x!|4pF4f!nElQwOUYvXcfv=voqP&9 zCbYei;jkvyp^f3u(J{Am1+wOD*@iqmIyyWU8>zl%UDFj?e9>&;Zum1U>|$RFT0?zH zv(^ewvM!FZz8|3uH_@A0De-!W^-DuudUGrA{RYa_%{|LFz;!D8;_Wvz2x#%9){3}V zBI5JVOPOm3!V@OUD*&$=0I%!tiUyEX>E;T+tJ^6wj(P=GcXFkdXknE--m93m2)wKV z77wg;6;}>$_ZU~kF>9+}Fk!%KSZf_Jeof$qypDyob1cZa`)DCvqcj7ZmI=1B8gFYVr|Jqdz+CgM&|r*Zd2A`n^h^PFE8eY`{>OmEFji{w-(jljZdR-P zMmImslFuKtrU8?+?&ptUKvgKy2Fi3FLl#W7Dajs%AzMC%@tRs+@_8JA4-#0N{d9DG z0jUF-CFO{cR1GDS(K{Az9o>A0Mb#w5uE#I`EqGbuRg4b`Z3T8c$|$hB!D(*<6OC^3 zG1jNayeIJ9ejHD;AhtwcbWQud5^Q5GFOEAU^tGWJ%!`;$A(UZ{7nADqn9F=x?kO$u zFjw#^G}VG}+-p*}L*zM1zxeTaQA5SnmOXo1C@|3>73G-t&!2)X`e#`1TXfjx?4o-q z^?6bDp@MZd>2vZt6-A4YcwT0!Ed{5p%Y4B)$b6B})FJWmnJ?P~(YMW)S=3iMzX$Y` z)%nlhY_GB4l~n%gq+dY#UTWWrqC9T;Z{3fogFh6Uh=a?8`6|Z7qC^wbb zl1f3MP@Cr0SrGOm^Q5eQ{_E#b<~QjVq+124%NMCbyv4xjf-s=kJ8YrrZH6xb_HC>e zDc`|*vHD%CmuTN(wTk&RJ9-+MZ?mIk%IPN8h+jE+*8C-t z3<<}{+GG9$L(o#){1pqLky< z*}UdQm1H#76LblNiI3Sw-JN;V$L*u;!BOVDa#u4EBAWLp)CA`Jc8&+^0zM*g9m;YV!kSO$yRtxw!-Unj<4DUd`{##lz#xN@CLQQWug_npxwl*oX8wD4Mn7O5~xeGJ5*CE@ql`RPK zgV+3lG9w-&kei&gkDpP%k%dEgirAt2ALGK#Dl@f85$vbt59ty&i2;=c<{vB3;<#43 znfObJS+^LNf1)I7H9Xm}{GX%qi3jzx`PX*&KU2WMOyw(9`M;1A-cm$^^xxw6Ep$8v zSd1{v@qZ_e&wjyZFN$>fD&5Z?%Kr|k{41r3B~PK3{aT3zroq{Aob12mKT`cK7xl+` z*L;U=^gk&y*^W*ND3_+4R)W98iE-2ZU7oLYm9X-QR~ijjdQ581tIOEX$*S5RqR(HW<4-&p0meRVZX(~06N+Dr>R7FdRSFiaooD&WX)VgQr^W|Puj$FaLbSd|%(UP7LSGU)E zP>q)Ml#=w2DxPgfkEqeIo-)!ss#2e+)m}{77$}MREzByE>1Yu4oM?GZx$9`Rc=qoT zhn%imV}8P_yDPXJ*H&jfV5M1*uC>ZMp1wBv9OyJ3w$iF|t(v%24c9uEN@-hDsXeJw zES0(;l|mYjK8$$o=V+9>bt=4P#Toqc-o+5He)$1qkX0T#pkjTb;(#VMy4<+shRcmd zZoG2ilN-O>1mq@1ZgK}%@&HEHLf5V4`|$&feh{%Oj2%0`%-aU>f}T8}m>(EqwIQ6? zyc@gQ8J!%pyN?nI5;_tOQbI){7l}_&LPNrX#0g61NVt)BixO@mI1;zxdfGV>^o%@8 z2@euJB;KTi7YRQS|3(QP5&k;p;fNhF4I5d6K{m5b>xFc7+wV$6@>V_tD- z6|N$eUg64f!T3?&;uQ$F=&5j(y1W&DO#3QaWiEdOqB906Fk6sQ0Sc?!3RjNqSG8xT za!=v&yzZR*>N8kZYM;TnUH`0#f%h}& zG1$G{foaWrQB49dg3sTV@TCnd_m>M`ax}lBCXIPCVe^WLHa;6{qz?R=8l9hdbwUXJ_JC@!P2s`r+5G>qKqCiEetV&2OOi z1rOF=Git8q6DlBg#d z2q}3jA#R#b=66xMxZA4R_s*-^A3=i*92siYpFk}|HQ!R}ect?g5bxdR@#69MzNqHf z6Wz6Y%_$WR;}6tmAc;{`P;Ikh1YM7KlWX@L!_FUCt%T?5N7SO`k7a8eq|eAdSCcc5 zh3wcF^Dort+E88{>i82#OKJQQ;Hl}_DvVn6<@8gWwsxtg(a#`GlN4NpuiVF|eWLug zs@MD_<>?c7{y`0Dbc$b-s$Z!7Q4Riv?$JA>x=yG}HTajL`X^GwgzBHw;2)FfUr7}g zs((|1uafGw>U5+MvvMBu-)ZMQ!m7qSRl%nsiy1z8m#`C*dRmHfWbk%W{1@u06U3Uv&aE=Llfebica2xR)Q)x`QLA@3U(1 zK~#9Z`MiBHVUVv)nA5g`iTVwhXKaP|A^@O3U%#ro-b~qwImSwF#iJN2U-^&Jnct=U z`FE*%Qz@i@Xr@_b;88K(jA!=48orhC>`6r3JOpDNnAF3xqDjQ1$a4T$-YBA18BHP@ z!>fr20uIIFtA%g3J#+YheE0&}yJQr@YzDFn%C8La4?EbqU``5PekV3LJRu79#|9=Q zW5dFngXyrLeG`efsEYzJo3zaE{=^`WGI`USD(_10r1vK#4h(xJcr;mj@&#WAaZPnt znhb^!ZQJ&3?>(fTn1u9D=~Sk#e>`?*a9?-t)}9NbxMB1LQ z*gU7N4zy3jPDW(*AvPX4#Dir6=j=tV)z-B71Bff0LIG;oTB zCI2dBGTZ94hKAM#t)cNE6dqa=7$(4|-5LgIc3oHlF?U#(tlNmXh6rqkB@e<85C%}( zxG)ipfPDm-y4IGu%!qFx5%EpN7qkMujf{>AYP3SIx<(=-0h>62fi>b-tU*qJwKxYj zC5Zk6mBkv2BSdyWk>OjyEp@Xw9kQsmF2<+x`%-*o@OoR}K*3pu0qTOh3>P&6Yhc<4 z!(`mr7Q%G04uWrgs3=sbH-}nET0E_YcIGc{D_>K-wtQXr`f_(@Q|QLfxI%Oa4w*uJ zL8stSa9gv^L6jwMLR%kv2@A1ugc8zVqJ^KcQY>4%Ku}+BfkXTk+XYy-B&bMq6+gYdFHuRlHy5Z)59gDPDWIx81b>bMd;2*UrU8)4*uElQA70njpA! z2jFW7k4#W#z^?7_s$_2W6_G0?0xyX`?QovtN^Ty!Cp|1!3QtNUH@}ozDGSQrN~z=) zmXR-IQ4V}5mEuqiIa8KY#NkX?S^5;{a5o^QJ_NN|X0xmV+LRfQosdw7&4iDp zK9wf)5tJRl*b(_i@8g8iOK^N738y(2^M&DE#m{aS>4g(alH5!rJPVPk=r^t)sT1@fpUPQj-Dc}P$#ST@sT^4hxGwap>4F%s5BPE z7L&XzRw_5d#au+FUwG+M9#Kw5mxz8MvBFCuOO%2&xR~llH7d2NR*NiS$OI1xTrCxl zj4h{)VfQo`pX>%(76}5%E{iL`?<~UV$}nD2T`Po}La~AiIxNn628F9AoapE8JhGC! z5-_hr6!eixTt}2M~uTE51GMr-& z!m|AHcSZkD5R$d$m5tvOTr$3nEje%J>hm-*n5_7vRyaE1?7kA*Yok?R|Gv>l5EcS( zIB)}gpH1|F_!S5b{A!`#vy&4;!{b0B4#vi;9cfPAInI4v@q)ehLZ9>WO%2>$1BfJN z)k+1knS~Fy=qI0w?^UI(*{!n@C8UJ>rG!rds!t<6eS}-Zw;KYvK%;oM3IIVNSMjDU z#<-0^5O@sBU-eQ84Ax3}KMv7how0vHTKjOEGkL@gam4tjmLL;3ae&dILP{ zeK1lgii({XhI>ps6#qsA*ez`O3Gfz}s&XZmN#Hp}VY=8^+J`w*D9fWtj{*}L4Y@FA z>D<7PAg$lC;w5c{{%w(8W zG&ABu(KLDTM$sp&CjcOo!59$A#%M37v#q!qXvVVOd3*N$!TSH~qS5`e#3<;t(OH2y z8XN<9D6_W~01i6>e$5Hzz(tNIE53TR!nbqcz(K^-K0G)^NEuuuewES#FfusdO_#M~ z_F=2fKbLsWO3v2{Hg9XP5=fDalK>Bj6ejmKKzPSpKY-xPZmkj zm-0ZFjvO2p$pV?&Ff=g+{Gw1fP&zre!s^%n5tMg|x(UWfc7Ub=otpw;^g%H>du2D; zsNcPBXbcd*%-ieuK-iC`bKlay)4|?RiAyZY*g1G3CVI!g)GPXmJt2DWfuOEU#dHl` za!qP|xJ9jRXb@p+>f10t)FYe?_BFHsbsuh18yi|xfaMXvrcDLMr`m);q^Yez1%Igu zqEodQF#MLf2DPQ7Rc)(!fb*?uL z>pWn>0Mqy8W1a6Uz`DR&h;<<$_|zhAG1kT25Y{1Y3DzZm5OTHDTZVNR@cLXWC&<29 z;ho30dp=Nu-i1gKte>lu-bGk1@-D`DF;|y(s~}Vn++SVlt;V|AyA11P-sM;?_ts!t z!_^f;w228DBHDxoh(nNTUdh#~K#d7!0Q{JsuM3Qi?H!FrJg$yr7u(R@0tx!cHnwIh zmUURxW4RQ|Wmwv=bYR(lr4!3WEL~V4STA|uU%Qh_AvFyNd1(uyy zc46tovKz}DELUT>b}w5K#kLQX0uYfc(A(jBz+4#Kv0<3(z((&O5~WUMIx6Hd1hE7f|STN>qMe}5{2+Ipks z5n1X&+@M%B0{zhbWsYjOxlX8R9MuYQy-=;BQflGIK!;pHiB)F%RCIOH2!paFR%dpI zL_H-M5C@3%G*Y4o0f8vdOoeITOsVW_1Z$rUp-S(*}4X3yQ<)?K!r>f{oy*N!;w|lC}ixcii8f((JtEQ?{ocCHd z9;9`9r)m{6a9qy6G^G=2+P&&}PcagvW`u#8oUijjC zgam)gXM%TN3eGnH^J65CpUvGQUMoHX#`3R8}l&qI@sR&+EkKvRh3bTltQgP(-Oj^+!{l%Rnf%}dafp`{(o%g|Jy$sNrX zp_z}icQjvuW+85Zqxmv4i*Pp_%`4C>!QF5)uR^mNx5d%C2F(iGA4l^QXyBn$>u6qw z2C>d07*4e6*PyAxt#k5x9hwH*Lr3!lG)=gfj^-QCwBW8fnr}kWhTH3C-h>8`&ejSI zp352_kg5;ie(TO}4L71$i1HObM<@k=+ z$e{T3p&@n|$2S0d3EV&N3^&kiDkQpNOu^Z7qJ$Bt3V!yUE|9q&2pSMjWq%TjWO z(aq%L(C;SeH(2htpvt%7+1P<+DlnVUeSw%aW!QIRR-_;MXyI-YusWWAvE;_=5GW$4 z9*EscSP<%%62Fp{&LGnT{rJ&}A8rQ@%Bqrw*sk7=-Y#7P$SKQc#2vBy8P`*izUXXy z8apx$cH-xqmsYWn{cus2Gd?0dj@&Q82V}q#@J7Aq$j8w|@ZCc0f-t8Bf$Pkd7PcXvljE$mG9;ieo8yv4(esgPc&EAfE zqwztxu8h!hYh~xIroH5RIy!<4&8kgeLI<6mlm{_yHZoDUOZdC)?GVv)P?w>D6O|3C z!h3gP&Z1IG8X1_I-fIl+rDt8fYWHTpYB9w|#)mX8rd7oMsY6(ty0D1+6K<#nBqrR1 zxG#0#X2iPz9EYL_VLAf<9b}|6BFJFfM39}j76jG-$fgZ&n)+}vtr3m~Ys{Kp-9*ru z`WAxU03(Do!skE_z!36KI2x?m8d@(H^+(LHWrX&r!<0@-Xzq81Ml*bwFd;;Ks~t7) zRSZ2v7iuAxPg}UD$bJqp?L4dH7L)g%mePFyhB4-rmU z5sC=yLF<;*=5+V9%eMxBdJsC`(0Vj_#YMU*#WQLPmm$a8uO942c%lO99`w=sES62x}~y-Fg^zv8So2<@}&_6dH+wC;^exl8Yz6 zdUEM;Ms%bmG$_SLLI9y~148sPgb~Mw0wlE|j8I);1J)qQKwzQzMsyxTk*4rTK#|cJ z(WwxEsE+b9H?*{35YPBN%+2n!nP|k>5Uu|L@+vdzjul!*@1)piG6s)$1zt3~&P68x zfh((TZP6&EQI3dvQxRH#xHl_GFAHr5Z46xzx+e4^*wzAw|AG)UFj&mv8fNGgVqy-n za$H}`^(B~(!(<%St1%6SIXLdd1l&rl3+R+J4JU9@I^2I7xSkR6MsMYM8&?tXW-Zr6 z)Ej;&*W0cyI5>x_P&ldxg`^8f`wL}b{9*FA9lo#IO0bg@nep7wm za>R!n@exP-q$57+h>tnq4?*gIOB*vaKzJ&c*YUWI^qx6UorJ( z?C0#SnP=7CI0-Hi3hmK<;i$D$f9oVvkq~)8lt)N8X(^DF{AtOTmdevoSz0PhOC@Qk zI4u>WrNXpSke2e(QZOy$rKQ|ou)mY_`+N3F_7C<``zxm*e$Cj8V=y=?Ydq~_I#FSf zz(%WT2g@SUVH&814PslnYr6|Ut=d5!kH8r7xzgcPn<=7%F28qjKg^N2nfdpK#aRY#!RU11& z!oM`J0XWa{b%Mjrr$d+Hf0XWZ^8z^YTiW?|s#>(ghR}n&EL^}cWTy-^qW+=6* zukJ+9jPNjmX0W;g8xS+2ek6vF84UbsetxtU1(yfEg_%9SE4wdNAgy zWNn)$V8)tm3Yf7L;U|``b$t{tV?Cl%EM=GWQoxMMdO8p=1I8HE4ulAE=>kuRKodwP zU?I8@_LZ`Z4V@diBAdE5Z@Ii@>$dGXuGqP&clVWhuDZGg$9jE4sST{@(tlBFwW=kE z)8MY*eP^ZL{%`UhdI5>s1Jl!B}#jM$C46B3QSxw#tG$y4FHgY zbJc9(eHr%wY!(vSI7i$Ex1AOIr8phD>}A&cB&gIaX6%Rz5#g~p`3XQ*fXv`TlGLi= zq+l)y}RcKF5-hrc!Lp3VVC zE09~1U6P|B8}UsQ+c#oSGGWJ-u;VU-oU@>o*=JcSBLz5TkvXihAR*6!Y;+b8c7m>D zaWb)Z*6kL-Bqx;5Z!hIUzOh1dyHYOEkyOLC+Z(9%nU7Ffr~B?vO$mE}l? zCxEKP>X@j}9A%=OR3WqdXji-~But*b8bm9`C z&9KO|7%h=jttJ_1LyxS9t})gaYsvIv*B|}3f&jLcp*_%_l5|@ox4lL4>LH*py3w=^ z?Ji3u%KGL_C+r9%_PrD_G{8HZG%lsKGtozAO?FP|*20+I&Aw ziKkz%I%B_RN@Imc`mK{v--YKlrujFCwnVu`QPOB9lfbEyvGij?=ZJ2IRucf&=%frA zjg4EX)yM{Mx&;hZC>ebgI$|YJw_k@!bP0C+NTiDuEJr;zMK+-_-H~pr!A2e3Qp+P- zSRq|c7QwzeOcLxbjC3gix_o)$avatZ=|P-e^@9W1ep)O1H<%|gO`jkQFtAQ|MpSAZv5#wDOf5O9B}0WU7# zsPoQbWT$9_B|5J6e&Cp+yCS<#$=*mW)^ioR+t^J`#nj|5l!}(8z656kDQuhc6SDAIz ztArLKOsrX)3%TtF1xlQY*m0?^A!fxSVZYO32WQQ!k zubpT=JyMZ=CRhh{iUeAbFxn)G1=R%6`&GpAzDFoTy@VuMLr7a{J-CC!XMAVuI*bie zD#ixf@1;bL{Q}(%(XNxH`!+`ltfS>T(5HBOxKFr2{7+W67{46y(K6^6sM<0y8w^T zMe0h1+`iq_iQRDxB@AeN^JnOyPbB8|mB+ONMO>(+;afh;%jm8xM+>6QiJpPs1h<6K zEfKyl_IsGNICMF_K?s2!9Va|~I67fWgef9II67%ehNFj!L*eL6#!cbqm@y_+4g&o~ z9R5bp0~jANY>vV+d6&TA(R?k1B^DM(6&A-7ct~8|Q_3?vm9w*E+Zna-8-_-_R75{X zSy7b(nPKHiGD^&hB7ae{W0q-c)_hj>WY>i)Y=tq*^d1__tjl;PZA8ja2Zlxf1|HhC z8R417eH(|w6me|qa3(xB2Q&Tq2FC_vreME~X#{9+Xkc(W0E<96Jb8d%qnNlBvC26b zfOgI~Z<;kJo@qH0v*WqeImdQQ9LDT-v9vqv+bL^eX>CiQMv>(`{evT7E`BZ}R?dM@ zOnauy7KcWM_SqK2oc_%E=TZGTVXfIeILnmf-`LUH(cjtKv29aVP?~d`yoF-Z6$cP}%3%z;cDdfb4ddywA063v4Y^+(j)E7d4SvCd~J2 z7qy5MEiZ)F@bKtOTj6^@bZ~euT{##64!#GlEnT|b>MV3)@@Dm$Vp*X5JQ9c(bN&AV zy}4C@<0S|OL6bxZ^-NvH)&XU^u#$4;XzxAX$T z!qb8*Nu&WA7t+`Qe$85hRz-cfOFfHm(1>$iY4ixBX=qr(5&;#Sp% zWAVY^yq#U!Hg@govT+n?bF+`0+wRd}3@+|n!2eIow#nLd!t-s=K3NU2!)6;DGYyJF z{XGVc2UOgHfkOyH|A_~%_wd2NT<7@}$<5oY?C9ByZrep<>OLo_qh<%j)sBrD;cmZm z`;}cjxuI-)XV;FNj?S*!gZ8scjXG`_a)qhjLIz^*&h0%t{U~myc$| zL)*B0&o&hgh1R`&YZu?qv8k(n_YSYv(ymMz2>W>1JFQa7$Ox3;!wE#by`t);FFbP)|r4O$DR8?=^Y!5-0A2lj|CR&^~6 zT5Cfi$RwKT8nxDDuv4@m4Z2+%)&}~BB0N>(yMebc^>MT-2FIdZL)5;VS-Z5h`UV*6 zKo+63H8(W9cbjxw3t7VJ8|vENb7P+@&pEF!D&mDGKB899qi%m$;Sq6p*eHt_q1(Y0fuXK;f2gc_Ovfq>n*zNJ2h(Z{|7F$(Do zEg%%3=o_=utu6Hh;_GnMj#gnhZ*2jdprH+3FIYFWHqDctrkPD7Tt&tBp*MmmrLN@s zEW$?@dz%`=rE}~RuS~tM0qr&a{M{RcJFHbiLlgQIF3uSQv&U||u@U|6!uUAsZw3ye zu?Z*-tQ*2DMe^G}>*~j`t0mkduvqkgtb;zLwIvtB^F{z5yQQ-+luZq7dSeSv4|-!O z9<~z48!2nh#NM{HI=!(CPD1}_pg<@2Y5%+>)j2+%SIf0*{oP7eg}%n%KCJ4VoZP%% zenDYTaj2w}&1askir6FMlk67`$+_f>tOvQC&-DWCF64SK*F(f;&P(B~#61;UozL|J zTo*tHeG&JRzr2;6si8 zKIFu>8s|9!Tpi?ofqE!UaP@lbzk#d6y!-%HN4RR^9eiV49p~y?cn4MB9V&o#_yG3- z?4V|V9m?)N`WTiEVYw5_U06O0GvG&nZulsck0E_GmX9-@a}VPc_d>o8%O|khkNpn- z>hK`rp5sVAg!IGMJ_0DjC$T(=1ncJ6Qe& z%m2pmK?T=`C@7*qua3+Ch@yPVb;1SSe+5`8359ar+QdBEZV1WGakK zs?YV0L5+Tl~PNfL*!uX0QVrAy0jEKnx4U>{;{S?O^` zCs|R3i?=rEBB|kGUu4Rc*emSI>{XT)UvtFQ9r5dq_=Y2X!x6vfh;KULTaNfGM||56 zzwLl-5?TG*Ai2utGO-KBV zBmR>k{<9*{GB77bHtP*9#PVL=XOUt>WFtZ;)fjZ zE=T-`BYxBo?{>tGJL0{L_z6dRz!4vG#N&?mkRv|gh>tqrFdfHmh_FZ^iAbWOZQe<`c``H+mODkSi`_~oVt8hVGl74-p-$7%GT}et{p%o zz+Dy_4Oo<1z*yjIOE4CX*l>S4jl>=xU9j=q(oRSh-)&;!U)hdv*vA5^utg-V96(%f zWbRnIs}3V!-i>k_1gZt<{6_)Wcnonw3JTgO7+4{Eb+IWbBD4xv#eh~}CEym*0bO~V z#SsUr{BcHbm5Lg`RoJ{I7!i}rU+@H!KDO{KB>k-NE~El%Q4^@i*y1K)CR?(40}v}! zt2=>MS^6Y$<+JK1k*k0$+q40gmF1f{!A(|k0(*+tip4}uw(=zF0A{6jZ3i$bmjIhm z##XKG0A^)%GhtT3ml9^B?lQux)VC96rC|wSRvI^S0JG9`GhtSmPoap#tmP?emax{R zv8iHhpTcG-e4+`rvi8$REMw~cYgx|LKZ8UKyYw^ItYDXY7Mqo<{c|kls%0G=9j;5* zhWQ{-n=ejb}J>v|5GI*J$B;i_kwUO@H+*8L(jjcoH5uxVmjUc#oCUH&q% zw6LBpV$;gDehJDpw(Vm`tzp|gjLllMsmxYo0sv*0ujgChG8+$)7$itYkL zO|FHXx#rrv(Z2rcE(3DN=P#2m8lYTsGQ8J%Dr>!!yR{ev0zn7x`N0$uodfTpGJl{~ z61nj!T?yXj!P)~HfUkOsk0rc)V9^kgzb}A2foP7AQ%Uh<`@oa6CD)b6>C1}-ji8aA z$m=WE?Mf8%6=Ge8)51;EAIL}9NnP+%5wm@)1UrD2@dpUo09#}?9O>N1cR(g93#(w- zgPypnBrGJd0Nh))W+5?uk*2RanJ7=I1%5=J1r`DAkd-;DT^v~qqu!DT@Dr#5fH<-a zhyaf|0EhzyKD1#~5@GMFqsxqC#_~wDuz})R!XF@lIh1SEATt<&clSX(P zW#+0Vvm95tnnI)^YvqypAdI2OE7_6y1jW0{qe>7j9aXad%;>Hl9fRn1RE^^nKObn# zJT4c4g47e!eG(aQxP3j1*7blI&L3EkUA4+1ZX%Bscd5x}iZrt$Zk(e)QT>4eWJN1e zY}WBJeNEGeCe)`;!=A>eFrDjkm22Wm5PX&1q$mJY1alh7Ao{N9`zFpz%T_4 z9J7nhq*8sWr$rMLQxoY_EY1OZkmw(MO^8KJ6)sWS{y@)E7;!c#kF=cy#oSslX2XaL z)9+ED4aYe_!>%njdWKZQ{RSSgJCS$BSd%P*59myHy{_f|5Txh2N27=ZrPLMD&9fFr z61yjtsc0U0x~bu&BkPE%&g$glR83Iq&@On4Q@^jEax7I6?S`U(iOEo1N~-x;s>?{V zfKCJEKkGE@gyvdES6&QVM+8IYtiD`HorXfZgCGhn`;`(u2QWmk8ki#ah}r#q5#4-3 zj)|L3J%Zx-;vPgdOk41n#Z(+d6Ut(A%8VPS5=*ix)N*V+PK~w*Y3lK;@V8{1F00MK_CIt8lg6mwfl@P(lx5) z57gq)3XB3Y_Oxtl3Q#?b#wI0gWutGk9?lii==7Y+V^GRUYD&U8lIHqwjfikvdESAw z)Q)Qii?$6|wC&LyTXwh-+kx`|N+EIux+b>KoyN}CuD)WqvskDo05f)@7hXa`)+!7_ z;%y_Mvxun7co;A+MthCkJ0rVM5Yp88B|k+BbeC(Dj_2y<0Hff+9nh1;mE&iU#-8!B zN#iO+qKRvS-7&7AC|%Kv!9KW{xvYKH7S+;&h=YR&0qKoyNE&Or%vaCr`w}c3IX~^d~6n@pNH*MW8>F zmFUVUjJOZ2l4mINnM~j|1WW9My?=V_d3kFd71kGcn+%j{^BhNg5;eslAbQ6IE(#5JhyX*zOzlE6H4zcX55AFQqDLt$;d}>rXWHpdZQp_c`t{3%sK2wI9u=s)Q3O9&P4BK z2L6AXO1G(A7+(;pjxD+A`dh1{H&+O#%qwj!i=wAv%}aCj=s)}*C7KbDpqM+;{3!z z^fUC0qK!nmg}K|Tm?^N{2t1;K5$~^A5Oy8%#D;-pd<|K4vH^%W2ARPL`mzi7!(cjP z(U@c-2(xYMnK-)(;|Kly0=E;PfG58Xu?8W&1Y1Y0Re%+XH%)-+>mR~(iE?wKXh-X% zAiSk%NB<6+5knGQcwq~ck~T0nG%Ubr7O*XwsbDVX-z>CcNd`NHZJ8tjk&B|}vapJ_ z1OG)qc^1gT*Y8xF;&j+plM5kRE&@WG4P((o$l?pQj1y$-VmuRb)1Rb)SprbWZoZfu z*M?8JCBw&s4Z7J}CUziaSkxu()V-_s>YlD$7p9`;)97DOrs`LaGYl2b}t0(&4RxY-6ugEK+Hkoj=*Gw-vhJi|3K8{Tp(yduKpg_ z%Pes81pw3%-I+ymk_kb)Fb9fbh|gk;(FJ<$LJ*DfaKjFU@S?yEk3D+XilI| zz!)1n0HC-CQ}4iV1&-Cm1O6xRg!-->0ISZ%FXq^#3J3`?P)WS=ESTtQ1a~$8kw*9E zzX(a3`0ou>C5Jd7w$T7?YSkLScjO2c2y5h9?*2);sJK95nMLJ4^!8CK|mGIcdgC;X(-i2{a!ALO42~% zjJpD4(n3L?>;WVxuXKKCW$B90=FsJ#9iglBy(Jz`6riLP<(1|0%lpdv%dabsmB-6{ z9wRgoI?h60V4!ZOIbXoFOSz|=D;?Y;;$C?+a;1xF5zaSp zcQ^NL;l3X3*~%4xTk>DQ5e7@y#g$&J?B>dq+_Q%VuHv3+xN-pkdhaSEahNi@c^Wh2ar-ZKuS75N(w+q8bC@4KuQWgN*;ie6o8a8 zfRsD{DQN&HDF7)c04e1Gq@)0(G!Gyp1pp-l043N6xdL>L0(6f8fRX}$k^+E|@+6iM z02!Uc762s$chLiYk^+E|0)UbVfRgeV=6#WF?N=aw4a+yMyoKeDaNatuHFB+mYg0(f zVEGaAK0|P!KL<+6B8OSTjtV})?I5U`p}CFsAR>qAFIu@WV^c*bG^JGnDpB=WrhJZlo;}N+1EYy0KJSPxIO0o= z__8B@$q~Qoh_5>0YmWH3BYxEpzwU@{IN~=Q@l8kkmLtCHh~IX^?>ORj9r1gP_{Wa; zCyw}iNBmPq{4+;PI^wh={=gAWJK`BfJnM)*bi^Ms5R1rG|CyuxIb-*NqC;b!V9G)e zY!t&Jtv0peCNa(3?&4o$2(ltsIo!EOzk)Q+6Z9OO`+*KX5Q+PN8vuF014!x2{{T{M z7I+XT&T<|^%ENMxBjsgz$C2`}V0Rp#g#2#8Boq`7CZVu^FbPEu!PcI~iXX-%$U^xY z040<>fO2P+2d+lnxMeA4>o&HizsX!u6rYBB^EE4Pt^`Mk<}{g$ZBK?A@iLg$S4pPEuy(1 z3R$r*{SwG=W3F$B@=nG4)Uuw2xfOe5JdAM`7W|NJq5>Nln~IgKn{7n7e_<23*eI|;x-6HEK# z1(zpE`l`jHmnW*Hx|WIQ?0glq-7~9^PLnr#vPyw3w%s&>%=x{w)=}H_8jXQCZGjxJ zI)h9id2g&{Dp}pNLPlw$Jt6FoU8j~>i1va+0`)I)qgubhlqXqc=-Q$%%bbF<|LHy_ znOGGS+!Am$4>KY-iPlHz(X0)?64XQ+!QL+KyC5}?r&+Kp;(>h|)?4f&o`yo$UZTyB zW=!SVszj4j>;m$(2D3z1!CZGfEU7gJqmC)?XltYuCy?iEqjQsTO=L}4DI%|d5uJ|r ziFK`5uZ@Itk$GKY9Wt*cn|k!p$fe>|6npXmIX}VUN_H)+@`z4G$*?2k`?J~Mz;7G9gSY$DyW_&uffwClH zo#Nn#5s7WGv(Wyhkk9IVXz=N1x6yq9y(_X=nK;|GC3<=FmdoL@pPSf1r#o`_0G~!x zh50qkqdi6s5D!~fb!;09&e%Z)=f6p%?<7X?MTlddoBHm;zHjdR#U zw^a&Xe>lk3kP(G6)!^hn-;eGxcEQ-)3w{FJv$%)C(8spPOgJO;u(+EX@b8RV$%F~n zSPjeX$6@iJN%ZTikO)GB%+8cc)u1%jj z6-yuiKPehG#b6Y<9?#hgz%1MVbb{=>L_ktm2*!YD9`t#PIyChi3eP9(9zZ)lM7v{0 z+c->AGt-XiQXEy6}B@EWqlvP7EpNP{VWPA zW#oOz_?hXdR4y*8@3!g0ZC2jDz%Y+qhm2>VA22>(9APt-vFvfQDQRxEG%Wa0OLNrH zD8a*)=7W|-4eqxzcUT%NxZBbkvow0J)6#s%(zt_lmgY`N!$rVFI{RIg#^V^wyux7S z^{5pUsg(DDbE#SX-#nMXfAd@lyOXdfigB@o;f_hmS*hj;0evR%cwLSSBxP%1Y4Q%ckteZkF7QAx<>AfdtZl7&Y9;cWu!_aNNyVLZYJr~@R5LyJO-q+q0a+K?`xN#DpTON6;MUEJbL;L7 zTP=P&vs5g2wWYelRw==Vr8;J-R04L0vOZ|5v|y#BI%=!tu<`MVn_%O6<6P>8=TiT6 zE>)IFb)`~Qr&7=oAxa*>Wp0V<(=$###&cGt`2smV#v7ar*Of;;#s=JwI2I2EUv0YJ zeFFSPdTC%CAK=OH0S~}SsUq;Nh+Bo{S`gi!O~MOI)M6M{ zgi)N{5OH0iBjTRWFERdqoW5YPy zRg_yP>^MeWN45Gfe6Cc`M;aM_?_~!5-)9+rMQSQ?A5MsNoQIEi|5jvrf>AKiaP&!L zJP9KyUUcE;31*yte3BU_vB3ZxrXesKJ;jVuP^g9~R*oTce&SwfNrm%^ZArELe^+O@ z2yX(J`9Wb<#SijAmu1S#iYKrDrdIplHRm7fB|h|n+YkWlqMiU)K&QXd_$BuLg|6GK z`QGzuZhKRMO1oaGYpice&x>nyZS{!y4$kFF&*_5mi%>ynt%aCJ58+c>_0YT*raWY* zP)v6rvKZoviCchi1i$lO)mBNsO5PZdA#%mz^J0PwYs`^hjX5%`^TfIUgo(OK*EGbs z!mq|o`mv|e4_!s4-?`{y%+1{R>Bz*b2Dcv)SwUK~1Zc}Uy*@}#WH%sp2I5N*p~gS$mF%je23cftEu<|J=f zDtq2sdF`F)+~h2J!QA%FcR?rkCg2tw5iYR>c3bqORldOk@xgr&U?0Z)l+lJXSfN9- z10Y|>x`#bPFe2}=JB|pBgJaGk8-&Md4CFOp7@%j^wpr_4V`HOZb$eoCBZ#@Mrt<31 z$;yG)$jInKrS!e6jAb_Vc;&?Cenh)iB}R_)bL8$9g99wr7EM6feD0R{f8Q_@=SP-^ zrz0|iG9e+vaxSkqoBJdquEKeS!V8g*NPD9dpF$Y?G7RYN8Dzn3_KN#Th!M%UA>y1^ zC0pk9pm6`Aqedp#)-MxYeL+#zZ!@Xg9Z}0Z4WY=bgMJ!5d zIM0{NHyIr`%f@Mi9)UFtKGk#4GzBw`6g|ZQ8@Ld1K|hnR)7=jsz~rEp3?_nz!aq4A zwt|EPefeN)Vjv+bXu_!C&p7to&0=KrB*p;Sx;WcJNBcn^b%?gTiJ@^0zijH1v&=O% zL&^EPBZ71U`Q5`HINNdPNkBCq`vEfaG8ZtVp9{ zWfy#z780+4Gaw6EguIc-0~xw;!F}N5>7W2BL=Lr&?8%~`FZvF?^}p40m{D>2O$*YB z`Z+LFT1#7NTN(ic;*r+IIo`Eeb3I}a6a8)EPx!yhbqb&V~JVO?}@ z!B_+?4}3CU-9Q02np!Bd2Ur=9vALNHj?Hb2Ewe(#faU|GT+ki}x)qAt(b59e4Ft+) zg$=T;E(}{D;$eW917f{DV=VtqTMpflM=I0yPb_o;qho_UXV{eV40ACv$k6W>qD;6j zHN(Y?lC6y(pJ@(}f zF6GK)ToL9$MVJTGjojPCl}%jf=E`QSY~jk~Tz`$3w{IT0QZmb08EEt zToHCdMc56M_rXx;Jp!YL0&}3YoNF*`!qBI{z^7fyHQ4er*zvS|TpQ-zPc!dcgusR2 zPX7!RunXv57svs-fcH7F!{O(bf`zv3ayX1)HHYAVmX=kN1uIu030Tp=h$n9y$vk0_ zO0C@1QOvW2v|{Bh_b|Ae;xx1sW#ZJ~o0OYxIee>FzhA6BAlA1tu{kO?AC#Lr$nlo5=By)IBUWkI2m@ z<>pbjc}#Aeked^7b5d?j$<0%8^R(Q2iapQNt;g95tZeOzkQK-UU$BIrE#%rlz!rSA z;IRd_Eoio&eu3?v@?K&uOW}*`OJEY9hJ8hz{Z+=+#b8&vQkmD%PGQ`gV`(>KS=ya zUJ$=X0xbU;vYQoLLw2*mYvIeQ6ct}f&b*=47@5uLJIHEQ-a$sQy~1V|6*jXzVKVC% zCbR2=$*e<|%;3CR&MG&MBd@fWp~9|SVKH0ZuI!cuvvv56H)za!^%WGDwti5E_ z>iO{S^#{QIOib~Wf+ zcXvgx^S$qT@BIZkbMBNgXXehGa^}oArk8cYlU{hz2~YaqNf&%>(98>(`9X6;&@2d= zBa8HvnerkmiOkax8q~gH1Fz5q9;Z(@Ut6o1V_Vg=8tu_r$#f&1wrK;QVx;4vHr{rb z6X#DzQbhZiGz2@>elUhDlkhQ%%Ph! z00tudfaY@HqO2E1d)+u%Sw~kC(x9KYSeF-V`!jIEAhSOJRw6i1;Vh+ufufe8mTQ)GY$l-dC$GC&E8wF9Qg05!=v z)tGgv;jY2$hYxGt`S;;}{rhn1Vfv5f1gYH2gS%wQ4(^63CQFQcR3hSE+L>Xgo`Rr(H<)gSKwN;54U6u zmn+y`$(pHgJT(}u68)8}KE|Ij&dFYVWcHMpPeFShor+w_CU=FyS1P+8x;vz;U zsP3oWwFb_netR-h&~Fc)jB1Bx;Y?v4h1GFbofG!_F~S}^M&2D1Hak2ULqq-K)_O0R za}EyR&PS7fn~okD_BG-RF?Ooc!?+|Kb4 z&N-KD$Ob!jkmwo0>7^gXXwXrA>L4Y--MkW+Z3u>&K{RL>!oWUIEH4E%X}Mdu&V?!t zLH5`+F#f|DfI_-ktl>;#cs<4UXk8sv=B*(M9HQIdk?5>Rt zyR)5CM}oO0?-4n%$7gkBI!TV?b7mzXj$(2qqmIep%uD9{GWO<}MA8IvG9}BGa21bW zeU3N^*KzosBacdWT&CyINga;oe~YC#oF`fOg*-`z*vZ2=k|_+kg-YW+g4)W-j-Tq&o6I4r5k8KH_}v81Y??lQ@e zss>96I&(uaHSmQhQyEUsEeajFFwRMAt0Or++RUmlVnZ#i_1@&S0n{E%P%MXM90XK}mjT{)yn`tjUM6Bb#-j zfORv5xHmA!sZ21miOhSMWGF1+t%G|5)(wkzLl32UIregvEd0Cp5=xI;@gl1KhtoZWq4N`Mx$Y@HH>wJ@2iHp z2{W@~YOt?@Z=u6+B>)`{_{mWFfsz)?Q5sGS&t-Q$y9?M|$nGL`m$18(-DT{qV0R_E zr?Y!Tvx?K0krUQ8D=pF7R&ul14X~TWu8&<0yKZ(hcGcE4qT*|e>1;7=y4T}Uyklrr zua2Qzy*7n*_4*Xr)w@d~5QjCUMzE>(jHFGy_XOJ1`^*SrVg1N6OVF0Pn77=cW+{_t z%bg~s%@MA&v=n*&^#5SP9W*n8$t^b-UiW3$z}&#n#NR^ZEGmcw6k`uGNk8 zAq=Qs*Xp4oduP{58fGFs(mInOzn4b4Rv#&^q6qAI$W5bND{HhOxg`$Dm9je2*x3`*NCZ@hO{a}GGr4Pp zUAcWE)fLNe*7UT5r+{2%O;5X43doZIa@R@$`7+=?-nBZUvX~?`rrfgFn2u2FCZKvy zWPu$CpCSvejDu1wXLsaH-R+HZc6;<@GwnCKk60Jfs4*Y*cfQ0^0iFsHPiauXb0@t} zCvm0>$V72I+JQk+QA@jsjSA*?TrNNZ;ZeN*@>8EWjdpIMnc*^~aqRrDuKuxLxQy!$ z+6=Ra>1KW`yO8q+0@l1ug#~yijVQT=gv3!`yV&@ zf35cSe{Sr);Zm{t#fRPfz5(3=-^sn>M*92U6yN^ayHD~RI^`R=51#t=*YDT^ZQl5M z;S?GF$|+B1fg=maS_K{IU327dGMk^zP7RTF(_L#CYS+}ac9Q9vt~E_Pkquq2uZ|=0 zHN8E3ooBC!_cX7OaqTe9GcDS=zO6I5rZ*bzU9+sWqZ@~yn~)Z%zVekL%g209rbl4h zef*J8rAG#M>Nz3o&veo$d;7AkE*ztGM%!~@eeLZ~S{{vdCc;J?nY%QpA)8OTk9nIp zySu0BYfwKROR|x-bw=9jn#mHu%AR%(gWLdBe_nT8cQ*&5M_O=vTi4yjPr0#<_`+y! zYZnZ2Z0I^W%7Ol7oYVJ2Wb$Zdb5{#a==muClO`~}z`Ge44Jy|6pgcPMr{0coC-SWlhEzu3L*9V&V+S*&@5Gc=(68BTZMzLwc>VsE5 zaPZn^4_DIfG-eDKom zpx*C2iS+lq`}B_Yx88f;l6z$8l0#2lCef81+_CwdF-nwWfAG`|?>~6QF;WBzRxjymYHy3TzW4O@_jaClVB6+{*F8*yES5RvPA{*n zlyPYJSX+Cvv>i&t+e^=BYw2w*ZH>0AZ|yB@YU}O6UHN2rw>|p7?yFK!26I;*y!rkE zH$VKrmdnv}AKY{y71HqTo?8w)aKnLTt~qe=8C?-K2 zr<-!{=F1OUvg^Q&_q_Y~lk=7wy#5EWDC7iv2$OBbX-j-w_U+!V{fPrlZ$mM2+IsLk zOzFXQTw7YoWt7IbPm~0g_CN+Z4r^6>e*9mogql z39NXQw|zZYA8Bv)v~|MvPG@t}OFBLHbHkD^Vam%=VK1a;Ftu#X>wwd(T+UN=%bn#f>xl7 zyD!=VavE*TM0kU=WF+h5MMdQZX#iRRn=Mpz&}YcdS}$rSL7if24LlQ&hBVOd!}gGB zsTuG*?pP$q%uIzJ$yi7b3yPzcTp-tpMznq0sbD+>cl46sMecq7RRDLCVP| z8}zE^O) zHK;b2lx8neux!}6EdYdbpgp7;GNCD)n96n<$W4g?tatb4sc;MS2!=tuLk+or5(B%WiJshtpfJ z&c@;%Uv(A-1Sup4Q7(M^2_aIWxoJ)pu(;=R1>vy{4c2i&SkqR+sy7yvEt{rKpPn=T zIObTLm(p|Z88&us!S!8TXSYR%4K{PoF?(UginbKPB9=#BHA9tSvP|FCJKfqi7^SHo*Q8ntPe z;BMj7p8mgRVV8+-w~n?o_NR-+;Yo?ddA$E;U^4vei_W;Rhkn{ zPx{Juw1P<>UyJc{i zorqVcI=j9k$$q>~ht4zyCfew8AgU;VLkkpKbwSOi7Y75{-*)>9WRsm+%}9NAJvFvg>-P7PI?hJh^6YzDN;Y=ogU z0~WG2mRE)hD85PhH8lo=RyRV*(U^gF$FG`EZNS>zMo2z-NMX%JXr1wtmDhweB9#XQ zz5rC$crD(rv?tDSa!?Vt(;D#Lk=EEq%{=sp~D21M5qHFd;O5;;}~j2AMpX$j3(I(wC<4*#yjGT z#B*eS9F~eY`NB4sX6

Wun35@3A9u++&k*$E|!L5V(Ii^ZS>-O|G{pDc%UL|fV- zdM65xXH&K8g6(YiNjyp9aq2zEM_&@mPsNZ7KK5wL_2n^Q*d!(SMUrZGFv_fXoDVZ%&y?qAyPDQXqa9?Wa#YjiVierLi0{f?HI@E{>97L_-!j^A^tYI7rc$tg62caI)!_?MgM{O= zE>fyeiiF3L03gM3YIRMyI-{~mt*Mxy!Z@u8Bb}H)X3WsYej@C$gT)qUM=!#2b%h%Z z4i1=BR#8=r3jevb&`~x)VYg?3mk?$~vb@BACU>D#$=8+-i0SQ^iaeT7_Fp7e+!M zgjBLBx=Omj0Tpy*WwM;a7K;L5j}>NME7+~ZWfDjf)TDu&#D0Z>DiJXg^paZ*Rd!eq z06Wa;qNJJSbcqFaV}W93MO7u0f$;?!7dbj05T-lJVP%$F$SIIZwqVO@aMz_A`3as0 z-FW4gnagknk+NQ=Ft!+58A2*HU<`Kh zseo@ig_W%xu`Ray4YLl6wn!rE@G6OlT|z!)vA9>JX% zssfiSWvrmEnhJ0jWSBib=oK~8<0@*mA-sn0k-k1P!w`wvH4_a{YzXJ_48$)K7QcXcJ&0f6{)}=WNnj{-hP*bT zH5el0s*H~NGm8v;uA$E}bmU!Z=qDNaC`0GVGx{<^UvB6t$TF$E5~3J}(n1$wl=XBm z#=ymxvkj%)P&y3-S7KP3o`zd7eTKflP*{~7ERZT+H;k=<_2U_~ZamV9XFxBW0=;+! z>BXa~GSG*oz+flqy(67>r0)*8?pV(quF62iouS~W43v`@3XE;SxSoN#6b9@vO1*c_ z;Tp}kh7S9jI_z`mu+OQ(KBxXLez4D}Lmi&}B7QF+`Ooni#BT`2zYhNm{PyGbJD__T zzu)5ro1Myg@ZX33A$}hr?D*!k2i!Vk7S^-J)(6zOil?-u-a;rCqy8Vg~s&QPAf@5lJPiQi`smHjjPzvA}~ z!2g7Q0j@$_h~LHdU4!3s=p5VO-vECH{G0K+6~B?t6$nolX?McA2fy#&cQ1bT;rAd? zKcsrAQRw$o?@90;L+IlOeHQ-n`28HeLHvG&-|z7IJ$|3y_b2>5$8U=U<%alOh2M7k zZo}_({O-i>Zu~AqntS2jkKbj0FUJopqCBE`g)#+KG9bmG8U1d>@Ob?FUSAsgpw;JOqk#)E?%|y)VcGjPYM7mE}mLEVZu!ErWO}>^-q{sebSO;r_Me7 zj5E(#zIw$PVVsD3lza0j!sGeo=1~SzOY-0KqW|1)eY;QNGYb2o_=Jo8Na;WKa)pTa7od+BxlZdDi$h##o^-&0-26A*_so+Rffa-JsV8FKcJ^Fuff zi|176Tzpu(sP?~rtPhKq2%y)~_#;*PSbbS#?-l9&M0&4E?=|WDRC+&?-hlLmq_Ftx=e(C)}dT&bam(u&S^nN3~-%9T->Afwzcck~O^bSbxp!D98-uu$~Kzbia z?<47bEWJNS?~wE?>3u4_KT7X2>HS%Hf05o_rS~`K{at$hklsI~_b=)FTY873*ROGV zo+rJ{(mP*z7fA0y>1~zXMbf)idY4G=Qt4eLy(^@*O?p>K?`r8?BfV>-cb)XEm);H1 zyHR>Oq<53_ZkFCH(z{i9JEgZvdb_1}hxG1}-aXR$j`Z%6-UHHmP0{q zgE|i5yYN)uNw0%^ofjq`-NNUsgG`-&&Ma5GOQfAP%QfF6(igCoF>e-Z!HLZ9ELVd| z1jy#2Ph>6Pl-Y~f%UQx+E*XOKi@c??TvaZS|9w2Ai4n^R`m=v?}Q%q%*T;0TTI))8K$4ZUg<{mLYvqt`#O8&-<;+8hD%g@i@nMxkS|YE zU4%A9i&S4C{TY`_zvc?*&)g>c+AF31l_!xcUz`ZlnUw9MYo&kkQwSa*PT7t}H2SRT zrC$fDnDjjRX+#%@`WxlroE_3{AOoj(IpLe7f9fsLpL;8Oyq|fuNq_z`$Uah>wiA!& z01I}(za4s97CwvcLa}HMyisED58;g#OP+%_Ml5|E-dM5h1$g7c@)zL+#fq2UjTb9_ z1aE>^^<#Jw#pw@1A!(5~<3T7Sog~hD2wt%`3tcNLian}uDPg79i;IS0GJ5D7+(%r! zW(s0e4VLf@K~S@Bt(esBYZZrZKYG+!WzvA&sz4x2D2k?!{8YDa`Qi}N$P`(VaD&k* z9vEy@t;tBBKzXx14hc@61YXw(hlEd@V3`Ah5#5?LFcfiHWH(YZvNio5ZluE{#X};~ z1t%cjWGQg6RX8~soLn7Fo*PcS0cV59vQ%n2fI+5@Rz@ zJS4^iPO)YT#PohobGlUnC_fu{7ZS0F0On$?uvloj@g z6005;8zX*e4tZ%-LsR+?2hVNt;6@4}<`IxqG!~+M)_mmhaP(;dW|27w>i#mD0^xKj zHN#qf*bI(c3`KMv&b@>oJNr^3aC3rX1LkBC8VFOfFHdA&1*KJP&VD*WcJ?!nz~Jm> z4wxn8RK%r}epVuT<6x7)xz`e4XI|HoKFE2$I$)k)nuDoXBZ;gnO}I2iwT=>CXN8(S zD9)p{T0dY;Gp9Fcl(|URU8L`H>fV|_arR!AmLe4G1f&(6NDbG~l)jfSbPkxMW@v9} z>0ODeJuqX&S>puQrS~FQ=e&IbW|>(IO^zwG-jK+=sR`GtIQQ2Xva^2!3A~*BThIyU z#f70*#!gW8ZGK8CQi!~M(d3I|P7|d9!mlM3bfw{uF22IgD$1oBuNIV^A&~C9T2P6W z-sEGX7m6k*ALL@UN-(Wx3ZcA+()c;eC88;-3DT-4^->9@Apz8krA_)oN8mO<-1)Ag zJZYTg8h~`+Fr4Qq(xG`2$*vt1G(rH!b;%&8V$7m+S=RPpfdyftEU<1C_yjdY)%A)) z;uP1j_{wLAJpy0j9@yR*Nw&qd_IvhOKg2z(LeipUJqKviQJI$Z-*KcO*wmXs#f49@K*$64ffXCuz3| zYbUZbdggmv{hkiDwHr_EZv0eksN0i|)L3`oahlTGVOVzoDpmSAJl5TSCT$jd9bW4m z0`JoLI(*iB00*r51-(6B11asg^#B6f4g5UTg9Oq~u^tjd8AVEwVm&OTf$(;(^@x~8 z8GO(UI3@tH{MPpB~&vXDUM?(>u##U1Dw zekhKNwVq{&H0Z|0&CCYALqK##$&Q|O>`*}P&d^Bd)!*FYqCo( zc1>})J+2a$G1YZ~%Tw$!UEX5XG?%Z~HQnX+xJq4V9+z9s*Ytm@!2T1wz(YXv6TFCt zg4}9oUdKyO$z7t6yG$qdR5!VE4RTNSkb8!g+%tXTp5-TZc^bK^)5%?tLGGGNavK9f zaMx!23huh>x8Z&@=RLTQ+)sry45E9#0e=XudO!RaRDNQlq;K&T{74Oo z;e=cAO%CC9{FP`7%@4WyL(SRNuSHXKlQ&Xk{f2;yNV)Y}0A?&wVZ8;QI8tf7ok*Tz zy(5}(IQj1hNJH{>0dVpI04Vu;qOodzm8-w1IoEn$H06@fR0HbxG}g-3WNe94TOUw( zATq=Hkbrc2ejgE_;gkEA03T}h2Lk-4+9v?aj;5S&4JvjB)o9A)kNwj`L3!36MN?jr z2bljv0Ac=&fJ|WiGXaG8F9hVq{P<<RP#z1NLP_(%`)r`el74}%_2s- zyxFR5yhP*Bzj<6Cyqm|NciJ$a{XIahY>f;-P@s9{TOm!6SqUP z&a)xS+6-vYwZ6U%-MRqafVD-TzBB1z@1)OE>p}#z(@(XwDnaub)SacFOJM@AV`=Tp{ZQ4xH;Ura(yvzC(q+LzTWme4x|l z1D#GE=*cc)w#%pbH2rc7sCL4F;vsRaYo~HZd`pn~ZH3%^p^|r=(8${?bn?y@Zt^Y= z#+~rC2+wnhb*JL=Pmgt%(&RBO6zDg111c&k*_0o0o~msn&N1 zEUE&sM+rh=nqTJ-Iv1h)9)-%m4J#Ky`F%neW|WTs<=pvmU9I!1=ak0z^XI$z=Qn$; z=anX}c@<%Q0Z`EtAb$}6auM!ug4CA~XrtzF)A|wWD`)zbftm2No@TuQ3QjYxA#^_> zu;@hKdzB!XwefRm{u+hZ_;e=qr$p*$OzO`agbS? zUli*t=iTqOey8j;VK$)JdRy7c&Gn8lC>LArf^2^8i>w16VbP?QX?<{zK&-R&Cg^*Q zLK1KNeWGm<)ApfLO0#Btq%>*ftyIp(fQrTf(;omJDOGk6A>)zg6TAkwTC|+v7h9jA zgvCWI$n!^nXoZNM)AoO&utfQvQTdCx{J$j1*R8)QO}e?0%KsZ6ZuY+eKvF7yaiaWx zpnSPr{M;#jiS;j(uw>FdsQUjVkXDNLIRqT0uml19DhOD@1e~Yh<)o?D;B3OXxd8eV z=!?BT#mpwQVh~a7`DdSXkvdeAf!XI`b!eK7+2j&+h$oXvRoKw2v@TZ%_+xEdYF(iY z#Fm;GDzc5BN$(q=>PjkeDVKQ_$9D|z2!1sYkgh7aKAA?@d#Q`ur;;9X^Db(O`x(X% zriPc}^Ll`iXHfF-l>BFu{O2l}&@5s**Rkr@N=`UH2{RK3-=%~H)W%h@RY(Xok?;%X#{UFIt*&HThMCv3qjrkLLm>TjL3UP zXyiRCbn+e%?psuAr_)}iTf5L6r<;#bZK2FLb~<;X+X<4N6oz4iKdL)u5hoDN&3G3u zaOYoU-HpU{d)-4I50B959b1OP)O$`(^n`mURN|eLPFPXr2W`B|81KWt%Y(}q)+0!K zhWULS2?^wn;!%Qtgzzp);QbzjCg@d}-tXIZ&tSZdIe5>s9!KIc&BqAu69h8eCkX-) z!h1#n?^6_-z$*vBr)|7vGTvt$yk}WZzY{yle1h=)kieoadfsyc0SVzfGlBPc3QgdZ z1L6xd-m@6*OTbIZqt?~dkC1q^`4qjO9}~!L=w*U{gz%n4c(H@_Td&wSRx^%QfrH=C z3hOl_UtvB?IDSeXzoVZKWWS9J`)v$T7*G@5)d_lsY`iNN@9TK7XB2D{-k>;-`3%*2 zAHz)Geg=VzFs(>n`UM9ksxHUtHz|;o;d1x?D?HkBFLoclrbw^(EYbcOhIttNErXON z=4aPwgSRMxkjdHVZKn}dS?{23tIR!w==TJ2BfLwH-3Xa>BfLjpgn>~fXR-GgHR8A# zKE#vL3_j~4iu0L2q-OY-;i4sY|9@Z*S*RISC7R(A4o)hIw-G={uLK~G?&Y!K zDm))CXRly@F(2=DURPCj|1_ zc$FX|CXAg4jIU8>YRCL3VeDdzKX)*8TLVbeZ5||yg9P%JGepn;j6?k|7p-dLQy1ay zO5lH;LM485Zfb-#sDN%RV82s9kM#?r>oGr|0^TH$8{wA(B?|bJeCnnGx)TNbnnLXY z0|6V@z5dNM7{&xvmoUt7QHs0R! zTJIrkulX^N`#yn_w$q360f9(O7~%;G9}+l^b?{F~% z6FqWVyHvN|@j2FIDEl1lnwJwqNtlY{Fm?rnUO@1a^#p~dQvW0)Lm=aN z8o0hO{~NB>Z(7eF~ zF2|u+IrakVM@|vnvVM%{Z^3Kh;Jr}c!d+3PeuHMKrZ4{yNGXb5wB6WT|};3 z!~jw_t2e(jNa_8PuB2&nh+*>r!nl`V;A1_e@+OJ?>y3^{&Y zczv4|Uj2erUVXIkIuG_Fs$83e%U$I7wJA^(BXhEqv|6a1NlXQ-^IrqO-qxTm6ZZ^ zg)n;mArW?KBJ7hy*lme0%OMX8lTWb@qCT zWIKG0N2vl3i_w*NuOB{a^}Fpi(rj4gLFUf=$prSFT^7#Z%l`jB5WKxykI#u4{ zcGg~7-S*qMTv!*m2V$3tqSJxkVg{3|FRt<>ZdCbl`MxfNsURY}gb2TkQ-kov30O&s zov=`6KBnw+=)V-o3nl$n+2#EVu!|4O%&Av!95J(gw#(* zruNwS9jfLwSn+`df45RX8wYz{0JbPioQz#Rryf> zL?%@ku0#CyP-S`k_5-KNR|@Mflz*kH^5YCPrJ>4ciCj-mm9Lal{-t96N-=+}n7>iX z-zw%?iupUmd|NT!QOw^f=DUh{Krs(0=6j0yzG8l$SWntjPPd*)t@6`c4pkY_11VL0 zDp}=pyUKgq&XzU9`k}ii!~Bq@i01(DKH+%)L><)>E^4aN6!-#yg=L^H20j2HbmjwjXrc!|%zqG%)f*c%Kw<2Ek@ z_EB?RCC7nZxErq)G5o(q(6^#lZ*nW@azy(jXQo!f_B^E(-^8{WBLbAOVpNsi%5M^_ z=(c`~MmGOOt@suoCgXPis8DJ}xM)Qtfd_yxMSP>}#D;uNT&JDC&B_-`sWvlQTBI zsq3lS>t(q&8qPPXTRULX)ZD6~fo}p-MC{p{0Z>{h7p_z8EeK9+-&;Y4+{Eon)WdD< zLdP;MqI&EG#KYe00FsO(?rnDq*$~Gq0vn-BV*r$vUIAR^6}$`*WS9PlL&6SWy^6wi$PV`!gP4L- z&l!Kpf!MpqvG!*~#tuow0MIbw=q6zeBH2xH%{RoLc|CQDy$n+hA_gvq;dl5tN5~!_ zIfic#=9?tu{SM}vh4l-hyjfy?lR@)F!u(5yDFs0es93e3`NjA){If$C}H&n>Y zvXHl<(Y(?~vjaS;*TAnm17)?=Va`s1UeLA;0GcyAa7tdY6N!kONf6EwYgJPzW<> zAVBm!Qrs%cTL|+94D%QFA%m2I3W4hs@)1Yag-EvD$AtP;iTV=<^=-mBgrv6#^EN_l zG0Y$1rwmdKLJb$FdH?W7j<8WnUiqI0^=%UMXY_d#s+y*6AyJ3F@#nEib^Q(9eYYye zgIN6E4C`|PQjkYHvwBbgOULG z{x78P)PRdTgHp8dVh^YpMTeRXD z027p1JIx2woeUQ24BtaYwsQu%1llwaaOl zVrrS&kt5kM+p}TS)w&}YMo90(qfSWg0kBI9N{iK`zI`xuuV}na#O@O?Sq+K1Mwkid z3qaXBMfBlyf9L2N_Ycnj?dEol`;nu-x(_)LRb*=JPlWJ$dVn73Jv~HK9HT%MJqI;> z1U0n2>lv6Fc~Dr7Qq)){>U$LReLLz06gAF?dW@nTx1*k*s31j=DDaaM^%M%bNfhG? zej3otg1+TvDA{-i!?ToZk7uBm%KaflO>m-~qp0Tz`9s2bfubflQ7=-|OOy*yKcc82 zfpW7@?#mSQ3YQyO2yv%LB)ohcy~I}$b4cK}$g3Xf*PLLG#xFo&0)ERB4sHkd7QkBs zgYQsutTh{ED6Dq@pT)xwfHMd<2;fWrWFXf1kfN!sA5k;`A5$~|eBFlHVp zCbJ2$azlg3u}UwjNV!}J{`u1a{Tj-17xm|e{DP_d#T4#2L73CfRUW!=qm85sHz-VKqLA)GxD6dC zRSn0<_}Xw=N7I*ms#=*eaA>Z~LqZpsR)8))Wa09Iu(Ah+S`{m2z*KM(B5$BkD{6r< z=~ms!$B@qkov|wLguOT!8-b9p52E9wj2Hufrr#`px{!>{1h?!DZfVBBO^&P38Z{6T zvGiaL46frAgcyxm5Xu0+|qZ_e#M~zT3kAv1PwC7T_I4 zu3Tsvw#Mx>3wLhWZ;rAi?A>XO#yqR;L>bn^ftV7+Dh~FXaE$}lvdv&)K%&iow0QvR zsXV^bq3R&B4m6ff^@~CJRI=?#V63%t-@Vti6;}MnI!QyfmRu&l%v}5_zd+ zHWKl}?QFoB@ba_UIs=Jl*w~UsCXw*uMB;^Ao9NbW^PBy6o>O zX||O|1AE-j*R}R`v_m-o^dz*=cp8BA6A5BGU9qVEW^(cb0VAK%VAwqY8gX` zel|(Eg4K8?4MI#nZpQ-laNFGpWZreqUDHb~3H$1Jf9-Q=boId=EwpLSqoaxsA<$yh z6VF1!(*#xrt-?~}mIq2cFc>%Yj%@Dg=!SBgPAF82cSFBfSI_cjZ!d=F9H@MfI#y?s zf>gF{nw1D^=!CL^mgvTG^r4n$XFPg(Ya6snQ47=i_N|LUEm_Ol6$=;eo3vG=N`oEz zdRXbnFm-|%&}FdzIzuqng0gsu?#@g?nHm*=^1RL#3iUWmkxoyEm!Z#Fv}EN9W6qh2 z>K4wcXVVBiNjoC|2cySEvMhUY2Oix&i?S1{&0Y;qlLdp0u; zJu^_I)g4*Sv$f7y-SudRNut!QAoq+CNb zGJS-ZQf>LXvl>zirm&^n6cZ=I^<59Q`B8vYx2O8WO{Cxs{eYF+rr2FRrV5%=%M`~j z$B_jEFl47XNpy0UAhlj)WfiFkSHFA_0J}e=x5@6xwNk%o(eS`w6(M~RtfFV!@Iyk817zhqKR)mJR;ccVdSLD@+_MwPyE9?Bd~NZ?eLZZC*o_q+`o#YUgUK0O*1S&JCNsXs zUaUxI%=B^5+`O5bXmMY!o$7dNE{zoDZTi1zi6+tB=)j}~N2{u;se*muiW&cb=^jT{ zmsUNasycs|)#=0lqrsAKxmFc|!Q`s)$_lNjqI`x{RT(Gy-6_1KCt)>EoY-*~?Lxw#sLE%mnwC>C#H9IwIZcEYc2z zTxFFrL!{XbntEV#8P=HL!jLkl%qat01I;f`iZcVsV#;PhTMksuRn91fTLon?UN;crVQz;pl?SnuZF5WxHCe4 zNdpmFn4Q+kYoKZ-VU8PGZ$i)rR1P!Mqz9*pl--12TsoZ-;lpjUz@*f#5&Jc3q|3W!bo9LVRK8+l{*1P=Wf#fHoN_GO0+M{+jTR^LO8CJpA1MONg#ES933+e2=G(<`GjvW zIZTG1fJ$SOoDeM!F!V6cO;^FlOgvd!TV+81IdsFli)*1@Zlu*ND};R5_~iXelH1r zKMDRI2|kttA5Vf$B*7<>;8RKP=_L3}5_~oZ?n#0_OoGoP!RM3U3rXnQUloG_3ZhO-E0n&z7Xc~)UKjqdH|$7tub4}T_KE!x_yt6$IYcV|v3u)p zl4yS`MBh9pjW}DW0PEy*Fhz1_xzb^NU8|qvnj>|Xr2BlrJ${x8%o78$W2)lu!ko7z zd}%Nhrufs+$W~Z-`)t?>%V@8MDW=T8Z0I!!1nNm=Neg%jOX^_&Jg;Lm z41ni%)Vngph|bwiG*VD78!AOcj-2hvRtiUrtcL;c(S@@iO*>|6VLg<8j2kuE1(sBB zR6TTej2|-_Dmo^NoefPJ6UWVlUX7x0^-!xZDL5NiHHw4vP^vL`!ffc&m{K$wDm6+b zkw%TF#dWUn;)E%rPs7ZpgF20AjdkEFPhVZd z1*%gNQ6~Dpdh&kd#FI`wWmetn`f^m%>noDB#~st-LDLg7y+P9#H2p;`Gi~xpPlPVz z3xTvL2F8W(`Q0!qHP3}em7csXf~v;Um7Yy*6{Om8@Wa zm6xO-fCN5DK$gux(qJTjBz_7RjSv_T3+BMmDglGxY)}9ry*W%sno|nYApl7_WywG( zxq~n~?vRp^T2>yCWezKg*yn-NBqNX{D>a}10okbmBM|_3?i7MY0tw76C+k#*BzeP< z6e3A}YLZb%GJ=v!KqYZA+A)heXr|-TN5zrJmN#T`j6&9e)U0EWb!2M5SOgTN28=@h z?Bb@7I1WfgrzQy^$rwsf3(N3kI^ADitC{IAi@nxe-0EICez0}CEI;@KePL`W$_YR@ zE;XPC0YO#e{=vA0Y_nwJsbIhTzQe@==_XK6niDjIU+_c~Z3_A*FWJ3w+NiN4RAcIkKL2PCo0(?Bt1ENGH5s@dUBYTej?5 zTi@8wSUUu2!wsUfo>JO|tiuEsxx!(k1m~06sr_*tSu#k$^3j9G##0Bwr-}`PwsG!Y zcrLf+R7LaoufWy)#(A-MVO-%xWvT28I>tP$5hk*?_AemY?t`&~_EZ0Yebh=r;YAp8 zY7>-tC8fSCAka>^e{V1c3olB&mr@6C{590SU_S>$7B?;lF9BKZm^+(B4(dJQW8))1 zG{*_l9BV<G*5JBh=As=Nd zf(EDVi!2}VD3KK?#e|M7e=7#XhLteRx2m{x)mpXj^!cZ|TGhrg2toJ^RFQ`$M#mkg z@k|^V7P*SUXXzyz${nzAb!_#pP#Ecpt!ZqGH9DavZ<>PgFbFrUjjg4uk#z{1PCao1 z`YVNyDf27?FAJmRSH%W=aHaHN_R*&i8OWt(O5|;9iZ%5&@8howZyaM$$?Euew)Uawdvd3u zMs#k#CJtckPO1)19EsLu9vQLvf9z272+ZKVj5{Kz^oVCZ7_^mSC(L$@Tis==P0^$K!mgedb&3hKwodsvg2zZBn=KXr>#t?}F#dZ6Xq0-mnG1rKPUaVj%F>6?o;b2jwO2wm zmDuzLtHzfHB+?+oS;ruOEy>jexoV_bKOXGpi}waQyLy8iT|LoYFPN;sve5Wobo%;W z$0k{6(!d{t3jQE;@B^4xGTpbRVZ}P)?XO&Z6!-Qx=2i^*%^YRP(>~zJKcefF$~JII z?|6Jv*5_R@GxWuW(q+{%)v}6^54<5o_l;0Ie%4Byn?C`%VM!~U8y>he&_`@|yKn6_{(QH$a5Vn%$~OllPbnEYn$oD1qN;j^sH%ZKQv`i< zP%XyQVY7tnI9?9YF*M;zIff?DmJA$}VkvL==BrggQ$QJg-*39Wc0|~kc#8#LQ!c|Hu`BspYIpIVO6sg-3%N` z=OF(KDw*~QL!{X$@HfR}&9oDUT#})wO(c-kZK2UDpq2!Fj)W)L2^Fr=Nk=8*43H`d z2egvFt;leSok7j50Onc8&A=IzSQh(G*cA@AO9G8RFxy6;F@pO48iLV4&|o9bbH4%# z!v8S>rUc4S!U3ZsuvfUOxi%tqZaomq`!6G6gt)F54tPoexYT8xW+OCmPXodQ|Jw)| zF>YXn1KyHA2@o%`5qolBL)cpU-$u*@;4Wo2;42Bd43tal0=&7a@wDuJtN<WXNm%+M1?a}gELys zwnjwx0&bAxNLIP}^8y&%qPT9`pVyxUJ$)kyj%5`2GH6IBbjUq{$sKj7H5Qi^bLQu` zh$1Tp%~=W&MWYHb8f*zXf0hcn+$9KGhcFQ?zz7tfSo{?HNIQfU9;udCO{cm%hD#K> zM!A&Ht}!lkv}>$O8|}(;>7&7Gb?1TUWaL1y$CC?IQ$o(fXdK_@i}^z2kemsPk+|!K z_2P?I<DeFi#&#Bn4J z*=n>J*s;)NR?&H0sHSp;2gc`Eq7TP=Aso43wb+T{JsikY{wIQNI3k2S1y}_st9D~q zq2?Flk0ePq6$eB(^U$%9Fchz$_zfkU50SDAxs0F$dG;ZaR%j^2hBC#Qi#5koNSLYk z=ka<~kK)50H0hD1!JBi)N?3SEBxlD$q34f*+ zyjLX#dih%Ry0k1VIJem-s_1EJDqfia29uMT;w6Ny!;ZYCk9&pw)ci8O#^Gr9w zh6fxnH-zE*vi@X|4KNx0wM(l3@l~ zNy=S7$lZ;#%D~{z-g%@uO3|U|I)FW%&&)OR%zSf1*hlP0RYQ0#!g;|!qhn)6EMvZ3 zMI6bDX(JJr-Ix>0Vg4dJwEb(P_0OK%UnIX|M>45&*!i{dDVwfTkq+CssuZA_-PHwa zl$|7~G+Y`b>nL%Ik;j#Qa-`3s+14-orndb*`lg%)S~IMHoR11e+|?S4x6I77#YJRI z8ai2KL@S?JS*=yzoIR}*hrKHzO)O%iRaBRSvS}=1Aqj-F!R{+;HCI;R6RoV8S?z(m z2FLNYnmEeVs%k^ontE(%@JS+zTSvYM5iL16m6VH$ps2&LuPA!~x zVWbnXB0Y&wMkCWTqfzW4mVjyQiyxT+Lj+9dP=9cmBz;(u!1i33J739%>S>QRzeEvw zu7MLw1!f`e<+}~lV`va?g-|ObTJuSqm9`EEiB?CPRiEmeDlk1_rok78(F1@=032TH zCmwVPg@aFR*iYybqvJ~!7%zCr60U$>Ny|vjWLLu34Ck<$$Lp_%&-X$ooH|RV^^~`qZLi5hXagOe-#}MQ8)!{hD z@YjLW?@1#S^j@g1_X{5mP|`3g)!{TJEl`Kkob;?ZoaTUUB(g*{h8K~E4HG16=@cUi z#VB+tkP|$^)tb#;_W_EQ^K>)zuchF73=@iUKJFr#rzu74ktx)=1q)m%7#=K##)h_S z;YkREZ^CqzxvXK)oQ7pES}kQNGa%Ij?nJbQjcLcFh#QF`p&ydyrA9Wy+{^2iEncv| z9cP(F&+>)^4fQJ;<|K^F+LQJdjspL$W>#vZHAgm6Vh{+;DAUSjgvzw?a)?}%*OZlO z6_v1^RT-)%$M;+Xk)N{4az_~TC@EEYZdI$xYtVHuZw7hR#2k6NSu@8_#~aE7?|6Zc zTuz4Mdvle%5%~q|j%2rx-BIk0W_Jv`W7!?Y?nFA3!TuYEP?-3<_QdD4CqAz|@p?o3sE1;bCgu1Q|CRY~t;fu6r8^y%a4`*H7UZKi1wCBN zK@U%Bgc@+ncriaMOMTqc8`2V8-5HchxeMBZm($gkQdf6U)DBDGT?b2iEFWph?nn_r zKZZg_a58nwOH0j6iCExgPBfO=Op-qBQM_8VFW&arL9|xuCMZZUG=BIZ>`Uw?dX3 zkqpTMuXH&CnL!tiCkc)&*u3#x52ptjTXNA^CCIcH@nI#4PJ7(oWeJHKry$N4UQXKj z_O2#~!Eu=d;G)@be?G*zTIiH@<*q|LzpFUK?ITUf&+ z+Fwh`cvgi`Y{ra04+O<=j|d%1ej?aERKvb4qzkob*l^22OAT*32rh;ya+=9b`4JH{ zRWm9eZGL3>nKQ^*9|ZIuC>WYqhK)!WL?p43sHzJ27!Om3O$g-sXjx=GRw4y<8XyF> zbjmUM)?nZbRa9UIE}QX%oKLMBL$eBrMKa9>!8P4K!tkUK^RrOT&&VH{KV1b|k{O^L zuTX1}ix?Pv!Xk!kvw_d)!}!dsP#?2G72d=6SP;$+LAXpq6dGcbAx0ZwjG>QZ!)if8 zA8+Ur41J=ZJF;*BOwlP28}N?C_&L?^oq!S53qgh)h>e-vLa;@Jw*=O`rWry?$3Ywp zw%%aq4f1ad5PS1_lmHe$nDG^t2xeozgaa;T_Be7?a)acma8)D${}V|#SYek#DL_y% z2UT8~LUNz%RGSHDgMAQ}+^hK?KO;T^=qQRM+MASFiJ9W~|&=B^RMPqG6V zy~XprA_A@zMN>(EJ0UrO2hGex>KxoQ<~VKYehS!&6{kv+1h(1{A_k>1jcJr4oF>BQ zf{qfmGA8f{2SP)kvs_d6PYs&7*nL zF&=GH|FVAo3$cWv%5*#+-wAJo6Ac|O~xVv^injAC*>65 zmGA_mq}*_4y^V?)9=iFZDQ;C;fS%}Jv(Yz09cwHfc!wA@w6g?rutsds=OTRKu>y$* zgrocFimqyN?FXne%42w2)SYXjnp97Mkk3X7SCiBK7 z$EGCnl{oouc5~#%A-{m&TxCLaZAR9L&Yp}cF%@I4%d6sr&LycAb-%3N34^G#ci)g4 z3eZQiq0P;DAfLJjbnc9$gp(STytUE$jE_Ao%9cL44Hp)-%3lE!dxJcfgp* zSRo+7Ek!+VFLk_9r{ht7Y|^N9TPYK@xCRk0e7tDY#_@1c`r}U${n3xxpIRk40gY*~ zX`pQ{3L3iK5s4WhyBLq+z0>>IJ2-^bQ#u$fWnqUj6*DeLJB0>=;gCX7IgnLTH4@m6 z=zt0xEkniLpo;efYjan+T65Rh!$7OCHeU`1t@&&7Y$$K-M5+bGq1K7OnCWaKW{~s+ zi%ij0LUOmiHu31KL@KAm*-E@6hV3PWCy8}|aYC#gxrvA$XC<$%BZhQ>RWqD_mOt#e z5}3mB&eQNqUiQjZ-uNL)x=aU^9OrfLu#pGk z>|G=q>%VjxGJ8ec>~#%`>Six!m}8#_jKJ+Fo2Q2RkDcqI_En;so+z>Yna}Oqc+bHV z^ya>Hupa7hr;6`|#*aFX$I6y6CRqsx97zwha(v^T7Ey!=@__ z37MrgjH=^v*>VU${oHvA=751><0t?Q=VsV2G`Lm*79ELP;C@Kz{V^XcyXh`jU2pnw zNN4o4^={%VoX(;Q{?5J*2chUni=W-ry`rmo0S*V+odXeXqN6HpU202LvuGnmuHH;o z6PE+Chw=AL?dj_4&S{I=ZB*9@>&@IM-t`nid!(_cyX>Pg+Z(l%O;hqFT-x%sj_&rT zWS?sEc^qrk>0I08Bb(tDM4hXlskcHm*aUKIm*Fa8x~;^(zN72#MqBi{IddB3kilbx?1@yZoP-lg-@)vuVh3inAfox0fM`{pz_QMkByWaL63lfN3D ztOQ*q3^4I@9%thiegigf=`aJdr=&`7r*jgWD*54gTcR=fPH9wuxC>uTw+|9<(Yw1f zLZ^5w8S9XcxCSz@eTR|8k2+?Br1#)Vt~c5u$JLDP9`xo;Vqs98W3bYV<6a0>@d;+P z#qF$1B%)|bF1DupLRNGU2V9n;;hvh1S)B0L{o zu=7pIQst#_ei`&G^!f$!>Q5VfZ8-;R#fei0`9hApC*Il?>&4~J5h#v_sl_oy;IyMH z$CaCmPS!oIExwA_Bb*`eLKDndk2CTO)3h=ASdE48lMC0n`{J#BN-=xWoM?A%tM>o3 z_vOKH9ru~@uIafkGq_2B9FQPDkVA4XIA%ayILP54N}x!Pk|++~0f^y1A_*D?CHV>? zN0A)gj-xnE;*heVBz9~kQEn%Zo%@bv5R%Dew`yzu+1md$Rl9%fR&l@IuipuPkh2?D zt}RfSM!$aj`t|GU_4^&)NAZ|Uurp=wkHk~Fg7I~>Rx-G_JWWpS*Dg1Dhl{x+)lb=~ z5nL#BDhX#snz_7Miyv<33^)EN=?vT%hjgfC`=mvi-UpXRh?_~d3u=13kMz2IaC6lA zp@`M{p@M>530e9FvrzpGAQ(gw>$wd1j^+om`cSSH{+XHHJcaDH39S}a! zX2MBxDC1Hl!PzsDflDYHFmX{1M+p0eAic?FNQRR~1rX+PC}1cHv352Cc@o?_Ap~M) zPlWi?hifOiH{s6Nmm#;%zM)>YbM_AnAV0iF*|jq_NUojmWzC{6_;2R(lxHYIR}I0r z79rd}J?b^McV-6q5cX&L5f1i|!)Jc5*MR@*VAfWx;c##e8M1x3yrWaYF|>J(QqAb= z>x087d|uJuXoY+(;_j4%g-#&X*H;fwlL28YpCsVUGJc)01& zrm?2WO$%BGq8adt2Vvx|_>H}7~yF>Lfa>_{X$C%?SRk@iohWe z$Ox@h1hOK~Cj$K!85qn5zkBGX*ggz{^5fL91+7Y206?Fxn z9TVDdp`8%gNufP1lv6@`LMTrPcwj20h4GYt2c`mt#|fdF6WV#9JT0_mgz~J=E(qm0 zpPLk3F`@m4P{_mbNAZJOrUFk(4YY8|tN8u6B4V%M z_;vg~fgjv8l~2N)@l*KwY5e^R{(cs}&m;Z?{Ln!f;&3b!%1^;#^9?2V)6n*5UqSlY z`28(@HT>@2cMlS$e^G+}qNw;tKdsK*^@V_5PJqXku|9}~F@^K7~ zK;#UB%a7TCRggRFX*xTBys*bpLI~`Od_72s^r)mBRSx22`oV)eJ-D4!wEEFV1XVa_ zP(=<>oG%ICI#|rR4)`e$!2b;QpnO*4=pu!$#N$5nB}M%iaIbw?`8haRn$e$kqQBro zZ#mK1PV`MD`c)_TH7ELuPV_A&`b$pqm!0UZIMJ^=(Qi1>Uv;A2bfVvKqThC+zve`L z-HHB&6a7sm`dd!)x1H$kIMLsAqQB=vf8U9I$BF)d6a7Oc`bSRm9Vhx-C;B}n`o~W6 zPn_tVI?+FKqJQp0|H6s>r4#)tC;Hb;^lzN#-#O90ccTB`MBjCyRVTXQME}u=-gTn? zu6lF+br%RtQO!HBBZrJxpGA9@v_Vr*^kf&x3 zd1}@%^p){CveB`Bnu*;7_}vNne~SMEUo+2!ju13f7_LQ{5&{3bu0)%Nkv;~nEWc9s z@(9%a@jg=fCz7Q0Pi`l*e?4rN!%BlrYX8PhDWvyrdV=)+&Eur^Z+VL7;#{I$KkRNCNXK|2L20n*B2b96~5qNUuu8+WzGY{!X z2F*_b^`XPPZh<;sMFPSP>fT96&ALviN(E4l_3&Mr)WDO@@*ffiL6@N$@a}|Y5T@6V zll$*N{wO_af>t6p$|*0nV05_gzMJXv-A&z$K#FKzbZ>?1rQRl~6mHQ&a{HBXtP8F= zkNA8mWMWMQ*TpEs3Q?sSGQgh6x>TiZr3l?KjGZA->Mqo4^2+C9J(CSxm4jc?t* ze@yvx?LTV&S?k;*x&hwj+l%c(D74R~MM0&jM?j@(7@*Si9|W7V@cRrMe5qf?+(_Nb zU~>5HRb>;r3P(fN1Je+f$Fj-jm?Y5D^(2Ib`MW8E&Oo72;|Y1MXjIXBD}`Pqx!;;i z))03OYHR+~tPxL=>u=eTYd`5<&ywR^$4%odwMMX3-t`95n!fJiHBPMYm>`8hjT`?B zJ_ep%56-`W$<3$Co_Xbs8;S4SzuyH3vQS}UnWcx%-YfQ13w?@NY_*szXWvfJ+|O3w zsfzQpm2!Ur-;=Hks900p;&vOnm%5pwMTPuZ@#M0aKZ*;GuC*4cq0kPf33ST%vWKG#WChrlE}*m=she*a81_@@?EDp;W$hS zb)I@pRLdjvB<`1$FsKqVyRdNYO2T%h7_4;>#v^HyicK|hfs4S9*7$;om4385%G!cr zkQ*8^gZ}a$9V1^yYgXE+M~j7W!JS$T6pxjUm5-B)?K5dlcQag zQF=S@ATD0uH_-)N`=iii8~=(iNKNwww~h1CoO~`-d5+&ElNVE!i*}>Prs64QwVrGz zE6LMHs}f}kS&6RZ@g&K%v64Iuvno-xik0YU5v3ya!MG)yOO-^21 zsa#y?Dow`mp~fe%l3<|rP`o%*o+7;^)Ngdi*(nW8jSv^1nsu~t!E4v4A;vtt>F{d1 zi}P_h|CIZ@`w6(uX?dXf(k>-QZwG5DQuu(cCr+~l4~iy6o>FOYI(?x6R}WZ%uD-FW zV4R0t2;imNH?Edvd*eWZ$Owm$-lujQV zk=JrhwJ_&H%TVQ>uNIzHkRZzgYoBh+yUVkSYJhnHs(+(0O|a#RRq#UL1vrB@bYsD~ zQodsD(5wrteuI~Ix?S;F`5GVaqJ_RFAHKnU{qu!+evC$1g62H8MGfUza@}f}!gX2? zYvweX?XJw$s8Lx}oggQE^72k!;D@Bc8aEdn6ICc)s1-0JQU8x}^8>g-HwexHBjGr~ zR^a~6UpT^+GXe7^cwwcE zuh)@0Sj#2s*73S@CZ*iKr`1>Q)#QtpwsP4Tzj$W?Ae`ub^B$*{(-)6jmqYxd1 zty&mWBGUR_v{j+^WA%HBcL;N#`rXAlD`X^4qq#ya_-d!`o7|W26{PaJmy@RrY-mZ* z{s;I0XaP@iYufnQ;+<)Kl@;(`!y~^;x2u+8z#)EFO@10F)TlBIkIJn&Bc_FH(-){s z!G=S-8LIcpE`jDKUEJ2AG$#gHXX!+p$n#i zp#BMhsRm`0Uu^u9`}eEIyAnS70|D6;Rt z0K0#aY2F4FYjR7MdKYoISJ$^HK`g6*wm_pKm$3KJ5Sh!exqC#3F&Epx<%b zO|%zE+D*$cN-DmllwYHgI_Z4`hl$D8lxpQQa-rBmS-`*8>BH$RD?f+@U#I-tR{qzK z|8>g0*IMaeLxK!Flz}$CB&)#beRNE&S=PE2xl?Sf%2jATor46UD&Os}e=0_+kTLL! zofb5{rdleYoHX4=qNyKnq6evE22ld0RJ;=-L>5J(l)iUlaph+IeyAR&&!@

W#$R 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 literal 107101 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�eleXx?USS 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 V>FV_}fR+bm{Sc0~{|^T$-+2dxYJ30y diff --git a/trunk/src/core/srs_core.cpp b/trunk/src/core/srs_core.cpp index 0aa6c5207..090dfe80f 100644 --- a/trunk/src/core/srs_core.cpp +++ b/trunk/src/core/srs_core.cpp @@ -111,3 +111,17 @@ void srs_vhost_resolve(std::string& vhost, std::string& app) } } } + +void srs_close_stfd(st_netfd_t& stfd) +{ + if (stfd) { + int fd = st_netfd_fileno(stfd); + st_netfd_close(stfd); + stfd = NULL; + + // st does not close it sometimes, + // close it manually. + close(fd); + } +} + diff --git a/trunk/src/core/srs_core.hpp b/trunk/src/core/srs_core.hpp index 254efa40e..763b460e7 100644 --- a/trunk/src/core/srs_core.hpp +++ b/trunk/src/core/srs_core.hpp @@ -46,6 +46,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include +#include + // generated by configure. #include @@ -102,4 +104,7 @@ extern std::string srs_dns_resolve(std::string host); // app...vhost...request_vhost extern void srs_vhost_resolve(std::string& vhost, std::string& app); +// close the netfd, and close the underlayer fd. +extern void srs_close_stfd(st_netfd_t& stfd); + #endif \ No newline at end of file diff --git a/trunk/src/core/srs_core_conn.cpp b/trunk/src/core/srs_core_conn.cpp index b11e8ea18..e148db79a 100644 --- a/trunk/src/core/srs_core_conn.cpp +++ b/trunk/src/core/srs_core_conn.cpp @@ -36,15 +36,7 @@ SrsConnection::SrsConnection(SrsServer* srs_server, st_netfd_t client_stfd) SrsConnection::~SrsConnection() { - if (stfd) { - int fd = st_netfd_fileno(stfd); - st_netfd_close(stfd); - stfd = NULL; - - // st does not close it sometimes, - // close it manually. - close(fd); - } + srs_close_stfd(stfd); } int SrsConnection::start() diff --git a/trunk/src/core/srs_core_conn.hpp b/trunk/src/core/srs_core_conn.hpp index 8f8519c95..4689eb7d7 100644 --- a/trunk/src/core/srs_core_conn.hpp +++ b/trunk/src/core/srs_core_conn.hpp @@ -30,8 +30,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include -#include - class SrsServer; class SrsConnection { diff --git a/trunk/src/core/srs_core_encoder.cpp b/trunk/src/core/srs_core_encoder.cpp index 3400948a5..f2b041ac6 100644 --- a/trunk/src/core/srs_core_encoder.cpp +++ b/trunk/src/core/srs_core_encoder.cpp @@ -483,13 +483,103 @@ void SrsFFMPEG::stop() SrsEncoder::SrsEncoder() { - tid = NULL; - loop = false; + pthread = new SrsThread(this, SRS_ENCODER_SLEEP_MS); + pithy_print = new SrsPithyPrint(SRS_STAGE_ENCODER); } SrsEncoder::~SrsEncoder() { on_unpublish(); + + srs_freep(pthread); +} + +int SrsEncoder::on_publish(SrsRequest* req) +{ + int ret = ERROR_SUCCESS; + + ret = parse_scope_engines(req); + + // ignore the loop encoder + if (ret == ERROR_ENCODER_LOOP) { + clear_engines(); + ret = ERROR_SUCCESS; + } + + // return for error or no engine. + if (ret != ERROR_SUCCESS || ffmpegs.empty()) { + return ret; + } + + // start thread to run all encoding engines. + if ((ret = pthread->start()) != ERROR_SUCCESS) { + srs_error("st_thread_create failed. ret=%d", ret); + return ret; + } + + return ret; +} + +void SrsEncoder::on_unpublish() +{ + pthread->stop(); + clear_engines(); +} + +int SrsEncoder::cycle() +{ + int ret = ERROR_SUCCESS; + + std::vector::iterator it; + for (it = ffmpegs.begin(); it != ffmpegs.end(); ++it) { + SrsFFMPEG* ffmpeg = *it; + + // start all ffmpegs. + if ((ret = ffmpeg->start()) != ERROR_SUCCESS) { + srs_error("ffmpeg start failed. ret=%d", ret); + return ret; + } + + // check ffmpeg status. + if ((ret = ffmpeg->cycle()) != ERROR_SUCCESS) { + srs_error("ffmpeg cycle failed. ret=%d", ret); + return ret; + } + } + + // pithy print + encoder(); + pithy_print->elapse(SRS_ENCODER_SLEEP_MS); + + return ret; +} + +void SrsEncoder::on_leave_loop() +{ + // kill ffmpeg when finished and it alive + std::vector::iterator it; + + for (it = ffmpegs.begin(); it != ffmpegs.end(); ++it) { + SrsFFMPEG* ffmpeg = *it; + ffmpeg->stop(); + } +} + +void SrsEncoder::clear_engines() +{ + std::vector::iterator it; + + for (it = ffmpegs.begin(); it != ffmpegs.end(); ++it) { + SrsFFMPEG* ffmpeg = *it; + srs_freep(ffmpeg); + } + + ffmpegs.clear(); +} + +SrsFFMPEG* SrsEncoder::at(int index) +{ + return ffmpegs[index]; } int SrsEncoder::parse_scope_engines(SrsRequest* req) @@ -531,60 +621,6 @@ int SrsEncoder::parse_scope_engines(SrsRequest* req) return ret; } -int SrsEncoder::on_publish(SrsRequest* req) -{ - int ret = ERROR_SUCCESS; - - ret = parse_scope_engines(req); - - // ignore the loop encoder - if (ret == ERROR_ENCODER_LOOP) { - ret = ERROR_SUCCESS; - } - - // return for error or no engine. - if (ret != ERROR_SUCCESS || ffmpegs.empty()) { - return ret; - } - - // start thread to run all encoding engines. - srs_assert(!tid); - if((tid = st_thread_create(encoder_thread, this, 1, 0)) == NULL) { - ret = ERROR_ST_CREATE_FORWARD_THREAD; - srs_error("st_thread_create failed. ret=%d", ret); - return ret; - } - - return ret; -} - -void SrsEncoder::on_unpublish() -{ - if (tid) { - loop = false; - st_thread_interrupt(tid); - st_thread_join(tid, NULL); - tid = NULL; - } - - clear_engines(); -} - -void SrsEncoder::clear_engines() -{ - std::vector::iterator it; - for (it = ffmpegs.begin(); it != ffmpegs.end(); ++it) { - SrsFFMPEG* ffmpeg = *it; - srs_freep(ffmpeg); - } - ffmpegs.clear(); -} - -SrsFFMPEG* SrsEncoder::at(int index) -{ - return ffmpegs[index]; -} - int SrsEncoder::parse_transcode(SrsRequest* req, SrsConfDirective* conf) { int ret = ERROR_SUCCESS; @@ -631,7 +667,6 @@ int SrsEncoder::parse_transcode(SrsRequest* req, SrsConfDirective* conf) // if got a loop, donot transcode the whole stream. if (ret == ERROR_ENCODER_LOOP) { - clear_engines(); break; } @@ -646,85 +681,14 @@ int SrsEncoder::parse_transcode(SrsRequest* req, SrsConfDirective* conf) return ret; } -int SrsEncoder::cycle() -{ - int ret = ERROR_SUCCESS; - - std::vector::iterator it; - for (it = ffmpegs.begin(); it != ffmpegs.end(); ++it) { - SrsFFMPEG* ffmpeg = *it; - - // start all ffmpegs. - if ((ret = ffmpeg->start()) != ERROR_SUCCESS) { - srs_error("ffmpeg start failed. ret=%d", ret); - return ret; - } - - // check ffmpeg status. - if ((ret = ffmpeg->cycle()) != ERROR_SUCCESS) { - srs_error("ffmpeg cycle failed. ret=%d", ret); - return ret; - } - } - - return ret; -} - -void SrsEncoder::encoder_cycle() -{ - int ret = ERROR_SUCCESS; - - log_context->generate_id(); - srs_trace("encoder cycle start"); - - SrsPithyPrint pithy_print(SRS_STAGE_ENCODER); - - while (loop) { - if ((ret = cycle()) != ERROR_SUCCESS) { - srs_warn("encoder cycle failed, ignored and retry, ret=%d", ret); - } else { - srs_info("encoder cycle success, retry"); - } - - if (!loop) { - break; - } - - encoder(&pithy_print); - pithy_print.elapse(SRS_ENCODER_SLEEP_MS); - - st_usleep(SRS_ENCODER_SLEEP_MS * 1000); - } - - // kill ffmpeg when finished and it alive - std::vector::iterator it; - for (it = ffmpegs.begin(); it != ffmpegs.end(); ++it) { - SrsFFMPEG* ffmpeg = *it; - ffmpeg->stop(); - } - - srs_trace("encoder cycle finished"); -} - -void SrsEncoder::encoder(SrsPithyPrint* pithy_print) +void SrsEncoder::encoder() { // reportable if (pithy_print->can_print()) { - srs_trace("-> time=%"PRId64", encoders=%d", - pithy_print->get_age(), (int)ffmpegs.size()); + // TODO: FIXME: show more info. + srs_trace("-> time=%"PRId64", encoders=%d", pithy_print->get_age(), (int)ffmpegs.size()); } } -void* SrsEncoder::encoder_thread(void* arg) -{ - SrsEncoder* obj = (SrsEncoder*)arg; - srs_assert(obj != NULL); - - obj->loop = true; - obj->encoder_cycle(); - - return NULL; -} - #endif diff --git a/trunk/src/core/srs_core_encoder.hpp b/trunk/src/core/srs_core_encoder.hpp index 6149ce4f3..4ecfd59c9 100644 --- a/trunk/src/core/srs_core_encoder.hpp +++ b/trunk/src/core/srs_core_encoder.hpp @@ -32,7 +32,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include -#include +#include class SrsConfDirective; class SrsRequest; @@ -85,28 +85,29 @@ public: * the encoder for a stream, * may use multiple ffmpegs to transcode the specified stream. */ -class SrsEncoder +class SrsEncoder : public ISrsThreadHandler { private: std::vector ffmpegs; private: - st_thread_t tid; - bool loop; + SrsThread* pthread; + SrsPithyPrint* pithy_print; public: SrsEncoder(); virtual ~SrsEncoder(); public: virtual int on_publish(SrsRequest* req); virtual void on_unpublish(); +// interface ISrsThreadHandler. +public: + virtual int cycle(); + virtual void on_leave_loop(); private: - virtual int parse_scope_engines(SrsRequest* req); virtual void clear_engines(); virtual SrsFFMPEG* at(int index); + virtual int parse_scope_engines(SrsRequest* req); virtual int parse_transcode(SrsRequest* req, SrsConfDirective* conf); - virtual int cycle(); - virtual void encoder_cycle(); - virtual void encoder(SrsPithyPrint* pithy_print); - static void* encoder_thread(void* arg); + virtual void encoder(); }; #endif diff --git a/trunk/src/core/srs_core_error.hpp b/trunk/src/core/srs_core_error.hpp index a5d5348f0..47004397e 100644 --- a/trunk/src/core/srs_core_error.hpp +++ b/trunk/src/core/srs_core_error.hpp @@ -37,8 +37,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define ERROR_ST_OPEN_SOCKET 102 #define ERROR_ST_CREATE_LISTEN_THREAD 103 #define ERROR_ST_CREATE_CYCLE_THREAD 104 -#define ERROR_ST_CREATE_FORWARD_THREAD 105 -#define ERROR_ST_CONNECT 106 +#define ERROR_ST_CONNECT 105 #define ERROR_SOCKET_CREATE 200 #define ERROR_SOCKET_SETREUSE 201 diff --git a/trunk/src/core/srs_core_forward.cpp b/trunk/src/core/srs_core_forward.cpp old mode 100755 new mode 100644 index ebca3f667..39059121b --- a/trunk/src/core/srs_core_forward.cpp +++ b/trunk/src/core/srs_core_forward.cpp @@ -1,387 +1,329 @@ -/* -The MIT License (MIT) - -Copyright (c) 2013 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 - -#include -#include -#include -#include -#include -#include -#include - -#define SRS_PULSE_TIMEOUT_MS 100 -#define SRS_FORWARDER_SLEEP_MS 2000 -#define SRS_SEND_TIMEOUT_US 3000000L -#define SRS_RECV_TIMEOUT_US SRS_SEND_TIMEOUT_US - -SrsForwarder::SrsForwarder() -{ - client = NULL; - stfd = NULL; - stream_id = 0; - - tid = NULL; - loop = false; -} - -SrsForwarder::~SrsForwarder() -{ - on_unpublish(); - - std::vector::iterator it; - for (it = msgs.begin(); it != msgs.end(); ++it) { - SrsSharedPtrMessage* msg = *it; - srs_freep(msg); - } - msgs.clear(); -} - -int SrsForwarder::on_publish(SrsRequest* req, std::string forward_server) -{ - int ret = ERROR_SUCCESS; - - // forward app - app = req->app; - - stream_name = req->stream; - server = forward_server; - std::string s_port = RTMP_DEFAULT_PORTS; - port = RTMP_DEFAULT_PORT; - - size_t pos = forward_server.find(":"); - if (pos != std::string::npos) { - s_port = forward_server.substr(pos + 1); - server = forward_server.substr(0, pos); - } - // discovery vhost - std::string vhost = req->vhost; - srs_vhost_resolve(vhost, s_port); - port = ::atoi(s_port.c_str()); - - // generate tcUrl - tc_url = "rtmp://"; - tc_url += vhost; - tc_url += "/"; - tc_url += req->app; - - // dead loop check - std::string source_ep = req->vhost; - source_ep += ":"; - source_ep += req->port; - - std::string dest_ep = vhost; - dest_ep += ":"; - dest_ep += s_port; - - if (source_ep == dest_ep) { - ret = ERROR_SYSTEM_FORWARD_LOOP; - srs_warn("farder loop detected. src=%s, dest=%s, ret=%d", - source_ep.c_str(), dest_ep.c_str(), ret); - return ret; - } - srs_trace("start forward %s to %s, stream: %s/%s", - source_ep.c_str(), dest_ep.c_str(), tc_url.c_str(), - stream_name.c_str()); - - // TODO: seems bug when republish and reforward. - - // start forward - if ((ret = open_socket()) != ERROR_SUCCESS) { - return ret; - } - - srs_assert(!tid); - if((tid = st_thread_create(forward_thread, this, 1, 0)) == NULL){ - ret = ERROR_ST_CREATE_FORWARD_THREAD; - srs_error("st_thread_create failed. ret=%d", ret); - return ret; - } - - return ret; -} - -void SrsForwarder::on_unpublish() -{ - if (tid) { - loop = false; - st_thread_interrupt(tid); - st_thread_join(tid, NULL); - tid = NULL; - } - - if (stfd) { - int fd = st_netfd_fileno(stfd); - st_netfd_close(stfd); - stfd = NULL; - - // st does not close it sometimes, - // close it manually. - close(fd); - } - - srs_freep(client); -} - -int SrsForwarder::on_meta_data(SrsSharedPtrMessage* metadata) -{ - int ret = ERROR_SUCCESS; - - msgs.push_back(metadata); - - return ret; -} - -int SrsForwarder::on_audio(SrsSharedPtrMessage* msg) -{ - int ret = ERROR_SUCCESS; - - // TODO: FIXME: must drop the msgs when server failed. - msgs.push_back(msg); - - return ret; -} - -int SrsForwarder::on_video(SrsSharedPtrMessage* msg) -{ - int ret = ERROR_SUCCESS; - - // TODO: FIXME: must drop the msgs when server failed. - msgs.push_back(msg); - - return ret; -} - -int SrsForwarder::open_socket() -{ - int ret = ERROR_SUCCESS; - - srs_trace("forward stream=%s, tcUrl=%s to server=%s, port=%d", - stream_name.c_str(), tc_url.c_str(), server.c_str(), port); - - int sock = socket(AF_INET, SOCK_STREAM, 0); - if(sock == -1){ - ret = ERROR_SOCKET_CREATE; - srs_error("create socket error. ret=%d", ret); - return ret; - } - - stfd = st_netfd_open_socket(sock); - if(stfd == NULL){ - ret = ERROR_ST_OPEN_SOCKET; - srs_error("st_netfd_open_socket failed. ret=%d", ret); - return ret; - } - - srs_freep(client); - client = new SrsRtmpClient(stfd); - - return ret; -} - -int SrsForwarder::connect_server() -{ - int ret = ERROR_SUCCESS; - - std::string ip = srs_dns_resolve(server); - if (ip.empty()) { - ret = ERROR_SYSTEM_IP_INVALID; - srs_error("dns resolve server error, ip empty. ret=%d", ret); - return ret; - } - - sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - addr.sin_addr.s_addr = inet_addr(ip.c_str()); - - if (st_connect(stfd, (const struct sockaddr*)&addr, sizeof(sockaddr_in), ST_UTIME_NO_TIMEOUT) == -1){ - ret = ERROR_ST_CONNECT; - srs_error("connect to server error. ip=%s, port=%d, ret=%d", ip.c_str(), port, ret); - return ret; - } - srs_trace("connect to server success. server=%s, ip=%s, port=%d", server.c_str(), ip.c_str(), port); - - return ret; -} - -int SrsForwarder::cycle() -{ - int ret = ERROR_SUCCESS; - - client->set_recv_timeout(SRS_RECV_TIMEOUT_US); - client->set_send_timeout(SRS_SEND_TIMEOUT_US); - - if ((ret = connect_server()) != ERROR_SUCCESS) { - return ret; - } - srs_assert(client); - - if ((ret = client->handshake()) != ERROR_SUCCESS) { - srs_error("handshake with server failed. ret=%d", ret); - return ret; - } - if ((ret = client->connect_app(app, tc_url)) != ERROR_SUCCESS) { - srs_error("connect with server failed, tcUrl=%s. ret=%d", tc_url.c_str(), ret); - return ret; - } - if ((ret = client->create_stream(stream_id)) != ERROR_SUCCESS) { - srs_error("connect with server failed, stream_id=%d. ret=%d", stream_id, ret); - return ret; - } - - // TODO: FIXME: need to cache the metadata and sequence header when reconnect. - - if ((ret = client->publish(stream_name, stream_id)) != ERROR_SUCCESS) { - srs_error("connect with server failed, stream_name=%s, stream_id=%d. ret=%d", - stream_name.c_str(), stream_id, ret); - return ret; - } - - if ((ret = forward()) != ERROR_SUCCESS) { - return ret; - } - - return ret; -} - -int SrsForwarder::forward() -{ - int ret = ERROR_SUCCESS; - - client->set_recv_timeout(SRS_PULSE_TIMEOUT_MS * 1000); - - SrsPithyPrint pithy_print(SRS_STAGE_FORWARDER); - - while (loop) { - pithy_print.elapse(SRS_PULSE_TIMEOUT_MS); - - // switch to other st-threads. - st_usleep(0); - - // read from client. - if (true) { - SrsCommonMessage* msg = NULL; - ret = client->recv_message(&msg); - - srs_verbose("play loop recv message. ret=%d", ret); - if (ret != ERROR_SUCCESS && ret != ERROR_SOCKET_TIMEOUT) { - srs_error("recv server control message failed. ret=%d", ret); - return ret; - } - } - - // ignore when no messages. - int count = (int)msgs.size(); - if (msgs.empty()) { - continue; - } - - // reportable - if (pithy_print.can_print()) { - srs_trace("-> time=%"PRId64", msgs=%d, obytes=%"PRId64", ibytes=%"PRId64", okbps=%d, ikbps=%d", - pithy_print.get_age(), count, client->get_send_bytes(), client->get_recv_bytes(), client->get_send_kbps(), client->get_recv_kbps()); - } - - // all msgs to forward. - int i = 0; - for (i = 0; i < count; i++) { - SrsSharedPtrMessage* msg = msgs[i]; - msgs[i] = NULL; - - // we erased the sendout messages, the msg must not be NULL. - srs_assert(msg); - - ret = client->send_message(msg); - if (ret != ERROR_SUCCESS) { - srs_error("forwarder send message to server failed. ret=%d", ret); - - // convert the index to count when error. - i++; - - break; - } - } - - // clear sendout mesages. - if (i < count) { - srs_warn("clear forwarded msg, total=%d, forwarded=%d, ret=%d", count, i, ret); - } else { - srs_info("clear forwarded msg, total=%d, forwarded=%d, ret=%d", count, i, ret); - } - msgs.erase(msgs.begin(), msgs.begin() + i); - - if (ret != ERROR_SUCCESS) { - break; - } - } - - return ret; -} - -void SrsForwarder::forward_cycle() -{ - int ret = ERROR_SUCCESS; - - log_context->generate_id(); - srs_trace("forward cycle start"); - - while (loop) { - if ((ret = cycle()) != ERROR_SUCCESS) { - srs_warn("forward cycle failed, ignored and retry, ret=%d", ret); - } else { - srs_info("forward cycle success, retry"); - } - - if (!loop) { - break; - } - - st_usleep(SRS_FORWARDER_SLEEP_MS * 1000); - - if ((ret = open_socket()) != ERROR_SUCCESS) { - srs_warn("forward cycle reopen failed, ignored and retry, ret=%d", ret); - } else { - srs_info("forward cycle reopen success"); - } - } - srs_trace("forward cycle finished"); -} - -void* SrsForwarder::forward_thread(void* arg) -{ - SrsForwarder* obj = (SrsForwarder*)arg; - srs_assert(obj != NULL); - - obj->loop = true; - obj->forward_cycle(); - - return NULL; -} - +/* +The MIT License (MIT) + +Copyright (c) 2013 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 + +#include +#include +#include +#include +#include +#include +#include + +#define SRS_PULSE_TIMEOUT_MS 100 +#define SRS_FORWARDER_SLEEP_MS 2000 +#define SRS_SEND_TIMEOUT_US 3000000L +#define SRS_RECV_TIMEOUT_US SRS_SEND_TIMEOUT_US + +SrsForwarder::SrsForwarder() +{ + client = NULL; + stfd = NULL; + stream_id = 0; + + pthread = new SrsThread(this, SRS_FORWARDER_SLEEP_MS); +} + +SrsForwarder::~SrsForwarder() +{ + on_unpublish(); + + std::vector::iterator it; + for (it = msgs.begin(); it != msgs.end(); ++it) { + SrsSharedPtrMessage* msg = *it; + srs_freep(msg); + } + msgs.clear(); + + srs_freep(pthread); +} + +int SrsForwarder::on_publish(SrsRequest* req, std::string forward_server) +{ + int ret = ERROR_SUCCESS; + + // forward app + app = req->app; + + stream_name = req->stream; + server = forward_server; + std::string s_port = RTMP_DEFAULT_PORTS; + port = RTMP_DEFAULT_PORT; + + size_t pos = forward_server.find(":"); + if (pos != std::string::npos) { + s_port = forward_server.substr(pos + 1); + server = forward_server.substr(0, pos); + } + // discovery vhost + std::string vhost = req->vhost; + srs_vhost_resolve(vhost, s_port); + port = ::atoi(s_port.c_str()); + + // generate tcUrl + tc_url = "rtmp://"; + tc_url += vhost; + tc_url += "/"; + tc_url += req->app; + + // dead loop check + std::string source_ep = req->vhost; + source_ep += ":"; + source_ep += req->port; + + std::string dest_ep = vhost; + dest_ep += ":"; + dest_ep += s_port; + + if (source_ep == dest_ep) { + ret = ERROR_SYSTEM_FORWARD_LOOP; + srs_warn("farder loop detected. src=%s, dest=%s, ret=%d", + source_ep.c_str(), dest_ep.c_str(), ret); + return ret; + } + srs_trace("start forward %s to %s, stream: %s/%s", + source_ep.c_str(), dest_ep.c_str(), tc_url.c_str(), + stream_name.c_str()); + + if ((ret = pthread->start()) != ERROR_SUCCESS) { + srs_error("start srs thread failed. ret=%d", ret); + return ret; + } + + return ret; +} + +void SrsForwarder::on_unpublish() +{ + pthread->stop(); + + close_underlayer_socket(); + + srs_freep(client); +} + +int SrsForwarder::on_meta_data(SrsSharedPtrMessage* metadata) +{ + int ret = ERROR_SUCCESS; + + msgs.push_back(metadata); + + return ret; +} + +int SrsForwarder::on_audio(SrsSharedPtrMessage* msg) +{ + int ret = ERROR_SUCCESS; + + // TODO: FIXME: must drop the msgs when server failed. + msgs.push_back(msg); + + return ret; +} + +int SrsForwarder::on_video(SrsSharedPtrMessage* msg) +{ + int ret = ERROR_SUCCESS; + + // TODO: FIXME: must drop the msgs when server failed. + msgs.push_back(msg); + + return ret; +} + +int SrsForwarder::cycle() +{ + int ret = ERROR_SUCCESS; + + if ((ret = connect_server()) != ERROR_SUCCESS) { + return ret; + } + srs_assert(client); + + client->set_recv_timeout(SRS_RECV_TIMEOUT_US); + client->set_send_timeout(SRS_SEND_TIMEOUT_US); + + if ((ret = client->handshake()) != ERROR_SUCCESS) { + srs_error("handshake with server failed. ret=%d", ret); + return ret; + } + if ((ret = client->connect_app(app, tc_url)) != ERROR_SUCCESS) { + srs_error("connect with server failed, tcUrl=%s. ret=%d", tc_url.c_str(), ret); + return ret; + } + if ((ret = client->create_stream(stream_id)) != ERROR_SUCCESS) { + srs_error("connect with server failed, stream_id=%d. ret=%d", stream_id, ret); + return ret; + } + + // TODO: FIXME: need to cache the metadata and sequence header when reconnect. + + if ((ret = client->publish(stream_name, stream_id)) != ERROR_SUCCESS) { + srs_error("connect with server failed, stream_name=%s, stream_id=%d. ret=%d", + stream_name.c_str(), stream_id, ret); + return ret; + } + + if ((ret = forward()) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +void SrsForwarder::close_underlayer_socket() +{ + srs_close_stfd(stfd); +} + +int SrsForwarder::connect_server() +{ + int ret = ERROR_SUCCESS; + + // reopen + close_underlayer_socket(); + + // open socket. + srs_trace("forward stream=%s, tcUrl=%s to server=%s, port=%d", + stream_name.c_str(), tc_url.c_str(), server.c_str(), port); + + int sock = socket(AF_INET, SOCK_STREAM, 0); + if(sock == -1){ + ret = ERROR_SOCKET_CREATE; + srs_error("create socket error. ret=%d", ret); + return ret; + } + + srs_assert(!stfd); + stfd = st_netfd_open_socket(sock); + if(stfd == NULL){ + ret = ERROR_ST_OPEN_SOCKET; + srs_error("st_netfd_open_socket failed. ret=%d", ret); + return ret; + } + + srs_freep(client); + client = new SrsRtmpClient(stfd); + + // connect to server. + std::string ip = srs_dns_resolve(server); + if (ip.empty()) { + ret = ERROR_SYSTEM_IP_INVALID; + srs_error("dns resolve server error, ip empty. ret=%d", ret); + return ret; + } + + sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = inet_addr(ip.c_str()); + + if (st_connect(stfd, (const struct sockaddr*)&addr, sizeof(sockaddr_in), ST_UTIME_NO_TIMEOUT) == -1){ + ret = ERROR_ST_CONNECT; + srs_error("connect to server error. ip=%s, port=%d, ret=%d", ip.c_str(), port, ret); + return ret; + } + srs_trace("connect to server success. server=%s, ip=%s, port=%d", server.c_str(), ip.c_str(), port); + + return ret; +} + +int SrsForwarder::forward() +{ + int ret = ERROR_SUCCESS; + + client->set_recv_timeout(SRS_PULSE_TIMEOUT_MS * 1000); + + SrsPithyPrint pithy_print(SRS_STAGE_FORWARDER); + + while (true) { + // switch to other st-threads. + st_usleep(0); + + // read from client. + if (true) { + SrsCommonMessage* msg = NULL; + ret = client->recv_message(&msg); + + srs_verbose("play loop recv message. ret=%d", ret); + if (ret != ERROR_SUCCESS && ret != ERROR_SOCKET_TIMEOUT) { + srs_error("recv server control message failed. ret=%d", ret); + return ret; + } + } + + // ignore when no messages. + int count = (int)msgs.size(); + if (msgs.empty()) { + continue; + } + + // pithy print + pithy_print.elapse(SRS_PULSE_TIMEOUT_MS); + if (pithy_print.can_print()) { + srs_trace("-> time=%"PRId64", msgs=%d, obytes=%"PRId64", ibytes=%"PRId64", okbps=%d, ikbps=%d", + pithy_print.get_age(), count, client->get_send_bytes(), client->get_recv_bytes(), client->get_send_kbps(), client->get_recv_kbps()); + } + + // all msgs to forward. + int i = 0; + for (i = 0; i < count; i++) { + SrsSharedPtrMessage* msg = msgs[i]; + msgs[i] = NULL; + + // we erased the sendout messages, the msg must not be NULL. + srs_assert(msg); + + ret = client->send_message(msg); + if (ret != ERROR_SUCCESS) { + srs_error("forwarder send message to server failed. ret=%d", ret); + + // convert the index to count when error. + i++; + + break; + } + } + + // clear sendout mesages. + if (i < count) { + srs_warn("clear forwarded msg, total=%d, forwarded=%d, ret=%d", count, i, ret); + } else { + srs_info("clear forwarded msg, total=%d, forwarded=%d, ret=%d", count, i, ret); + } + msgs.erase(msgs.begin(), msgs.begin() + i); + + if (ret != ERROR_SUCCESS) { + break; + } + } + + return ret; +} + diff --git a/trunk/src/core/srs_core_forward.hpp b/trunk/src/core/srs_core_forward.hpp index 405ea94a7..01bb7251b 100644 --- a/trunk/src/core/srs_core_forward.hpp +++ b/trunk/src/core/srs_core_forward.hpp @@ -32,7 +32,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include -#include +#include class SrsSharedPtrMessage; class SrsOnMetaDataPacket; @@ -42,7 +42,7 @@ class SrsRequest; /** * forward the stream to other servers. */ -class SrsForwarder +class SrsForwarder : public ISrsThreadHandler { private: std::string app; @@ -53,8 +53,7 @@ private: int port; private: st_netfd_t stfd; - st_thread_t tid; - bool loop; + SrsThread* pthread; private: SrsRtmpClient* client; std::vector msgs; @@ -67,14 +66,13 @@ public: virtual int on_meta_data(SrsSharedPtrMessage* metadata); virtual int on_audio(SrsSharedPtrMessage* msg); virtual int on_video(SrsSharedPtrMessage* msg); -private: - virtual int open_socket(); - virtual int connect_server(); -private: +// interface ISrsThreadHandler. +public: virtual int cycle(); +private: + virtual void close_underlayer_socket(); + virtual int connect_server(); virtual int forward(); - virtual void forward_cycle(); - static void* forward_thread(void* arg); }; #endif diff --git a/trunk/src/core/srs_core_http.cpp b/trunk/src/core/srs_core_http.cpp index 461d5cd2d..867f3635f 100644 --- a/trunk/src/core/srs_core_http.cpp +++ b/trunk/src/core/srs_core_http.cpp @@ -184,15 +184,7 @@ void SrsHttpClient::disconnect() { connected = false; - if (stfd) { - int fd = st_netfd_fileno(stfd); - st_netfd_close(stfd); - stfd = NULL; - - // st does not close it sometimes, - // close it manually. - ::close(fd); - } + srs_close_stfd(stfd); } int SrsHttpClient::connect(SrsHttpUri* uri) diff --git a/trunk/src/core/srs_core_http.hpp b/trunk/src/core/srs_core_http.hpp index 0848dd66b..0989717bb 100644 --- a/trunk/src/core/srs_core_http.hpp +++ b/trunk/src/core/srs_core_http.hpp @@ -36,7 +36,6 @@ class SrsSocket; #include -#include #include #define SRS_HTTP_HEADER_BUFFER 1024 diff --git a/trunk/src/core/srs_core_log.cpp b/trunk/src/core/srs_core_log.cpp index 3b74f3006..27aa5ae0a 100644 --- a/trunk/src/core/srs_core_log.cpp +++ b/trunk/src/core/srs_core_log.cpp @@ -29,8 +29,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include -#include - ILogContext::ILogContext() { } diff --git a/trunk/src/core/srs_core_protocol.hpp b/trunk/src/core/srs_core_protocol.hpp index fe52d5872..9cda280ca 100644 --- a/trunk/src/core/srs_core_protocol.hpp +++ b/trunk/src/core/srs_core_protocol.hpp @@ -33,8 +33,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include -#include - #include #include diff --git a/trunk/src/core/srs_core_rtmp.cpp b/trunk/src/core/srs_core_rtmp.cpp old mode 100755 new mode 100644 index 9c5e63d50..0fccf4de3 --- a/trunk/src/core/srs_core_rtmp.cpp +++ b/trunk/src/core/srs_core_rtmp.cpp @@ -1,1127 +1,1127 @@ -/* -The MIT License (MIT) - -Copyright (c) 2013 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 -#include -#include -#include -#include - -/** -* the signature for packets to client. -*/ -#define RTMP_SIG_FMS_VER "3,5,3,888" -#define RTMP_SIG_AMF0_VER 0 -#define RTMP_SIG_CLIENT_ID "ASAICiss" - -/** -* onStatus consts. -*/ -#define StatusLevel "level" -#define StatusCode "code" -#define StatusDescription "description" -#define StatusDetails "details" -#define StatusClientId "clientid" -// status value -#define StatusLevelStatus "status" -// code value -#define StatusCodeConnectSuccess "NetConnection.Connect.Success" -#define StatusCodeStreamReset "NetStream.Play.Reset" -#define StatusCodeStreamStart "NetStream.Play.Start" -#define StatusCodeStreamPause "NetStream.Pause.Notify" -#define StatusCodeStreamUnpause "NetStream.Unpause.Notify" -#define StatusCodePublishStart "NetStream.Publish.Start" -#define StatusCodeDataStart "NetStream.Data.Start" -#define StatusCodeUnpublishSuccess "NetStream.Unpublish.Success" - -// FMLE -#define RTMP_AMF0_COMMAND_ON_FC_PUBLISH "onFCPublish" -#define RTMP_AMF0_COMMAND_ON_FC_UNPUBLISH "onFCUnpublish" - -// default stream id for response the createStream request. -#define SRS_DEFAULT_SID 1 - -SrsRequest::SrsRequest() -{ - objectEncoding = RTMP_SIG_AMF0_VER; -} - -SrsRequest::~SrsRequest() -{ -} - -int SrsRequest::discovery_app() -{ - int ret = ERROR_SUCCESS; - - 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_verbose("discovery schema=%s", schema.c_str()); - } - - if ((pos = url.find("/")) != std::string::npos) { - vhost = url.substr(0, pos); - url = url.substr(vhost.length() + 1); - srs_verbose("discovery vhost=%s", vhost.c_str()); - } - - port = RTMP_DEFAULT_PORTS; - if ((pos = vhost.find(":")) != std::string::npos) { - port = vhost.substr(pos + 1); - vhost = vhost.substr(0, pos); - srs_verbose("discovery vhost=%s, port=%s", vhost.c_str(), port.c_str()); - } - - app = url; - srs_vhost_resolve(vhost, app); - - // resolve the vhost from config - SrsConfDirective* parsed_vhost = config->get_vhost(vhost); - if (parsed_vhost) { - vhost = parsed_vhost->arg0(); - } - - // TODO: discovery the params of vhost. - - srs_info("discovery app success. schema=%s, vhost=%s, port=%s, app=%s", - schema.c_str(), vhost.c_str(), port.c_str(), app.c_str()); - - if (schema.empty() || vhost.empty() || port.empty() || app.empty()) { - ret = ERROR_RTMP_REQ_TCURL; - srs_error("discovery tcUrl failed. " - "tcUrl=%s, schema=%s, vhost=%s, port=%s, app=%s, ret=%d", - tcUrl.c_str(), schema.c_str(), vhost.c_str(), port.c_str(), app.c_str(), ret); - return ret; - } - - strip(); - - return ret; -} - -std::string SrsRequest::get_stream_url() -{ - std::string url = ""; - - url += vhost; - url += "/"; - url += app; - url += "/"; - url += stream; - - return url; -} - -void SrsRequest::strip() -{ - trim(vhost, "/ \n\r\t"); - trim(app, "/ \n\r\t"); - trim(stream, "/ \n\r\t"); -} - -std::string& SrsRequest::trim(std::string& str, std::string chs) -{ - for (int i = 0; i < (int)chs.length(); i++) { - char ch = chs.at(i); - - for (std::string::iterator it = str.begin(); it != str.end();) { - if (ch == *it) { - it = str.erase(it); - } else { - ++it; - } - } - } - - return str; -} - -SrsResponse::SrsResponse() -{ - stream_id = SRS_DEFAULT_SID; -} - -SrsResponse::~SrsResponse() -{ -} - -SrsRtmpClient::SrsRtmpClient(st_netfd_t _stfd) -{ - stfd = _stfd; - protocol = new SrsProtocol(stfd); -} - -SrsRtmpClient::~SrsRtmpClient() -{ - srs_freep(protocol); -} - -void SrsRtmpClient::set_recv_timeout(int64_t timeout_us) -{ - protocol->set_recv_timeout(timeout_us); -} - -void SrsRtmpClient::set_send_timeout(int64_t timeout_us) -{ - protocol->set_send_timeout(timeout_us); -} - -int64_t SrsRtmpClient::get_recv_bytes() -{ - return protocol->get_recv_bytes(); -} - -int64_t SrsRtmpClient::get_send_bytes() -{ - return protocol->get_send_bytes(); -} - -int SrsRtmpClient::get_recv_kbps() -{ - return protocol->get_recv_kbps(); -} - -int SrsRtmpClient::get_send_kbps() -{ - return protocol->get_send_kbps(); -} - -int SrsRtmpClient::recv_message(SrsCommonMessage** pmsg) -{ - return protocol->recv_message(pmsg); -} - -int SrsRtmpClient::send_message(ISrsMessage* msg) -{ - return protocol->send_message(msg); -} - -int SrsRtmpClient::handshake() -{ - int ret = ERROR_SUCCESS; - - SrsSocket skt(stfd); - - SrsComplexHandshake complex_hs; - SrsSimpleHandshake simple_hs; - if ((ret = simple_hs.handshake_with_server(skt, complex_hs)) != ERROR_SUCCESS) { - return ret; - } - - return ret; -} - -int SrsRtmpClient::connect_app(std::string app, std::string tc_url) -{ - int ret = ERROR_SUCCESS; - - // Connect(vhost, app) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsConnectAppPacket* pkt = new SrsConnectAppPacket(); - msg->set_packet(pkt, 0); - - pkt->command_object = new SrsAmf0Object(); - pkt->command_object->set("app", new SrsAmf0String(app.c_str())); - pkt->command_object->set("swfUrl", new SrsAmf0String()); - pkt->command_object->set("tcUrl", new SrsAmf0String(tc_url.c_str())); - pkt->command_object->set("fpad", new SrsAmf0Boolean(false)); - pkt->command_object->set("capabilities", new SrsAmf0Number(239)); - pkt->command_object->set("audioCodecs", new SrsAmf0Number(3575)); - pkt->command_object->set("videoCodecs", new SrsAmf0Number(252)); - pkt->command_object->set("videoFunction", new SrsAmf0Number(1)); - pkt->command_object->set("pageUrl", new SrsAmf0String()); - pkt->command_object->set("objectEncoding", new SrsAmf0Number(0)); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - return ret; - } - } - - // Set Window Acknowledgement size(2500000) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsSetWindowAckSizePacket* pkt = new SrsSetWindowAckSizePacket(); - - pkt->ackowledgement_window_size = 2500000; - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - return ret; - } - } - - // expect connect _result - SrsCommonMessage* msg = NULL; - SrsConnectAppResPacket* pkt = NULL; - if ((ret = srs_rtmp_expect_message(protocol, &msg, &pkt)) != ERROR_SUCCESS) { - srs_error("expect connect app response message failed. ret=%d", ret); - return ret; - } - SrsAutoFree(SrsCommonMessage, msg, false); - srs_info("get connect app response message"); - - return ret; -} - -int SrsRtmpClient::create_stream(int& stream_id) -{ - int ret = ERROR_SUCCESS; - - // CreateStream - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsCreateStreamPacket* pkt = new SrsCreateStreamPacket(); - - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - return ret; - } - } - - // CreateStream _result. - if (true) { - SrsCommonMessage* msg = NULL; - SrsCreateStreamResPacket* pkt = NULL; - if ((ret = srs_rtmp_expect_message(protocol, &msg, &pkt)) != ERROR_SUCCESS) { - srs_error("expect create stream response message failed. ret=%d", ret); - return ret; - } - SrsAutoFree(SrsCommonMessage, msg, false); - srs_info("get create stream response message"); - - stream_id = (int)pkt->stream_id; - } - - return ret; -} - -int SrsRtmpClient::play(std::string stream, int stream_id) -{ - int ret = ERROR_SUCCESS; - - // Play(stream) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsPlayPacket* pkt = new SrsPlayPacket(); - - pkt->stream_name = stream; - msg->set_packet(pkt, stream_id); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send play stream failed. " - "stream=%s, stream_id=%d, ret=%d", - stream.c_str(), stream_id, ret); - return ret; - } - } - - // SetBufferLength(1000ms) - int buffer_length_ms = 1000; - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsUserControlPacket* pkt = new SrsUserControlPacket(); - - pkt->event_type = SrcPCUCSetBufferLength; - pkt->event_data = stream_id; - pkt->extra_data = buffer_length_ms; - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send set buffer length failed. " - "stream=%s, stream_id=%d, bufferLength=%d, ret=%d", - stream.c_str(), stream_id, buffer_length_ms, ret); - return ret; - } - } - - return ret; -} - -int SrsRtmpClient::publish(std::string stream, int stream_id) -{ - int ret = ERROR_SUCCESS; - - // publish(stream) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsPublishPacket* pkt = new SrsPublishPacket(); - - pkt->stream_name = stream; - msg->set_packet(pkt, stream_id); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send publish message failed. " - "stream=%s, stream_id=%d, ret=%d", - stream.c_str(), stream_id, ret); - return ret; - } - } - - return ret; -} - -SrsRtmp::SrsRtmp(st_netfd_t client_stfd) -{ - protocol = new SrsProtocol(client_stfd); - stfd = client_stfd; -} - -SrsRtmp::~SrsRtmp() -{ - srs_freep(protocol); -} - -SrsProtocol* SrsRtmp::get_protocol() -{ - return protocol; -} - -void SrsRtmp::set_recv_timeout(int64_t timeout_us) -{ - protocol->set_recv_timeout(timeout_us); -} - -int64_t SrsRtmp::get_recv_timeout() -{ - return protocol->get_recv_timeout(); -} - -void SrsRtmp::set_send_timeout(int64_t timeout_us) -{ - protocol->set_send_timeout(timeout_us); -} - -int64_t SrsRtmp::get_recv_bytes() -{ - return protocol->get_recv_bytes(); -} - -int64_t SrsRtmp::get_send_bytes() -{ - return protocol->get_send_bytes(); -} - -int SrsRtmp::get_recv_kbps() -{ - return protocol->get_recv_kbps(); -} - -int SrsRtmp::get_send_kbps() -{ - return protocol->get_send_kbps(); -} - -int SrsRtmp::recv_message(SrsCommonMessage** pmsg) -{ - return protocol->recv_message(pmsg); -} - -int SrsRtmp::send_message(ISrsMessage* msg) -{ - return protocol->send_message(msg); -} - -int SrsRtmp::handshake() -{ - int ret = ERROR_SUCCESS; - - SrsSocket skt(stfd); - - SrsComplexHandshake complex_hs; - SrsSimpleHandshake simple_hs; - if ((ret = simple_hs.handshake_with_client(skt, complex_hs)) != ERROR_SUCCESS) { - return ret; - } - - return ret; -} - -int SrsRtmp::connect_app(SrsRequest* req) -{ - int ret = ERROR_SUCCESS; - - SrsCommonMessage* msg = NULL; - SrsConnectAppPacket* pkt = NULL; - if ((ret = srs_rtmp_expect_message(protocol, &msg, &pkt)) != ERROR_SUCCESS) { - srs_error("expect connect app message failed. ret=%d", ret); - return ret; - } - SrsAutoFree(SrsCommonMessage, msg, false); - srs_info("get connect app message"); - - SrsAmf0Any* prop = NULL; - - if ((prop = pkt->command_object->ensure_property_string("tcUrl")) == NULL) { - ret = ERROR_RTMP_REQ_CONNECT; - srs_error("invalid request, must specifies the tcUrl. ret=%d", ret); - return ret; - } - req->tcUrl = srs_amf0_convert(prop)->value; - - if ((prop = pkt->command_object->ensure_property_string("pageUrl")) != NULL) { - req->pageUrl = srs_amf0_convert(prop)->value; - } - - if ((prop = pkt->command_object->ensure_property_string("swfUrl")) != NULL) { - req->swfUrl = srs_amf0_convert(prop)->value; - } - - if ((prop = pkt->command_object->ensure_property_number("objectEncoding")) != NULL) { - req->objectEncoding = srs_amf0_convert(prop)->value; - } - - srs_info("get connect app message params success."); - - return req->discovery_app(); -} - -int SrsRtmp::set_window_ack_size(int ack_size) -{ - int ret = ERROR_SUCCESS; - - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsSetWindowAckSizePacket* pkt = new SrsSetWindowAckSizePacket(); - - pkt->ackowledgement_window_size = ack_size; - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send ack size message failed. ret=%d", ret); - return ret; - } - srs_info("send ack size message success. ack_size=%d", ack_size); - - return ret; -} - -int SrsRtmp::set_peer_bandwidth(int bandwidth, int type) -{ - int ret = ERROR_SUCCESS; - - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsSetPeerBandwidthPacket* pkt = new SrsSetPeerBandwidthPacket(); - - pkt->bandwidth = bandwidth; - pkt->type = type; - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send set bandwidth message failed. ret=%d", ret); - return ret; - } - srs_info("send set bandwidth message " - "success. bandwidth=%d, type=%d", bandwidth, type); - - return ret; -} - -int SrsRtmp::response_connect_app(SrsRequest* req) -{ - int ret = ERROR_SUCCESS; - - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsConnectAppResPacket* pkt = new SrsConnectAppResPacket(); - - pkt->props->set("fmsVer", new SrsAmf0String("FMS/"RTMP_SIG_FMS_VER)); - pkt->props->set("capabilities", new SrsAmf0Number(127)); - pkt->props->set("mode", new SrsAmf0Number(1)); - - pkt->info->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); - pkt->info->set(StatusCode, new SrsAmf0String(StatusCodeConnectSuccess)); - pkt->info->set(StatusDescription, new SrsAmf0String("Connection succeeded")); - pkt->info->set("objectEncoding", new SrsAmf0Number(req->objectEncoding)); - SrsASrsAmf0EcmaArray* data = new SrsASrsAmf0EcmaArray(); - pkt->info->set("data", data); - - data->set("srs_version", new SrsAmf0String(RTMP_SIG_FMS_VER)); - data->set("srs_server", new SrsAmf0String(RTMP_SIG_SRS_NAME)); - data->set("srs_license", new SrsAmf0String(RTMP_SIG_SRS_LICENSE)); - data->set("srs_role", new SrsAmf0String(RTMP_SIG_SRS_ROLE)); - data->set("srs_url", new SrsAmf0String(RTMP_SIG_SRS_URL)); - data->set("srs_version", new SrsAmf0String(RTMP_SIG_SRS_VERSION)); - data->set("srs_site", new SrsAmf0String(RTMP_SIG_SRS_WEB)); - data->set("srs_email", new SrsAmf0String(RTMP_SIG_SRS_EMAIL)); - data->set("srs_copyright", new SrsAmf0String(RTMP_SIG_SRS_COPYRIGHT)); - - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send connect app response message failed. ret=%d", ret); - return ret; - } - srs_info("send connect app response message success."); - - return ret; -} - -int SrsRtmp::on_bw_done() -{ - int ret = ERROR_SUCCESS; - - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsOnBWDonePacket* pkt = new SrsOnBWDonePacket(); - - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send onBWDone message failed. ret=%d", ret); - return ret; - } - srs_info("send onBWDone message success."); - - return ret; -} - -int SrsRtmp::identify_client(int stream_id, SrsClientType& type, std::string& stream_name) -{ - type = SrsClientUnknown; - int ret = ERROR_SUCCESS; - - while (true) { - SrsCommonMessage* msg = NULL; - if ((ret = protocol->recv_message(&msg)) != ERROR_SUCCESS) { - srs_error("recv identify client message failed. ret=%d", ret); - return ret; - } - - SrsAutoFree(SrsCommonMessage, msg, false); - - if (!msg->header.is_amf0_command() && !msg->header.is_amf3_command()) { - srs_trace("identify ignore messages except " - "AMF0/AMF3 command message. type=%#x", msg->header.message_type); - continue; - } - - if ((ret = msg->decode_packet(protocol)) != ERROR_SUCCESS) { - srs_error("identify decode message failed. ret=%d", ret); - return ret; - } - - SrsPacket* pkt = msg->get_packet(); - if (dynamic_cast(pkt)) { - srs_info("identify client by create stream, play or flash publish."); - return identify_create_stream_client( - dynamic_cast(pkt), stream_id, type, stream_name); - } - if (dynamic_cast(pkt)) { - srs_info("identify client by releaseStream, fmle publish."); - return identify_fmle_publish_client( - dynamic_cast(pkt), type, stream_name); - } - - srs_trace("ignore AMF0/AMF3 command message."); - } - - return ret; -} - -int SrsRtmp::set_chunk_size(int chunk_size) -{ - int ret = ERROR_SUCCESS; - - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsSetChunkSizePacket* pkt = new SrsSetChunkSizePacket(); - - pkt->chunk_size = chunk_size; - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send set chunk size message failed. ret=%d", ret); - return ret; - } - srs_info("send set chunk size message success. chunk_size=%d", chunk_size); - - return ret; -} - -int SrsRtmp::start_play(int stream_id) -{ - int ret = ERROR_SUCCESS; - - // StreamBegin - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsUserControlPacket* pkt = new SrsUserControlPacket(); - - pkt->event_type = SrcPCUCStreamBegin; - pkt->event_data = stream_id; - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send PCUC(StreamBegin) message failed. ret=%d", ret); - return ret; - } - srs_info("send PCUC(StreamBegin) message success."); - } - - // onStatus(NetStream.Play.Reset) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); - - pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); - pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeStreamReset)); - pkt->data->set(StatusDescription, new SrsAmf0String("Playing and resetting stream.")); - pkt->data->set(StatusDetails, new SrsAmf0String("stream")); - pkt->data->set(StatusClientId, new SrsAmf0String(RTMP_SIG_CLIENT_ID)); - - msg->set_packet(pkt, stream_id); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send onStatus(NetStream.Play.Reset) message failed. ret=%d", ret); - return ret; - } - srs_info("send onStatus(NetStream.Play.Reset) message success."); - } - - // onStatus(NetStream.Play.Start) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); - - pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); - pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeStreamStart)); - pkt->data->set(StatusDescription, new SrsAmf0String("Started playing stream.")); - pkt->data->set(StatusDetails, new SrsAmf0String("stream")); - pkt->data->set(StatusClientId, new SrsAmf0String(RTMP_SIG_CLIENT_ID)); - - msg->set_packet(pkt, stream_id); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send onStatus(NetStream.Play.Reset) message failed. ret=%d", ret); - return ret; - } - srs_info("send onStatus(NetStream.Play.Reset) message success."); - } - - // |RtmpSampleAccess(false, false) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsSampleAccessPacket* pkt = new SrsSampleAccessPacket(); - - msg->set_packet(pkt, stream_id); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send |RtmpSampleAccess(false, false) message failed. ret=%d", ret); - return ret; - } - srs_info("send |RtmpSampleAccess(false, false) message success."); - } - - // onStatus(NetStream.Data.Start) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsOnStatusDataPacket* pkt = new SrsOnStatusDataPacket(); - - pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeDataStart)); - - msg->set_packet(pkt, stream_id); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send onStatus(NetStream.Data.Start) message failed. ret=%d", ret); - return ret; - } - srs_info("send onStatus(NetStream.Data.Start) message success."); - } - - srs_info("start play success."); - - return ret; -} - -int SrsRtmp::on_play_client_pause(int stream_id, bool is_pause) -{ - int ret = ERROR_SUCCESS; - - if (is_pause) { - // onStatus(NetStream.Pause.Notify) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); - - pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); - pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeStreamPause)); - pkt->data->set(StatusDescription, new SrsAmf0String("Paused stream.")); - - msg->set_packet(pkt, stream_id); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send onStatus(NetStream.Pause.Notify) message failed. ret=%d", ret); - return ret; - } - srs_info("send onStatus(NetStream.Pause.Notify) message success."); - } - // StreamEOF - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsUserControlPacket* pkt = new SrsUserControlPacket(); - - pkt->event_type = SrcPCUCStreamEOF; - pkt->event_data = stream_id; - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send PCUC(StreamEOF) message failed. ret=%d", ret); - return ret; - } - srs_info("send PCUC(StreamEOF) message success."); - } - } else { - // onStatus(NetStream.Unpause.Notify) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); - - pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); - pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeStreamUnpause)); - pkt->data->set(StatusDescription, new SrsAmf0String("Unpaused stream.")); - - msg->set_packet(pkt, stream_id); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send onStatus(NetStream.Unpause.Notify) message failed. ret=%d", ret); - return ret; - } - srs_info("send onStatus(NetStream.Unpause.Notify) message success."); - } - // StreanBegin - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsUserControlPacket* pkt = new SrsUserControlPacket(); - - pkt->event_type = SrcPCUCStreamBegin; - pkt->event_data = stream_id; - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send PCUC(StreanBegin) message failed. ret=%d", ret); - return ret; - } - srs_info("send PCUC(StreanBegin) message success."); - } - } - - return ret; -} - -int SrsRtmp::start_fmle_publish(int stream_id) -{ - int ret = ERROR_SUCCESS; - - // FCPublish - double fc_publish_tid = 0; - if (true) { - SrsCommonMessage* msg = NULL; - SrsFMLEStartPacket* pkt = NULL; - if ((ret = srs_rtmp_expect_message(protocol, &msg, &pkt)) != ERROR_SUCCESS) { - srs_error("recv FCPublish message failed. ret=%d", ret); - return ret; - } - srs_info("recv FCPublish request message success."); - - SrsAutoFree(SrsCommonMessage, msg, false); - fc_publish_tid = pkt->transaction_id; - } - // FCPublish response - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsFMLEStartResPacket* pkt = new SrsFMLEStartResPacket(fc_publish_tid); - - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send FCPublish response message failed. ret=%d", ret); - return ret; - } - srs_info("send FCPublish response message success."); - } - - // createStream - double create_stream_tid = 0; - if (true) { - SrsCommonMessage* msg = NULL; - SrsCreateStreamPacket* pkt = NULL; - if ((ret = srs_rtmp_expect_message(protocol, &msg, &pkt)) != ERROR_SUCCESS) { - srs_error("recv createStream message failed. ret=%d", ret); - return ret; - } - srs_info("recv createStream request message success."); - - SrsAutoFree(SrsCommonMessage, msg, false); - create_stream_tid = pkt->transaction_id; - } - // createStream response - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsCreateStreamResPacket* pkt = new SrsCreateStreamResPacket(create_stream_tid, stream_id); - - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send createStream response message failed. ret=%d", ret); - return ret; - } - srs_info("send createStream response message success."); - } - - // publish - if (true) { - SrsCommonMessage* msg = NULL; - SrsPublishPacket* pkt = NULL; - if ((ret = srs_rtmp_expect_message(protocol, &msg, &pkt)) != ERROR_SUCCESS) { - srs_error("recv publish message failed. ret=%d", ret); - return ret; - } - srs_info("recv publish request message success."); - - SrsAutoFree(SrsCommonMessage, msg, false); - } - // publish response onFCPublish(NetStream.Publish.Start) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); - - pkt->command_name = RTMP_AMF0_COMMAND_ON_FC_PUBLISH; - pkt->data->set(StatusCode, new SrsAmf0String(StatusCodePublishStart)); - pkt->data->set(StatusDescription, new SrsAmf0String("Started publishing stream.")); - - msg->set_packet(pkt, stream_id); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send onFCPublish(NetStream.Publish.Start) message failed. ret=%d", ret); - return ret; - } - srs_info("send onFCPublish(NetStream.Publish.Start) message success."); - } - // publish response onStatus(NetStream.Publish.Start) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); - - pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); - pkt->data->set(StatusCode, new SrsAmf0String(StatusCodePublishStart)); - pkt->data->set(StatusDescription, new SrsAmf0String("Started publishing stream.")); - pkt->data->set(StatusClientId, new SrsAmf0String(RTMP_SIG_CLIENT_ID)); - - msg->set_packet(pkt, stream_id); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send onStatus(NetStream.Publish.Start) message failed. ret=%d", ret); - return ret; - } - srs_info("send onStatus(NetStream.Publish.Start) message success."); - } - - srs_info("FMLE publish success."); - - return ret; -} - -int SrsRtmp::fmle_unpublish(int stream_id, double unpublish_tid) -{ - int ret = ERROR_SUCCESS; - - // publish response onFCUnpublish(NetStream.unpublish.Success) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); - - pkt->command_name = RTMP_AMF0_COMMAND_ON_FC_UNPUBLISH; - pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeUnpublishSuccess)); - pkt->data->set(StatusDescription, new SrsAmf0String("Stop publishing stream.")); - - msg->set_packet(pkt, stream_id); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send onFCUnpublish(NetStream.unpublish.Success) message failed. ret=%d", ret); - return ret; - } - srs_info("send onFCUnpublish(NetStream.unpublish.Success) message success."); - } - // FCUnpublish response - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsFMLEStartResPacket* pkt = new SrsFMLEStartResPacket(unpublish_tid); - - msg->set_packet(pkt, stream_id); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send FCUnpublish response message failed. ret=%d", ret); - return ret; - } - srs_info("send FCUnpublish response message success."); - } - // publish response onStatus(NetStream.Unpublish.Success) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); - - pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); - pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeUnpublishSuccess)); - pkt->data->set(StatusDescription, new SrsAmf0String("Stream is now unpublished")); - pkt->data->set(StatusClientId, new SrsAmf0String(RTMP_SIG_CLIENT_ID)); - - msg->set_packet(pkt, stream_id); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send onStatus(NetStream.Unpublish.Success) message failed. ret=%d", ret); - return ret; - } - srs_info("send onStatus(NetStream.Unpublish.Success) message success."); - } - - srs_info("FMLE unpublish success."); - - return ret; -} - -int SrsRtmp::start_flash_publish(int stream_id) -{ - int ret = ERROR_SUCCESS; - - // publish response onStatus(NetStream.Publish.Start) - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); - - pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); - pkt->data->set(StatusCode, new SrsAmf0String(StatusCodePublishStart)); - pkt->data->set(StatusDescription, new SrsAmf0String("Started publishing stream.")); - pkt->data->set(StatusClientId, new SrsAmf0String(RTMP_SIG_CLIENT_ID)); - - msg->set_packet(pkt, stream_id); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send onStatus(NetStream.Publish.Start) message failed. ret=%d", ret); - return ret; - } - srs_info("send onStatus(NetStream.Publish.Start) message success."); - } - - srs_info("flash publish success."); - - return ret; -} - -int SrsRtmp::identify_create_stream_client(SrsCreateStreamPacket* req, int stream_id, SrsClientType& type, std::string& stream_name) -{ - int ret = ERROR_SUCCESS; - - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsCreateStreamResPacket* pkt = new SrsCreateStreamResPacket(req->transaction_id, stream_id); - - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send createStream response message failed. ret=%d", ret); - return ret; - } - srs_info("send createStream response message success."); - } - - while (true) { - SrsCommonMessage* msg = NULL; - if ((ret = protocol->recv_message(&msg)) != ERROR_SUCCESS) { - srs_error("recv identify client message failed. ret=%d", ret); - return ret; - } - - SrsAutoFree(SrsCommonMessage, msg, false); - - if (!msg->header.is_amf0_command() && !msg->header.is_amf3_command()) { - srs_trace("identify ignore messages except " - "AMF0/AMF3 command message. type=%#x", msg->header.message_type); - continue; - } - - if ((ret = msg->decode_packet(protocol)) != ERROR_SUCCESS) { - srs_error("identify decode message failed. ret=%d", ret); - return ret; - } - - SrsPacket* pkt = msg->get_packet(); - if (dynamic_cast(pkt)) { - SrsPlayPacket* play = dynamic_cast(pkt); - type = SrsClientPlay; - stream_name = play->stream_name; - srs_trace("identity client type=play, stream_name=%s", stream_name.c_str()); - return ret; - } - if (dynamic_cast(pkt)) { - srs_info("identify client by publish, falsh publish."); - return identify_flash_publish_client( - dynamic_cast(pkt), type, stream_name); - } - - srs_trace("ignore AMF0/AMF3 command message."); - } - - return ret; -} - -int SrsRtmp::identify_fmle_publish_client(SrsFMLEStartPacket* req, SrsClientType& type, std::string& stream_name) -{ - int ret = ERROR_SUCCESS; - - type = SrsClientFMLEPublish; - stream_name = req->stream_name; - - // releaseStream response - if (true) { - SrsCommonMessage* msg = new SrsCommonMessage(); - SrsFMLEStartResPacket* pkt = new SrsFMLEStartResPacket(req->transaction_id); - - msg->set_packet(pkt, 0); - - if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { - srs_error("send releaseStream response message failed. ret=%d", ret); - return ret; - } - srs_info("send releaseStream response message success."); - } - - return ret; -} - -int SrsRtmp::identify_flash_publish_client(SrsPublishPacket* req, SrsClientType& type, std::string& stream_name) -{ - int ret = ERROR_SUCCESS; - - type = SrsClientFlashPublish; - stream_name = req->stream_name; - - return ret; -} - +/* +The MIT License (MIT) + +Copyright (c) 2013 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 +#include +#include +#include +#include + +/** +* the signature for packets to client. +*/ +#define RTMP_SIG_FMS_VER "3,5,3,888" +#define RTMP_SIG_AMF0_VER 0 +#define RTMP_SIG_CLIENT_ID "ASAICiss" + +/** +* onStatus consts. +*/ +#define StatusLevel "level" +#define StatusCode "code" +#define StatusDescription "description" +#define StatusDetails "details" +#define StatusClientId "clientid" +// status value +#define StatusLevelStatus "status" +// code value +#define StatusCodeConnectSuccess "NetConnection.Connect.Success" +#define StatusCodeStreamReset "NetStream.Play.Reset" +#define StatusCodeStreamStart "NetStream.Play.Start" +#define StatusCodeStreamPause "NetStream.Pause.Notify" +#define StatusCodeStreamUnpause "NetStream.Unpause.Notify" +#define StatusCodePublishStart "NetStream.Publish.Start" +#define StatusCodeDataStart "NetStream.Data.Start" +#define StatusCodeUnpublishSuccess "NetStream.Unpublish.Success" + +// FMLE +#define RTMP_AMF0_COMMAND_ON_FC_PUBLISH "onFCPublish" +#define RTMP_AMF0_COMMAND_ON_FC_UNPUBLISH "onFCUnpublish" + +// default stream id for response the createStream request. +#define SRS_DEFAULT_SID 1 + +SrsRequest::SrsRequest() +{ + objectEncoding = RTMP_SIG_AMF0_VER; +} + +SrsRequest::~SrsRequest() +{ +} + +int SrsRequest::discovery_app() +{ + int ret = ERROR_SUCCESS; + + 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_verbose("discovery schema=%s", schema.c_str()); + } + + if ((pos = url.find("/")) != std::string::npos) { + vhost = url.substr(0, pos); + url = url.substr(vhost.length() + 1); + srs_verbose("discovery vhost=%s", vhost.c_str()); + } + + port = RTMP_DEFAULT_PORTS; + if ((pos = vhost.find(":")) != std::string::npos) { + port = vhost.substr(pos + 1); + vhost = vhost.substr(0, pos); + srs_verbose("discovery vhost=%s, port=%s", vhost.c_str(), port.c_str()); + } + + app = url; + srs_vhost_resolve(vhost, app); + + // resolve the vhost from config + SrsConfDirective* parsed_vhost = config->get_vhost(vhost); + if (parsed_vhost) { + vhost = parsed_vhost->arg0(); + } + + // TODO: discovery the params of vhost. + + srs_info("discovery app success. schema=%s, vhost=%s, port=%s, app=%s", + schema.c_str(), vhost.c_str(), port.c_str(), app.c_str()); + + if (schema.empty() || vhost.empty() || port.empty() || app.empty()) { + ret = ERROR_RTMP_REQ_TCURL; + srs_error("discovery tcUrl failed. " + "tcUrl=%s, schema=%s, vhost=%s, port=%s, app=%s, ret=%d", + tcUrl.c_str(), schema.c_str(), vhost.c_str(), port.c_str(), app.c_str(), ret); + return ret; + } + + strip(); + + return ret; +} + +std::string SrsRequest::get_stream_url() +{ + std::string url = ""; + + url += vhost; + url += "/"; + url += app; + url += "/"; + url += stream; + + return url; +} + +void SrsRequest::strip() +{ + trim(vhost, "/ \n\r\t"); + trim(app, "/ \n\r\t"); + trim(stream, "/ \n\r\t"); +} + +std::string& SrsRequest::trim(std::string& str, std::string chs) +{ + for (int i = 0; i < (int)chs.length(); i++) { + char ch = chs.at(i); + + for (std::string::iterator it = str.begin(); it != str.end();) { + if (ch == *it) { + it = str.erase(it); + } else { + ++it; + } + } + } + + return str; +} + +SrsResponse::SrsResponse() +{ + stream_id = SRS_DEFAULT_SID; +} + +SrsResponse::~SrsResponse() +{ +} + +SrsRtmpClient::SrsRtmpClient(st_netfd_t _stfd) +{ + stfd = _stfd; + protocol = new SrsProtocol(stfd); +} + +SrsRtmpClient::~SrsRtmpClient() +{ + srs_freep(protocol); +} + +void SrsRtmpClient::set_recv_timeout(int64_t timeout_us) +{ + protocol->set_recv_timeout(timeout_us); +} + +void SrsRtmpClient::set_send_timeout(int64_t timeout_us) +{ + protocol->set_send_timeout(timeout_us); +} + +int64_t SrsRtmpClient::get_recv_bytes() +{ + return protocol->get_recv_bytes(); +} + +int64_t SrsRtmpClient::get_send_bytes() +{ + return protocol->get_send_bytes(); +} + +int SrsRtmpClient::get_recv_kbps() +{ + return protocol->get_recv_kbps(); +} + +int SrsRtmpClient::get_send_kbps() +{ + return protocol->get_send_kbps(); +} + +int SrsRtmpClient::recv_message(SrsCommonMessage** pmsg) +{ + return protocol->recv_message(pmsg); +} + +int SrsRtmpClient::send_message(ISrsMessage* msg) +{ + return protocol->send_message(msg); +} + +int SrsRtmpClient::handshake() +{ + int ret = ERROR_SUCCESS; + + SrsSocket skt(stfd); + + SrsComplexHandshake complex_hs; + SrsSimpleHandshake simple_hs; + if ((ret = simple_hs.handshake_with_server(skt, complex_hs)) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +int SrsRtmpClient::connect_app(std::string app, std::string tc_url) +{ + int ret = ERROR_SUCCESS; + + // Connect(vhost, app) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsConnectAppPacket* pkt = new SrsConnectAppPacket(); + msg->set_packet(pkt, 0); + + pkt->command_object = new SrsAmf0Object(); + pkt->command_object->set("app", new SrsAmf0String(app.c_str())); + pkt->command_object->set("swfUrl", new SrsAmf0String()); + pkt->command_object->set("tcUrl", new SrsAmf0String(tc_url.c_str())); + pkt->command_object->set("fpad", new SrsAmf0Boolean(false)); + pkt->command_object->set("capabilities", new SrsAmf0Number(239)); + pkt->command_object->set("audioCodecs", new SrsAmf0Number(3575)); + pkt->command_object->set("videoCodecs", new SrsAmf0Number(252)); + pkt->command_object->set("videoFunction", new SrsAmf0Number(1)); + pkt->command_object->set("pageUrl", new SrsAmf0String()); + pkt->command_object->set("objectEncoding", new SrsAmf0Number(0)); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + return ret; + } + } + + // Set Window Acknowledgement size(2500000) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsSetWindowAckSizePacket* pkt = new SrsSetWindowAckSizePacket(); + + pkt->ackowledgement_window_size = 2500000; + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + return ret; + } + } + + // expect connect _result + SrsCommonMessage* msg = NULL; + SrsConnectAppResPacket* pkt = NULL; + if ((ret = srs_rtmp_expect_message(protocol, &msg, &pkt)) != ERROR_SUCCESS) { + srs_error("expect connect app response message failed. ret=%d", ret); + return ret; + } + SrsAutoFree(SrsCommonMessage, msg, false); + srs_info("get connect app response message"); + + return ret; +} + +int SrsRtmpClient::create_stream(int& stream_id) +{ + int ret = ERROR_SUCCESS; + + // CreateStream + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsCreateStreamPacket* pkt = new SrsCreateStreamPacket(); + + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + return ret; + } + } + + // CreateStream _result. + if (true) { + SrsCommonMessage* msg = NULL; + SrsCreateStreamResPacket* pkt = NULL; + if ((ret = srs_rtmp_expect_message(protocol, &msg, &pkt)) != ERROR_SUCCESS) { + srs_error("expect create stream response message failed. ret=%d", ret); + return ret; + } + SrsAutoFree(SrsCommonMessage, msg, false); + srs_info("get create stream response message"); + + stream_id = (int)pkt->stream_id; + } + + return ret; +} + +int SrsRtmpClient::play(std::string stream, int stream_id) +{ + int ret = ERROR_SUCCESS; + + // Play(stream) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsPlayPacket* pkt = new SrsPlayPacket(); + + pkt->stream_name = stream; + msg->set_packet(pkt, stream_id); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send play stream failed. " + "stream=%s, stream_id=%d, ret=%d", + stream.c_str(), stream_id, ret); + return ret; + } + } + + // SetBufferLength(1000ms) + int buffer_length_ms = 1000; + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsUserControlPacket* pkt = new SrsUserControlPacket(); + + pkt->event_type = SrcPCUCSetBufferLength; + pkt->event_data = stream_id; + pkt->extra_data = buffer_length_ms; + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send set buffer length failed. " + "stream=%s, stream_id=%d, bufferLength=%d, ret=%d", + stream.c_str(), stream_id, buffer_length_ms, ret); + return ret; + } + } + + return ret; +} + +int SrsRtmpClient::publish(std::string stream, int stream_id) +{ + int ret = ERROR_SUCCESS; + + // publish(stream) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsPublishPacket* pkt = new SrsPublishPacket(); + + pkt->stream_name = stream; + msg->set_packet(pkt, stream_id); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send publish message failed. " + "stream=%s, stream_id=%d, ret=%d", + stream.c_str(), stream_id, ret); + return ret; + } + } + + return ret; +} + +SrsRtmp::SrsRtmp(st_netfd_t client_stfd) +{ + protocol = new SrsProtocol(client_stfd); + stfd = client_stfd; +} + +SrsRtmp::~SrsRtmp() +{ + srs_freep(protocol); +} + +SrsProtocol* SrsRtmp::get_protocol() +{ + return protocol; +} + +void SrsRtmp::set_recv_timeout(int64_t timeout_us) +{ + protocol->set_recv_timeout(timeout_us); +} + +int64_t SrsRtmp::get_recv_timeout() +{ + return protocol->get_recv_timeout(); +} + +void SrsRtmp::set_send_timeout(int64_t timeout_us) +{ + protocol->set_send_timeout(timeout_us); +} + +int64_t SrsRtmp::get_recv_bytes() +{ + return protocol->get_recv_bytes(); +} + +int64_t SrsRtmp::get_send_bytes() +{ + return protocol->get_send_bytes(); +} + +int SrsRtmp::get_recv_kbps() +{ + return protocol->get_recv_kbps(); +} + +int SrsRtmp::get_send_kbps() +{ + return protocol->get_send_kbps(); +} + +int SrsRtmp::recv_message(SrsCommonMessage** pmsg) +{ + return protocol->recv_message(pmsg); +} + +int SrsRtmp::send_message(ISrsMessage* msg) +{ + return protocol->send_message(msg); +} + +int SrsRtmp::handshake() +{ + int ret = ERROR_SUCCESS; + + SrsSocket skt(stfd); + + SrsComplexHandshake complex_hs; + SrsSimpleHandshake simple_hs; + if ((ret = simple_hs.handshake_with_client(skt, complex_hs)) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +int SrsRtmp::connect_app(SrsRequest* req) +{ + int ret = ERROR_SUCCESS; + + SrsCommonMessage* msg = NULL; + SrsConnectAppPacket* pkt = NULL; + if ((ret = srs_rtmp_expect_message(protocol, &msg, &pkt)) != ERROR_SUCCESS) { + srs_error("expect connect app message failed. ret=%d", ret); + return ret; + } + SrsAutoFree(SrsCommonMessage, msg, false); + srs_info("get connect app message"); + + SrsAmf0Any* prop = NULL; + + if ((prop = pkt->command_object->ensure_property_string("tcUrl")) == NULL) { + ret = ERROR_RTMP_REQ_CONNECT; + srs_error("invalid request, must specifies the tcUrl. ret=%d", ret); + return ret; + } + req->tcUrl = srs_amf0_convert(prop)->value; + + if ((prop = pkt->command_object->ensure_property_string("pageUrl")) != NULL) { + req->pageUrl = srs_amf0_convert(prop)->value; + } + + if ((prop = pkt->command_object->ensure_property_string("swfUrl")) != NULL) { + req->swfUrl = srs_amf0_convert(prop)->value; + } + + if ((prop = pkt->command_object->ensure_property_number("objectEncoding")) != NULL) { + req->objectEncoding = srs_amf0_convert(prop)->value; + } + + srs_info("get connect app message params success."); + + return req->discovery_app(); +} + +int SrsRtmp::set_window_ack_size(int ack_size) +{ + int ret = ERROR_SUCCESS; + + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsSetWindowAckSizePacket* pkt = new SrsSetWindowAckSizePacket(); + + pkt->ackowledgement_window_size = ack_size; + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send ack size message failed. ret=%d", ret); + return ret; + } + srs_info("send ack size message success. ack_size=%d", ack_size); + + return ret; +} + +int SrsRtmp::set_peer_bandwidth(int bandwidth, int type) +{ + int ret = ERROR_SUCCESS; + + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsSetPeerBandwidthPacket* pkt = new SrsSetPeerBandwidthPacket(); + + pkt->bandwidth = bandwidth; + pkt->type = type; + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send set bandwidth message failed. ret=%d", ret); + return ret; + } + srs_info("send set bandwidth message " + "success. bandwidth=%d, type=%d", bandwidth, type); + + return ret; +} + +int SrsRtmp::response_connect_app(SrsRequest* req) +{ + int ret = ERROR_SUCCESS; + + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsConnectAppResPacket* pkt = new SrsConnectAppResPacket(); + + pkt->props->set("fmsVer", new SrsAmf0String("FMS/"RTMP_SIG_FMS_VER)); + pkt->props->set("capabilities", new SrsAmf0Number(127)); + pkt->props->set("mode", new SrsAmf0Number(1)); + + pkt->info->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); + pkt->info->set(StatusCode, new SrsAmf0String(StatusCodeConnectSuccess)); + pkt->info->set(StatusDescription, new SrsAmf0String("Connection succeeded")); + pkt->info->set("objectEncoding", new SrsAmf0Number(req->objectEncoding)); + SrsASrsAmf0EcmaArray* data = new SrsASrsAmf0EcmaArray(); + pkt->info->set("data", data); + + data->set("srs_version", new SrsAmf0String(RTMP_SIG_FMS_VER)); + data->set("srs_server", new SrsAmf0String(RTMP_SIG_SRS_NAME)); + data->set("srs_license", new SrsAmf0String(RTMP_SIG_SRS_LICENSE)); + data->set("srs_role", new SrsAmf0String(RTMP_SIG_SRS_ROLE)); + data->set("srs_url", new SrsAmf0String(RTMP_SIG_SRS_URL)); + data->set("srs_version", new SrsAmf0String(RTMP_SIG_SRS_VERSION)); + data->set("srs_site", new SrsAmf0String(RTMP_SIG_SRS_WEB)); + data->set("srs_email", new SrsAmf0String(RTMP_SIG_SRS_EMAIL)); + data->set("srs_copyright", new SrsAmf0String(RTMP_SIG_SRS_COPYRIGHT)); + + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send connect app response message failed. ret=%d", ret); + return ret; + } + srs_info("send connect app response message success."); + + return ret; +} + +int SrsRtmp::on_bw_done() +{ + int ret = ERROR_SUCCESS; + + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsOnBWDonePacket* pkt = new SrsOnBWDonePacket(); + + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send onBWDone message failed. ret=%d", ret); + return ret; + } + srs_info("send onBWDone message success."); + + return ret; +} + +int SrsRtmp::identify_client(int stream_id, SrsClientType& type, std::string& stream_name) +{ + type = SrsClientUnknown; + int ret = ERROR_SUCCESS; + + while (true) { + SrsCommonMessage* msg = NULL; + if ((ret = protocol->recv_message(&msg)) != ERROR_SUCCESS) { + srs_error("recv identify client message failed. ret=%d", ret); + return ret; + } + + SrsAutoFree(SrsCommonMessage, msg, false); + + if (!msg->header.is_amf0_command() && !msg->header.is_amf3_command()) { + srs_trace("identify ignore messages except " + "AMF0/AMF3 command message. type=%#x", msg->header.message_type); + continue; + } + + if ((ret = msg->decode_packet(protocol)) != ERROR_SUCCESS) { + srs_error("identify decode message failed. ret=%d", ret); + return ret; + } + + SrsPacket* pkt = msg->get_packet(); + if (dynamic_cast(pkt)) { + srs_info("identify client by create stream, play or flash publish."); + return identify_create_stream_client( + dynamic_cast(pkt), stream_id, type, stream_name); + } + if (dynamic_cast(pkt)) { + srs_info("identify client by releaseStream, fmle publish."); + return identify_fmle_publish_client( + dynamic_cast(pkt), type, stream_name); + } + + srs_trace("ignore AMF0/AMF3 command message."); + } + + return ret; +} + +int SrsRtmp::set_chunk_size(int chunk_size) +{ + int ret = ERROR_SUCCESS; + + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsSetChunkSizePacket* pkt = new SrsSetChunkSizePacket(); + + pkt->chunk_size = chunk_size; + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send set chunk size message failed. ret=%d", ret); + return ret; + } + srs_info("send set chunk size message success. chunk_size=%d", chunk_size); + + return ret; +} + +int SrsRtmp::start_play(int stream_id) +{ + int ret = ERROR_SUCCESS; + + // StreamBegin + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsUserControlPacket* pkt = new SrsUserControlPacket(); + + pkt->event_type = SrcPCUCStreamBegin; + pkt->event_data = stream_id; + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send PCUC(StreamBegin) message failed. ret=%d", ret); + return ret; + } + srs_info("send PCUC(StreamBegin) message success."); + } + + // onStatus(NetStream.Play.Reset) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); + + pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); + pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeStreamReset)); + pkt->data->set(StatusDescription, new SrsAmf0String("Playing and resetting stream.")); + pkt->data->set(StatusDetails, new SrsAmf0String("stream")); + pkt->data->set(StatusClientId, new SrsAmf0String(RTMP_SIG_CLIENT_ID)); + + msg->set_packet(pkt, stream_id); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send onStatus(NetStream.Play.Reset) message failed. ret=%d", ret); + return ret; + } + srs_info("send onStatus(NetStream.Play.Reset) message success."); + } + + // onStatus(NetStream.Play.Start) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); + + pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); + pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeStreamStart)); + pkt->data->set(StatusDescription, new SrsAmf0String("Started playing stream.")); + pkt->data->set(StatusDetails, new SrsAmf0String("stream")); + pkt->data->set(StatusClientId, new SrsAmf0String(RTMP_SIG_CLIENT_ID)); + + msg->set_packet(pkt, stream_id); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send onStatus(NetStream.Play.Reset) message failed. ret=%d", ret); + return ret; + } + srs_info("send onStatus(NetStream.Play.Reset) message success."); + } + + // |RtmpSampleAccess(false, false) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsSampleAccessPacket* pkt = new SrsSampleAccessPacket(); + + msg->set_packet(pkt, stream_id); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send |RtmpSampleAccess(false, false) message failed. ret=%d", ret); + return ret; + } + srs_info("send |RtmpSampleAccess(false, false) message success."); + } + + // onStatus(NetStream.Data.Start) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsOnStatusDataPacket* pkt = new SrsOnStatusDataPacket(); + + pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeDataStart)); + + msg->set_packet(pkt, stream_id); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send onStatus(NetStream.Data.Start) message failed. ret=%d", ret); + return ret; + } + srs_info("send onStatus(NetStream.Data.Start) message success."); + } + + srs_info("start play success."); + + return ret; +} + +int SrsRtmp::on_play_client_pause(int stream_id, bool is_pause) +{ + int ret = ERROR_SUCCESS; + + if (is_pause) { + // onStatus(NetStream.Pause.Notify) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); + + pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); + pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeStreamPause)); + pkt->data->set(StatusDescription, new SrsAmf0String("Paused stream.")); + + msg->set_packet(pkt, stream_id); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send onStatus(NetStream.Pause.Notify) message failed. ret=%d", ret); + return ret; + } + srs_info("send onStatus(NetStream.Pause.Notify) message success."); + } + // StreamEOF + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsUserControlPacket* pkt = new SrsUserControlPacket(); + + pkt->event_type = SrcPCUCStreamEOF; + pkt->event_data = stream_id; + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send PCUC(StreamEOF) message failed. ret=%d", ret); + return ret; + } + srs_info("send PCUC(StreamEOF) message success."); + } + } else { + // onStatus(NetStream.Unpause.Notify) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); + + pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); + pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeStreamUnpause)); + pkt->data->set(StatusDescription, new SrsAmf0String("Unpaused stream.")); + + msg->set_packet(pkt, stream_id); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send onStatus(NetStream.Unpause.Notify) message failed. ret=%d", ret); + return ret; + } + srs_info("send onStatus(NetStream.Unpause.Notify) message success."); + } + // StreanBegin + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsUserControlPacket* pkt = new SrsUserControlPacket(); + + pkt->event_type = SrcPCUCStreamBegin; + pkt->event_data = stream_id; + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send PCUC(StreanBegin) message failed. ret=%d", ret); + return ret; + } + srs_info("send PCUC(StreanBegin) message success."); + } + } + + return ret; +} + +int SrsRtmp::start_fmle_publish(int stream_id) +{ + int ret = ERROR_SUCCESS; + + // FCPublish + double fc_publish_tid = 0; + if (true) { + SrsCommonMessage* msg = NULL; + SrsFMLEStartPacket* pkt = NULL; + if ((ret = srs_rtmp_expect_message(protocol, &msg, &pkt)) != ERROR_SUCCESS) { + srs_error("recv FCPublish message failed. ret=%d", ret); + return ret; + } + srs_info("recv FCPublish request message success."); + + SrsAutoFree(SrsCommonMessage, msg, false); + fc_publish_tid = pkt->transaction_id; + } + // FCPublish response + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsFMLEStartResPacket* pkt = new SrsFMLEStartResPacket(fc_publish_tid); + + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send FCPublish response message failed. ret=%d", ret); + return ret; + } + srs_info("send FCPublish response message success."); + } + + // createStream + double create_stream_tid = 0; + if (true) { + SrsCommonMessage* msg = NULL; + SrsCreateStreamPacket* pkt = NULL; + if ((ret = srs_rtmp_expect_message(protocol, &msg, &pkt)) != ERROR_SUCCESS) { + srs_error("recv createStream message failed. ret=%d", ret); + return ret; + } + srs_info("recv createStream request message success."); + + SrsAutoFree(SrsCommonMessage, msg, false); + create_stream_tid = pkt->transaction_id; + } + // createStream response + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsCreateStreamResPacket* pkt = new SrsCreateStreamResPacket(create_stream_tid, stream_id); + + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send createStream response message failed. ret=%d", ret); + return ret; + } + srs_info("send createStream response message success."); + } + + // publish + if (true) { + SrsCommonMessage* msg = NULL; + SrsPublishPacket* pkt = NULL; + if ((ret = srs_rtmp_expect_message(protocol, &msg, &pkt)) != ERROR_SUCCESS) { + srs_error("recv publish message failed. ret=%d", ret); + return ret; + } + srs_info("recv publish request message success."); + + SrsAutoFree(SrsCommonMessage, msg, false); + } + // publish response onFCPublish(NetStream.Publish.Start) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); + + pkt->command_name = RTMP_AMF0_COMMAND_ON_FC_PUBLISH; + pkt->data->set(StatusCode, new SrsAmf0String(StatusCodePublishStart)); + pkt->data->set(StatusDescription, new SrsAmf0String("Started publishing stream.")); + + msg->set_packet(pkt, stream_id); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send onFCPublish(NetStream.Publish.Start) message failed. ret=%d", ret); + return ret; + } + srs_info("send onFCPublish(NetStream.Publish.Start) message success."); + } + // publish response onStatus(NetStream.Publish.Start) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); + + pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); + pkt->data->set(StatusCode, new SrsAmf0String(StatusCodePublishStart)); + pkt->data->set(StatusDescription, new SrsAmf0String("Started publishing stream.")); + pkt->data->set(StatusClientId, new SrsAmf0String(RTMP_SIG_CLIENT_ID)); + + msg->set_packet(pkt, stream_id); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send onStatus(NetStream.Publish.Start) message failed. ret=%d", ret); + return ret; + } + srs_info("send onStatus(NetStream.Publish.Start) message success."); + } + + srs_info("FMLE publish success."); + + return ret; +} + +int SrsRtmp::fmle_unpublish(int stream_id, double unpublish_tid) +{ + int ret = ERROR_SUCCESS; + + // publish response onFCUnpublish(NetStream.unpublish.Success) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); + + pkt->command_name = RTMP_AMF0_COMMAND_ON_FC_UNPUBLISH; + pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeUnpublishSuccess)); + pkt->data->set(StatusDescription, new SrsAmf0String("Stop publishing stream.")); + + msg->set_packet(pkt, stream_id); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send onFCUnpublish(NetStream.unpublish.Success) message failed. ret=%d", ret); + return ret; + } + srs_info("send onFCUnpublish(NetStream.unpublish.Success) message success."); + } + // FCUnpublish response + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsFMLEStartResPacket* pkt = new SrsFMLEStartResPacket(unpublish_tid); + + msg->set_packet(pkt, stream_id); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send FCUnpublish response message failed. ret=%d", ret); + return ret; + } + srs_info("send FCUnpublish response message success."); + } + // publish response onStatus(NetStream.Unpublish.Success) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); + + pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); + pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeUnpublishSuccess)); + pkt->data->set(StatusDescription, new SrsAmf0String("Stream is now unpublished")); + pkt->data->set(StatusClientId, new SrsAmf0String(RTMP_SIG_CLIENT_ID)); + + msg->set_packet(pkt, stream_id); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send onStatus(NetStream.Unpublish.Success) message failed. ret=%d", ret); + return ret; + } + srs_info("send onStatus(NetStream.Unpublish.Success) message success."); + } + + srs_info("FMLE unpublish success."); + + return ret; +} + +int SrsRtmp::start_flash_publish(int stream_id) +{ + int ret = ERROR_SUCCESS; + + // publish response onStatus(NetStream.Publish.Start) + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); + + pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus)); + pkt->data->set(StatusCode, new SrsAmf0String(StatusCodePublishStart)); + pkt->data->set(StatusDescription, new SrsAmf0String("Started publishing stream.")); + pkt->data->set(StatusClientId, new SrsAmf0String(RTMP_SIG_CLIENT_ID)); + + msg->set_packet(pkt, stream_id); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send onStatus(NetStream.Publish.Start) message failed. ret=%d", ret); + return ret; + } + srs_info("send onStatus(NetStream.Publish.Start) message success."); + } + + srs_info("flash publish success."); + + return ret; +} + +int SrsRtmp::identify_create_stream_client(SrsCreateStreamPacket* req, int stream_id, SrsClientType& type, std::string& stream_name) +{ + int ret = ERROR_SUCCESS; + + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsCreateStreamResPacket* pkt = new SrsCreateStreamResPacket(req->transaction_id, stream_id); + + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send createStream response message failed. ret=%d", ret); + return ret; + } + srs_info("send createStream response message success."); + } + + while (true) { + SrsCommonMessage* msg = NULL; + if ((ret = protocol->recv_message(&msg)) != ERROR_SUCCESS) { + srs_error("recv identify client message failed. ret=%d", ret); + return ret; + } + + SrsAutoFree(SrsCommonMessage, msg, false); + + if (!msg->header.is_amf0_command() && !msg->header.is_amf3_command()) { + srs_trace("identify ignore messages except " + "AMF0/AMF3 command message. type=%#x", msg->header.message_type); + continue; + } + + if ((ret = msg->decode_packet(protocol)) != ERROR_SUCCESS) { + srs_error("identify decode message failed. ret=%d", ret); + return ret; + } + + SrsPacket* pkt = msg->get_packet(); + if (dynamic_cast(pkt)) { + SrsPlayPacket* play = dynamic_cast(pkt); + type = SrsClientPlay; + stream_name = play->stream_name; + srs_trace("identity client type=play, stream_name=%s", stream_name.c_str()); + return ret; + } + if (dynamic_cast(pkt)) { + srs_info("identify client by publish, falsh publish."); + return identify_flash_publish_client( + dynamic_cast(pkt), type, stream_name); + } + + srs_trace("ignore AMF0/AMF3 command message."); + } + + return ret; +} + +int SrsRtmp::identify_fmle_publish_client(SrsFMLEStartPacket* req, SrsClientType& type, std::string& stream_name) +{ + int ret = ERROR_SUCCESS; + + type = SrsClientFMLEPublish; + stream_name = req->stream_name; + + // releaseStream response + if (true) { + SrsCommonMessage* msg = new SrsCommonMessage(); + SrsFMLEStartResPacket* pkt = new SrsFMLEStartResPacket(req->transaction_id); + + msg->set_packet(pkt, 0); + + if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) { + srs_error("send releaseStream response message failed. ret=%d", ret); + return ret; + } + srs_info("send releaseStream response message success."); + } + + return ret; +} + +int SrsRtmp::identify_flash_publish_client(SrsPublishPacket* req, SrsClientType& type, std::string& stream_name) +{ + int ret = ERROR_SUCCESS; + + type = SrsClientFlashPublish; + stream_name = req->stream_name; + + return ret; +} + diff --git a/trunk/src/core/srs_core_rtmp.hpp b/trunk/src/core/srs_core_rtmp.hpp index 3c18eb479..56fc95112 100644 --- a/trunk/src/core/srs_core_rtmp.hpp +++ b/trunk/src/core/srs_core_rtmp.hpp @@ -32,8 +32,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include -#include - class SrsProtocol; class ISrsMessage; class SrsCommonMessage; diff --git a/trunk/src/core/srs_core_server.cpp b/trunk/src/core/srs_core_server.cpp index ab6e22102..4833673f4 100644 --- a/trunk/src/core/srs_core_server.cpp +++ b/trunk/src/core/srs_core_server.cpp @@ -30,8 +30,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include -#include - #include #include #include @@ -48,24 +46,16 @@ SrsListener::SrsListener(SrsServer* _server, SrsListenerType _type) port = 0; server = _server; type = _type; - - tid = NULL; - loop = false; + + pthread = new SrsThread(this, 0); } SrsListener::~SrsListener() { - if (stfd) { - st_netfd_close(stfd); - stfd = NULL; - } + srs_close_stfd(stfd); - if (tid) { - loop = false; - st_thread_interrupt(tid); - st_thread_join(tid, NULL); - tid = NULL; - } + pthread->stop(); + srs_freep(pthread); // st does not close it sometimes, // close it manually. @@ -118,8 +108,7 @@ int SrsListener::listen(int _port) } srs_verbose("st open socket success. fd=%d", fd); - if ((tid = st_thread_create(listen_thread, this, 1, 0)) == NULL) { - ret = ERROR_ST_CREATE_LISTEN_THREAD; + if ((ret = pthread->start()) != ERROR_SUCCESS) { srs_error("st_thread_create listen thread error. ret=%d", ret); return ret; } @@ -130,41 +119,32 @@ int SrsListener::listen(int _port) return ret; } -void SrsListener::listen_cycle() +void SrsListener::on_enter_loop() +{ + srs_trace("listen cycle start, port=%d, type=%d, fd=%d", port, type, fd); +} + +int SrsListener::cycle() { int ret = ERROR_SUCCESS; - log_context->generate_id(); - srs_trace("listen cycle start, port=%d, type=%d, fd=%d", port, type, fd); + st_netfd_t client_stfd = st_accept(stfd, NULL, NULL, ST_UTIME_NO_TIMEOUT); + + if(client_stfd == NULL){ + // ignore error. + srs_warn("ignore accept thread stoppped for accept client error"); + return ret; + } + srs_verbose("get a client. fd=%d", st_netfd_fileno(client_stfd)); - while (loop) { - st_netfd_t client_stfd = st_accept(stfd, NULL, NULL, ST_UTIME_NO_TIMEOUT); - - if(client_stfd == NULL){ - // ignore error. - srs_warn("ignore accept thread stoppped for accept client error"); - continue; - } - 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); - continue; - } - - srs_verbose("accept client finished. conns=%d, ret=%d", (int)conns.size(), ret); + if ((ret = server->accept_client(type, client_stfd)) != ERROR_SUCCESS) { + srs_warn("accept client error. ret=%d", ret); + return ret; } -} - -void* SrsListener::listen_thread(void* arg) -{ - SrsListener* obj = (SrsListener*)arg; - srs_assert(obj != NULL); - obj->loop = true; - obj->listen_cycle(); + srs_verbose("accept client finished. conns=%d, ret=%d", (int)conns.size(), ret); - return NULL; + return ret; } SrsServer::SrsServer() @@ -312,8 +292,7 @@ int SrsServer::accept_client(SrsListenerType type, st_netfd_t client_stfd) srs_error("exceed the max connections, drop client: " "clients=%d, max=%d, fd=%d", (int)conns.size(), max_connections, fd); - st_netfd_close(client_stfd); - ::close(fd); + srs_close_stfd(client_stfd); return ret; } diff --git a/trunk/src/core/srs_core_server.hpp b/trunk/src/core/srs_core_server.hpp index 1d4bf5c9f..fcc9d61b8 100644 --- a/trunk/src/core/srs_core_server.hpp +++ b/trunk/src/core/srs_core_server.hpp @@ -32,9 +32,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include -#include - #include +#include class SrsServer; class SrsConnection; @@ -45,7 +44,7 @@ enum SrsListenerType SrsListenerApi }; -class SrsListener +class SrsListener : public ISrsThreadHandler { public: SrsListenerType type; @@ -54,16 +53,16 @@ private: st_netfd_t stfd; int port; SrsServer* server; - st_thread_t tid; - bool loop; + SrsThread* pthread; public: SrsListener(SrsServer* _server, SrsListenerType _type); virtual ~SrsListener(); public: virtual int listen(int port); -private: - virtual void listen_cycle(); - static void* listen_thread(void* arg); +// interface ISrsThreadHandler. +public: + virtual void on_enter_loop(); + virtual int cycle(); }; class SrsServer : public SrsReloadHandler diff --git a/trunk/src/core/srs_core_socket.hpp b/trunk/src/core/srs_core_socket.hpp index 74887fc12..42e8cf752 100644 --- a/trunk/src/core/srs_core_socket.hpp +++ b/trunk/src/core/srs_core_socket.hpp @@ -30,8 +30,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include -#include - /** * the socket provides TCP socket over st, * that is, the sync socket mechanism. diff --git a/trunk/src/core/srs_core_source.cpp b/trunk/src/core/srs_core_source.cpp old mode 100755 new mode 100644 index 8071cd6e9..e95f81be8 --- a/trunk/src/core/srs_core_source.cpp +++ b/trunk/src/core/srs_core_source.cpp @@ -1,750 +1,752 @@ -/* -The MIT License (MIT) - -Copyright (c) 2013 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 -#include -#include -#include -#include -#include -#include -#include - -#define CONST_MAX_JITTER_MS 500 -#define DEFAULT_FRAME_TIME_MS 10 -#define PAUSED_SHRINK_SIZE 250 - -SrsRtmpJitter::SrsRtmpJitter() -{ - last_pkt_correct_time = last_pkt_time = 0; -} - -SrsRtmpJitter::~SrsRtmpJitter() -{ -} - -int SrsRtmpJitter::correct(SrsSharedPtrMessage* msg, int tba, int tbv, int64_t* corrected_time) -{ - int ret = ERROR_SUCCESS; - - int sample_rate = tba; - int frame_rate = tbv; - - /** - * we use a very simple time jitter detect/correct algorithm: - * 1. delta: ensure the delta is positive and valid, - * we set the delta to DEFAULT_FRAME_TIME_MS, - * if the delta of time is nagative or greater than CONST_MAX_JITTER_MS. - * 2. last_pkt_time: specifies the original packet time, - * is used to detect next jitter. - * 3. last_pkt_correct_time: simply add the positive delta, - * and enforce the time monotonically. - */ - u_int32_t time = msg->header.timestamp; - int32_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) { - delta = (int32_t)(delta * 1000.0 / sample_rate); - } else if (msg->header.is_video() && frame_rate > 0) { - delta = (int32_t)(delta * 1.0 / frame_rate); - } else { - delta = DEFAULT_FRAME_TIME_MS; - } - - // sometimes, the time is absolute time, so correct it again. - if (delta < 0 || delta > CONST_MAX_JITTER_MS) { - delta = DEFAULT_FRAME_TIME_MS; - } - - srs_info("jitter detected, last_pts=%d, pts=%d, diff=%d, last_time=%d, time=%d, diff=%d", - last_pkt_time, time, time - last_pkt_time, last_pkt_correct_time, last_pkt_correct_time + delta, delta); - } else { - srs_verbose("timestamp no jitter. time=%d, last_pkt=%d, correct_to=%d", - time, last_pkt_time, last_pkt_correct_time + delta); - } - - last_pkt_correct_time = srs_max(0, last_pkt_correct_time + delta); - - if (corrected_time) { - *corrected_time = last_pkt_correct_time; - } - msg->header.timestamp = last_pkt_correct_time; - - last_pkt_time = time; - - return ret; -} - -int SrsRtmpJitter::get_time() -{ - return (int)last_pkt_correct_time; -} - -SrsConsumer::SrsConsumer(SrsSource* _source) -{ - source = _source; - paused = false; - jitter = new SrsRtmpJitter(); -} - -SrsConsumer::~SrsConsumer() -{ - clear(); - - source->on_consumer_destroy(this); - srs_freep(jitter); -} - -int SrsConsumer::get_time() -{ - return jitter->get_time(); -} - -int SrsConsumer::enqueue(SrsSharedPtrMessage* msg, int tba, int tbv) -{ - int ret = ERROR_SUCCESS; - - if ((ret = jitter->correct(msg, tba, tbv)) != ERROR_SUCCESS) { - srs_freep(msg); - return ret; - } - - // TODO: check the queue size and drop packets if overflow. - msgs.push_back(msg); - - return ret; -} - -int SrsConsumer::get_packets(int max_count, SrsSharedPtrMessage**& pmsgs, int& count) -{ - int ret = ERROR_SUCCESS; - - if (msgs.empty()) { - return ret; - } - - if (paused) { - if ((int)msgs.size() >= PAUSED_SHRINK_SIZE) { - shrink(); - } - return ret; - } - - if (max_count == 0) { - count = (int)msgs.size(); - } else { - count = srs_min(max_count, (int)msgs.size()); - } - - pmsgs = new SrsSharedPtrMessage*[count]; - - for (int i = 0; i < count; i++) { - pmsgs[i] = msgs[i]; - } - - if (count == (int)msgs.size()) { - msgs.clear(); - } else { - msgs.erase(msgs.begin(), msgs.begin() + count); - } - - return ret; -} - -int SrsConsumer::on_play_client_pause(bool is_pause) -{ - int ret = ERROR_SUCCESS; - - srs_trace("stream consumer change pause state %d=>%d", paused, is_pause); - paused = is_pause; - - return ret; -} - -void SrsConsumer::shrink() -{ - int i = 0; - std::vector::iterator it; - - // issue the last video iframe. - bool has_video = false; - int frame_to_remove = 0; - std::vector::iterator iframe = msgs.end(); - for (i = 0, it = msgs.begin(); it != msgs.end(); ++it, i++) { - SrsSharedPtrMessage* msg = *it; - if (msg->header.is_video()) { - has_video = true; - if (SrsCodec::video_is_keyframe(msg->payload, msg->size)) { - iframe = it; - frame_to_remove = i + 1; - } - } - } - - // last iframe is the first elem, ignore it. - if (iframe == msgs.begin()) { - return; - } - - // recalc the frame to remove - if (iframe == msgs.end()) { - frame_to_remove = 0; - } - if (!has_video) { - frame_to_remove = (int)msgs.size(); - } - - srs_trace("shrink the cache queue, has_video=%d, has_iframe=%d, size=%d, removed=%d", - has_video, iframe != msgs.end(), (int)msgs.size(), frame_to_remove); - - // if no video, remove all audio. - if (!has_video) { - clear(); - return; - } - - // if exists video Iframe, remove the frames before it. - if (iframe != msgs.end()) { - for (it = msgs.begin(); it != iframe; ++it) { - SrsSharedPtrMessage* msg = *it; - srs_freep(msg); - } - msgs.erase(msgs.begin(), iframe); - } -} - -void SrsConsumer::clear() -{ - std::vector::iterator it; - for (it = msgs.begin(); it != msgs.end(); ++it) { - SrsSharedPtrMessage* msg = *it; - srs_freep(msg); - } - msgs.clear(); -} - -SrsGopCache::SrsGopCache() -{ - cached_video_count = 0; - enable_gop_cache = true; -} - -SrsGopCache::~SrsGopCache() -{ - clear(); -} - -void SrsGopCache::set(bool enabled) -{ - enable_gop_cache = enabled; - - if (!enabled) { - srs_info("disable gop cache, clear %d packets.", (int)gop_cache.size()); - clear(); - return; - } - - srs_info("enable gop cache"); -} - -int SrsGopCache::cache(SrsSharedPtrMessage* msg) -{ - int ret = ERROR_SUCCESS; - - if (!enable_gop_cache) { - srs_verbose("gop cache is disabled."); - return ret; - } - - // got video, update the video count if acceptable - if (msg->header.is_video()) { - cached_video_count++; - } - - // no acceptable video or pure audio, disable the cache. - if (cached_video_count == 0) { - srs_verbose("ignore any frame util got a h264 video frame."); - return ret; - } - - // clear gop cache when got key frame - if (msg->header.is_video() && SrsCodec::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()); - - clear(); - - // curent msg is video frame, so we set to 1. - cached_video_count = 1; - } - - // cache the frame. - gop_cache.push_back(msg->copy()); - - return ret; -} - -void SrsGopCache::clear() -{ - std::vector::iterator it; - for (it = gop_cache.begin(); it != gop_cache.end(); ++it) { - SrsSharedPtrMessage* msg = *it; - srs_freep(msg); - } - gop_cache.clear(); - - cached_video_count = 0; -} - -int SrsGopCache::dump(SrsConsumer* consumer, int tba, int tbv) -{ - int ret = ERROR_SUCCESS; - - std::vector::iterator it; - for (it = gop_cache.begin(); it != gop_cache.end(); ++it) { - SrsSharedPtrMessage* msg = *it; - if ((ret = consumer->enqueue(msg->copy(), tba, tbv)) != ERROR_SUCCESS) { - srs_error("dispatch cached gop failed. ret=%d", ret); - return ret; - } - } - srs_trace("dispatch cached gop success. count=%d, duration=%d", (int)gop_cache.size(), consumer->get_time()); - - return ret; -} - -std::map SrsSource::pool; - -SrsSource* SrsSource::find(std::string stream_url) -{ - if (pool.find(stream_url) == pool.end()) { - pool[stream_url] = new SrsSource(stream_url); - srs_verbose("create new source for url=%s", stream_url.c_str()); - } - - return pool[stream_url]; -} - -SrsSource::SrsSource(std::string _stream_url) -{ - stream_url = _stream_url; - -#ifdef SRS_HLS - hls = new SrsHls(); -#endif -#ifdef SRS_FFMPEG - encoder = new SrsEncoder(); -#endif - - cache_metadata = cache_sh_video = cache_sh_audio = NULL; - - frame_rate = sample_rate = 0; - _can_publish = true; - - gop_cache = new SrsGopCache(); -} - -SrsSource::~SrsSource() -{ - if (true) { - std::vector::iterator it; - for (it = consumers.begin(); it != consumers.end(); ++it) { - SrsConsumer* consumer = *it; - srs_freep(consumer); - } - consumers.clear(); - } - - if (true) { - std::vector::iterator it; - for (it = forwarders.begin(); it != forwarders.end(); ++it) { - SrsForwarder* forwarder = *it; - srs_freep(forwarder); - } - forwarders.clear(); - } - - srs_freep(cache_metadata); - srs_freep(cache_sh_video); - srs_freep(cache_sh_audio); - - srs_freep(gop_cache); - -#ifdef SRS_HLS - srs_freep(hls); -#endif -#ifdef SRS_FFMPEG - srs_freep(encoder); -#endif -} - -bool SrsSource::can_publish() -{ - return _can_publish; -} - -int SrsSource::on_meta_data(SrsCommonMessage* msg, SrsOnMetaDataPacket* metadata) -{ - int ret = ERROR_SUCCESS; - -#ifdef SRS_HLS - if ((ret = hls->on_meta_data(metadata)) != ERROR_SUCCESS) { - srs_error("hls process onMetaData message failed. ret=%d", ret); - return ret; - } -#endif - - metadata->metadata->set("server", new SrsAmf0String( - RTMP_SIG_SRS_KEY" "RTMP_SIG_SRS_VERSION" ("RTMP_SIG_SRS_URL_SHORT")")); - - SrsAmf0Any* prop = NULL; - if ((prop = metadata->metadata->get_property("audiosamplerate")) != NULL) { - if (prop->is_number()) { - sample_rate = (int)(srs_amf0_convert(prop)->value); - } - } - if ((prop = metadata->metadata->get_property("framerate")) != NULL) { - if (prop->is_number()) { - frame_rate = (int)(srs_amf0_convert(prop)->value); - } - } - - // encode the metadata to payload - int size = metadata->get_payload_length(); - if (size <= 0) { - srs_warn("ignore the invalid metadata. size=%d", size); - return ret; - } - srs_verbose("get metadata size success."); - - char* payload = new char[size]; - memset(payload, 0, size); - if ((ret = metadata->encode(size, payload)) != ERROR_SUCCESS) { - srs_error("encode metadata error. ret=%d", ret); - srs_freepa(payload); - return ret; - } - srs_verbose("encode metadata success."); - - // create a shared ptr message. - srs_freep(cache_metadata); - cache_metadata = new SrsSharedPtrMessage(); - - // dump message to shared ptr message. - if ((ret = cache_metadata->initialize(msg, payload, size)) != ERROR_SUCCESS) { - srs_error("initialize the cache metadata failed. ret=%d", ret); - return ret; - } - srs_verbose("initialize shared ptr metadata success."); - - // copy to all consumer - if (true) { - std::vector::iterator it; - for (it = consumers.begin(); it != consumers.end(); ++it) { - SrsConsumer* consumer = *it; - if ((ret = consumer->enqueue(cache_metadata->copy(), sample_rate, frame_rate)) != ERROR_SUCCESS) { - srs_error("dispatch the metadata failed. ret=%d", ret); - return ret; - } - } - srs_trace("dispatch metadata success."); - } - - // copy to all forwarders - if (true) { - 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) { - srs_error("forwarder process onMetaData message failed. ret=%d", ret); - return ret; - } - } - } - - return ret; -} - -int SrsSource::on_audio(SrsCommonMessage* audio) -{ - int ret = ERROR_SUCCESS; - - SrsSharedPtrMessage* msg = new SrsSharedPtrMessage(); - SrsAutoFree(SrsSharedPtrMessage, msg, false); - if ((ret = msg->initialize(audio)) != ERROR_SUCCESS) { - srs_error("initialize the audio failed. ret=%d", ret); - return ret; - } - srs_verbose("initialize shared ptr audio success."); - -#ifdef SRS_HLS - if ((ret = hls->on_audio(msg->copy())) != ERROR_SUCCESS) { - srs_warn("hls process audio message failed, ignore and disable hls. ret=%d", ret); - - // unpublish, ignore ret. - hls->on_unpublish(); - - // ignore. - ret = ERROR_SUCCESS; - } -#endif - - // copy to all consumer - if (true) { - std::vector::iterator it; - for (it = consumers.begin(); it != consumers.end(); ++it) { - SrsConsumer* consumer = *it; - if ((ret = consumer->enqueue(msg->copy(), sample_rate, frame_rate)) != ERROR_SUCCESS) { - srs_error("dispatch the audio failed. ret=%d", ret); - return ret; - } - } - srs_info("dispatch audio success."); - } - - // copy to all forwarders. - if (true) { - std::vector::iterator it; - for (it = forwarders.begin(); it != forwarders.end(); ++it) { - SrsForwarder* forwarder = *it; - if ((ret = forwarder->on_audio(msg->copy())) != ERROR_SUCCESS) { - srs_error("forwarder process audio message failed. ret=%d", ret); - return ret; - } - } - } - - // cache the sequence header if h264 - if (SrsCodec::audio_is_sequence_header(msg->payload, msg->size)) { - srs_freep(cache_sh_audio); - cache_sh_audio = msg->copy(); - srs_trace("update audio sequence header success. size=%d", msg->header.payload_length); - return ret; - } - - // cache the last gop packets - if ((ret = gop_cache->cache(msg)) != ERROR_SUCCESS) { - srs_error("shrink gop cache failed. ret=%d", ret); - return ret; - } - srs_verbose("cache gop success."); - - return ret; -} - -int SrsSource::on_video(SrsCommonMessage* video) -{ - int ret = ERROR_SUCCESS; - - SrsSharedPtrMessage* msg = new SrsSharedPtrMessage(); - SrsAutoFree(SrsSharedPtrMessage, msg, false); - if ((ret = msg->initialize(video)) != ERROR_SUCCESS) { - srs_error("initialize the video failed. ret=%d", ret); - return ret; - } - srs_verbose("initialize shared ptr video success."); - -#ifdef SRS_HLS - if ((ret = hls->on_video(msg->copy())) != ERROR_SUCCESS) { - srs_warn("hls process video message failed, ignore and disable hls. ret=%d", ret); - - // unpublish, ignore ret. - hls->on_unpublish(); - - // ignore. - ret = ERROR_SUCCESS; - } -#endif - - // copy to all consumer - if (true) { - std::vector::iterator it; - for (it = consumers.begin(); it != consumers.end(); ++it) { - SrsConsumer* consumer = *it; - if ((ret = consumer->enqueue(msg->copy(), sample_rate, frame_rate)) != ERROR_SUCCESS) { - srs_error("dispatch the video failed. ret=%d", ret); - return ret; - } - } - srs_info("dispatch video success."); - } - - // copy to all forwarders. - if (true) { - std::vector::iterator it; - for (it = forwarders.begin(); it != forwarders.end(); ++it) { - SrsForwarder* forwarder = *it; - if ((ret = forwarder->on_video(msg->copy())) != ERROR_SUCCESS) { - srs_error("forwarder process video message failed. ret=%d", ret); - return ret; - } - } - } - - // cache the sequence header if h264 - if (SrsCodec::video_is_sequence_header(msg->payload, msg->size)) { - srs_freep(cache_sh_video); - cache_sh_video = msg->copy(); - srs_trace("update video sequence header success. size=%d", msg->header.payload_length); - return ret; - } - - // cache the last gop packets - if ((ret = gop_cache->cache(msg)) != ERROR_SUCCESS) { - srs_error("shrink gop cache failed. ret=%d", ret); - return ret; - } - srs_verbose("cache gop success."); - - return ret; -} - -int SrsSource::on_publish(SrsRequest* req) -{ - int ret = ERROR_SUCCESS; - - _can_publish = false; - - // TODO: support reload. - - // create forwarders - SrsConfDirective* conf = config->get_forward(req->vhost); - for (int i = 0; conf && i < (int)conf->args.size(); i++) { - std::string forward_server = conf->args.at(i); - - SrsForwarder* forwarder = new SrsForwarder(); - forwarders.push_back(forwarder); - - if ((ret = forwarder->on_publish(req, forward_server)) != ERROR_SUCCESS) { - srs_error("start forwarder failed. " - "vhost=%s, app=%s, stream=%s, forward-to=%s", - req->vhost.c_str(), req->app.c_str(), req->stream.c_str(), - forward_server.c_str()); - return ret; - } - } - -#ifdef SRS_FFMPEG - if ((ret = encoder->on_publish(req)) != ERROR_SUCCESS) { - return ret; - } -#endif - -#ifdef SRS_HLS - if ((ret = hls->on_publish(req)) != ERROR_SUCCESS) { - return ret; - } -#endif - - return ret; -} - -void SrsSource::on_unpublish() -{ - // close all forwarders - std::vector::iterator it; - for (it = forwarders.begin(); it != forwarders.end(); ++it) { - SrsForwarder* forwarder = *it; - forwarder->on_unpublish(); - srs_freep(forwarder); - } - forwarders.clear(); - -#ifdef SRS_FFMPEG - encoder->on_unpublish(); -#endif - - // TODO: HLS should continue previous sequence and stream. -#ifdef SRS_HLS - hls->on_unpublish(); -#endif - - gop_cache->clear(); - - srs_freep(cache_metadata); - frame_rate = sample_rate = 0; - - srs_freep(cache_sh_video); - srs_freep(cache_sh_audio); - - srs_trace("clear cache/metadata/sequence-headers when unpublish."); - - _can_publish = true; -} - - int SrsSource::create_consumer(SrsConsumer*& consumer) -{ - int ret = ERROR_SUCCESS; - - consumer = new SrsConsumer(this); - consumers.push_back(consumer); - - if (cache_metadata && (ret = consumer->enqueue(cache_metadata->copy(), sample_rate, frame_rate)) != ERROR_SUCCESS) { - srs_error("dispatch metadata failed. ret=%d", ret); - return ret; - } - srs_info("dispatch metadata success"); - - if (cache_sh_video && (ret = consumer->enqueue(cache_sh_video->copy(), sample_rate, frame_rate)) != 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(), sample_rate, frame_rate)) != ERROR_SUCCESS) { - srs_error("dispatch audio sequence header failed. ret=%d", ret); - return ret; - } - srs_info("dispatch audio sequence header success"); - - if ((ret = gop_cache->dump(consumer, sample_rate, frame_rate)) != ERROR_SUCCESS) { - return ret; - } - - return ret; -} - -void SrsSource::on_consumer_destroy(SrsConsumer* consumer) -{ - std::vector::iterator it; - it = std::find(consumers.begin(), consumers.end(), consumer); - if (it != consumers.end()) { - consumers.erase(it); - } - srs_info("handle consumer destroy success."); -} - -void SrsSource::set_cache(bool enabled) -{ - gop_cache->set(enabled); -} - +/* +The MIT License (MIT) + +Copyright (c) 2013 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 +#include +#include +#include +#include +#include +#include +#include + +#define CONST_MAX_JITTER_MS 500 +#define DEFAULT_FRAME_TIME_MS 10 +#define PAUSED_SHRINK_SIZE 250 + +SrsRtmpJitter::SrsRtmpJitter() +{ + last_pkt_correct_time = last_pkt_time = 0; +} + +SrsRtmpJitter::~SrsRtmpJitter() +{ +} + +int SrsRtmpJitter::correct(SrsSharedPtrMessage* msg, int tba, int tbv, int64_t* corrected_time) +{ + int ret = ERROR_SUCCESS; + + int sample_rate = tba; + int frame_rate = tbv; + + /** + * we use a very simple time jitter detect/correct algorithm: + * 1. delta: ensure the delta is positive and valid, + * we set the delta to DEFAULT_FRAME_TIME_MS, + * if the delta of time is nagative or greater than CONST_MAX_JITTER_MS. + * 2. last_pkt_time: specifies the original packet time, + * is used to detect next jitter. + * 3. last_pkt_correct_time: simply add the positive delta, + * and enforce the time monotonically. + */ + u_int32_t time = msg->header.timestamp; + int32_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) { + delta = (int32_t)(delta * 1000.0 / sample_rate); + } else if (msg->header.is_video() && frame_rate > 0) { + delta = (int32_t)(delta * 1.0 / frame_rate); + } else { + delta = DEFAULT_FRAME_TIME_MS; + } + + // sometimes, the time is absolute time, so correct it again. + if (delta < 0 || delta > CONST_MAX_JITTER_MS) { + delta = DEFAULT_FRAME_TIME_MS; + } + + srs_info("jitter detected, last_pts=%d, pts=%d, diff=%d, last_time=%d, time=%d, diff=%d", + last_pkt_time, time, time - last_pkt_time, last_pkt_correct_time, last_pkt_correct_time + delta, delta); + } else { + srs_verbose("timestamp no jitter. time=%d, last_pkt=%d, correct_to=%d", + time, last_pkt_time, last_pkt_correct_time + delta); + } + + last_pkt_correct_time = srs_max(0, last_pkt_correct_time + delta); + + if (corrected_time) { + *corrected_time = last_pkt_correct_time; + } + msg->header.timestamp = last_pkt_correct_time; + + last_pkt_time = time; + + return ret; +} + +int SrsRtmpJitter::get_time() +{ + return (int)last_pkt_correct_time; +} + +SrsConsumer::SrsConsumer(SrsSource* _source) +{ + source = _source; + paused = false; + jitter = new SrsRtmpJitter(); +} + +SrsConsumer::~SrsConsumer() +{ + clear(); + + source->on_consumer_destroy(this); + srs_freep(jitter); +} + +int SrsConsumer::get_time() +{ + return jitter->get_time(); +} + +int SrsConsumer::enqueue(SrsSharedPtrMessage* msg, int tba, int tbv) +{ + int ret = ERROR_SUCCESS; + + if ((ret = jitter->correct(msg, tba, tbv)) != ERROR_SUCCESS) { + srs_freep(msg); + return ret; + } + + // TODO: check the queue size and drop packets if overflow. + msgs.push_back(msg); + + return ret; +} + +int SrsConsumer::get_packets(int max_count, SrsSharedPtrMessage**& pmsgs, int& count) +{ + int ret = ERROR_SUCCESS; + + if (msgs.empty()) { + return ret; + } + + if (paused) { + if ((int)msgs.size() >= PAUSED_SHRINK_SIZE) { + shrink(); + } + return ret; + } + + if (max_count == 0) { + count = (int)msgs.size(); + } else { + count = srs_min(max_count, (int)msgs.size()); + } + + pmsgs = new SrsSharedPtrMessage*[count]; + + for (int i = 0; i < count; i++) { + pmsgs[i] = msgs[i]; + } + + if (count == (int)msgs.size()) { + msgs.clear(); + } else { + msgs.erase(msgs.begin(), msgs.begin() + count); + } + + return ret; +} + +int SrsConsumer::on_play_client_pause(bool is_pause) +{ + int ret = ERROR_SUCCESS; + + srs_trace("stream consumer change pause state %d=>%d", paused, is_pause); + paused = is_pause; + + return ret; +} + +void SrsConsumer::shrink() +{ + int i = 0; + std::vector::iterator it; + + // issue the last video iframe. + bool has_video = false; + int frame_to_remove = 0; + std::vector::iterator iframe = msgs.end(); + for (i = 0, it = msgs.begin(); it != msgs.end(); ++it, i++) { + SrsSharedPtrMessage* msg = *it; + if (msg->header.is_video()) { + has_video = true; + if (SrsCodec::video_is_keyframe(msg->payload, msg->size)) { + iframe = it; + frame_to_remove = i + 1; + } + } + } + + // last iframe is the first elem, ignore it. + if (iframe == msgs.begin()) { + return; + } + + // recalc the frame to remove + if (iframe == msgs.end()) { + frame_to_remove = 0; + } + if (!has_video) { + frame_to_remove = (int)msgs.size(); + } + + srs_trace("shrink the cache queue, has_video=%d, has_iframe=%d, size=%d, removed=%d", + has_video, iframe != msgs.end(), (int)msgs.size(), frame_to_remove); + + // if no video, remove all audio. + if (!has_video) { + clear(); + return; + } + + // if exists video Iframe, remove the frames before it. + if (iframe != msgs.end()) { + for (it = msgs.begin(); it != iframe; ++it) { + SrsSharedPtrMessage* msg = *it; + srs_freep(msg); + } + msgs.erase(msgs.begin(), iframe); + } +} + +void SrsConsumer::clear() +{ + std::vector::iterator it; + for (it = msgs.begin(); it != msgs.end(); ++it) { + SrsSharedPtrMessage* msg = *it; + srs_freep(msg); + } + msgs.clear(); +} + +SrsGopCache::SrsGopCache() +{ + cached_video_count = 0; + enable_gop_cache = true; +} + +SrsGopCache::~SrsGopCache() +{ + clear(); +} + +void SrsGopCache::set(bool enabled) +{ + enable_gop_cache = enabled; + + if (!enabled) { + srs_info("disable gop cache, clear %d packets.", (int)gop_cache.size()); + clear(); + return; + } + + srs_info("enable gop cache"); +} + +int SrsGopCache::cache(SrsSharedPtrMessage* msg) +{ + int ret = ERROR_SUCCESS; + + if (!enable_gop_cache) { + srs_verbose("gop cache is disabled."); + return ret; + } + + // got video, update the video count if acceptable + if (msg->header.is_video()) { + cached_video_count++; + } + + // no acceptable video or pure audio, disable the cache. + if (cached_video_count == 0) { + srs_verbose("ignore any frame util got a h264 video frame."); + return ret; + } + + // clear gop cache when got key frame + if (msg->header.is_video() && SrsCodec::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()); + + clear(); + + // curent msg is video frame, so we set to 1. + cached_video_count = 1; + } + + // cache the frame. + gop_cache.push_back(msg->copy()); + + return ret; +} + +void SrsGopCache::clear() +{ + std::vector::iterator it; + for (it = gop_cache.begin(); it != gop_cache.end(); ++it) { + SrsSharedPtrMessage* msg = *it; + srs_freep(msg); + } + gop_cache.clear(); + + cached_video_count = 0; +} + +int SrsGopCache::dump(SrsConsumer* consumer, int tba, int tbv) +{ + int ret = ERROR_SUCCESS; + + std::vector::iterator it; + for (it = gop_cache.begin(); it != gop_cache.end(); ++it) { + SrsSharedPtrMessage* msg = *it; + if ((ret = consumer->enqueue(msg->copy(), tba, tbv)) != ERROR_SUCCESS) { + srs_error("dispatch cached gop failed. ret=%d", ret); + return ret; + } + } + srs_trace("dispatch cached gop success. count=%d, duration=%d", (int)gop_cache.size(), consumer->get_time()); + + return ret; +} + +std::map SrsSource::pool; + +SrsSource* SrsSource::find(std::string stream_url) +{ + if (pool.find(stream_url) == pool.end()) { + pool[stream_url] = new SrsSource(stream_url); + srs_verbose("create new source for url=%s", stream_url.c_str()); + } + + return pool[stream_url]; +} + +SrsSource::SrsSource(std::string _stream_url) +{ + stream_url = _stream_url; + +#ifdef SRS_HLS + hls = new SrsHls(); +#endif +#ifdef SRS_FFMPEG + encoder = new SrsEncoder(); +#endif + + cache_metadata = cache_sh_video = cache_sh_audio = NULL; + + frame_rate = sample_rate = 0; + _can_publish = true; + + gop_cache = new SrsGopCache(); +} + +SrsSource::~SrsSource() +{ + if (true) { + std::vector::iterator it; + for (it = consumers.begin(); it != consumers.end(); ++it) { + SrsConsumer* consumer = *it; + srs_freep(consumer); + } + consumers.clear(); + } + + if (true) { + std::vector::iterator it; + for (it = forwarders.begin(); it != forwarders.end(); ++it) { + SrsForwarder* forwarder = *it; + srs_freep(forwarder); + } + forwarders.clear(); + } + + srs_freep(cache_metadata); + srs_freep(cache_sh_video); + srs_freep(cache_sh_audio); + + srs_freep(gop_cache); + +#ifdef SRS_HLS + srs_freep(hls); +#endif +#ifdef SRS_FFMPEG + srs_freep(encoder); +#endif +} + +bool SrsSource::can_publish() +{ + return _can_publish; +} + +int SrsSource::on_meta_data(SrsCommonMessage* msg, SrsOnMetaDataPacket* metadata) +{ + int ret = ERROR_SUCCESS; + +#ifdef SRS_HLS + if ((ret = hls->on_meta_data(metadata)) != ERROR_SUCCESS) { + srs_error("hls process onMetaData message failed. ret=%d", ret); + return ret; + } +#endif + + metadata->metadata->set("server", new SrsAmf0String( + RTMP_SIG_SRS_KEY" "RTMP_SIG_SRS_VERSION" ("RTMP_SIG_SRS_URL_SHORT")")); + metadata->metadata->set("contributor", + new SrsAmf0String(RTMP_SIG_SRS_CONTRIBUTOR)); + + SrsAmf0Any* prop = NULL; + if ((prop = metadata->metadata->get_property("audiosamplerate")) != NULL) { + if (prop->is_number()) { + sample_rate = (int)(srs_amf0_convert(prop)->value); + } + } + if ((prop = metadata->metadata->get_property("framerate")) != NULL) { + if (prop->is_number()) { + frame_rate = (int)(srs_amf0_convert(prop)->value); + } + } + + // encode the metadata to payload + int size = metadata->get_payload_length(); + if (size <= 0) { + srs_warn("ignore the invalid metadata. size=%d", size); + return ret; + } + srs_verbose("get metadata size success."); + + char* payload = new char[size]; + memset(payload, 0, size); + if ((ret = metadata->encode(size, payload)) != ERROR_SUCCESS) { + srs_error("encode metadata error. ret=%d", ret); + srs_freepa(payload); + return ret; + } + srs_verbose("encode metadata success."); + + // create a shared ptr message. + srs_freep(cache_metadata); + cache_metadata = new SrsSharedPtrMessage(); + + // dump message to shared ptr message. + if ((ret = cache_metadata->initialize(msg, payload, size)) != ERROR_SUCCESS) { + srs_error("initialize the cache metadata failed. ret=%d", ret); + return ret; + } + srs_verbose("initialize shared ptr metadata success."); + + // copy to all consumer + if (true) { + std::vector::iterator it; + for (it = consumers.begin(); it != consumers.end(); ++it) { + SrsConsumer* consumer = *it; + if ((ret = consumer->enqueue(cache_metadata->copy(), sample_rate, frame_rate)) != ERROR_SUCCESS) { + srs_error("dispatch the metadata failed. ret=%d", ret); + return ret; + } + } + srs_trace("dispatch metadata success."); + } + + // copy to all forwarders + if (true) { + 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) { + srs_error("forwarder process onMetaData message failed. ret=%d", ret); + return ret; + } + } + } + + return ret; +} + +int SrsSource::on_audio(SrsCommonMessage* audio) +{ + int ret = ERROR_SUCCESS; + + SrsSharedPtrMessage* msg = new SrsSharedPtrMessage(); + SrsAutoFree(SrsSharedPtrMessage, msg, false); + if ((ret = msg->initialize(audio)) != ERROR_SUCCESS) { + srs_error("initialize the audio failed. ret=%d", ret); + return ret; + } + srs_verbose("initialize shared ptr audio success."); + +#ifdef SRS_HLS + if ((ret = hls->on_audio(msg->copy())) != ERROR_SUCCESS) { + srs_warn("hls process audio message failed, ignore and disable hls. ret=%d", ret); + + // unpublish, ignore ret. + hls->on_unpublish(); + + // ignore. + ret = ERROR_SUCCESS; + } +#endif + + // copy to all consumer + if (true) { + std::vector::iterator it; + for (it = consumers.begin(); it != consumers.end(); ++it) { + SrsConsumer* consumer = *it; + if ((ret = consumer->enqueue(msg->copy(), sample_rate, frame_rate)) != ERROR_SUCCESS) { + srs_error("dispatch the audio failed. ret=%d", ret); + return ret; + } + } + srs_info("dispatch audio success."); + } + + // copy to all forwarders. + if (true) { + std::vector::iterator it; + for (it = forwarders.begin(); it != forwarders.end(); ++it) { + SrsForwarder* forwarder = *it; + if ((ret = forwarder->on_audio(msg->copy())) != ERROR_SUCCESS) { + srs_error("forwarder process audio message failed. ret=%d", ret); + return ret; + } + } + } + + // cache the sequence header if h264 + if (SrsCodec::audio_is_sequence_header(msg->payload, msg->size)) { + srs_freep(cache_sh_audio); + cache_sh_audio = msg->copy(); + srs_trace("update audio sequence header success. size=%d", msg->header.payload_length); + return ret; + } + + // cache the last gop packets + if ((ret = gop_cache->cache(msg)) != ERROR_SUCCESS) { + srs_error("shrink gop cache failed. ret=%d", ret); + return ret; + } + srs_verbose("cache gop success."); + + return ret; +} + +int SrsSource::on_video(SrsCommonMessage* video) +{ + int ret = ERROR_SUCCESS; + + SrsSharedPtrMessage* msg = new SrsSharedPtrMessage(); + SrsAutoFree(SrsSharedPtrMessage, msg, false); + if ((ret = msg->initialize(video)) != ERROR_SUCCESS) { + srs_error("initialize the video failed. ret=%d", ret); + return ret; + } + srs_verbose("initialize shared ptr video success."); + +#ifdef SRS_HLS + if ((ret = hls->on_video(msg->copy())) != ERROR_SUCCESS) { + srs_warn("hls process video message failed, ignore and disable hls. ret=%d", ret); + + // unpublish, ignore ret. + hls->on_unpublish(); + + // ignore. + ret = ERROR_SUCCESS; + } +#endif + + // copy to all consumer + if (true) { + std::vector::iterator it; + for (it = consumers.begin(); it != consumers.end(); ++it) { + SrsConsumer* consumer = *it; + if ((ret = consumer->enqueue(msg->copy(), sample_rate, frame_rate)) != ERROR_SUCCESS) { + srs_error("dispatch the video failed. ret=%d", ret); + return ret; + } + } + srs_info("dispatch video success."); + } + + // copy to all forwarders. + if (true) { + std::vector::iterator it; + for (it = forwarders.begin(); it != forwarders.end(); ++it) { + SrsForwarder* forwarder = *it; + if ((ret = forwarder->on_video(msg->copy())) != ERROR_SUCCESS) { + srs_error("forwarder process video message failed. ret=%d", ret); + return ret; + } + } + } + + // cache the sequence header if h264 + if (SrsCodec::video_is_sequence_header(msg->payload, msg->size)) { + srs_freep(cache_sh_video); + cache_sh_video = msg->copy(); + srs_trace("update video sequence header success. size=%d", msg->header.payload_length); + return ret; + } + + // cache the last gop packets + if ((ret = gop_cache->cache(msg)) != ERROR_SUCCESS) { + srs_error("shrink gop cache failed. ret=%d", ret); + return ret; + } + srs_verbose("cache gop success."); + + return ret; +} + +int SrsSource::on_publish(SrsRequest* req) +{ + int ret = ERROR_SUCCESS; + + _can_publish = false; + + // TODO: support reload. + + // create forwarders + SrsConfDirective* conf = config->get_forward(req->vhost); + for (int i = 0; conf && i < (int)conf->args.size(); i++) { + std::string forward_server = conf->args.at(i); + + SrsForwarder* forwarder = new SrsForwarder(); + forwarders.push_back(forwarder); + + if ((ret = forwarder->on_publish(req, forward_server)) != ERROR_SUCCESS) { + srs_error("start forwarder failed. " + "vhost=%s, app=%s, stream=%s, forward-to=%s", + req->vhost.c_str(), req->app.c_str(), req->stream.c_str(), + forward_server.c_str()); + return ret; + } + } + +#ifdef SRS_FFMPEG + if ((ret = encoder->on_publish(req)) != ERROR_SUCCESS) { + return ret; + } +#endif + +#ifdef SRS_HLS + if ((ret = hls->on_publish(req)) != ERROR_SUCCESS) { + return ret; + } +#endif + + return ret; +} + +void SrsSource::on_unpublish() +{ + // close all forwarders + std::vector::iterator it; + for (it = forwarders.begin(); it != forwarders.end(); ++it) { + SrsForwarder* forwarder = *it; + forwarder->on_unpublish(); + srs_freep(forwarder); + } + forwarders.clear(); + +#ifdef SRS_FFMPEG + encoder->on_unpublish(); +#endif + + // TODO: HLS should continue previous sequence and stream. +#ifdef SRS_HLS + hls->on_unpublish(); +#endif + + gop_cache->clear(); + + srs_freep(cache_metadata); + frame_rate = sample_rate = 0; + + srs_freep(cache_sh_video); + srs_freep(cache_sh_audio); + + srs_trace("clear cache/metadata/sequence-headers when unpublish."); + + _can_publish = true; +} + + int SrsSource::create_consumer(SrsConsumer*& consumer) +{ + int ret = ERROR_SUCCESS; + + consumer = new SrsConsumer(this); + consumers.push_back(consumer); + + if (cache_metadata && (ret = consumer->enqueue(cache_metadata->copy(), sample_rate, frame_rate)) != ERROR_SUCCESS) { + srs_error("dispatch metadata failed. ret=%d", ret); + return ret; + } + srs_info("dispatch metadata success"); + + if (cache_sh_video && (ret = consumer->enqueue(cache_sh_video->copy(), sample_rate, frame_rate)) != 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(), sample_rate, frame_rate)) != ERROR_SUCCESS) { + srs_error("dispatch audio sequence header failed. ret=%d", ret); + return ret; + } + srs_info("dispatch audio sequence header success"); + + if ((ret = gop_cache->dump(consumer, sample_rate, frame_rate)) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +void SrsSource::on_consumer_destroy(SrsConsumer* consumer) +{ + std::vector::iterator it; + it = std::find(consumers.begin(), consumers.end(), consumer); + if (it != consumers.end()) { + consumers.erase(it); + } + srs_info("handle consumer destroy success."); +} + +void SrsSource::set_cache(bool enabled) +{ + gop_cache->set(enabled); +} + diff --git a/trunk/src/core/srs_core_thread.cpp b/trunk/src/core/srs_core_thread.cpp new file mode 100644 index 000000000..fbbe4b300 --- /dev/null +++ b/trunk/src/core/srs_core_thread.cpp @@ -0,0 +1,156 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013 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 + +ISrsThreadHandler::ISrsThreadHandler() +{ +} + +ISrsThreadHandler::~ISrsThreadHandler() +{ +} + +void ISrsThreadHandler::on_enter_loop() +{ +} + +int ISrsThreadHandler::on_before_cycle() +{ + int ret = ERROR_SUCCESS; + return ret; +} + +int ISrsThreadHandler::on_end_cycle() +{ + int ret = ERROR_SUCCESS; + return ret; +} + +void ISrsThreadHandler::on_leave_loop() +{ +} + +SrsThread::SrsThread(ISrsThreadHandler* thread_handler, int64_t interval_ms) +{ + handler = thread_handler; + cycle_interval_milliseconds = interval_ms; + + tid = NULL; + loop = false; +} + +SrsThread::~SrsThread() +{ + stop(); +} + +int SrsThread::start() +{ + int ret = ERROR_SUCCESS; + + if(tid) { + srs_info("thread already running."); + return ret; + } + + if((tid = st_thread_create(thread_fun, this, 1, 0)) == NULL){ + ret = ERROR_ST_CREATE_CYCLE_THREAD; + srs_error("st_thread_create failed. ret=%d", ret); + return ret; + } + + return ret; +} + +void SrsThread::stop() +{ + if (tid) { + loop = false; + + // the interrupt will cause the socket to read/write error, + // which will terminate the cycle thread. + st_thread_interrupt(tid); + + // wait the thread to exit. + st_thread_join(tid, NULL); + + tid = NULL; + } +} + +void SrsThread::thread_cycle() +{ + int ret = ERROR_SUCCESS; + + srs_assert(handler); + + log_context->generate_id(); + srs_trace("thread cycle start"); + + handler->on_end_cycle(); + + loop = true; + while (loop) { + if ((ret = handler->on_before_cycle()) != ERROR_SUCCESS) { + srs_warn("thread on before cycle failed, ignored and retry, ret=%d", ret); + goto failed; + } + srs_info("thread on before cycle success"); + + if ((ret = handler->cycle()) != ERROR_SUCCESS) { + srs_warn("thread cycle failed, ignored and retry, ret=%d", ret); + goto failed; + } + srs_info("thread cycle success"); + + if ((ret = handler->on_end_cycle()) != ERROR_SUCCESS) { + srs_warn("thread on end cycle failed, ignored and retry, ret=%d", ret); + goto failed; + } + srs_info("thread on end cycle success"); + +failed: + if (!loop) { + break; + } + + st_usleep(cycle_interval_milliseconds * 1000); + } + + handler->on_leave_loop(); + srs_trace("thread cycle finished"); +} + +void* SrsThread::thread_fun(void* arg) +{ + SrsThread* obj = (SrsThread*)arg; + srs_assert(obj); + + obj->thread_cycle(); + + return NULL; +} \ No newline at end of file diff --git a/trunk/src/core/srs_core_thread.hpp b/trunk/src/core/srs_core_thread.hpp new file mode 100644 index 000000000..fb3ef8136 --- /dev/null +++ b/trunk/src/core/srs_core_thread.hpp @@ -0,0 +1,98 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013 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_CORE_THREAD_HPP +#define SRS_CORE_THREAD_HPP + +/* +#include +*/ +#include + +/** +* the handler for the thread, callback interface. +* the thread model defines as: +* handler->on_enter_loop() +* while loop: +* handler->on_before_cycle() +* handler->cycle() +* handler->on_end_cycle() +* if !loop then break for user stop thread. +* sleep(CycleIntervalMilliseconds) +* handler->on_leave_loop() +* when stop, the thread will interrupt the st_thread, +* which will cause the socket to return error and +* terminate the cycle thread. +*/ +class ISrsThreadHandler +{ +public: + ISrsThreadHandler(); + virtual ~ISrsThreadHandler(); +public: + virtual void on_enter_loop(); + virtual int on_before_cycle(); + virtual int cycle() = 0; + virtual int on_end_cycle(); + virtual void on_leave_loop(); +}; + +/** +* provides servies from st_thread_t, +* for common thread usage. +*/ +class SrsThread +{ +private: + st_thread_t tid; + bool loop; +private: + ISrsThreadHandler* handler; + int64_t cycle_interval_milliseconds; +public: + /** + * initialize the thread. + * @param thread_handler, the cycle handler for the thread. + * @param interval_ms, the sleep interval when cycle finished. + */ + SrsThread(ISrsThreadHandler* thread_handler, int64_t interval_ms); + virtual ~SrsThread(); +public: + /** + * start the thread, invoke the cycle of handler util + * user stop the thread. + * @remark ignore any error of cycle of handler. + * @remark user can start multiple times, ignore if already started. + */ + virtual int start(); + /** + * stop the thread, wait for the thread to terminate. + * @remark user can stop multiple times, ignore if already stopped. + */ + virtual void stop(); +private: + virtual void thread_cycle(); + static void* thread_fun(void* arg); +}; + +#endif diff --git a/trunk/src/srs/srs.upp b/trunk/src/srs/srs.upp index 7286f909c..53f5277c1 100755 --- a/trunk/src/srs/srs.upp +++ b/trunk/src/srs/srs.upp @@ -1,60 +1,62 @@ file - main readonly separator, - ..\main\srs_main_server.cpp, - auto readonly separator, - ..\..\objs\srs_auto_headers.hpp, - core readonly separator, - ..\core\srs_core.hpp, - ..\core\srs_core.cpp, - ..\core\srs_core_error.hpp, - ..\core\srs_core_error.cpp, - ..\core\srs_core_autofree.hpp, - ..\core\srs_core_autofree.cpp, - ..\core\srs_core_server.hpp, - ..\core\srs_core_server.cpp, - ..\core\srs_core_reload.hpp, - ..\core\srs_core_reload.cpp, - ..\core\srs_core_config.hpp, - ..\core\srs_core_config.cpp, - ..\core\srs_core_refer.hpp, - ..\core\srs_core_refer.cpp, - ..\core\srs_core_conn.hpp, - ..\core\srs_core_conn.cpp, - ..\core\srs_core_client.hpp, - ..\core\srs_core_client.cpp, - ..\core\srs_core_http.hpp, - ..\core\srs_core_http.cpp, - ..\core\srs_core_source.hpp, - ..\core\srs_core_source.cpp, - ..\core\srs_core_forward.hpp, - ..\core\srs_core_forward.cpp, - ..\core\srs_core_encoder.hpp, - ..\core\srs_core_encoder.cpp, - ..\core\srs_core_hls.hpp, - ..\core\srs_core_hls.cpp, - ..\core\srs_core_codec.hpp, - ..\core\srs_core_codec.cpp, - ..\core\srs_core_rtmp.hpp, - ..\core\srs_core_rtmp.cpp, - ..\core\srs_core_handshake.hpp, - ..\core\srs_core_handshake.cpp, - ..\core\srs_core_protocol.hpp, - ..\core\srs_core_protocol.cpp, - ..\core\srs_core_amf0.hpp, - ..\core\srs_core_amf0.cpp, - ..\core\srs_core_stream.hpp, - ..\core\srs_core_stream.cpp, - ..\core\srs_core_socket.hpp, - ..\core\srs_core_socket.cpp, - ..\core\srs_core_buffer.hpp, - ..\core\srs_core_buffer.cpp, - ..\core\srs_core_pithy_print.hpp, - ..\core\srs_core_pithy_print.cpp, - ..\core\srs_core_log.hpp, - ..\core\srs_core_log.cpp, - research readonly separator, - ..\..\research\ts_info.cc; + main readonly separator, + ..\main\srs_main_server.cpp, + auto readonly separator, + ..\..\objs\srs_auto_headers.hpp, + core readonly separator, + ..\core\srs_core.hpp, + ..\core\srs_core.cpp, + ..\core\srs_core_amf0.hpp, + ..\core\srs_core_amf0.cpp, + ..\core\srs_core_autofree.hpp, + ..\core\srs_core_autofree.cpp, + ..\core\srs_core_buffer.hpp, + ..\core\srs_core_buffer.cpp, + ..\core\srs_core_client.hpp, + ..\core\srs_core_client.cpp, + ..\core\srs_core_codec.hpp, + ..\core\srs_core_codec.cpp, + ..\core\srs_core_config.hpp, + ..\core\srs_core_config.cpp, + ..\core\srs_core_conn.hpp, + ..\core\srs_core_conn.cpp, + ..\core\srs_core_encoder.hpp, + ..\core\srs_core_encoder.cpp, + ..\core\srs_core_error.hpp, + ..\core\srs_core_error.cpp, + ..\core\srs_core_forward.hpp, + ..\core\srs_core_forward.cpp, + ..\core\srs_core_handshake.hpp, + ..\core\srs_core_handshake.cpp, + ..\core\srs_core_hls.hpp, + ..\core\srs_core_hls.cpp, + ..\core\srs_core_http.hpp, + ..\core\srs_core_http.cpp, + ..\core\srs_core_log.hpp, + ..\core\srs_core_log.cpp, + ..\core\srs_core_pithy_print.hpp, + ..\core\srs_core_pithy_print.cpp, + ..\core\srs_core_protocol.hpp, + ..\core\srs_core_protocol.cpp, + ..\core\srs_core_refer.hpp, + ..\core\srs_core_refer.cpp, + ..\core\srs_core_reload.hpp, + ..\core\srs_core_reload.cpp, + ..\core\srs_core_rtmp.hpp, + ..\core\srs_core_rtmp.cpp, + ..\core\srs_core_thread.hpp, + ..\core\srs_core_thread.cpp, + ..\core\srs_core_server.hpp, + ..\core\srs_core_server.cpp, + ..\core\srs_core_stream.hpp, + ..\core\srs_core_stream.cpp, + ..\core\srs_core_socket.hpp, + ..\core\srs_core_socket.cpp, + ..\core\srs_core_source.hpp, + ..\core\srs_core_source.cpp, + research readonly separator, + ..\..\research\ts_info.cc; mainconfig - "" = "MAIN"; + "" = "MAIN"; From fd583570217918a00db79a38e693d2bb14d48570 Mon Sep 17 00:00:00 2001 From: winlin Date: Sat, 14 Dec 2013 14:07:11 +0800 Subject: [PATCH 12/37] update readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 83ba3905d..cd2b189c0 100755 --- a/README.md +++ b/README.md @@ -198,6 +198,7 @@ usr sys idl wai hiq siq| read writ| recv send| in out | int csw * nginx v1.5.0: 139524 lines
### History +* 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 released. 19186 lines. * v0.8, 2013-12-08, support http hooks: on_connect/close/publish/unpublish/play/stop. From 3dd7156f0dbec00a584a5f418b682a997699f306 Mon Sep 17 00:00:00 2001 From: winlin Date: Sat, 14 Dec 2013 15:56:20 +0800 Subject: [PATCH 13/37] refine the config buffer. --- README.md | 3 +- trunk/src/core/srs_core_config.cpp | 109 +++++++++++++---------------- trunk/src/core/srs_core_config.hpp | 20 +----- trunk/src/core/srs_core_error.hpp | 5 +- 4 files changed, 55 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index cd2b189c0..102eae950 100755 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ simple-rtmp-server srs(simple rtmp origin live server) over state-threads.
srs is a simple, high-performance, running in single process, origin live server.
-srs supports rtmp, HLS, transcoding, forward, http hooks.
+srs supports vhost, rtmp, HLS, transcoding, forward, http hooks.
blog: [http://blog.csdn.net/win_lin](http://blog.csdn.net/win_lin)
see also: [https://github.com/winlinvip/simple-rtmp-server](https://github.com/winlinvip/simple-rtmp-server)
see also: [http://winlinvip.github.io/simple-rtmp-server](http://winlinvip.github.io/simple-rtmp-server) @@ -198,6 +198,7 @@ usr sys idl wai hiq siq| read writ| recv send| in out | int csw * nginx v1.5.0: 139524 lines
### History +* v0.9, 2013-12-14, 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 released. 19186 lines. diff --git a/trunk/src/core/srs_core_config.cpp b/trunk/src/core/srs_core_config.cpp index 96bdad2fe..fd917a689 100644 --- a/trunk/src/core/srs_core_config.cpp +++ b/trunk/src/core/srs_core_config.cpp @@ -58,15 +58,35 @@ bool is_common_space(char ch) return (ch == ' ' || ch == '\t' || ch == CR || ch == LF); } -#define CONF_BUFFER_SIZE 1024 * 1024 +class SrsFileBuffer +{ +private: + int fd; + // last available position. + char* last; + // end of buffer. + char* end; +public: + // start of buffer. + char* start; + // current consumed position. + char* pos; + // current parsed line. + int line; + + SrsFileBuffer(); + virtual ~SrsFileBuffer(); + virtual int fullfill(const char* filename); + virtual bool empty(); +}; SrsFileBuffer::SrsFileBuffer() { fd = -1; line = 0; - pos = last = start = new char[CONF_BUFFER_SIZE]; - end = start + CONF_BUFFER_SIZE; + pos = last = start = NULL; + end = start; } SrsFileBuffer::~SrsFileBuffer() @@ -77,7 +97,7 @@ SrsFileBuffer::~SrsFileBuffer() srs_freepa(start); } -int SrsFileBuffer::open(const char* filename) +int SrsFileBuffer::fullfill(const char* filename) { assert(fd == -1); @@ -88,9 +108,29 @@ int SrsFileBuffer::open(const char* filename) line = 1; + int size = FILE_SIZE(fd) - FILE_OFFSET(fd); + if (size <= 0) { + return ERROR_SYSTEM_CONFIG_EOF; + } + + srs_freepa(start); + pos = last = start = new char[size]; + end = start + size; + + int n = read(fd, start, size); + if (n != size) { + srs_error("read file read error. expect %d, actual %d bytes.", size, n); + return ERROR_SYSTEM_CONFIG_INVALID; + } + return ERROR_SUCCESS; } +bool SrsFileBuffer::empty() +{ + return pos >= end; +} + SrsConfDirective::SrsConfDirective() { } @@ -156,7 +196,7 @@ int SrsConfDirective::parse(const char* filename) SrsFileBuffer buffer; - if ((ret = buffer.open(filename)) != ERROR_SUCCESS) { + if ((ret = buffer.fullfill(filename)) != ERROR_SUCCESS) { return ret; } @@ -240,11 +280,15 @@ int SrsConfDirective::read_token(SrsFileBuffer* buffer, std::vector bool last_space = true; while (true) { - if ((ret = refill_buffer(buffer, d_quoted, s_quoted, startline, pstart)) != ERROR_SUCCESS) { + if (buffer->empty()) { + ret = ERROR_SYSTEM_CONFIG_EOF; + if (!args.empty() || !last_space) { srs_error("line %d: unexpected end of file, expecting ; or \"}\"", buffer->line); return ERROR_SYSTEM_CONFIG_INVALID; } + srs_error("end of file. ret=%d", ret); + return ret; } @@ -363,59 +407,6 @@ int SrsConfDirective::read_token(SrsFileBuffer* buffer, std::vector return ret; } -int SrsConfDirective::refill_buffer(SrsFileBuffer* buffer, bool d_quoted, bool s_quoted, int startline, char*& pstart) -{ - int ret = ERROR_SUCCESS; - - if (buffer->pos < buffer->last) { - return ret; - } - - int size = FILE_SIZE(buffer->fd) - FILE_OFFSET(buffer->fd); - if (size > CONF_BUFFER_SIZE) { - ret = ERROR_SYSTEM_CONFIG_TOO_LARGE; - srs_error("config file too large, max=%d, actual=%d, ret=%d", - CONF_BUFFER_SIZE, size, ret); - return ret; - } - - if (size <= 0) { - return ERROR_SYSTEM_CONFIG_EOF; - } - - int len = buffer->pos - buffer->start; - if (len >= CONF_BUFFER_SIZE) { - buffer->line = startline; - - if (!d_quoted && !s_quoted) { - srs_error("line %d: too long parameter \"%*s...\" started", - buffer->line, 10, buffer->start); - - } else { - srs_error("line %d: too long parameter, " - "probably missing terminating '%c' character", buffer->line, d_quoted? '"':'\''); - } - return ERROR_SYSTEM_CONFIG_INVALID; - } - - if (len) { - memmove(buffer->start, pstart, len); - } - - size = srs_min(size, buffer->end - (buffer->start + len)); - int n = read(buffer->fd, buffer->start + len, size); - if (n != size) { - srs_error("read file read error. expect %d, actual %d bytes.", size, n); - return ERROR_SYSTEM_CONFIG_INVALID; - } - - buffer->pos = buffer->start + len; - buffer->last = buffer->pos + n; - pstart = buffer->start; - - return ret; -} - SrsConfig* config = new SrsConfig(); SrsConfig::SrsConfig() diff --git a/trunk/src/core/srs_core_config.hpp b/trunk/src/core/srs_core_config.hpp index 50538cb53..50d7c1766 100644 --- a/trunk/src/core/srs_core_config.hpp +++ b/trunk/src/core/srs_core_config.hpp @@ -49,24 +49,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // in ms, for HLS aac flush the audio #define SRS_CONF_DEFAULT_AAC_DELAY 300 -class SrsFileBuffer -{ -public: - int fd; - int line; - // start of buffer. - char* start; - // end of buffer. - char* end; - // current consumed position. - char* pos; - // last available position. - char* last; - - SrsFileBuffer(); - virtual ~SrsFileBuffer(); - virtual int open(const char* filename); -}; +class SrsFileBuffer; class SrsConfDirective { @@ -89,7 +72,6 @@ public: enum SrsDirectiveType{parse_file, parse_block}; virtual int parse_conf(SrsFileBuffer* buffer, SrsDirectiveType type); virtual int read_token(SrsFileBuffer* buffer, std::vector& args); - virtual int refill_buffer(SrsFileBuffer* buffer, bool d_quoted, bool s_quoted, int startline, char*& pstart); }; /** diff --git a/trunk/src/core/srs_core_error.hpp b/trunk/src/core/srs_core_error.hpp index 47004397e..55372e6d1 100644 --- a/trunk/src/core/srs_core_error.hpp +++ b/trunk/src/core/srs_core_error.hpp @@ -83,9 +83,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define ERROR_SYSTEM_CONFIG_EOF 409 #define ERROR_SYSTEM_STREAM_BUSY 410 #define ERROR_SYSTEM_IP_INVALID 411 -#define ERROR_SYSTEM_CONFIG_TOO_LARGE 412 -#define ERROR_SYSTEM_FORWARD_LOOP 413 -#define ERROR_SYSTEM_WAITPID 414 +#define ERROR_SYSTEM_FORWARD_LOOP 412 +#define ERROR_SYSTEM_WAITPID 413 // see librtmp. // failed when open ssl create the dh From 5b29d0ec429d50fc474fee2185de9792ec696201 Mon Sep 17 00:00:00 2001 From: winlin Date: Sat, 14 Dec 2013 16:44:27 +0800 Subject: [PATCH 14/37] refine the config --- trunk/src/core/srs_core_config.cpp | 54 +++++++++++++++++------------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/trunk/src/core/srs_core_config.cpp b/trunk/src/core/srs_core_config.cpp index fd917a689..d799dddb3 100644 --- a/trunk/src/core/srs_core_config.cpp +++ b/trunk/src/core/srs_core_config.cpp @@ -61,14 +61,13 @@ bool is_common_space(char ch) class SrsFileBuffer { private: - int fd; // last available position. char* last; // end of buffer. char* end; -public: // start of buffer. char* start; +public: // current consumed position. char* pos; // current parsed line. @@ -82,7 +81,6 @@ public: SrsFileBuffer::SrsFileBuffer() { - fd = -1; line = 0; pos = last = start = NULL; @@ -91,39 +89,48 @@ SrsFileBuffer::SrsFileBuffer() SrsFileBuffer::~SrsFileBuffer() { - if (fd > 0) { - close(fd); - } srs_freepa(start); } int SrsFileBuffer::fullfill(const char* filename) { - assert(fd == -1); + int ret = ERROR_SUCCESS; + + int fd = -1; + int nread = 0; + int filesize = 0; if ((fd = ::open(filename, O_RDONLY, 0)) < 0) { - srs_error("open conf file error. errno=%d(%s)", errno, strerror(errno)); - return ERROR_SYSTEM_CONFIG_INVALID; + ret = ERROR_SYSTEM_CONFIG_INVALID; + srs_error("open conf file error. ret=%d", ret); + goto finish; + } + + if ((filesize = FILE_SIZE(fd) - FILE_OFFSET(fd)) <= 0) { + ret = ERROR_SYSTEM_CONFIG_EOF; + srs_error("read conf file error. ret=%d", ret); + goto finish; + } + + srs_freepa(start); + pos = last = start = new char[filesize]; + end = start + filesize; + + if ((nread = read(fd, start, filesize)) != filesize) { + ret = ERROR_SYSTEM_CONFIG_INVALID; + srs_error("read file read error. expect %d, actual %d bytes, ret=%d", + filesize, nread, ret); + goto finish; } line = 1; - int size = FILE_SIZE(fd) - FILE_OFFSET(fd); - if (size <= 0) { - return ERROR_SYSTEM_CONFIG_EOF; - } - - srs_freepa(start); - pos = last = start = new char[size]; - end = start + size; - - int n = read(fd, start, size); - if (n != size) { - srs_error("read file read error. expect %d, actual %d bytes.", size, n); - return ERROR_SYSTEM_CONFIG_INVALID; +finish: + if (fd > 0) { + ::close(fd); } - return ERROR_SUCCESS; + return ret; } bool SrsFileBuffer::empty() @@ -1245,6 +1252,7 @@ int SrsConfig::parse_file(const char* filename) "directive \"listen\" is empty, ret=%d", (conf? conf->conf_line:0), ret); return ret; } + // TODO: check the hls. // TODO: check other config. // TODO: check hls. From 010b7a7595f49a960b8a62e885423fb427d731d7 Mon Sep 17 00:00:00 2001 From: winlin Date: Sat, 14 Dec 2013 21:19:47 +0800 Subject: [PATCH 15/37] refine config. --- trunk/conf/srs.conf | 80 ++--- trunk/src/core/srs_core_client.cpp | 15 +- trunk/src/core/srs_core_config.cpp | 383 ++++++++++++++---------- trunk/src/core/srs_core_config.hpp | 108 ++++--- trunk/src/core/srs_core_hls.cpp | 24 +- trunk/src/core/srs_core_pithy_print.cpp | 35 +-- 6 files changed, 331 insertions(+), 314 deletions(-) diff --git a/trunk/conf/srs.conf b/trunk/conf/srs.conf index a76f811e7..793c84134 100755 --- a/trunk/conf/srs.conf +++ b/trunk/conf/srs.conf @@ -4,7 +4,7 @@ listen 1935; # some client does not support chunk size change, # however, most clients supports it and it can improve # performance about 10%. -# if not specified, set to 4096. +# default: 4096 chunk_size 65000; # the logs dir. # if enabled ffmpeg, each stracoding stream will create a log file. @@ -21,11 +21,13 @@ max_connections 2000; vhost __defaultVhost__ { enabled on; gop_cache on; - hls on; - hls_path ./objs/nginx/html; - hls_fragment 5; - hls_window 30; forward 127.0.0.1:19350; + hls { + hls on; + hls_path ./objs/nginx/html; + hls_fragment 5; + hls_window 30; + } transcode { enabled on; ffmpeg ./objs/ffmpeg/bin/ffmpeg; @@ -81,11 +83,13 @@ vhost __defaultVhost__ { vhost dev { enabled on; gop_cache on; - hls off; - hls_path ./objs/nginx/html; - hls_fragment 5; - hls_window 30; forward 127.0.0.1:19350; + hls { + hls off; + hls_path ./objs/nginx/html; + hls_fragment 5; + hls_window 30; + } http_hooks { enabled off; on_connect http://127.0.0.1:8085/api/v1/clients; @@ -626,36 +630,40 @@ vhost removed.vhost.com { enabled off; } # the vhost with hls specified. -vhost no-hls.vhost.com { - # whether the hls is enabled. - # if off, donot write hls(ts and m3u8) when publish. - # default: on - hls 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 /data/nginx/html; - # the hls fragment in seconds, the duration of a piece of ts. - # default: 10 - hls_fragment 10; - # the hls window in seconds, the number of ts in m3u8. - # default: 60 - hls_window 60; +vhost with-hls.vhost.com { + hls { + # whether the hls is enabled. + # if off, donot write hls(ts and m3u8) when publish. + # default: off + hls 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 /data/nginx/html; + # the hls fragment in seconds, the duration of a piece of ts. + # default: 10 + hls_fragment 10; + # the hls window in seconds, the number of ts in m3u8. + # default: 60 + hls_window 60; + } } # the vhost with hls disabled. vhost no-hls.vhost.com { - # whether the hls is enabled. - # if off, donot write hls(ts and m3u8) when publish. - # default: on - hls off; + hls { + # whether the hls is enabled. + # if off, donot write hls(ts and m3u8) when publish. + # default: off + hls off; + } } # the vhost for min delay, donot cache any stream. vhost min.delay.com { diff --git a/trunk/src/core/srs_core_client.cpp b/trunk/src/core/srs_core_client.cpp index 1b84b3287..766a2900d 100644 --- a/trunk/src/core/srs_core_client.cpp +++ b/trunk/src/core/srs_core_client.cpp @@ -150,11 +150,7 @@ int SrsClient::service_cycle() req->strip(); srs_trace("identify client success. type=%d, stream_name=%s", type, req->stream.c_str()); - int chunk_size = 4096; - SrsConfDirective* conf = config->get_chunk_size(); - if (conf && !conf->arg0().empty()) { - chunk_size = ::atoi(conf->arg0().c_str()); - } + int chunk_size = config->get_chunk_size(); if ((ret = rtmp->set_chunk_size(chunk_size)) != ERROR_SUCCESS) { srs_error("set chunk_size=%d failed. ret=%d", chunk_size, ret); return ret; @@ -175,14 +171,9 @@ int SrsClient::service_cycle() return ret; } - bool enabled_cache = true; - conf = config->get_gop_cache(req->vhost); - if (conf && conf->arg0() == "off") { - enabled_cache = false; - } - source->set_cache(enabled_cache); - + bool enabled_cache = config->get_gop_cache(req->vhost); srs_info("source found, url=%s, enabled_cache=%d", req->get_stream_url().c_str(), enabled_cache); + source->set_cache(enabled_cache); switch (type) { case SrsClientPlay: { diff --git a/trunk/src/core/srs_core_config.cpp b/trunk/src/core/srs_core_config.cpp index d799dddb3..cd910b21b 100644 --- a/trunk/src/core/srs_core_config.cpp +++ b/trunk/src/core/srs_core_config.cpp @@ -35,6 +35,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include +using namespace std; #include #include @@ -152,7 +153,7 @@ SrsConfDirective::~SrsConfDirective() directives.clear(); } -std::string SrsConfDirective::arg0() +string SrsConfDirective::arg0() { if (args.size() > 0) { return args.at(0); @@ -161,7 +162,7 @@ std::string SrsConfDirective::arg0() return ""; } -std::string SrsConfDirective::arg1() +string SrsConfDirective::arg1() { if (args.size() > 1) { return args.at(1); @@ -170,7 +171,7 @@ std::string SrsConfDirective::arg1() return ""; } -std::string SrsConfDirective::arg2() +string SrsConfDirective::arg2() { if (args.size() > 2) { return args.at(2); @@ -184,7 +185,7 @@ SrsConfDirective* SrsConfDirective::at(int index) return directives.at(index); } -SrsConfDirective* SrsConfDirective::get(std::string _name) +SrsConfDirective* SrsConfDirective::get(string _name) { std::vector::iterator it; for (it = directives.begin(); it != directives.end(); ++it) { @@ -216,7 +217,7 @@ int SrsConfDirective::parse_conf(SrsFileBuffer* buffer, SrsDirectiveType type) int ret = ERROR_SUCCESS; while (true) { - std::vector args; + std::vector args; ret = read_token(buffer, args); /** @@ -271,7 +272,7 @@ int SrsConfDirective::parse_conf(SrsFileBuffer* buffer, SrsDirectiveType type) } // see: ngx_conf_read_token -int SrsConfDirective::read_token(SrsFileBuffer* buffer, std::vector& args) +int SrsConfDirective::read_token(SrsFileBuffer* buffer, std::vector& args) { int ret = ERROR_SUCCESS; @@ -395,7 +396,7 @@ int SrsConfDirective::read_token(SrsFileBuffer* buffer, std::vector memcpy(word, pstart, len); word[len - 1] = 0; - std::string word_str = word; + string word_str = word; if (!word_str.empty()) { args.push_back(word_str); } @@ -537,7 +538,102 @@ int SrsConfig::parse_options(int argc, char** argv) return parse_file(config_file.c_str()); } -SrsConfDirective* SrsConfig::get_vhost(std::string vhost) +int SrsConfig::parse_file(const char* filename) +{ + int ret = ERROR_SUCCESS; + + config_file = filename; + + if (config_file.empty()) { + return ERROR_SYSTEM_CONFIG_INVALID; + } + + if ((ret = root->parse(config_file.c_str())) != ERROR_SUCCESS) { + return ret; + } + + SrsConfDirective* conf = NULL; + if ((conf = get_listen()) == NULL || conf->args.size() == 0) { + ret = ERROR_SYSTEM_CONFIG_INVALID; + srs_error("line %d: conf error, " + "directive \"listen\" is empty, ret=%d", (conf? conf->conf_line:0), ret); + return ret; + } + + // TODO: check the hls. + // TODO: check forward. + // TODO: check ffmpeg. + // TODO: check http. + + return ret; +} + +int SrsConfig::parse_argv(int& i, char** argv) +{ + int ret = ERROR_SUCCESS; + + char* p = argv[i]; + + if (*p++ != '-') { + ret = ERROR_SYSTEM_CONFIG_INVALID; + srs_error("invalid options(index=%d, value=%s), " + "must starts with -, see help: %s -h, ret=%d", i, argv[i], argv[0], ret); + return ret; + } + + while (*p) { + switch (*p++) { + case '?': + case 'h': + show_help = true; + break; + case 'v': + case 'V': + show_version = true; + break; + case 'c': + if (*p) { + config_file = p; + return ret; + } + if (argv[++i]) { + config_file = argv[i]; + return ret; + } + ret = ERROR_SYSTEM_CONFIG_INVALID; + srs_error("option \"-c\" requires parameter, ret=%d", ret); + return ret; + default: + ret = ERROR_SYSTEM_CONFIG_INVALID; + srs_error("invalid option: \"%c\", see help: %s -h, ret=%d", *(p - 1), argv[0], ret); + return ret; + } + } + + return ret; +} + +void SrsConfig::print_help(char** argv) +{ + printf(RTMP_SIG_SRS_NAME" "RTMP_SIG_SRS_VERSION + " Copyright (c) 2013 winlin\n" + "Contributors: "RTMP_SIG_SRS_CONTRIBUTOR"\n" + "Build: "SRS_BUILD_DATE" Configuration: "SRS_CONFIGURE"\n" + "Usage: %s [-h?vV] [-c ]\n" + "\n" + "Options:\n" + " -?-h : show help\n" + " -v-V : show version and exit\n" + " -c filename : set configuration file\n" + "\n" + RTMP_SIG_SRS_WEB"\n" + RTMP_SIG_SRS_URL"\n" + "Email: "RTMP_SIG_SRS_EMAIL"\n" + "\n", + argv[0]); +} + +SrsConfDirective* SrsConfig::get_vhost(string vhost) { srs_assert(root); @@ -560,7 +656,7 @@ SrsConfDirective* SrsConfig::get_vhost(std::string vhost) return NULL; } -SrsConfDirective* SrsConfig::get_vhost_on_connect(std::string vhost) +SrsConfDirective* SrsConfig::get_vhost_on_connect(string vhost) { SrsConfDirective* conf = get_vhost(vhost); @@ -581,7 +677,7 @@ SrsConfDirective* SrsConfig::get_vhost_on_connect(std::string vhost) return conf->get("on_connect"); } -SrsConfDirective* SrsConfig::get_vhost_on_close(std::string vhost) +SrsConfDirective* SrsConfig::get_vhost_on_close(string vhost) { SrsConfDirective* conf = get_vhost(vhost); @@ -602,7 +698,7 @@ SrsConfDirective* SrsConfig::get_vhost_on_close(std::string vhost) return conf->get("on_close"); } -SrsConfDirective* SrsConfig::get_vhost_on_publish(std::string vhost) +SrsConfDirective* SrsConfig::get_vhost_on_publish(string vhost) { SrsConfDirective* conf = get_vhost(vhost); @@ -623,7 +719,7 @@ SrsConfDirective* SrsConfig::get_vhost_on_publish(std::string vhost) return conf->get("on_publish"); } -SrsConfDirective* SrsConfig::get_vhost_on_unpublish(std::string vhost) +SrsConfDirective* SrsConfig::get_vhost_on_unpublish(string vhost) { SrsConfDirective* conf = get_vhost(vhost); @@ -644,7 +740,7 @@ SrsConfDirective* SrsConfig::get_vhost_on_unpublish(std::string vhost) return conf->get("on_unpublish"); } -SrsConfDirective* SrsConfig::get_vhost_on_play(std::string vhost) +SrsConfDirective* SrsConfig::get_vhost_on_play(string vhost) { SrsConfDirective* conf = get_vhost(vhost); @@ -665,7 +761,7 @@ SrsConfDirective* SrsConfig::get_vhost_on_play(std::string vhost) return conf->get("on_play"); } -SrsConfDirective* SrsConfig::get_vhost_on_stop(std::string vhost) +SrsConfDirective* SrsConfig::get_vhost_on_stop(string vhost) { SrsConfDirective* conf = get_vhost(vhost); @@ -686,7 +782,7 @@ SrsConfDirective* SrsConfig::get_vhost_on_stop(std::string vhost) return conf->get("on_stop"); } -bool SrsConfig::get_vhost_enabled(std::string vhost) +bool SrsConfig::get_vhost_enabled(string vhost) { SrsConfDirective* vhost_conf = get_vhost(vhost); @@ -706,7 +802,7 @@ bool SrsConfig::get_vhost_enabled(std::string vhost) return true; } -SrsConfDirective* SrsConfig::get_transcode(std::string vhost, std::string scope) +SrsConfDirective* SrsConfig::get_transcode(string vhost, string scope) { SrsConfDirective* conf = get_vhost(vhost); @@ -740,7 +836,7 @@ bool SrsConfig::get_transcode_enabled(SrsConfDirective* transcode) return true; } -std::string SrsConfig::get_transcode_ffmpeg(SrsConfDirective* transcode) +string SrsConfig::get_transcode_ffmpeg(SrsConfDirective* transcode) { if (!transcode) { return ""; @@ -785,7 +881,7 @@ bool SrsConfig::get_engine_enabled(SrsConfDirective* engine) return true; } -std::string SrsConfig::get_engine_vcodec(SrsConfDirective* engine) +string SrsConfig::get_engine_vcodec(SrsConfDirective* engine) { if (!engine) { return ""; @@ -869,7 +965,7 @@ int SrsConfig::get_engine_vthreads(SrsConfDirective* engine) return ::atoi(conf->arg0().c_str()); } -std::string SrsConfig::get_engine_vprofile(SrsConfDirective* engine) +string SrsConfig::get_engine_vprofile(SrsConfDirective* engine) { if (!engine) { return ""; @@ -883,7 +979,7 @@ std::string SrsConfig::get_engine_vprofile(SrsConfDirective* engine) return conf->arg0(); } -std::string SrsConfig::get_engine_vpreset(SrsConfDirective* engine) +string SrsConfig::get_engine_vpreset(SrsConfDirective* engine) { if (!engine) { return ""; @@ -897,7 +993,7 @@ std::string SrsConfig::get_engine_vpreset(SrsConfDirective* engine) return conf->arg0(); } -void SrsConfig::get_engine_vparams(SrsConfDirective* engine, std::vector& vparams) +void SrsConfig::get_engine_vparams(SrsConfDirective* engine, std::vector& vparams) { if (!engine) { return; @@ -919,7 +1015,7 @@ void SrsConfig::get_engine_vparams(SrsConfDirective* engine, std::vector& vfilter) +void SrsConfig::get_engine_vfilter(SrsConfDirective* engine, std::vector& vfilter) { if (!engine) { return; @@ -941,7 +1037,7 @@ void SrsConfig::get_engine_vfilter(SrsConfDirective* engine, std::vectorarg0().c_str()); } -void SrsConfig::get_engine_aparams(SrsConfDirective* engine, std::vector& aparams) +void SrsConfig::get_engine_aparams(SrsConfDirective* engine, std::vector& aparams) { if (!engine) { return; @@ -1019,7 +1115,7 @@ void SrsConfig::get_engine_aparams(SrsConfDirective* engine, std::vectorarg0(); } -std::string SrsConfig::get_log_dir() +string SrsConfig::get_log_dir() { srs_assert(root); @@ -1057,18 +1153,22 @@ int SrsConfig::get_max_connections() return ::atoi(conf->arg0().c_str()); } -SrsConfDirective* SrsConfig::get_gop_cache(std::string vhost) +bool SrsConfig::get_gop_cache(string vhost) { SrsConfDirective* conf = get_vhost(vhost); if (!conf) { - return NULL; + return true; } - return conf->get("gop_cache"); + if (conf && conf->arg0() == "off") { + return false; + } + + return true; } -SrsConfDirective* SrsConfig::get_forward(std::string vhost) +SrsConfDirective* SrsConfig::get_forward(string vhost) { SrsConfDirective* conf = get_vhost(vhost); @@ -1079,7 +1179,7 @@ SrsConfDirective* SrsConfig::get_forward(std::string vhost) return conf->get("forward"); } -SrsConfDirective* SrsConfig::get_hls(std::string vhost) +SrsConfDirective* SrsConfig::get_hls(string vhost) { SrsConfDirective* conf = get_vhost(vhost); @@ -1090,12 +1190,12 @@ SrsConfDirective* SrsConfig::get_hls(std::string vhost) return conf->get("hls"); } -bool SrsConfig::get_hls_enabled(std::string vhost) +bool SrsConfig::get_hls_enabled(string vhost) { SrsConfDirective* hls = get_hls(vhost); if (!hls) { - return true; + return false; } if (hls->arg0() == "off") { @@ -1105,40 +1205,58 @@ bool SrsConfig::get_hls_enabled(std::string vhost) return true; } -SrsConfDirective* SrsConfig::get_hls_path(std::string vhost) +string SrsConfig::get_hls_path(string vhost) { - SrsConfDirective* conf = get_vhost(vhost); - - if (!conf) { - return NULL; + SrsConfDirective* hls = get_hls(vhost); + + if (!hls) { + return SRS_CONF_DEFAULT_HLS_PATH; } - return conf->get("hls_path"); + SrsConfDirective* conf = hls->get("hls_path"); + + if (!conf) { + return SRS_CONF_DEFAULT_HLS_PATH; + } + + return conf->arg0(); } -SrsConfDirective* SrsConfig::get_hls_fragment(std::string vhost) +double SrsConfig::get_hls_fragment(string vhost) { - SrsConfDirective* conf = get_vhost(vhost); - - if (!conf) { - return NULL; + SrsConfDirective* hls = get_hls(vhost); + + if (!hls) { + return SRS_CONF_DEFAULT_HLS_FRAGMENT; } - return conf->get("hls_fragment"); + SrsConfDirective* conf = hls->get("hls_fragment"); + + if (!conf) { + return SRS_CONF_DEFAULT_HLS_FRAGMENT; + } + + return ::atof(conf->arg0().c_str()); } -SrsConfDirective* SrsConfig::get_hls_window(std::string vhost) +double SrsConfig::get_hls_window(string vhost) { - SrsConfDirective* conf = get_vhost(vhost); - - if (!conf) { - return NULL; + SrsConfDirective* hls = get_hls(vhost); + + if (!hls) { + return SRS_CONF_DEFAULT_HLS_WINDOW; } - return conf->get("hls_window"); + SrsConfDirective* conf = hls->get("hls_window"); + + if (!conf) { + return SRS_CONF_DEFAULT_HLS_WINDOW; + } + + return ::atof(conf->arg0().c_str()); } -SrsConfDirective* SrsConfig::get_refer(std::string vhost) +SrsConfDirective* SrsConfig::get_refer(string vhost) { SrsConfDirective* conf = get_vhost(vhost); @@ -1149,7 +1267,7 @@ SrsConfDirective* SrsConfig::get_refer(std::string vhost) return conf->get("refer"); } -SrsConfDirective* SrsConfig::get_refer_play(std::string vhost) +SrsConfDirective* SrsConfig::get_refer_play(string vhost) { SrsConfDirective* conf = get_vhost(vhost); @@ -1160,7 +1278,7 @@ SrsConfDirective* SrsConfig::get_refer_play(std::string vhost) return conf->get("refer_play"); } -SrsConfDirective* SrsConfig::get_refer_publish(std::string vhost) +SrsConfDirective* SrsConfig::get_refer_publish(string vhost) { SrsConfDirective* conf = get_vhost(vhost); @@ -1176,156 +1294,89 @@ SrsConfDirective* SrsConfig::get_listen() return root->get("listen"); } -SrsConfDirective* SrsConfig::get_chunk_size() +int SrsConfig::get_chunk_size() { - return root->get("chunk_size"); + SrsConfDirective* conf = root->get("chunk_size"); + if (!conf) { + return SRS_CONF_DEFAULT_CHUNK_SIZE; + } + + return ::atoi(conf->arg0().c_str()); } -SrsConfDirective* SrsConfig::get_pithy_print_publish() +int SrsConfig::get_pithy_print_publish() { SrsConfDirective* pithy = root->get("pithy_print"); if (!pithy) { - return NULL; + return SRS_STAGE_PUBLISH_USER_INTERVAL_MS; } - return pithy->get("publish"); + pithy = pithy->get("publish"); + if (!pithy) { + return SRS_STAGE_PUBLISH_USER_INTERVAL_MS; + } + + return ::atoi(pithy->arg0().c_str()); } -SrsConfDirective* SrsConfig::get_pithy_print_forwarder() +int SrsConfig::get_pithy_print_forwarder() { SrsConfDirective* pithy = root->get("pithy_print"); if (!pithy) { - return NULL; + return SRS_STAGE_FORWARDER_INTERVAL_MS; } - return pithy->get("forwarder"); + pithy = pithy->get("forwarder"); + if (!pithy) { + return SRS_STAGE_FORWARDER_INTERVAL_MS; + } + + return ::atoi(pithy->arg0().c_str()); } -SrsConfDirective* SrsConfig::get_pithy_print_hls() +int SrsConfig::get_pithy_print_hls() { SrsConfDirective* pithy = root->get("pithy_print"); if (!pithy) { - return NULL; + return SRS_STAGE_HLS_INTERVAL_MS; } - return pithy->get("hls"); + pithy = pithy->get("hls"); + if (!pithy) { + return SRS_STAGE_HLS_INTERVAL_MS; + } + + return ::atoi(pithy->arg0().c_str()); } -SrsConfDirective* SrsConfig::get_pithy_print_encoder() +int SrsConfig::get_pithy_print_encoder() { SrsConfDirective* pithy = root->get("encoder"); if (!pithy) { - return NULL; + return SRS_STAGE_ENCODER_INTERVAL_MS; } - return pithy->get("forwarder"); + pithy = pithy->get("forwarder"); + if (!pithy) { + return SRS_STAGE_ENCODER_INTERVAL_MS; + } + + return ::atoi(pithy->arg0().c_str()); } -SrsConfDirective* SrsConfig::get_pithy_print_play() +int SrsConfig::get_pithy_print_play() { SrsConfDirective* pithy = root->get("pithy_print"); if (!pithy) { - return NULL; + return SRS_STAGE_PLAY_USER_INTERVAL_MS; } - return pithy->get("play"); -} - -int SrsConfig::parse_file(const char* filename) -{ - int ret = ERROR_SUCCESS; - - config_file = filename; - - if (config_file.empty()) { - return ERROR_SYSTEM_CONFIG_INVALID; + pithy = pithy->get("play"); + if (!pithy) { + return SRS_STAGE_PLAY_USER_INTERVAL_MS; } - if ((ret = root->parse(config_file.c_str())) != ERROR_SUCCESS) { - return ret; - } - - SrsConfDirective* conf = NULL; - if ((conf = get_listen()) == NULL || conf->args.size() == 0) { - ret = ERROR_SYSTEM_CONFIG_INVALID; - srs_error("line %d: conf error, " - "directive \"listen\" is empty, ret=%d", (conf? conf->conf_line:0), ret); - return ret; - } - - // TODO: check the hls. - // TODO: check other config. - // TODO: check hls. - // TODO: check ssl. - // TODO: check ffmpeg. - // TODO: check http. - - return ret; -} - -int SrsConfig::parse_argv(int& i, char** argv) -{ - int ret = ERROR_SUCCESS; - - char* p = argv[i]; - - if (*p++ != '-') { - ret = ERROR_SYSTEM_CONFIG_INVALID; - srs_error("invalid options(index=%d, value=%s), " - "must starts with -, see help: %s -h, ret=%d", i, argv[i], argv[0], ret); - return ret; - } - - while (*p) { - switch (*p++) { - case '?': - case 'h': - show_help = true; - break; - case 'v': - case 'V': - show_version = true; - break; - case 'c': - if (*p) { - config_file = p; - return ret; - } - if (argv[++i]) { - config_file = argv[i]; - return ret; - } - ret = ERROR_SYSTEM_CONFIG_INVALID; - srs_error("option \"-c\" requires parameter, ret=%d", ret); - return ret; - default: - ret = ERROR_SYSTEM_CONFIG_INVALID; - srs_error("invalid option: \"%c\", see help: %s -h, ret=%d", *(p - 1), argv[0], ret); - return ret; - } - } - - return ret; -} - -void SrsConfig::print_help(char** argv) -{ - printf(RTMP_SIG_SRS_NAME" "RTMP_SIG_SRS_VERSION - " Copyright (c) 2013 winlin\n" - "Contributors: "RTMP_SIG_SRS_CONTRIBUTOR"\n" - "Build: "SRS_BUILD_DATE" Configuration: "SRS_CONFIGURE"\n" - "Usage: %s [-h?vV] [-c ]\n" - "\n" - "Options:\n" - " -?-h : show help\n" - " -v-V : show version and exit\n" - " -c filename : set configuration file\n" - "\n" - RTMP_SIG_SRS_WEB"\n" - RTMP_SIG_SRS_URL"\n" - "Email: "RTMP_SIG_SRS_EMAIL"\n" - "\n", - argv[0]); + return ::atoi(pithy->arg0().c_str()); } bool srs_directive_equals(SrsConfDirective* a, SrsConfDirective* b) diff --git a/trunk/src/core/srs_core_config.hpp b/trunk/src/core/srs_core_config.hpp index 50d7c1766..f5acb48c6 100644 --- a/trunk/src/core/srs_core_config.hpp +++ b/trunk/src/core/srs_core_config.hpp @@ -49,6 +49,14 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // in ms, for HLS aac flush the audio #define SRS_CONF_DEFAULT_AAC_DELAY 300 +#define SRS_CONF_DEFAULT_CHUNK_SIZE 4096 + +#define SRS_STAGE_PLAY_USER_INTERVAL_MS 1300 +#define SRS_STAGE_PUBLISH_USER_INTERVAL_MS 1100 +#define SRS_STAGE_FORWARDER_INTERVAL_MS 2000 +#define SRS_STAGE_ENCODER_INTERVAL_MS 2000 +#define SRS_STAGE_HLS_INTERVAL_MS 2000 + class SrsFileBuffer; class SrsConfDirective @@ -98,59 +106,61 @@ public: virtual void unsubscribe(SrsReloadHandler* handler); public: virtual int parse_options(int argc, char** argv); -public: - virtual SrsConfDirective* get_vhost(std::string vhost); - virtual bool get_vhost_enabled(std::string vhost); - virtual SrsConfDirective* get_vhost_on_connect(std::string vhost); - virtual SrsConfDirective* get_vhost_on_close(std::string vhost); - virtual SrsConfDirective* get_vhost_on_publish(std::string vhost); - virtual SrsConfDirective* get_vhost_on_unpublish(std::string vhost); - virtual SrsConfDirective* get_vhost_on_play(std::string vhost); - virtual SrsConfDirective* get_vhost_on_stop(std::string vhost); - virtual SrsConfDirective* get_transcode(std::string vhost, std::string scope); - virtual bool get_transcode_enabled(SrsConfDirective* transcode); - virtual std::string get_transcode_ffmpeg(SrsConfDirective* transcode); - virtual void get_transcode_engines(SrsConfDirective* transcode, std::vector& engines); - virtual bool get_engine_enabled(SrsConfDirective* engine); - virtual std::string get_engine_vcodec(SrsConfDirective* engine); - virtual int get_engine_vbitrate(SrsConfDirective* engine); - virtual double get_engine_vfps(SrsConfDirective* engine); - virtual int get_engine_vwidth(SrsConfDirective* engine); - virtual int get_engine_vheight(SrsConfDirective* engine); - virtual int get_engine_vthreads(SrsConfDirective* engine); - virtual std::string get_engine_vprofile(SrsConfDirective* engine); - virtual std::string get_engine_vpreset(SrsConfDirective* engine); - virtual void get_engine_vparams(SrsConfDirective* engine, std::vector& vparams); - virtual void get_engine_vfilter(SrsConfDirective* engine, std::vector& vfilter); - virtual std::string get_engine_acodec(SrsConfDirective* engine); - virtual int get_engine_abitrate(SrsConfDirective* engine); - virtual int get_engine_asample_rate(SrsConfDirective* engine); - virtual int get_engine_achannels(SrsConfDirective* engine); - virtual void get_engine_aparams(SrsConfDirective* engine, std::vector& aparams); - virtual std::string get_engine_output(SrsConfDirective* engine); - virtual std::string get_log_dir(); - virtual int get_max_connections(); - virtual SrsConfDirective* get_gop_cache(std::string vhost); - virtual SrsConfDirective* get_forward(std::string vhost); - virtual SrsConfDirective* get_hls(std::string vhost); - virtual bool get_hls_enabled(std::string vhost); - virtual SrsConfDirective* get_hls_path(std::string vhost); - virtual SrsConfDirective* get_hls_fragment(std::string vhost); - virtual SrsConfDirective* get_hls_window(std::string vhost); - virtual SrsConfDirective* get_refer(std::string vhost); - virtual SrsConfDirective* get_refer_play(std::string vhost); - virtual SrsConfDirective* get_refer_publish(std::string vhost); - virtual SrsConfDirective* get_listen(); - virtual SrsConfDirective* get_chunk_size(); - virtual SrsConfDirective* get_pithy_print_publish(); - virtual SrsConfDirective* get_pithy_print_forwarder(); - virtual SrsConfDirective* get_pithy_print_encoder(); - virtual SrsConfDirective* get_pithy_print_hls(); - virtual SrsConfDirective* get_pithy_print_play(); private: virtual int parse_file(const char* filename); virtual int parse_argv(int& i, char** argv); virtual void print_help(char** argv); +public: + virtual SrsConfDirective* get_vhost(std::string vhost); + virtual bool get_vhost_enabled(std::string vhost); + virtual SrsConfDirective* get_vhost_on_connect(std::string vhost); + virtual SrsConfDirective* get_vhost_on_close(std::string vhost); + virtual SrsConfDirective* get_vhost_on_publish(std::string vhost); + virtual SrsConfDirective* get_vhost_on_unpublish(std::string vhost); + virtual SrsConfDirective* get_vhost_on_play(std::string vhost); + virtual SrsConfDirective* get_vhost_on_stop(std::string vhost); + virtual SrsConfDirective* get_transcode(std::string vhost, std::string scope); + virtual bool get_transcode_enabled(SrsConfDirective* transcode); + virtual std::string get_transcode_ffmpeg(SrsConfDirective* transcode); + virtual void get_transcode_engines(SrsConfDirective* transcode, std::vector& engines); + virtual bool get_engine_enabled(SrsConfDirective* engine); + virtual std::string get_engine_vcodec(SrsConfDirective* engine); + virtual int get_engine_vbitrate(SrsConfDirective* engine); + virtual double get_engine_vfps(SrsConfDirective* engine); + virtual int get_engine_vwidth(SrsConfDirective* engine); + virtual int get_engine_vheight(SrsConfDirective* engine); + virtual int get_engine_vthreads(SrsConfDirective* engine); + virtual std::string get_engine_vprofile(SrsConfDirective* engine); + virtual std::string get_engine_vpreset(SrsConfDirective* engine); + virtual void get_engine_vparams(SrsConfDirective* engine, std::vector& vparams); + virtual void get_engine_vfilter(SrsConfDirective* engine, std::vector& vfilter); + virtual std::string get_engine_acodec(SrsConfDirective* engine); + virtual int get_engine_abitrate(SrsConfDirective* engine); + virtual int get_engine_asample_rate(SrsConfDirective* engine); + virtual int get_engine_achannels(SrsConfDirective* engine); + virtual void get_engine_aparams(SrsConfDirective* engine, std::vector& aparams); + virtual std::string get_engine_output(SrsConfDirective* engine); + virtual std::string get_log_dir(); + virtual int get_max_connections(); + virtual bool get_gop_cache(std::string vhost); + virtual SrsConfDirective* get_forward(std::string vhost); +private: + virtual SrsConfDirective* get_hls(std::string vhost); +public: + virtual bool get_hls_enabled(std::string vhost); + virtual std::string get_hls_path(std::string vhost); + virtual double get_hls_fragment(std::string vhost); + virtual double get_hls_window(std::string vhost); + virtual SrsConfDirective* get_refer(std::string vhost); + virtual SrsConfDirective* get_refer_play(std::string vhost); + virtual SrsConfDirective* get_refer_publish(std::string vhost); + virtual SrsConfDirective* get_listen(); + virtual int get_chunk_size(); + virtual int get_pithy_print_publish(); + virtual int get_pithy_print_forwarder(); + virtual int get_pithy_print_encoder(); + virtual int get_pithy_print_hls(); + virtual int get_pithy_print_play(); }; /** diff --git a/trunk/src/core/srs_core_hls.cpp b/trunk/src/core/srs_core_hls.cpp index 37848f6e3..ff5b8ffbf 100644 --- a/trunk/src/core/srs_core_hls.cpp +++ b/trunk/src/core/srs_core_hls.cpp @@ -1157,29 +1157,11 @@ int SrsHls::on_publish(SrsRequest* req) hls_enabled = true; // TODO: subscribe the reload event. - int hls_fragment = 0; - int hls_window = 0; - - SrsConfDirective* conf = NULL; - if ((conf = config->get_hls_fragment(vhost)) != NULL && !conf->arg0().empty()) { - hls_fragment = ::atoi(conf->arg0().c_str()); - } - if (hls_fragment <= 0) { - hls_fragment = SRS_CONF_DEFAULT_HLS_FRAGMENT; - } - - if ((conf = config->get_hls_window(vhost)) != NULL && !conf->arg0().empty()) { - hls_window = ::atoi(conf->arg0().c_str()); - } - if (hls_window <= 0) { - hls_window = SRS_CONF_DEFAULT_HLS_WINDOW; - } + int hls_fragment = config->get_hls_fragment(vhost); + int hls_window = config->get_hls_window(vhost); // get the hls path config - std::string hls_path = SRS_CONF_DEFAULT_HLS_PATH; - if ((conf = config->get_hls_path(vhost)) != NULL) { - hls_path = conf->arg0(); - } + std::string hls_path = config->get_hls_path(vhost); // open muxer if ((ret = muxer->update_config(app, stream, hls_path, hls_fragment, hls_window)) != ERROR_SUCCESS) { diff --git a/trunk/src/core/srs_core_pithy_print.cpp b/trunk/src/core/srs_core_pithy_print.cpp index 3323a10bb..4e3431ba6 100644 --- a/trunk/src/core/srs_core_pithy_print.cpp +++ b/trunk/src/core/srs_core_pithy_print.cpp @@ -32,11 +32,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #define SRS_STAGE_DEFAULT_INTERVAL_MS 1200 -#define SRS_STAGE_PLAY_USER_INTERVAL_MS 1300 -#define SRS_STAGE_PUBLISH_USER_INTERVAL_MS 1100 -#define SRS_STAGE_FORWARDER_INTERVAL_MS 2000 -#define SRS_STAGE_ENCODER_INTERVAL_MS 2000 -#define SRS_STAGE_HLS_INTERVAL_MS 2000 struct SrsStageInfo : public SrsReloadHandler { @@ -61,43 +56,23 @@ struct SrsStageInfo : public SrsReloadHandler { switch (stage_id) { case SRS_STAGE_PLAY_USER: { - pithy_print_time_ms = SRS_STAGE_PLAY_USER_INTERVAL_MS; - SrsConfDirective* conf = config->get_pithy_print_play(); - if (conf && !conf->arg0().empty()) { - pithy_print_time_ms = ::atoi(conf->arg0().c_str()); - } + pithy_print_time_ms = config->get_pithy_print_play(); break; } case SRS_STAGE_PUBLISH_USER: { - pithy_print_time_ms = SRS_STAGE_PUBLISH_USER_INTERVAL_MS; - SrsConfDirective* conf = config->get_pithy_print_publish(); - if (conf && !conf->arg0().empty()) { - pithy_print_time_ms = ::atoi(conf->arg0().c_str()); - } + pithy_print_time_ms = config->get_pithy_print_publish(); break; } case SRS_STAGE_FORWARDER: { - pithy_print_time_ms = SRS_STAGE_FORWARDER_INTERVAL_MS; - SrsConfDirective* conf = config->get_pithy_print_forwarder(); - if (conf && !conf->arg0().empty()) { - pithy_print_time_ms = ::atoi(conf->arg0().c_str()); - } + pithy_print_time_ms = config->get_pithy_print_forwarder(); break; } case SRS_STAGE_ENCODER: { - pithy_print_time_ms = SRS_STAGE_ENCODER_INTERVAL_MS; - SrsConfDirective* conf = config->get_pithy_print_encoder(); - if (conf && !conf->arg0().empty()) { - pithy_print_time_ms = ::atoi(conf->arg0().c_str()); - } + pithy_print_time_ms = config->get_pithy_print_encoder(); break; } case SRS_STAGE_HLS: { - pithy_print_time_ms = SRS_STAGE_HLS_INTERVAL_MS; - SrsConfDirective* conf = config->get_pithy_print_hls(); - if (conf && !conf->arg0().empty()) { - pithy_print_time_ms = ::atoi(conf->arg0().c_str()); - } + pithy_print_time_ms = config->get_pithy_print_hls(); break; } default: { From f016914ac1d3414e6351bfe7b8aa540a092fa781 Mon Sep 17 00:00:00 2001 From: winlin Date: Sat, 14 Dec 2013 21:34:54 +0800 Subject: [PATCH 16/37] rename SrsReloadHandler to ISrsReloadHandler --- trunk/src/core/srs_core_config.cpp | 14 +++++++------- trunk/src/core/srs_core_config.hpp | 6 +++--- trunk/src/core/srs_core_pithy_print.cpp | 2 +- trunk/src/core/srs_core_reload.cpp | 8 ++++---- trunk/src/core/srs_core_reload.hpp | 6 +++--- trunk/src/core/srs_core_server.hpp | 2 +- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/trunk/src/core/srs_core_config.cpp b/trunk/src/core/srs_core_config.cpp index cd910b21b..5338021a5 100644 --- a/trunk/src/core/srs_core_config.cpp +++ b/trunk/src/core/srs_core_config.cpp @@ -452,12 +452,12 @@ int SrsConfig::reload() conf.root = NULL; // merge config. - std::vector::iterator it; + std::vector::iterator it; // merge config: listen if (!srs_directive_equals(root->get("listen"), old_root->get("listen"))) { for (it = subscribes.begin(); it != subscribes.end(); ++it) { - SrsReloadHandler* subscribe = *it; + ISrsReloadHandler* subscribe = *it; if ((ret = subscribe->on_reload_listen()) != ERROR_SUCCESS) { srs_error("notify subscribes reload listen failed. ret=%d", ret); return ret; @@ -468,7 +468,7 @@ int SrsConfig::reload() // merge config: pithy_print if (!srs_directive_equals(root->get("pithy_print"), old_root->get("pithy_print"))) { for (it = subscribes.begin(); it != subscribes.end(); ++it) { - SrsReloadHandler* subscribe = *it; + ISrsReloadHandler* subscribe = *it; if ((ret = subscribe->on_reload_pithy_print()) != ERROR_SUCCESS) { srs_error("notify subscribes pithy_print listen failed. ret=%d", ret); return ret; @@ -482,9 +482,9 @@ int SrsConfig::reload() return ret; } -void SrsConfig::subscribe(SrsReloadHandler* handler) +void SrsConfig::subscribe(ISrsReloadHandler* handler) { - std::vector::iterator it; + std::vector::iterator it; it = std::find(subscribes.begin(), subscribes.end(), handler); if (it != subscribes.end()) { @@ -494,9 +494,9 @@ void SrsConfig::subscribe(SrsReloadHandler* handler) subscribes.push_back(handler); } -void SrsConfig::unsubscribe(SrsReloadHandler* handler) +void SrsConfig::unsubscribe(ISrsReloadHandler* handler) { - std::vector::iterator it; + std::vector::iterator it; it = std::find(subscribes.begin(), subscribes.end(), handler); if (it == subscribes.end()) { diff --git a/trunk/src/core/srs_core_config.hpp b/trunk/src/core/srs_core_config.hpp index f5acb48c6..076b5e09c 100644 --- a/trunk/src/core/srs_core_config.hpp +++ b/trunk/src/core/srs_core_config.hpp @@ -96,14 +96,14 @@ private: bool show_version; std::string config_file; SrsConfDirective* root; - std::vector subscribes; + std::vector subscribes; public: SrsConfig(); virtual ~SrsConfig(); public: virtual int reload(); - virtual void subscribe(SrsReloadHandler* handler); - virtual void unsubscribe(SrsReloadHandler* handler); + virtual void subscribe(ISrsReloadHandler* handler); + virtual void unsubscribe(ISrsReloadHandler* handler); public: virtual int parse_options(int argc, char** argv); private: diff --git a/trunk/src/core/srs_core_pithy_print.cpp b/trunk/src/core/srs_core_pithy_print.cpp index 4e3431ba6..abb568596 100644 --- a/trunk/src/core/srs_core_pithy_print.cpp +++ b/trunk/src/core/srs_core_pithy_print.cpp @@ -33,7 +33,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define SRS_STAGE_DEFAULT_INTERVAL_MS 1200 -struct SrsStageInfo : public SrsReloadHandler +struct SrsStageInfo : public ISrsReloadHandler { int stage_id; int pithy_print_time_ms; diff --git a/trunk/src/core/srs_core_reload.cpp b/trunk/src/core/srs_core_reload.cpp index 684f3c2df..a29cdfbe8 100644 --- a/trunk/src/core/srs_core_reload.cpp +++ b/trunk/src/core/srs_core_reload.cpp @@ -25,20 +25,20 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include -SrsReloadHandler::SrsReloadHandler() +ISrsReloadHandler::ISrsReloadHandler() { } -SrsReloadHandler::~SrsReloadHandler() +ISrsReloadHandler::~ISrsReloadHandler() { } -int SrsReloadHandler::on_reload_listen() +int ISrsReloadHandler::on_reload_listen() { return ERROR_SUCCESS; } -int SrsReloadHandler::on_reload_pithy_print() +int ISrsReloadHandler::on_reload_pithy_print() { return ERROR_SUCCESS; } diff --git a/trunk/src/core/srs_core_reload.hpp b/trunk/src/core/srs_core_reload.hpp index 68813a3fa..2b48b7009 100644 --- a/trunk/src/core/srs_core_reload.hpp +++ b/trunk/src/core/srs_core_reload.hpp @@ -32,11 +32,11 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. /** * the handler for config reload. */ -class SrsReloadHandler +class ISrsReloadHandler { public: - SrsReloadHandler(); - virtual ~SrsReloadHandler(); + ISrsReloadHandler(); + virtual ~ISrsReloadHandler(); public: virtual int on_reload_listen(); virtual int on_reload_pithy_print(); diff --git a/trunk/src/core/srs_core_server.hpp b/trunk/src/core/srs_core_server.hpp index fcc9d61b8..79664657d 100644 --- a/trunk/src/core/srs_core_server.hpp +++ b/trunk/src/core/srs_core_server.hpp @@ -65,7 +65,7 @@ public: virtual int cycle(); }; -class SrsServer : public SrsReloadHandler +class SrsServer : public ISrsReloadHandler { friend class SrsListener; private: From e2bb38c48395bd3d996fd311b82534f6490d5399 Mon Sep 17 00:00:00 2001 From: winlin Date: Sat, 14 Dec 2013 22:54:10 +0800 Subject: [PATCH 17/37] support reload the removed vhost --- trunk/conf/srs.conf | 4 +- trunk/src/core/srs_core_client.cpp | 16 +++++++ trunk/src/core/srs_core_client.hpp | 6 ++- trunk/src/core/srs_core_config.cpp | 65 ++++++++++++++++++++++++++-- trunk/src/core/srs_core_config.hpp | 2 + trunk/src/core/srs_core_protocol.cpp | 5 +++ trunk/src/core/srs_core_protocol.hpp | 1 + trunk/src/core/srs_core_reload.cpp | 5 +++ trunk/src/core/srs_core_reload.hpp | 3 ++ trunk/src/core/srs_core_rtmp.cpp | 11 +++++ trunk/src/core/srs_core_rtmp.hpp | 1 + trunk/src/core/srs_core_socket.cpp | 5 +++ trunk/src/core/srs_core_socket.hpp | 1 + 13 files changed, 119 insertions(+), 6 deletions(-) diff --git a/trunk/conf/srs.conf b/trunk/conf/srs.conf index 793c84134..ad6f26f11 100755 --- a/trunk/conf/srs.conf +++ b/trunk/conf/srs.conf @@ -83,7 +83,7 @@ vhost __defaultVhost__ { vhost dev { enabled on; gop_cache on; - forward 127.0.0.1:19350; + #forward 127.0.0.1:19350; hls { hls off; hls_path ./objs/nginx/html; @@ -100,7 +100,7 @@ vhost dev { on_stop http://127.0.0.1:8085/api/v1/sessions; } transcode { - enabled on; + enabled off; ffmpeg ./objs/ffmpeg/bin/ffmpeg; engine dev { enabled on; diff --git a/trunk/src/core/srs_core_client.cpp b/trunk/src/core/srs_core_client.cpp index 766a2900d..ef2a8328e 100644 --- a/trunk/src/core/srs_core_client.cpp +++ b/trunk/src/core/srs_core_client.cpp @@ -55,6 +55,8 @@ SrsClient::SrsClient(SrsServer* srs_server, st_netfd_t client_stfd) #ifdef SRS_HTTP http_hooks = new SrsHttpHooks(); #endif + + config->subscribe(this); } SrsClient::~SrsClient() @@ -67,6 +69,8 @@ SrsClient::~SrsClient() #ifdef SRS_HTTP srs_freep(http_hooks); #endif + + config->unsubscribe(this); } // TODO: return detail message when error for client. @@ -113,6 +117,18 @@ int SrsClient::do_cycle() return ret; } + +int SrsClient::on_reload_vhost_removed(SrsConfDirective* vhost) +{ + int ret = ERROR_SUCCESS; + + // if the vhost connected is removed, disconnect the client. + if (req->vhost == vhost->arg0()) { + srs_close_stfd(stfd); + } + + return ret; +} int SrsClient::service_cycle() { diff --git a/trunk/src/core/srs_core_client.hpp b/trunk/src/core/srs_core_client.hpp index 0d84b527d..3273fb93b 100644 --- a/trunk/src/core/srs_core_client.hpp +++ b/trunk/src/core/srs_core_client.hpp @@ -31,6 +31,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include +#include class SrsRtmp; class SrsRequest; @@ -46,7 +47,7 @@ class SrsHttpHooks; /** * the client provides the main logic control for RTMP clients. */ -class SrsClient : public SrsConnection +class SrsClient : public SrsConnection, public ISrsReloadHandler { private: char* ip; @@ -62,6 +63,9 @@ public: virtual ~SrsClient(); protected: virtual int do_cycle(); +// interface ISrsReloadHandler +public: + virtual int on_reload_vhost_removed(SrsConfDirective* vhost); private: // when valid and connected to vhost/app, service the client. virtual int service_cycle(); diff --git a/trunk/src/core/srs_core_config.cpp b/trunk/src/core/srs_core_config.cpp index 5338021a5..f5b6922d9 100644 --- a/trunk/src/core/srs_core_config.cpp +++ b/trunk/src/core/srs_core_config.cpp @@ -198,6 +198,19 @@ SrsConfDirective* SrsConfDirective::get(string _name) return NULL; } +SrsConfDirective* SrsConfDirective::get(string _name, string _arg0) +{ + std::vector::iterator it; + for (it = directives.begin(); it != directives.end(); ++it) { + SrsConfDirective* directive = *it; + if (directive->name == _name && directive->arg0() == _arg0) { + return directive; + } + } + + return NULL; +} + int SrsConfDirective::parse(const char* filename) { int ret = ERROR_SUCCESS; @@ -465,6 +478,7 @@ int SrsConfig::reload() } srs_trace("reload listen success."); } + // merge config: pithy_print if (!srs_directive_equals(root->get("pithy_print"), old_root->get("pithy_print"))) { for (it = subscribes.begin(); it != subscribes.end(); ++it) { @@ -476,6 +490,46 @@ int SrsConfig::reload() } srs_trace("reload pithy_print success."); } + + // merge config: vhost added, directly supported. + + // merge config: vhost removed/disabled/modified. + for (int i = 0; i < (int)old_root->directives.size(); i++) { + SrsConfDirective* old_vhost = old_root->at(i); + // only process vhost directives. + if (old_vhost->name != "vhost") { + continue; + } + + SrsConfDirective* new_vhost = root->get("vhost", old_vhost->arg0()); + // ignore if absolutely equal + if (new_vhost && srs_directive_equals(old_vhost, new_vhost)) { + continue; + } + // ignore if enable the new vhost when old vhost is disabled. + if (get_vhost_enabled(new_vhost) && !get_vhost_enabled(old_vhost)) { + continue; + } + // ignore if both old and new vhost are disabled. + if (!get_vhost_enabled(new_vhost) && !get_vhost_enabled(old_vhost)) { + continue; + } + + // merge config: vhost removed/disabled. + if (!get_vhost_enabled(new_vhost) && get_vhost_enabled(old_vhost)) { + srs_trace("vhost %s disabled, reload it.", old_vhost->name.c_str()); + for (it = subscribes.begin(); it != subscribes.end(); ++it) { + ISrsReloadHandler* subscribe = *it; + if ((ret = subscribe->on_reload_vhost_removed(old_vhost)) != ERROR_SUCCESS) { + srs_error("notify subscribes pithy_print remove vhost failed. ret=%d", ret); + return ret; + } + } + srs_trace("reload remove vhost success."); + } + + // merge config: vhost modified. + } // TODO: suppor reload hls/forward/ffmpeg/http @@ -785,12 +839,17 @@ SrsConfDirective* SrsConfig::get_vhost_on_stop(string vhost) bool SrsConfig::get_vhost_enabled(string vhost) { SrsConfDirective* vhost_conf = get_vhost(vhost); + + return get_vhost_enabled(vhost_conf); +} - if (!vhost_conf) { - return true; +bool SrsConfig::get_vhost_enabled(SrsConfDirective* vhost) +{ + if (!vhost) { + return false; } - SrsConfDirective* conf = vhost_conf->get("enabled"); + SrsConfDirective* conf = vhost->get("enabled"); if (!conf) { return true; } diff --git a/trunk/src/core/srs_core_config.hpp b/trunk/src/core/srs_core_config.hpp index 076b5e09c..d173d6ec2 100644 --- a/trunk/src/core/srs_core_config.hpp +++ b/trunk/src/core/srs_core_config.hpp @@ -74,6 +74,7 @@ public: std::string arg2(); SrsConfDirective* at(int index); SrsConfDirective* get(std::string _name); + SrsConfDirective* get(std::string _name, std::string _arg0); public: virtual int parse(const char* filename); public: @@ -113,6 +114,7 @@ private: public: virtual SrsConfDirective* get_vhost(std::string vhost); virtual bool get_vhost_enabled(std::string vhost); + virtual bool get_vhost_enabled(SrsConfDirective* vhost); virtual SrsConfDirective* get_vhost_on_connect(std::string vhost); virtual SrsConfDirective* get_vhost_on_close(std::string vhost); virtual SrsConfDirective* get_vhost_on_publish(std::string vhost); diff --git a/trunk/src/core/srs_core_protocol.cpp b/trunk/src/core/srs_core_protocol.cpp index 940666f09..1b50d18e2 100644 --- a/trunk/src/core/srs_core_protocol.cpp +++ b/trunk/src/core/srs_core_protocol.cpp @@ -307,6 +307,11 @@ void SrsProtocol::set_send_timeout(int64_t timeout_us) return skt->set_send_timeout(timeout_us); } +int64_t SrsProtocol::get_send_timeout() +{ + return skt->get_send_timeout(); +} + int64_t SrsProtocol::get_recv_bytes() { return skt->get_recv_bytes(); diff --git a/trunk/src/core/srs_core_protocol.hpp b/trunk/src/core/srs_core_protocol.hpp index 9cda280ca..f9c5db5fe 100644 --- a/trunk/src/core/srs_core_protocol.hpp +++ b/trunk/src/core/srs_core_protocol.hpp @@ -115,6 +115,7 @@ public: virtual void set_recv_timeout(int64_t timeout_us); virtual int64_t get_recv_timeout(); virtual void set_send_timeout(int64_t timeout_us); + virtual int64_t get_send_timeout(); virtual int64_t get_recv_bytes(); virtual int64_t get_send_bytes(); virtual int get_recv_kbps(); diff --git a/trunk/src/core/srs_core_reload.cpp b/trunk/src/core/srs_core_reload.cpp index a29cdfbe8..54a856309 100644 --- a/trunk/src/core/srs_core_reload.cpp +++ b/trunk/src/core/srs_core_reload.cpp @@ -43,3 +43,8 @@ int ISrsReloadHandler::on_reload_pithy_print() return ERROR_SUCCESS; } +int ISrsReloadHandler::on_reload_vhost_removed(SrsConfDirective* /*vhost*/) +{ + return ERROR_SUCCESS; +} + diff --git a/trunk/src/core/srs_core_reload.hpp b/trunk/src/core/srs_core_reload.hpp index 2b48b7009..b92109797 100644 --- a/trunk/src/core/srs_core_reload.hpp +++ b/trunk/src/core/srs_core_reload.hpp @@ -29,6 +29,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include +class SrsConfDirective; + /** * the handler for config reload. */ @@ -40,6 +42,7 @@ public: public: virtual int on_reload_listen(); virtual int on_reload_pithy_print(); + virtual int on_reload_vhost_removed(SrsConfDirective* vhost); }; #endif \ No newline at end of file diff --git a/trunk/src/core/srs_core_rtmp.cpp b/trunk/src/core/srs_core_rtmp.cpp index 0fccf4de3..b48b0795e 100644 --- a/trunk/src/core/srs_core_rtmp.cpp +++ b/trunk/src/core/srs_core_rtmp.cpp @@ -231,6 +231,9 @@ int SrsRtmpClient::handshake() SrsSocket skt(stfd); + skt.set_recv_timeout(protocol->get_recv_timeout()); + skt.set_send_timeout(protocol->get_send_timeout()); + SrsComplexHandshake complex_hs; SrsSimpleHandshake simple_hs; if ((ret = simple_hs.handshake_with_server(skt, complex_hs)) != ERROR_SUCCESS) { @@ -422,6 +425,11 @@ void SrsRtmp::set_send_timeout(int64_t timeout_us) protocol->set_send_timeout(timeout_us); } +int64_t SrsRtmp::get_send_timeout() +{ + return protocol->get_send_timeout(); +} + int64_t SrsRtmp::get_recv_bytes() { return protocol->get_recv_bytes(); @@ -458,6 +466,9 @@ int SrsRtmp::handshake() SrsSocket skt(stfd); + skt.set_recv_timeout(protocol->get_recv_timeout()); + skt.set_send_timeout(protocol->get_send_timeout()); + SrsComplexHandshake complex_hs; SrsSimpleHandshake simple_hs; if ((ret = simple_hs.handshake_with_client(skt, complex_hs)) != ERROR_SUCCESS) { diff --git a/trunk/src/core/srs_core_rtmp.hpp b/trunk/src/core/srs_core_rtmp.hpp index 56fc95112..f6e76d1b7 100644 --- a/trunk/src/core/srs_core_rtmp.hpp +++ b/trunk/src/core/srs_core_rtmp.hpp @@ -144,6 +144,7 @@ public: virtual void set_recv_timeout(int64_t timeout_us); virtual int64_t get_recv_timeout(); virtual void set_send_timeout(int64_t timeout_us); + virtual int64_t get_send_timeout(); virtual int64_t get_recv_bytes(); virtual int64_t get_send_bytes(); virtual int get_recv_kbps(); diff --git a/trunk/src/core/srs_core_socket.cpp b/trunk/src/core/srs_core_socket.cpp index e4c6a0c64..365b3ed77 100644 --- a/trunk/src/core/srs_core_socket.cpp +++ b/trunk/src/core/srs_core_socket.cpp @@ -52,6 +52,11 @@ void SrsSocket::set_send_timeout(int64_t timeout_us) send_timeout = timeout_us; } +int64_t SrsSocket::get_send_timeout() +{ + return send_timeout; +} + int64_t SrsSocket::get_recv_bytes() { return recv_bytes; diff --git a/trunk/src/core/srs_core_socket.hpp b/trunk/src/core/srs_core_socket.hpp index 42e8cf752..f355dbfb1 100644 --- a/trunk/src/core/srs_core_socket.hpp +++ b/trunk/src/core/srs_core_socket.hpp @@ -50,6 +50,7 @@ public: virtual void set_recv_timeout(int64_t timeout_us); virtual int64_t get_recv_timeout(); virtual void set_send_timeout(int64_t timeout_us); + virtual int64_t get_send_timeout(); virtual int64_t get_recv_bytes(); virtual int64_t get_send_bytes(); virtual int get_recv_kbps(); From 2f397d0460c9c24ea2afb3cc371094e4e75b5c29 Mon Sep 17 00:00:00 2001 From: winlin Date: Sun, 15 Dec 2013 12:04:28 +0800 Subject: [PATCH 18/37] support reload the gop_cache --- README.md | 2 +- trunk/src/core/srs_core_client.cpp | 17 +++++++++---- trunk/src/core/srs_core_client.hpp | 2 +- trunk/src/core/srs_core_config.cpp | 40 ++++++++++++++++++++++++------ trunk/src/core/srs_core_reload.cpp | 9 ++++++- trunk/src/core/srs_core_reload.hpp | 5 ++-- trunk/src/core/srs_core_source.cpp | 30 +++++++++++++++++++--- trunk/src/core/srs_core_source.hpp | 15 ++++++++--- 8 files changed, 95 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 102eae950..57964586d 100755 --- a/README.md +++ b/README.md @@ -198,7 +198,7 @@ usr sys idl wai hiq siq| read writ| recv send| in out | int csw * nginx v1.5.0: 139524 lines
### History -* v0.9, 2013-12-14, support reload the hls/forwarder/transcoder. +* 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 released. 19186 lines. diff --git a/trunk/src/core/srs_core_client.cpp b/trunk/src/core/srs_core_client.cpp index ef2a8328e..163b76187 100644 --- a/trunk/src/core/srs_core_client.cpp +++ b/trunk/src/core/srs_core_client.cpp @@ -26,6 +26,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include +using namespace std; + #include #include #include @@ -118,15 +120,20 @@ int SrsClient::do_cycle() return ret; } -int SrsClient::on_reload_vhost_removed(SrsConfDirective* vhost) +int SrsClient::on_reload_vhost_removed(string vhost) { int ret = ERROR_SUCCESS; - // if the vhost connected is removed, disconnect the client. - if (req->vhost == vhost->arg0()) { - srs_close_stfd(stfd); + if (req->vhost != vhost) { + return ret; } + // if the vhost connected is removed, disconnect the client. + srs_trace("vhost %s removed/disabled, close client url=%s", + vhost.c_str(), req->get_stream_url().c_str()); + + srs_close_stfd(stfd); + return ret; } @@ -174,7 +181,7 @@ int SrsClient::service_cycle() srs_trace("set chunk_size=%d success", chunk_size); // find a source to publish. - SrsSource* source = SrsSource::find(req->get_stream_url()); + SrsSource* source = SrsSource::find(req->get_stream_url(), req->vhost); srs_assert(source != NULL); // check publish available. diff --git a/trunk/src/core/srs_core_client.hpp b/trunk/src/core/srs_core_client.hpp index 3273fb93b..1c7c7f044 100644 --- a/trunk/src/core/srs_core_client.hpp +++ b/trunk/src/core/srs_core_client.hpp @@ -65,7 +65,7 @@ protected: virtual int do_cycle(); // interface ISrsReloadHandler public: - virtual int on_reload_vhost_removed(SrsConfDirective* vhost); + virtual int on_reload_vhost_removed(std::string vhost); private: // when valid and connected to vhost/app, service the client. virtual int service_cycle(); diff --git a/trunk/src/core/srs_core_config.cpp b/trunk/src/core/srs_core_config.cpp index f5b6922d9..d29a4d1f4 100644 --- a/trunk/src/core/srs_core_config.cpp +++ b/trunk/src/core/srs_core_config.cpp @@ -500,39 +500,60 @@ int SrsConfig::reload() if (old_vhost->name != "vhost") { continue; } + + std::string vhost = old_vhost->arg0(); - SrsConfDirective* new_vhost = root->get("vhost", old_vhost->arg0()); + SrsConfDirective* new_vhost = root->get("vhost", vhost); // ignore if absolutely equal if (new_vhost && srs_directive_equals(old_vhost, new_vhost)) { + srs_trace("vhost %s absolutely equal, ignore.", vhost.c_str()); continue; } // ignore if enable the new vhost when old vhost is disabled. if (get_vhost_enabled(new_vhost) && !get_vhost_enabled(old_vhost)) { + srs_trace("vhost %s disabled=>enabled, ignore.", vhost.c_str()); continue; } // ignore if both old and new vhost are disabled. if (!get_vhost_enabled(new_vhost) && !get_vhost_enabled(old_vhost)) { + srs_trace("vhost %s disabled=>disabled, ignore.", vhost.c_str()); continue; } // merge config: vhost removed/disabled. if (!get_vhost_enabled(new_vhost) && get_vhost_enabled(old_vhost)) { - srs_trace("vhost %s disabled, reload it.", old_vhost->name.c_str()); + srs_trace("vhost %s disabled, reload it.", vhost.c_str()); for (it = subscribes.begin(); it != subscribes.end(); ++it) { ISrsReloadHandler* subscribe = *it; - if ((ret = subscribe->on_reload_vhost_removed(old_vhost)) != ERROR_SUCCESS) { - srs_error("notify subscribes pithy_print remove vhost failed. ret=%d", ret); + if ((ret = subscribe->on_reload_vhost_removed(vhost)) != ERROR_SUCCESS) { + srs_error("notify subscribes pithy_print remove " + "vhost %s failed. ret=%d", vhost.c_str(), ret); return ret; } } - srs_trace("reload remove vhost success."); + srs_trace("reload remove vhost %s success.", vhost.c_str()); } // merge config: vhost modified. + srs_trace("vhost %s modified, reload its detail.", vhost.c_str()); + if (get_vhost_enabled(new_vhost) && get_vhost_enabled(old_vhost)) { + if (!srs_directive_equals(new_vhost->get("gop_cache"), old_vhost->get("gop_cache"))) { + for (it = subscribes.begin(); it != subscribes.end(); ++it) { + ISrsReloadHandler* subscribe = *it; + if ((ret = subscribe->on_reload_gop_cache(vhost)) != ERROR_SUCCESS) { + srs_error("vhost %s notify subscribes gop_cache failed. ret=%d", vhost.c_str(), ret); + return ret; + } + } + srs_trace("vhost %s reload gop_cache success.", vhost.c_str()); + } + // TODO: suppor reload hls/forward/ffmpeg/http + continue; + } + srs_warn("invalid reload path, enabled old: %d, new: %d", + get_vhost_enabled(old_vhost), get_vhost_enabled(new_vhost)); } - // TODO: suppor reload hls/forward/ffmpeg/http - return ret; } @@ -1440,6 +1461,11 @@ int SrsConfig::get_pithy_print_play() bool srs_directive_equals(SrsConfDirective* a, SrsConfDirective* b) { + // both NULL, equal. + if (!a && !b) { + return true; + } + if (!a || !b) { return false; } diff --git a/trunk/src/core/srs_core_reload.cpp b/trunk/src/core/srs_core_reload.cpp index 54a856309..d27cefac5 100644 --- a/trunk/src/core/srs_core_reload.cpp +++ b/trunk/src/core/srs_core_reload.cpp @@ -23,6 +23,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include +using namespace std; + #include ISrsReloadHandler::ISrsReloadHandler() @@ -43,7 +45,12 @@ int ISrsReloadHandler::on_reload_pithy_print() return ERROR_SUCCESS; } -int ISrsReloadHandler::on_reload_vhost_removed(SrsConfDirective* /*vhost*/) +int ISrsReloadHandler::on_reload_vhost_removed(string /*vhost*/) +{ + return ERROR_SUCCESS; +} + +int ISrsReloadHandler::on_reload_gop_cache(string /*vhost*/) { return ERROR_SUCCESS; } diff --git a/trunk/src/core/srs_core_reload.hpp b/trunk/src/core/srs_core_reload.hpp index b92109797..01a31a787 100644 --- a/trunk/src/core/srs_core_reload.hpp +++ b/trunk/src/core/srs_core_reload.hpp @@ -29,7 +29,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include -class SrsConfDirective; +#include /** * the handler for config reload. @@ -42,7 +42,8 @@ public: public: virtual int on_reload_listen(); virtual int on_reload_pithy_print(); - virtual int on_reload_vhost_removed(SrsConfDirective* vhost); + virtual int on_reload_vhost_removed(std::string vhost); + virtual int on_reload_gop_cache(std::string vhost); }; #endif \ No newline at end of file diff --git a/trunk/src/core/srs_core_source.cpp b/trunk/src/core/srs_core_source.cpp index e95f81be8..f58c9aa57 100644 --- a/trunk/src/core/srs_core_source.cpp +++ b/trunk/src/core/srs_core_source.cpp @@ -24,6 +24,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include +using namespace std; #include #include @@ -344,19 +345,21 @@ int SrsGopCache::dump(SrsConsumer* consumer, int tba, int tbv) std::map SrsSource::pool; -SrsSource* SrsSource::find(std::string stream_url) +SrsSource* SrsSource::find(string stream_url, string vhost) { if (pool.find(stream_url) == pool.end()) { - pool[stream_url] = new SrsSource(stream_url); - srs_verbose("create new source for url=%s", stream_url.c_str()); + pool[stream_url] = new SrsSource(stream_url, vhost); + srs_verbose("create new source for " + "url=%s, vhost=%s", stream_url.c_str(), vhost.c_str()); } return pool[stream_url]; } -SrsSource::SrsSource(std::string _stream_url) +SrsSource::SrsSource(string _stream_url, string _vhost) { stream_url = _stream_url; + vhost = _vhost; #ifdef SRS_HLS hls = new SrsHls(); @@ -407,6 +410,25 @@ SrsSource::~SrsSource() #endif } +int SrsSource::on_reload_gop_cache(string _vhost) +{ + int ret = ERROR_SUCCESS; + + if (vhost != _vhost) { + return ret; + } + + // gop cache changed. + bool enabled_cache = config->get_gop_cache(vhost); + + srs_trace("vhost %s gop_cache changed to %d, source url=%s", + vhost.c_str(), enabled_cache, stream_url.c_str()); + + set_cache(enabled_cache); + + return ret; +} + bool SrsSource::can_publish() { return _can_publish; diff --git a/trunk/src/core/srs_core_source.hpp b/trunk/src/core/srs_core_source.hpp index f951e63ad..f119b6754 100644 --- a/trunk/src/core/srs_core_source.hpp +++ b/trunk/src/core/srs_core_source.hpp @@ -34,6 +34,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include +#include + class SrsSource; class SrsCommonMessage; class SrsOnMetaDataPacket; @@ -158,19 +160,21 @@ public: /** * live streaming source. */ -class SrsSource +class SrsSource : public ISrsReloadHandler { private: static std::map pool; public: /** * find stream by vhost/app/stream. - * @stream_url the stream url, for example, myserver.xxx.com/app/stream + * @param stream_url the stream url, for example, myserver.xxx.com/app/stream + * @param vhost the vhost to constructor the object. * @return the matched source, never be NULL. * @remark stream_url should without port and schema. */ - static SrsSource* find(std::string stream_url); + static SrsSource* find(std::string stream_url, std::string vhost); private: + std::string vhost; std::string stream_url; // to delivery stream to clients. std::vector consumers; @@ -206,8 +210,11 @@ private: // the cached audio sequence header. SrsSharedPtrMessage* cache_sh_audio; public: - SrsSource(std::string _stream_url); + SrsSource(std::string _stream_url, std::string _vhost); virtual ~SrsSource(); +// interface ISrsReloadHandler +public: + virtual int on_reload_gop_cache(std::string _vhost); public: virtual bool can_publish(); virtual int on_meta_data(SrsCommonMessage* msg, SrsOnMetaDataPacket* metadata); From 7622878979c9a5dcc554dd58b8271e39f0b7db16 Mon Sep 17 00:00:00 2001 From: winlin Date: Sun, 15 Dec 2013 12:08:01 +0800 Subject: [PATCH 19/37] support reload the gop_cache --- trunk/src/core/srs_core_client.cpp | 4 ++-- trunk/src/core/srs_core_config.cpp | 12 ++++++++++++ trunk/src/core/srs_core_reload.cpp | 5 +++++ trunk/src/core/srs_core_reload.hpp | 1 + trunk/src/core/srs_core_source.cpp | 4 ++++ 5 files changed, 24 insertions(+), 2 deletions(-) diff --git a/trunk/src/core/srs_core_client.cpp b/trunk/src/core/srs_core_client.cpp index 163b76187..17d7583f8 100644 --- a/trunk/src/core/srs_core_client.cpp +++ b/trunk/src/core/srs_core_client.cpp @@ -63,6 +63,8 @@ SrsClient::SrsClient(SrsServer* srs_server, st_netfd_t client_stfd) SrsClient::~SrsClient() { + config->unsubscribe(this); + srs_freepa(ip); srs_freep(req); srs_freep(res); @@ -71,8 +73,6 @@ SrsClient::~SrsClient() #ifdef SRS_HTTP srs_freep(http_hooks); #endif - - config->unsubscribe(this); } // TODO: return detail message when error for client. diff --git a/trunk/src/core/srs_core_config.cpp b/trunk/src/core/srs_core_config.cpp index d29a4d1f4..a373f3563 100644 --- a/trunk/src/core/srs_core_config.cpp +++ b/trunk/src/core/srs_core_config.cpp @@ -537,6 +537,7 @@ int SrsConfig::reload() // merge config: vhost modified. srs_trace("vhost %s modified, reload its detail.", vhost.c_str()); if (get_vhost_enabled(new_vhost) && get_vhost_enabled(old_vhost)) { + // gop_cache if (!srs_directive_equals(new_vhost->get("gop_cache"), old_vhost->get("gop_cache"))) { for (it = subscribes.begin(); it != subscribes.end(); ++it) { ISrsReloadHandler* subscribe = *it; @@ -547,6 +548,17 @@ int SrsConfig::reload() } srs_trace("vhost %s reload gop_cache success.", vhost.c_str()); } + // forward + if (!srs_directive_equals(new_vhost->get("forward"), old_vhost->get("forward"))) { + for (it = subscribes.begin(); it != subscribes.end(); ++it) { + ISrsReloadHandler* subscribe = *it; + if ((ret = subscribe->on_reload_forward(vhost)) != ERROR_SUCCESS) { + srs_error("vhost %s notify subscribes forward failed. ret=%d", vhost.c_str(), ret); + return ret; + } + } + srs_trace("vhost %s reload forward success.", vhost.c_str()); + } // TODO: suppor reload hls/forward/ffmpeg/http continue; } diff --git a/trunk/src/core/srs_core_reload.cpp b/trunk/src/core/srs_core_reload.cpp index d27cefac5..7e6752ea6 100644 --- a/trunk/src/core/srs_core_reload.cpp +++ b/trunk/src/core/srs_core_reload.cpp @@ -55,3 +55,8 @@ int ISrsReloadHandler::on_reload_gop_cache(string /*vhost*/) return ERROR_SUCCESS; } +int ISrsReloadHandler::on_reload_forward(string /*vhost*/) +{ + return ERROR_SUCCESS; +} + diff --git a/trunk/src/core/srs_core_reload.hpp b/trunk/src/core/srs_core_reload.hpp index 01a31a787..9f40927b7 100644 --- a/trunk/src/core/srs_core_reload.hpp +++ b/trunk/src/core/srs_core_reload.hpp @@ -44,6 +44,7 @@ public: virtual int on_reload_pithy_print(); virtual int on_reload_vhost_removed(std::string vhost); virtual int on_reload_gop_cache(std::string vhost); + virtual int on_reload_forward(std::string vhost); }; #endif \ No newline at end of file diff --git a/trunk/src/core/srs_core_source.cpp b/trunk/src/core/srs_core_source.cpp index f58c9aa57..cf405f100 100644 --- a/trunk/src/core/srs_core_source.cpp +++ b/trunk/src/core/srs_core_source.cpp @@ -374,10 +374,14 @@ SrsSource::SrsSource(string _stream_url, string _vhost) _can_publish = true; gop_cache = new SrsGopCache(); + + config->subscribe(this); } SrsSource::~SrsSource() { + config->unsubscribe(this); + if (true) { std::vector::iterator it; for (it = consumers.begin(); it != consumers.end(); ++it) { From b90b64954e44fa4fb7d3781679a5abebfbe80215 Mon Sep 17 00:00:00 2001 From: winlin Date: Sun, 15 Dec 2013 12:34:22 +0800 Subject: [PATCH 20/37] support reload the forwarder --- trunk/conf/srs.conf | 2 +- trunk/src/core/srs_core_client.cpp | 2 +- trunk/src/core/srs_core_rtmp.cpp | 35 +++++++-- trunk/src/core/srs_core_rtmp.hpp | 7 ++ trunk/src/core/srs_core_source.cpp | 114 ++++++++++++++++++++--------- trunk/src/core/srs_core_source.hpp | 28 +++++-- 6 files changed, 134 insertions(+), 54 deletions(-) diff --git a/trunk/conf/srs.conf b/trunk/conf/srs.conf index ad6f26f11..d7477abf6 100755 --- a/trunk/conf/srs.conf +++ b/trunk/conf/srs.conf @@ -83,7 +83,7 @@ vhost __defaultVhost__ { vhost dev { enabled on; gop_cache on; - #forward 127.0.0.1:19350; + forward 127.0.0.1:19350; hls { hls off; hls_path ./objs/nginx/html; diff --git a/trunk/src/core/srs_core_client.cpp b/trunk/src/core/srs_core_client.cpp index 17d7583f8..c41bc62d2 100644 --- a/trunk/src/core/srs_core_client.cpp +++ b/trunk/src/core/srs_core_client.cpp @@ -181,7 +181,7 @@ int SrsClient::service_cycle() srs_trace("set chunk_size=%d success", chunk_size); // find a source to publish. - SrsSource* source = SrsSource::find(req->get_stream_url(), req->vhost); + SrsSource* source = SrsSource::find(req); srs_assert(source != NULL); // check publish available. diff --git a/trunk/src/core/srs_core_rtmp.cpp b/trunk/src/core/srs_core_rtmp.cpp index b48b0795e..ba70ec643 100644 --- a/trunk/src/core/srs_core_rtmp.cpp +++ b/trunk/src/core/srs_core_rtmp.cpp @@ -32,6 +32,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include +using namespace std; + /** * the signature for packets to client. */ @@ -75,6 +77,23 @@ SrsRequest::~SrsRequest() { } +SrsRequest* SrsRequest::copy() +{ + SrsRequest* cp = new SrsRequest(); + + cp->app = app; + cp->objectEncoding = objectEncoding; + cp->pageUrl = pageUrl; + cp->port = port; + cp->schema = schema; + cp->stream = stream; + cp->swfUrl = swfUrl; + cp->tcUrl = tcUrl; + cp->vhost = vhost; + + return cp; +} + int SrsRequest::discovery_app() { int ret = ERROR_SUCCESS; @@ -128,7 +147,7 @@ int SrsRequest::discovery_app() return ret; } -std::string SrsRequest::get_stream_url() +string SrsRequest::get_stream_url() { std::string url = ""; @@ -148,7 +167,7 @@ void SrsRequest::strip() trim(stream, "/ \n\r\t"); } -std::string& SrsRequest::trim(std::string& str, std::string chs) +std::string& SrsRequest::trim(string& str, string chs) { for (int i = 0; i < (int)chs.length(); i++) { char ch = chs.at(i); @@ -243,7 +262,7 @@ int SrsRtmpClient::handshake() return ret; } -int SrsRtmpClient::connect_app(std::string app, std::string tc_url) +int SrsRtmpClient::connect_app(string app, string tc_url) { int ret = ERROR_SUCCESS; @@ -329,7 +348,7 @@ int SrsRtmpClient::create_stream(int& stream_id) return ret; } -int SrsRtmpClient::play(std::string stream, int stream_id) +int SrsRtmpClient::play(string stream, int stream_id) { int ret = ERROR_SUCCESS; @@ -371,7 +390,7 @@ int SrsRtmpClient::play(std::string stream, int stream_id) return ret; } -int SrsRtmpClient::publish(std::string stream, int stream_id) +int SrsRtmpClient::publish(string stream, int stream_id) { int ret = ERROR_SUCCESS; @@ -1045,7 +1064,7 @@ int SrsRtmp::start_flash_publish(int stream_id) return ret; } -int SrsRtmp::identify_create_stream_client(SrsCreateStreamPacket* req, int stream_id, SrsClientType& type, std::string& stream_name) +int SrsRtmp::identify_create_stream_client(SrsCreateStreamPacket* req, int stream_id, SrsClientType& type, string& stream_name) { int ret = ERROR_SUCCESS; @@ -1102,7 +1121,7 @@ int SrsRtmp::identify_create_stream_client(SrsCreateStreamPacket* req, int strea return ret; } -int SrsRtmp::identify_fmle_publish_client(SrsFMLEStartPacket* req, SrsClientType& type, std::string& stream_name) +int SrsRtmp::identify_fmle_publish_client(SrsFMLEStartPacket* req, SrsClientType& type, string& stream_name) { int ret = ERROR_SUCCESS; @@ -1126,7 +1145,7 @@ int SrsRtmp::identify_fmle_publish_client(SrsFMLEStartPacket* req, SrsClientType return ret; } -int SrsRtmp::identify_flash_publish_client(SrsPublishPacket* req, SrsClientType& type, std::string& stream_name) +int SrsRtmp::identify_flash_publish_client(SrsPublishPacket* req, SrsClientType& type, string& stream_name) { int ret = ERROR_SUCCESS; diff --git a/trunk/src/core/srs_core_rtmp.hpp b/trunk/src/core/srs_core_rtmp.hpp index f6e76d1b7..7e17079d6 100644 --- a/trunk/src/core/srs_core_rtmp.hpp +++ b/trunk/src/core/srs_core_rtmp.hpp @@ -65,6 +65,13 @@ struct SrsRequest SrsRequest(); virtual ~SrsRequest(); + + /** + * deep copy the request, for source to use it to support reload, + * for when initialize the source, the request is valid, + * when reload it, the request maybe invalid, so need to copy it. + */ + virtual SrsRequest* copy(); /** * disconvery vhost/app from tcUrl. diff --git a/trunk/src/core/srs_core_source.cpp b/trunk/src/core/srs_core_source.cpp index cf405f100..0c5ad90e3 100644 --- a/trunk/src/core/srs_core_source.cpp +++ b/trunk/src/core/srs_core_source.cpp @@ -345,21 +345,22 @@ int SrsGopCache::dump(SrsConsumer* consumer, int tba, int tbv) std::map SrsSource::pool; -SrsSource* SrsSource::find(string stream_url, string vhost) +SrsSource* SrsSource::find(SrsRequest* req) { + string stream_url = req->get_stream_url(); + string vhost = req->vhost; + if (pool.find(stream_url) == pool.end()) { - pool[stream_url] = new SrsSource(stream_url, vhost); - srs_verbose("create new source for " - "url=%s, vhost=%s", stream_url.c_str(), vhost.c_str()); + pool[stream_url] = new SrsSource(req); + srs_verbose("create new source for url=%s, vhost=%s", stream_url.c_str(), vhost.c_str()); } return pool[stream_url]; } -SrsSource::SrsSource(string _stream_url, string _vhost) +SrsSource::SrsSource(SrsRequest* _req) { - stream_url = _stream_url; - vhost = _vhost; + req = _req->copy(); #ifdef SRS_HLS hls = new SrsHls(); @@ -412,13 +413,15 @@ SrsSource::~SrsSource() #ifdef SRS_FFMPEG srs_freep(encoder); #endif + + srs_freep(req); } -int SrsSource::on_reload_gop_cache(string _vhost) +int SrsSource::on_reload_gop_cache(string vhost) { int ret = ERROR_SUCCESS; - if (vhost != _vhost) { + if (req->vhost != vhost) { return ret; } @@ -426,13 +429,32 @@ int SrsSource::on_reload_gop_cache(string _vhost) bool enabled_cache = config->get_gop_cache(vhost); srs_trace("vhost %s gop_cache changed to %d, source url=%s", - vhost.c_str(), enabled_cache, stream_url.c_str()); + vhost.c_str(), enabled_cache, req->get_stream_url().c_str()); set_cache(enabled_cache); return ret; } +int SrsSource::on_reload_forward(string vhost) +{ + int ret = ERROR_SUCCESS; + + if (req->vhost != vhost) { + return ret; + } + + // forwarders + destroy_forwarders(); + if ((ret = create_forwarders()) != ERROR_SUCCESS) { + srs_error("create forwarders failed. ret=%d", ret); + return ret; + } + srs_trace("vhost %s forwarders reload success", vhost.c_str()); + + return ret; +} + bool SrsSource::can_publish() { return _can_publish; @@ -656,31 +678,23 @@ int SrsSource::on_video(SrsCommonMessage* video) return ret; } -int SrsSource::on_publish(SrsRequest* req) +int SrsSource::on_publish(SrsRequest* _req) { int ret = ERROR_SUCCESS; + // update the request object. + srs_freep(req); + req = _req->copy(); + srs_assert(req); + _can_publish = false; - - // TODO: support reload. // create forwarders - SrsConfDirective* conf = config->get_forward(req->vhost); - for (int i = 0; conf && i < (int)conf->args.size(); i++) { - std::string forward_server = conf->args.at(i); - - SrsForwarder* forwarder = new SrsForwarder(); - forwarders.push_back(forwarder); - - if ((ret = forwarder->on_publish(req, forward_server)) != ERROR_SUCCESS) { - srs_error("start forwarder failed. " - "vhost=%s, app=%s, stream=%s, forward-to=%s", - req->vhost.c_str(), req->app.c_str(), req->stream.c_str(), - forward_server.c_str()); - return ret; - } + if ((ret = create_forwarders()) != ERROR_SUCCESS) { + srs_error("create forwarders failed. ret=%d", ret); + return ret; } - + #ifdef SRS_FFMPEG if ((ret = encoder->on_publish(req)) != ERROR_SUCCESS) { return ret; @@ -698,14 +712,8 @@ int SrsSource::on_publish(SrsRequest* req) void SrsSource::on_unpublish() { - // close all forwarders - std::vector::iterator it; - for (it = forwarders.begin(); it != forwarders.end(); ++it) { - SrsForwarder* forwarder = *it; - forwarder->on_unpublish(); - srs_freep(forwarder); - } - forwarders.clear(); + // destroy all forwarders + destroy_forwarders(); #ifdef SRS_FFMPEG encoder->on_unpublish(); @@ -776,3 +784,37 @@ void SrsSource::set_cache(bool enabled) gop_cache->set(enabled); } +int SrsSource::create_forwarders() +{ + int ret = ERROR_SUCCESS; + + SrsConfDirective* conf = config->get_forward(req->vhost); + for (int i = 0; conf && i < (int)conf->args.size(); i++) { + std::string forward_server = conf->args.at(i); + + SrsForwarder* forwarder = new SrsForwarder(); + forwarders.push_back(forwarder); + + if ((ret = forwarder->on_publish(req, forward_server)) != ERROR_SUCCESS) { + srs_error("start forwarder failed. " + "vhost=%s, app=%s, stream=%s, forward-to=%s", + req->vhost.c_str(), req->app.c_str(), req->stream.c_str(), + forward_server.c_str()); + return ret; + } + } + + return ret; +} + +void SrsSource::destroy_forwarders() +{ + std::vector::iterator it; + for (it = forwarders.begin(); it != forwarders.end(); ++it) { + SrsForwarder* forwarder = *it; + forwarder->on_unpublish(); + srs_freep(forwarder); + } + forwarders.clear(); +} + diff --git a/trunk/src/core/srs_core_source.hpp b/trunk/src/core/srs_core_source.hpp index f119b6754..d3fbc6bef 100644 --- a/trunk/src/core/srs_core_source.hpp +++ b/trunk/src/core/srs_core_source.hpp @@ -167,15 +167,14 @@ private: public: /** * find stream by vhost/app/stream. - * @param stream_url the stream url, for example, myserver.xxx.com/app/stream - * @param vhost the vhost to constructor the object. + * @param req the client request. * @return the matched source, never be NULL. * @remark stream_url should without port and schema. */ - static SrsSource* find(std::string stream_url, std::string vhost); + static SrsSource* find(SrsRequest* req); private: - std::string vhost; - std::string stream_url; + // deep copy of client request. + SrsRequest* req; // to delivery stream to clients. std::vector consumers; // hls handler. @@ -210,22 +209,35 @@ private: // the cached audio sequence header. SrsSharedPtrMessage* cache_sh_audio; public: - SrsSource(std::string _stream_url, std::string _vhost); + /** + * @param _req the client request object, + * this object will deep copy it for reload. + */ + SrsSource(SrsRequest* _req); virtual ~SrsSource(); // interface ISrsReloadHandler public: - virtual int on_reload_gop_cache(std::string _vhost); + virtual int on_reload_gop_cache(std::string vhost); + virtual int on_reload_forward(std::string vhost); public: virtual bool can_publish(); virtual int on_meta_data(SrsCommonMessage* msg, SrsOnMetaDataPacket* metadata); virtual int on_audio(SrsCommonMessage* audio); virtual int on_video(SrsCommonMessage* video); - virtual int on_publish(SrsRequest* req); + /** + * publish stream event notify. + * @param _req the request from client, the source will deep copy it, + * for when reload the request of client maybe invalid. + */ + virtual int on_publish(SrsRequest* _req); virtual void on_unpublish(); public: virtual int create_consumer(SrsConsumer*& consumer); virtual void on_consumer_destroy(SrsConsumer* consumer); virtual void set_cache(bool enabled); +private: + virtual int create_forwarders(); + virtual void destroy_forwarders(); }; #endif \ No newline at end of file From aaba290c1d047f90db8825dbab14ad4d13564795 Mon Sep 17 00:00:00 2001 From: winlin Date: Sun, 15 Dec 2013 12:44:09 +0800 Subject: [PATCH 21/37] add reload readme. --- README.md | 18 ++++++++++++++++-- trunk/conf/srs.conf | 2 +- trunk/configure | 10 +++++----- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 57964586d..cee802905 100755 --- a/README.md +++ b/README.md @@ -19,13 +19,19 @@ cd simple-rtmp-server-*.*/trunk ./configure --with-ssl --with-hls --with-ffmpeg --with-http make +or get the latest code:
+

+git clone  https://github.com/winlinvip/simple-rtmp-server
+cd simple-rtmp-server/trunk
+./configure --with-ssl --with-hls --with-ffmpeg --with-http
+
step 2: start srs
-./objs/simple_rtmp_server -c conf/srs.conf
+./objs/srs -c conf/srs.conf
 
step 3(optinal): start srs listen at 19350 to forward to
-./objs/simple_rtmp_server -c conf/srs.19350.conf
+./objs/srs -c conf/srs.19350.conf
 
step 4(optional): start nginx for HLS
@@ -82,6 +88,14 @@ rtmp url: rtmp://demo:19350/live/livestream_sd
 m3u8 url: http://demo:80/forward/live/livestream_sd.m3u8
 for android: http://demo:80/forward/live/livestream_sd.html
 
+step 11: modify the config and reload it (all features support reload)
+
+killall -1 srs
+
+or use specified signal name to reload:
+
+killall -s SIGHUP srs
+
### Architecture System Architecture: diff --git a/trunk/conf/srs.conf b/trunk/conf/srs.conf index d7477abf6..ad6f26f11 100755 --- a/trunk/conf/srs.conf +++ b/trunk/conf/srs.conf @@ -83,7 +83,7 @@ vhost __defaultVhost__ { vhost dev { enabled on; gop_cache on; - forward 127.0.0.1:19350; + #forward 127.0.0.1:19350; hls { hls off; hls_path ./objs/nginx/html; diff --git a/trunk/configure b/trunk/configure index 40455a1de..9d69081a7 100755 --- a/trunk/configure +++ b/trunk/configure @@ -57,11 +57,11 @@ help: @echo " server build the srs(simple rtmp server) over st(state-threads)" clean: - (rm -f Makefile; cd ${SRS_OBJS}; rm -rf Makefile *.hpp src st_*_load) + (rm -f Makefile; cd ${SRS_OBJS}; rm -rf srs Makefile *.hpp src st_*_load) server: _prepare_dir @echo "build the srs(simple rtmp server) over st(state-threads)" - \$(MAKE) -f ${SRS_OBJS}/${SRS_MAKEFILE} simple_rtmp_server + \$(MAKE) -f ${SRS_OBJS}/${SRS_MAKEFILE} srs # the ./configure will generate it. _prepare_dir: @@ -87,7 +87,7 @@ GCC = g++ LINK = \$(GCC) AR = ar -.PHONY: default simple_rtmp_server +.PHONY: default srs default: @@ -142,7 +142,7 @@ if [ $SRS_SSL = YES ]; then else LINK_OPTIONS="-ldl" fi -BUILD_KEY="simple_rtmp_server" APP_MAIN="srs_main_server" APP_NAME="simple_rtmp_server" SO_PATH="" . auto/apps.sh +BUILD_KEY="srs" APP_MAIN="srs_main_server" APP_NAME="srs" SO_PATH="" . auto/apps.sh echo 'configure ok! ' @@ -187,4 +187,4 @@ fi if [ $SRS_HTTP = YES ]; then echo -e "\" python ./research/api-server/server.py 8085 \" to start the api-server" fi -echo "\" ./objs/simple_rtmp_server -c conf/srs.conf \" to start the srs live server" +echo "\" ./objs/srs -c conf/srs.conf \" to start the srs live server" From fb67f9116781a9feadfc3456136802de8be52a07 Mon Sep 17 00:00:00 2001 From: winlin Date: Sun, 15 Dec 2013 13:07:39 +0800 Subject: [PATCH 22/37] support reload the hls/forwarder/transcoder --- trunk/src/core/srs_core_config.cpp | 22 +++++++++++++++ trunk/src/core/srs_core_reload.cpp | 10 +++++++ trunk/src/core/srs_core_reload.hpp | 2 ++ trunk/src/core/srs_core_source.cpp | 43 ++++++++++++++++++++++++++++++ trunk/src/core/srs_core_source.hpp | 2 ++ 5 files changed, 79 insertions(+) diff --git a/trunk/src/core/srs_core_config.cpp b/trunk/src/core/srs_core_config.cpp index a373f3563..a7dc6aa15 100644 --- a/trunk/src/core/srs_core_config.cpp +++ b/trunk/src/core/srs_core_config.cpp @@ -559,6 +559,28 @@ int SrsConfig::reload() } srs_trace("vhost %s reload forward success.", vhost.c_str()); } + // hls + if (!srs_directive_equals(new_vhost->get("hls"), old_vhost->get("hls"))) { + for (it = subscribes.begin(); it != subscribes.end(); ++it) { + ISrsReloadHandler* subscribe = *it; + if ((ret = subscribe->on_reload_hls(vhost)) != ERROR_SUCCESS) { + srs_error("vhost %s notify subscribes hls failed. ret=%d", vhost.c_str(), ret); + return ret; + } + } + srs_trace("vhost %s reload hls success.", vhost.c_str()); + } + // transcode + if (!srs_directive_equals(new_vhost->get("transcode"), old_vhost->get("transcode"))) { + for (it = subscribes.begin(); it != subscribes.end(); ++it) { + ISrsReloadHandler* subscribe = *it; + if ((ret = subscribe->on_reload_transcode(vhost)) != ERROR_SUCCESS) { + srs_error("vhost %s notify subscribes transcode failed. ret=%d", vhost.c_str(), ret); + return ret; + } + } + srs_trace("vhost %s reload transcode success.", vhost.c_str()); + } // TODO: suppor reload hls/forward/ffmpeg/http continue; } diff --git a/trunk/src/core/srs_core_reload.cpp b/trunk/src/core/srs_core_reload.cpp index 7e6752ea6..71cd5a3c1 100644 --- a/trunk/src/core/srs_core_reload.cpp +++ b/trunk/src/core/srs_core_reload.cpp @@ -60,3 +60,13 @@ int ISrsReloadHandler::on_reload_forward(string /*vhost*/) return ERROR_SUCCESS; } +int ISrsReloadHandler::on_reload_hls(string /*vhost*/) +{ + return ERROR_SUCCESS; +} + +int ISrsReloadHandler::on_reload_transcode(string /*vhost*/) +{ + return ERROR_SUCCESS; +} + diff --git a/trunk/src/core/srs_core_reload.hpp b/trunk/src/core/srs_core_reload.hpp index 9f40927b7..a819bd7dd 100644 --- a/trunk/src/core/srs_core_reload.hpp +++ b/trunk/src/core/srs_core_reload.hpp @@ -45,6 +45,8 @@ public: virtual int on_reload_vhost_removed(std::string vhost); virtual int on_reload_gop_cache(std::string vhost); virtual int on_reload_forward(std::string vhost); + virtual int on_reload_hls(std::string vhost); + virtual int on_reload_transcode(std::string vhost); }; #endif \ No newline at end of file diff --git a/trunk/src/core/srs_core_source.cpp b/trunk/src/core/srs_core_source.cpp index 0c5ad90e3..32b3586ca 100644 --- a/trunk/src/core/srs_core_source.cpp +++ b/trunk/src/core/srs_core_source.cpp @@ -455,6 +455,47 @@ int SrsSource::on_reload_forward(string vhost) return ret; } +int SrsSource::on_reload_hls(string vhost) +{ + int ret = ERROR_SUCCESS; + + if (req->vhost != vhost) { + return ret; + } + + // TODO: HLS should continue previous sequence and stream. +#ifdef SRS_HLS + hls->on_unpublish(); + if ((ret = hls->on_publish(req)) != ERROR_SUCCESS) { + srs_error("hls publish failed. ret=%d", ret); + return ret; + } + srs_trace("vhost %s hls reload success", vhost.c_str()); +#endif + + return ret; +} + +int SrsSource::on_reload_transcode(string vhost) +{ + int ret = ERROR_SUCCESS; + + if (req->vhost != vhost) { + return ret; + } + +#ifdef SRS_FFMPEG + encoder->on_unpublish(); + if ((ret = encoder->on_publish(req)) != ERROR_SUCCESS) { + srs_error("start encoder failed. ret=%d", ret); + return ret; + } + srs_trace("vhost %s transcode reload success", vhost.c_str()); +#endif + + return ret; +} + bool SrsSource::can_publish() { return _can_publish; @@ -697,12 +738,14 @@ int SrsSource::on_publish(SrsRequest* _req) #ifdef SRS_FFMPEG if ((ret = encoder->on_publish(req)) != ERROR_SUCCESS) { + srs_error("start encoder failed. ret=%d", ret); return ret; } #endif #ifdef SRS_HLS if ((ret = hls->on_publish(req)) != ERROR_SUCCESS) { + srs_error("start hls failed. ret=%d", ret); return ret; } #endif diff --git a/trunk/src/core/srs_core_source.hpp b/trunk/src/core/srs_core_source.hpp index d3fbc6bef..aeada9d3d 100644 --- a/trunk/src/core/srs_core_source.hpp +++ b/trunk/src/core/srs_core_source.hpp @@ -219,6 +219,8 @@ public: public: virtual int on_reload_gop_cache(std::string vhost); virtual int on_reload_forward(std::string vhost); + virtual int on_reload_hls(std::string vhost); + virtual int on_reload_transcode(std::string vhost); public: virtual bool can_publish(); virtual int on_meta_data(SrsCommonMessage* msg, SrsOnMetaDataPacket* metadata); From 3ad692bf97aa7beb02b38f5db5f102fc6050a8b2 Mon Sep 17 00:00:00 2001 From: winlin Date: Sun, 15 Dec 2013 13:13:14 +0800 Subject: [PATCH 23/37] update readme --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index cee802905..d9d9d82e1 100755 --- a/README.md +++ b/README.md @@ -215,7 +215,7 @@ usr sys idl wai hiq siq| read writ| recv send| in out | int csw * 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 released. 19186 lines. +* 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: 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. @@ -224,32 +224,32 @@ usr sys idl wai hiq siq| read writ| recv send| in out | int csw * 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 released. 17605 lines. +* 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-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 released. 16094 lines. +* 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, 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 released. 14449 lines. +* 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-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 released. 12500 lines. +* 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, 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 released. 11773 lines. +* 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, support refer/play-refer/publish-refer. * v0.3, 2013-11-04, support vhosts specified config. * v0.3, 2013-11-02, support listen multiple ports. @@ -257,12 +257,12 @@ usr sys idl wai hiq siq| read writ| recv send| in out | int csw * 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 released. 10125 lines. +* 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, 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 released. 8287 lines. +* 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, 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. From b3ef28f831b07419d237c5129390e265176ac7f2 Mon Sep 17 00:00:00 2001 From: winlin Date: Sun, 15 Dec 2013 13:23:03 +0800 Subject: [PATCH 24/37] fix bug of get hls enabled config --- trunk/conf/srs.conf | 12 ++++++------ trunk/src/core/srs_core_config.cpp | 10 ++++++++-- trunk/src/core/srs_core_hls.cpp | 2 -- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/trunk/conf/srs.conf b/trunk/conf/srs.conf index ad6f26f11..1c6777a79 100755 --- a/trunk/conf/srs.conf +++ b/trunk/conf/srs.conf @@ -23,14 +23,14 @@ vhost __defaultVhost__ { gop_cache on; forward 127.0.0.1:19350; hls { - hls on; + enabled on; hls_path ./objs/nginx/html; hls_fragment 5; hls_window 30; } transcode { - enabled on; - ffmpeg ./objs/ffmpeg/bin/ffmpeg; + enabled on; + ffmpeg ./objs/ffmpeg/bin/ffmpeg; engine ld { enabled on; vfilter { @@ -85,7 +85,7 @@ vhost dev { gop_cache on; #forward 127.0.0.1:19350; hls { - hls off; + enabled on; hls_path ./objs/nginx/html; hls_fragment 5; hls_window 30; @@ -635,7 +635,7 @@ vhost with-hls.vhost.com { # whether the hls is enabled. # if off, donot write hls(ts and m3u8) when publish. # default: off - hls on; + enabled on; # the hls output path. # the app dir is auto created under the hls_path. # for example, for rtmp stream: @@ -662,7 +662,7 @@ vhost no-hls.vhost.com { # whether the hls is enabled. # if off, donot write hls(ts and m3u8) when publish. # default: off - hls off; + enabled off; } } # the vhost for min delay, donot cache any stream. diff --git a/trunk/src/core/srs_core_config.cpp b/trunk/src/core/srs_core_config.cpp index a7dc6aa15..904dc0fed 100644 --- a/trunk/src/core/srs_core_config.cpp +++ b/trunk/src/core/srs_core_config.cpp @@ -1312,11 +1312,17 @@ bool SrsConfig::get_hls_enabled(string vhost) return false; } - if (hls->arg0() == "off") { + SrsConfDirective* conf = hls->get("enabled"); + + if (!conf) { return false; } - return true; + if (conf->arg0() == "on") { + return true; + } + + return false; } string SrsConfig::get_hls_path(string vhost) diff --git a/trunk/src/core/srs_core_hls.cpp b/trunk/src/core/srs_core_hls.cpp index ff5b8ffbf..14a82cd54 100644 --- a/trunk/src/core/srs_core_hls.cpp +++ b/trunk/src/core/srs_core_hls.cpp @@ -1148,7 +1148,6 @@ int SrsHls::on_publish(SrsRequest* req) std::string stream = req->stream; std::string app = req->app; - // TODO: support reload. if (!config->get_hls_enabled(vhost)) { return ret; } @@ -1156,7 +1155,6 @@ int SrsHls::on_publish(SrsRequest* req) // if enabled, open the muxer. hls_enabled = true; - // TODO: subscribe the reload event. int hls_fragment = config->get_hls_fragment(vhost); int hls_window = config->get_hls_window(vhost); From d911c85a12a2ab66dfce0d60f9372797cd804437 Mon Sep 17 00:00:00 2001 From: winlin Date: Sun, 15 Dec 2013 13:27:08 +0800 Subject: [PATCH 25/37] add todo for the reload --- trunk/src/core/srs_core_hls.cpp | 2 -- trunk/src/core/srs_core_source.cpp | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/trunk/src/core/srs_core_hls.cpp b/trunk/src/core/srs_core_hls.cpp index 14a82cd54..76b6df87c 100644 --- a/trunk/src/core/srs_core_hls.cpp +++ b/trunk/src/core/srs_core_hls.cpp @@ -1253,7 +1253,6 @@ int SrsHls::on_audio(SrsSharedPtrMessage* audio) SrsAutoFree(SrsSharedPtrMessage, audio, false); - // TODO: maybe donot need to demux the aac? if (!hls_enabled) { return ret; } @@ -1296,7 +1295,6 @@ int SrsHls::on_video(SrsSharedPtrMessage* video) SrsAutoFree(SrsSharedPtrMessage, video, false); - // TODO: maybe donot need to demux the avc? if (!hls_enabled) { return ret; } diff --git a/trunk/src/core/srs_core_source.cpp b/trunk/src/core/srs_core_source.cpp index 32b3586ca..94556df84 100644 --- a/trunk/src/core/srs_core_source.cpp +++ b/trunk/src/core/srs_core_source.cpp @@ -450,6 +450,7 @@ int SrsSource::on_reload_forward(string vhost) srs_error("create forwarders failed. ret=%d", ret); return ret; } + // TODO: FIXME: must feed it the sequence header. srs_trace("vhost %s forwarders reload success", vhost.c_str()); return ret; @@ -470,6 +471,7 @@ int SrsSource::on_reload_hls(string vhost) srs_error("hls publish failed. ret=%d", ret); return ret; } + // TODO: FIXME: must feed it the sequence header. srs_trace("vhost %s hls reload success", vhost.c_str()); #endif From 63cec6f967e359e4d5d06dcd0fd2840d45156acd Mon Sep 17 00:00:00 2001 From: winlin Date: Sun, 15 Dec 2013 13:41:17 +0800 Subject: [PATCH 26/37] update readme --- README.md | 22 +++++++++++----------- trunk/conf/srs.conf | 11 ++++++++++- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index d9d9d82e1..a509d266c 100755 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ see also: [http://winlinvip.github.io/simple-rtmp-server](http://winlinvip.githu winlin(winterserver): [http://blog.csdn.net/win_lin](http://blog.csdn.net/win_lin) ### Usage -step 1: build srs
+step 1: build srs
 tar xf simple-rtmp-server-*.*.tar.gz
 cd simple-rtmp-server-*.*/trunk
@@ -25,23 +25,23 @@ git clone  https://github.com/winlinvip/simple-rtmp-server
 cd simple-rtmp-server/trunk
 ./configure --with-ssl --with-hls --with-ffmpeg --with-http
 
-step 2: start srs
+step 2: start srs
 ./objs/srs -c conf/srs.conf
 
-step 3(optinal): start srs listen at 19350 to forward to
+step 3(optinal): start srs listen at 19350 to forward to
 ./objs/srs -c conf/srs.19350.conf
 
-step 4(optional): start nginx for HLS
+step 4(optinal): start nginx for HLS
 sudo ./objs/nginx/sbin/nginx
 
-step 5(optional): start http hooks for srs callback
+step 5(optinal): start http hooks for srs callback
 python ./research/api-server/server.py 8085
 
-step 6: publish live stream
+step 6: publish live stream
 FMS URL: rtmp://127.0.0.1:1935/live
 Stream:  livestream
@@ -53,7 +53,7 @@ For example, use ffmpeg to publish:
         sleep 1; \
     done
 
-step 7: add server ip to client hosts as demo.
+step 7: add server ip to client hosts as demo.
 # edit the folowing file:
 # linux: /etc/hosts
@@ -61,13 +61,13 @@ step 7: add server ip to client hosts as demo. 
# where server ip is 192.168.2.111 192.168.2.111 demo
-step 8: play live stream.
+step 8: play live stream.
 rtmp url: rtmp://demo:1935/live/livestream
 m3u8 url: http://demo:80/live/livestream.m3u8
 for android: http://demo:80/live/livestream.html
 
-step 9: play live stream auto transcoded
+step 9(optinal): play live stream auto transcoded
 rtmp url: rtmp://demo:1935/live/livestream_ld
 m3u8 url: http://demo:80/live/livestream_ld.m3u8
@@ -76,7 +76,7 @@ rtmp url: rtmp://demo:1935/live/livestream_sd
 m3u8 url: http://demo:80/live/livestream_sd.m3u8
 for android: http://demo:80/live/livestream_sd.html
 
-step 10: play live stream auto forwarded, the hls dir change to /forward
+step 10(optinal): play live stream auto forwarded, the hls dir change to /forward
 rtmp url: rtmp://demo:19350/live/livestream
 m3u8 url: http://demo:80/forward/live/livestream.m3u8
@@ -88,7 +88,7 @@ rtmp url: rtmp://demo:19350/live/livestream_sd
 m3u8 url: http://demo:80/forward/live/livestream_sd.m3u8
 for android: http://demo:80/forward/live/livestream_sd.html
 
-step 11: modify the config and reload it (all features support reload)
+step 11(optinal): modify the config and reload it (all features support reload)
 killall -1 srs
 
diff --git a/trunk/conf/srs.conf b/trunk/conf/srs.conf index 1c6777a79..a9a1368ec 100755 --- a/trunk/conf/srs.conf +++ b/trunk/conf/srs.conf @@ -28,6 +28,15 @@ vhost __defaultVhost__ { hls_fragment 5; hls_window 30; } + http_hooks { + enabled off; + on_connect http://127.0.0.1:8085/api/v1/clients; + on_close http://127.0.0.1:8085/api/v1/clients; + on_publish http://127.0.0.1:8085/api/v1/streams; + on_unpublish http://127.0.0.1:8085/api/v1/streams; + on_play http://127.0.0.1:8085/api/v1/sessions; + on_stop http://127.0.0.1:8085/api/v1/sessions; + } transcode { enabled on; ffmpeg ./objs/ffmpeg/bin/ffmpeg; @@ -85,7 +94,7 @@ vhost dev { gop_cache on; #forward 127.0.0.1:19350; hls { - enabled on; + enabled off; hls_path ./objs/nginx/html; hls_fragment 5; hls_window 30; From c7ec6f511c2a80fd78847adce624d188ec048f62 Mon Sep 17 00:00:00 2001 From: winlin Date: Sun, 15 Dec 2013 16:53:37 +0800 Subject: [PATCH 27/37] fix the script error --- README.md | 2 +- trunk/auto/build_ffmpeg.sh | 5 +++-- trunk/auto/depends.sh | 0 3 files changed, 4 insertions(+), 3 deletions(-) mode change 100755 => 100644 trunk/auto/depends.sh diff --git a/README.md b/README.md index a509d266c..212fb2b6e 100755 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ for android: http://demo:80/forward/live/livestream_sd.html
 killall -1 srs
 
-or use specified signal name to reload:
+or use specified signal to reload:
 killall -s SIGHUP srs
 
diff --git a/trunk/auto/build_ffmpeg.sh b/trunk/auto/build_ffmpeg.sh index 439f73ae9..5d841c8df 100644 --- a/trunk/auto/build_ffmpeg.sh +++ b/trunk/auto/build_ffmpeg.sh @@ -86,7 +86,8 @@ else --enable-postproc --enable-bzlib --enable-zlib --enable-parsers \ --enable-libfreetype \ --enable-libx264 --enable-libmp3lame --enable-libaacplus \ - --enable-pthreads --extra-libs=-lpthread --enable-encoders --enable-decoders --enable-avfilter --enable-muxers --enable-demuxers && + --enable-pthreads --extra-libs=-lpthread \ + --enable-encoders --enable-decoders --enable-avfilter --enable-muxers --enable-demuxers && make && make install - ret=$?; if [[ 0 -ne ${ret} ]]; then echo "build x264 failed"; exit 1; fi + ret=$?; if [[ 0 -ne ${ret} ]]; then echo "build ffmpeg failed"; exit 1; fi fi diff --git a/trunk/auto/depends.sh b/trunk/auto/depends.sh old mode 100755 new mode 100644 From 270041b225971bfd22572a72a4642da6db8d76eb Mon Sep 17 00:00:00 2001 From: winlin Date: Sun, 15 Dec 2013 17:09:25 +0800 Subject: [PATCH 28/37] fix the forwarder reconnect bug, feed it the sequence header. --- README.md | 1 + trunk/auto/build_ffmpeg.sh | 4 +++- trunk/conf/srs.conf | 2 +- trunk/src/core/srs_core_forward.cpp | 12 +++++++++--- trunk/src/core/srs_core_forward.hpp | 4 +++- trunk/src/core/srs_core_source.cpp | 26 ++++++++++++++++++++++++-- trunk/src/core/srs_core_source.hpp | 3 +++ 7 files changed, 44 insertions(+), 8 deletions(-) mode change 100644 => 100755 trunk/auto/build_ffmpeg.sh diff --git a/README.md b/README.md index 212fb2b6e..67e7b3c9b 100755 --- a/README.md +++ b/README.md @@ -212,6 +212,7 @@ usr sys idl wai hiq siq| read writ| recv send| in out | int csw * nginx v1.5.0: 139524 lines
### History +* v0.9, 2013-12-15, fix the forwarder reconnect bug, feed it the sequence header. * 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. diff --git a/trunk/auto/build_ffmpeg.sh b/trunk/auto/build_ffmpeg.sh old mode 100644 new mode 100755 index 5d841c8df..2e177e160 --- a/trunk/auto/build_ffmpeg.sh +++ b/trunk/auto/build_ffmpeg.sh @@ -59,7 +59,9 @@ else echo "build x264" 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 && ./configure --prefix=${ff_release_dir} --disable-opencl --bit-depth=8 --enable-static && make && make install + cd x264-snapshot-20131129-2245-stable && + ./configure --prefix=${ff_release_dir} --disable-opencl --bit-depth=8 --enable-static && + make && make install ret=$?; if [[ 0 -ne ${ret} ]]; then echo "build x264 failed"; exit 1; fi fi diff --git a/trunk/conf/srs.conf b/trunk/conf/srs.conf index a9a1368ec..dc94b75ef 100755 --- a/trunk/conf/srs.conf +++ b/trunk/conf/srs.conf @@ -92,7 +92,7 @@ vhost __defaultVhost__ { vhost dev { enabled on; gop_cache on; - #forward 127.0.0.1:19350; + forward 127.0.0.1:19350; hls { enabled off; hls_path ./objs/nginx/html; diff --git a/trunk/src/core/srs_core_forward.cpp b/trunk/src/core/srs_core_forward.cpp index 39059121b..c6bca4fbb 100644 --- a/trunk/src/core/srs_core_forward.cpp +++ b/trunk/src/core/srs_core_forward.cpp @@ -35,14 +35,17 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include #include +#include #define SRS_PULSE_TIMEOUT_MS 100 #define SRS_FORWARDER_SLEEP_MS 2000 #define SRS_SEND_TIMEOUT_US 3000000L #define SRS_RECV_TIMEOUT_US SRS_SEND_TIMEOUT_US -SrsForwarder::SrsForwarder() +SrsForwarder::SrsForwarder(SrsSource* _source) { + source = _source; + client = NULL; stfd = NULL; stream_id = 0; @@ -182,14 +185,17 @@ int SrsForwarder::cycle() return ret; } - // TODO: FIXME: need to cache the metadata and sequence header when reconnect. - if ((ret = client->publish(stream_name, stream_id)) != ERROR_SUCCESS) { srs_error("connect with server failed, stream_name=%s, stream_id=%d. ret=%d", stream_name.c_str(), stream_id, ret); return ret; } + if ((ret = source->on_forwarder_start(this)) != ERROR_SUCCESS) { + srs_error("callback the source to feed the sequence header failed. ret=%d", ret); + return ret; + } + if ((ret = forward()) != ERROR_SUCCESS) { return ret; } diff --git a/trunk/src/core/srs_core_forward.hpp b/trunk/src/core/srs_core_forward.hpp index 01bb7251b..d2d38242e 100644 --- a/trunk/src/core/srs_core_forward.hpp +++ b/trunk/src/core/srs_core_forward.hpp @@ -38,6 +38,7 @@ class SrsSharedPtrMessage; class SrsOnMetaDataPacket; class SrsRtmpClient; class SrsRequest; +class SrsSource; /** * forward the stream to other servers. @@ -55,10 +56,11 @@ private: st_netfd_t stfd; SrsThread* pthread; private: + SrsSource* source; SrsRtmpClient* client; std::vector msgs; public: - SrsForwarder(); + SrsForwarder(SrsSource* _source); virtual ~SrsForwarder(); public: virtual int on_publish(SrsRequest* req, std::string forward_server); diff --git a/trunk/src/core/srs_core_source.cpp b/trunk/src/core/srs_core_source.cpp index 94556df84..fab9361bc 100644 --- a/trunk/src/core/srs_core_source.cpp +++ b/trunk/src/core/srs_core_source.cpp @@ -450,7 +450,7 @@ int SrsSource::on_reload_forward(string vhost) srs_error("create forwarders failed. ret=%d", ret); return ret; } - // TODO: FIXME: must feed it the sequence header. + srs_trace("vhost %s forwarders reload success", vhost.c_str()); return ret; @@ -498,6 +498,28 @@ int SrsSource::on_reload_transcode(string vhost) return ret; } +int SrsSource::on_forwarder_start(SrsForwarder* forwarder) +{ + int ret = ERROR_SUCCESS; + + // 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) { + 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) { + 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) { + srs_error("forwarder process audio sequence header message failed. ret=%d", ret); + return ret; + } + + return ret; +} + bool SrsSource::can_publish() { return _can_publish; @@ -837,7 +859,7 @@ int SrsSource::create_forwarders() for (int i = 0; conf && i < (int)conf->args.size(); i++) { std::string forward_server = conf->args.at(i); - SrsForwarder* forwarder = new SrsForwarder(); + SrsForwarder* forwarder = new SrsForwarder(this); forwarders.push_back(forwarder); if ((ret = forwarder->on_publish(req, forward_server)) != ERROR_SUCCESS) { diff --git a/trunk/src/core/srs_core_source.hpp b/trunk/src/core/srs_core_source.hpp index aeada9d3d..897918c7e 100644 --- a/trunk/src/core/srs_core_source.hpp +++ b/trunk/src/core/srs_core_source.hpp @@ -221,6 +221,9 @@ public: virtual int on_reload_forward(std::string vhost); virtual int on_reload_hls(std::string vhost); virtual int on_reload_transcode(std::string vhost); +// for the SrsForwarder to callback to request the sequence headers. +public: + virtual int on_forwarder_start(SrsForwarder* forwarder); public: virtual bool can_publish(); virtual int on_meta_data(SrsCommonMessage* msg, SrsOnMetaDataPacket* metadata); From b4093bfbe4093d9abd7e8c9f4feeaaca21d92f00 Mon Sep 17 00:00:00 2001 From: winlin Date: Sun, 15 Dec 2013 18:25:55 +0800 Subject: [PATCH 29/37] support set live queue length --- README.md | 1 + trunk/conf/srs.conf | 9 +- trunk/src/core/srs_core_config.cpp | 28 +++ trunk/src/core/srs_core_config.hpp | 5 + trunk/src/core/srs_core_forward.cpp | 8 + trunk/src/core/srs_core_forward.hpp | 4 + trunk/src/core/srs_core_reload.cpp | 5 + trunk/src/core/srs_core_reload.hpp | 1 + trunk/src/core/srs_core_source.cpp | 260 ++++++++++++++++++---------- trunk/src/core/srs_core_source.hpp | 50 +++++- 10 files changed, 276 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index 67e7b3c9b..a62d389e3 100755 --- a/README.md +++ b/README.md @@ -212,6 +212,7 @@ usr sys idl wai hiq siq| read writ| recv send| in out | int csw * nginx v1.5.0: 139524 lines
### History +* v0.9, 2013-12-15, drop the old whole gop when live message queue full. * v0.9, 2013-12-15, fix the forwarder reconnect bug, feed it the sequence header. * v0.9, 2013-12-15, support reload the hls/forwarder/transcoder. * v0.9, 2013-12-14, refine the thread model for the retry threads. diff --git a/trunk/conf/srs.conf b/trunk/conf/srs.conf index dc94b75ef..2e1826be2 100755 --- a/trunk/conf/srs.conf +++ b/trunk/conf/srs.conf @@ -21,6 +21,7 @@ max_connections 2000; vhost __defaultVhost__ { enabled on; gop_cache on; + queue_length 30; forward 127.0.0.1:19350; hls { enabled on; @@ -92,7 +93,8 @@ vhost __defaultVhost__ { vhost dev { enabled on; gop_cache on; - forward 127.0.0.1:19350; + queue_length 30; + #forward 127.0.0.1:19350; hls { enabled off; hls_path ./objs/nginx/html; @@ -685,6 +687,11 @@ vhost min.delay.com { # set to on if requires client fast startup. # default: on gop_cache off; + # the max live queue length in seconds. + # if the messages in the queue exceed the max length, + # drop the old whole gop. + # default: 30 + queue_length 10; } # the vhost for antisuck. vhost refer.anti_suck.com { diff --git a/trunk/src/core/srs_core_config.cpp b/trunk/src/core/srs_core_config.cpp index 904dc0fed..fea6d9dd2 100644 --- a/trunk/src/core/srs_core_config.cpp +++ b/trunk/src/core/srs_core_config.cpp @@ -548,6 +548,17 @@ int SrsConfig::reload() } srs_trace("vhost %s reload gop_cache success.", vhost.c_str()); } + // queue_length + if (!srs_directive_equals(new_vhost->get("queue_length"), old_vhost->get("queue_length"))) { + for (it = subscribes.begin(); it != subscribes.end(); ++it) { + ISrsReloadHandler* subscribe = *it; + if ((ret = subscribe->on_reload_queue_length(vhost)) != ERROR_SUCCESS) { + srs_error("vhost %s notify subscribes queue_length failed. ret=%d", vhost.c_str(), ret); + return ret; + } + } + srs_trace("vhost %s reload queue_length success.", vhost.c_str()); + } // forward if (!srs_directive_equals(new_vhost->get("forward"), old_vhost->get("forward"))) { for (it = subscribes.begin(); it != subscribes.end(); ++it) { @@ -1275,6 +1286,7 @@ bool SrsConfig::get_gop_cache(string vhost) return true; } + conf = conf->get("gop_cache"); if (conf && conf->arg0() == "off") { return false; } @@ -1282,6 +1294,22 @@ bool SrsConfig::get_gop_cache(string vhost) return true; } +double SrsConfig::get_queue_length(string vhost) +{ + SrsConfDirective* conf = get_vhost(vhost); + + if (!conf) { + return SRS_CONF_DEFAULT_QUEUE_LENGTH; + } + + conf = conf->get("queue_length"); + if (conf || conf->arg0().empty()) { + return SRS_CONF_DEFAULT_QUEUE_LENGTH; + } + + return ::atoi(conf->arg0().c_str()); +} + SrsConfDirective* SrsConfig::get_forward(string vhost) { SrsConfDirective* conf = get_vhost(vhost); diff --git a/trunk/src/core/srs_core_config.hpp b/trunk/src/core/srs_core_config.hpp index d173d6ec2..b7ccf8d89 100644 --- a/trunk/src/core/srs_core_config.hpp +++ b/trunk/src/core/srs_core_config.hpp @@ -48,6 +48,10 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define SRS_CONF_DEFAULT_AAC_SYNC 100 // in ms, for HLS aac flush the audio #define SRS_CONF_DEFAULT_AAC_DELAY 300 +// in seconds, the live queue length. +#define SRS_CONF_DEFAULT_QUEUE_LENGTH 30 +// in seconds, the paused queue length. +#define SRS_CONF_DEFAULT_PAUSED_LENGTH 10 #define SRS_CONF_DEFAULT_CHUNK_SIZE 4096 @@ -145,6 +149,7 @@ public: virtual std::string get_log_dir(); virtual int get_max_connections(); virtual bool get_gop_cache(std::string vhost); + virtual double get_queue_length(std::string vhost); virtual SrsConfDirective* get_forward(std::string vhost); private: virtual SrsConfDirective* get_hls(std::string vhost); diff --git a/trunk/src/core/srs_core_forward.cpp b/trunk/src/core/srs_core_forward.cpp index c6bca4fbb..bb2de325e 100644 --- a/trunk/src/core/srs_core_forward.cpp +++ b/trunk/src/core/srs_core_forward.cpp @@ -51,12 +51,14 @@ SrsForwarder::SrsForwarder(SrsSource* _source) stream_id = 0; pthread = new SrsThread(this, SRS_FORWARDER_SLEEP_MS); + queue = new SrsMessageQueue(); } SrsForwarder::~SrsForwarder() { on_unpublish(); + // TODO: FIXME: remove it. std::vector::iterator it; for (it = msgs.begin(); it != msgs.end(); ++it) { SrsSharedPtrMessage* msg = *it; @@ -65,6 +67,12 @@ SrsForwarder::~SrsForwarder() msgs.clear(); srs_freep(pthread); + srs_freep(queue); +} + +void SrsForwarder::set_queue_size(double queue_size) +{ + queue->set_queue_size(queue_size); } int SrsForwarder::on_publish(SrsRequest* req, std::string forward_server) diff --git a/trunk/src/core/srs_core_forward.hpp b/trunk/src/core/srs_core_forward.hpp index d2d38242e..37cb71269 100644 --- a/trunk/src/core/srs_core_forward.hpp +++ b/trunk/src/core/srs_core_forward.hpp @@ -36,6 +36,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. class SrsSharedPtrMessage; class SrsOnMetaDataPacket; +class SrsMessageQueue; class SrsRtmpClient; class SrsRequest; class SrsSource; @@ -58,10 +59,13 @@ private: private: SrsSource* source; SrsRtmpClient* client; + SrsMessageQueue* queue; std::vector msgs; public: SrsForwarder(SrsSource* _source); virtual ~SrsForwarder(); +public: + virtual void set_queue_size(double queue_size); public: virtual int on_publish(SrsRequest* req, std::string forward_server); virtual void on_unpublish(); diff --git a/trunk/src/core/srs_core_reload.cpp b/trunk/src/core/srs_core_reload.cpp index 71cd5a3c1..f6feee15e 100644 --- a/trunk/src/core/srs_core_reload.cpp +++ b/trunk/src/core/srs_core_reload.cpp @@ -55,6 +55,11 @@ int ISrsReloadHandler::on_reload_gop_cache(string /*vhost*/) return ERROR_SUCCESS; } +int ISrsReloadHandler::on_reload_queue_length(string /*vhost*/) +{ + return ERROR_SUCCESS; +} + int ISrsReloadHandler::on_reload_forward(string /*vhost*/) { return ERROR_SUCCESS; diff --git a/trunk/src/core/srs_core_reload.hpp b/trunk/src/core/srs_core_reload.hpp index a819bd7dd..3d8f3e8b9 100644 --- a/trunk/src/core/srs_core_reload.hpp +++ b/trunk/src/core/srs_core_reload.hpp @@ -44,6 +44,7 @@ public: virtual int on_reload_pithy_print(); virtual int on_reload_vhost_removed(std::string vhost); virtual int on_reload_gop_cache(std::string vhost); + virtual int on_reload_queue_length(std::string vhost); virtual int on_reload_forward(std::string vhost); virtual int on_reload_hls(std::string vhost); virtual int on_reload_transcode(std::string vhost); diff --git a/trunk/src/core/srs_core_source.cpp b/trunk/src/core/srs_core_source.cpp index fab9361bc..332f164f0 100644 --- a/trunk/src/core/srs_core_source.cpp +++ b/trunk/src/core/srs_core_source.cpp @@ -39,7 +39,6 @@ using namespace std; #define CONST_MAX_JITTER_MS 500 #define DEFAULT_FRAME_TIME_MS 10 -#define PAUSED_SHRINK_SIZE 250 SrsRtmpJitter::SrsRtmpJitter() { @@ -50,9 +49,21 @@ SrsRtmpJitter::~SrsRtmpJitter() { } +// TODO: FIXME: remove the 64bits time, change the timestamp in heaer to 64bits. int SrsRtmpJitter::correct(SrsSharedPtrMessage* msg, int tba, int tbv, int64_t* corrected_time) { int ret = ERROR_SUCCESS; + + // set to 0 for metadata. + if (!msg->header.is_video() && !msg->header.is_audio()) { + if (corrected_time) { + *corrected_time = 0; + } + + msg->header.timestamp = 0; + + return ret; + } int sample_rate = tba; int frame_rate = tbv; @@ -110,55 +121,50 @@ int SrsRtmpJitter::get_time() return (int)last_pkt_correct_time; } -SrsConsumer::SrsConsumer(SrsSource* _source) +SrsMessageQueue::SrsMessageQueue() { - source = _source; - paused = false; - jitter = new SrsRtmpJitter(); + queue_size_ms = 0; + av_start_time = av_end_time = -1; } -SrsConsumer::~SrsConsumer() +SrsMessageQueue::~SrsMessageQueue() { clear(); - - source->on_consumer_destroy(this); - srs_freep(jitter); } -int SrsConsumer::get_time() +void SrsMessageQueue::set_queue_size(double queue_size) { - return jitter->get_time(); + queue_size_ms = (int)(queue_size * 1000); } -int SrsConsumer::enqueue(SrsSharedPtrMessage* msg, int tba, int tbv) +int SrsMessageQueue::enqueue(SrsSharedPtrMessage* msg) { int ret = ERROR_SUCCESS; - if ((ret = jitter->correct(msg, tba, tbv)) != ERROR_SUCCESS) { - srs_freep(msg); - return ret; + if (msg->header.is_video() || msg->header.is_audio()) { + if (av_start_time == -1) { + av_start_time = msg->header.timestamp; + } + + av_end_time = msg->header.timestamp; } - // TODO: check the queue size and drop packets if overflow. msgs.push_back(msg); + + while (av_end_time - av_start_time > queue_size_ms) { + shrink(); + } return ret; } -int SrsConsumer::get_packets(int max_count, SrsSharedPtrMessage**& pmsgs, int& count) +int SrsMessageQueue::get_packets(int max_count, SrsSharedPtrMessage**& pmsgs, int& count) { int ret = ERROR_SUCCESS; if (msgs.empty()) { return ret; } - - if (paused) { - if ((int)msgs.size() >= PAUSED_SHRINK_SIZE) { - shrink(); - } - return ret; - } if (max_count == 0) { count = (int)msgs.size(); @@ -181,6 +187,112 @@ int SrsConsumer::get_packets(int max_count, SrsSharedPtrMessage**& pmsgs, int& c return ret; } +void SrsMessageQueue::shrink() +{ + int iframe_index = -1; + + // issue the first iframe. + // skip the first frame, whatever the type of it, + // 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]; + + if (msg->header.is_video()) { + if (SrsCodec::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; + + break; + } + } + } + + // no iframe, clear the queue. + if (iframe_index < 0) { + clear(); + return; + } + + // remove the first gop from the front + for (int i = 0; i < iframe_index; i++) { + SrsSharedPtrMessage* msg = msgs[i]; + srs_freep(msg); + } + msgs.erase(msgs.begin(), msgs.begin() + iframe_index); + + srs_trace("shrink the cache queue, " + "size=%d, removed=%d", (int)msgs.size(), iframe_index); +} + +void SrsMessageQueue::clear() +{ + std::vector::iterator it; + + for (it = msgs.begin(); it != msgs.end(); ++it) { + SrsSharedPtrMessage* msg = *it; + srs_freep(msg); + } + + msgs.clear(); + + av_start_time = av_end_time = -1; +} + +SrsConsumer::SrsConsumer(SrsSource* _source) +{ + source = _source; + paused = false; + jitter = new SrsRtmpJitter(); + queue = new SrsMessageQueue(); +} + +SrsConsumer::~SrsConsumer() +{ + source->on_consumer_destroy(this); + srs_freep(jitter); + srs_freep(queue); +} + +void SrsConsumer::set_queue_size(double queue_size) +{ + queue->set_queue_size(queue_size); +} + +int SrsConsumer::get_time() +{ + return jitter->get_time(); +} + +int SrsConsumer::enqueue(SrsSharedPtrMessage* msg, int tba, int tbv) +{ + int ret = ERROR_SUCCESS; + + if ((ret = jitter->correct(msg, tba, tbv)) != ERROR_SUCCESS) { + srs_freep(msg); + return ret; + } + + if ((ret = queue->enqueue(msg)) != ERROR_SUCCESS) { + return ret; + } + + return ret; +} + +int SrsConsumer::get_packets(int max_count, SrsSharedPtrMessage**& pmsgs, int& count) +{ + // paused, return nothing. + if (paused) { + return ERROR_SUCCESS; + } + + return queue->get_packets(max_count, pmsgs, count); +} + int SrsConsumer::on_play_client_pause(bool is_pause) { int ret = ERROR_SUCCESS; @@ -191,68 +303,6 @@ int SrsConsumer::on_play_client_pause(bool is_pause) return ret; } -void SrsConsumer::shrink() -{ - int i = 0; - std::vector::iterator it; - - // issue the last video iframe. - bool has_video = false; - int frame_to_remove = 0; - std::vector::iterator iframe = msgs.end(); - for (i = 0, it = msgs.begin(); it != msgs.end(); ++it, i++) { - SrsSharedPtrMessage* msg = *it; - if (msg->header.is_video()) { - has_video = true; - if (SrsCodec::video_is_keyframe(msg->payload, msg->size)) { - iframe = it; - frame_to_remove = i + 1; - } - } - } - - // last iframe is the first elem, ignore it. - if (iframe == msgs.begin()) { - return; - } - - // recalc the frame to remove - if (iframe == msgs.end()) { - frame_to_remove = 0; - } - if (!has_video) { - frame_to_remove = (int)msgs.size(); - } - - srs_trace("shrink the cache queue, has_video=%d, has_iframe=%d, size=%d, removed=%d", - has_video, iframe != msgs.end(), (int)msgs.size(), frame_to_remove); - - // if no video, remove all audio. - if (!has_video) { - clear(); - return; - } - - // if exists video Iframe, remove the frames before it. - if (iframe != msgs.end()) { - for (it = msgs.begin(); it != iframe; ++it) { - SrsSharedPtrMessage* msg = *it; - srs_freep(msg); - } - msgs.erase(msgs.begin(), iframe); - } -} - -void SrsConsumer::clear() -{ - std::vector::iterator it; - for (it = msgs.begin(); it != msgs.end(); ++it) { - SrsSharedPtrMessage* msg = *it; - srs_freep(msg); - } - msgs.clear(); -} - SrsGopCache::SrsGopCache() { cached_video_count = 0; @@ -436,6 +486,41 @@ int SrsSource::on_reload_gop_cache(string vhost) return ret; } +int SrsSource::on_reload_queue_length(string vhost) +{ + int ret = ERROR_SUCCESS; + + if (req->vhost != vhost) { + return ret; + } + + double queue_size = config->get_queue_length(req->vhost); + + if (true) { + std::vector::iterator it; + + for (it = consumers.begin(); it != consumers.end(); ++it) { + SrsConsumer* consumer = *it; + consumer->set_queue_size(queue_size); + } + + srs_trace("consumers reload queue size success."); + } + + if (true) { + std::vector::iterator it; + + for (it = forwarders.begin(); it != forwarders.end(); ++it) { + SrsForwarder* forwarder = *it; + forwarder->set_queue_size(queue_size); + } + + srs_trace("forwarders reload queue size success."); + } + + return ret; +} + int SrsSource::on_reload_forward(string vhost) { int ret = ERROR_SUCCESS; @@ -735,7 +820,7 @@ int SrsSource::on_video(SrsCommonMessage* video) // cache the last gop packets if ((ret = gop_cache->cache(msg)) != ERROR_SUCCESS) { - srs_error("shrink gop cache failed. ret=%d", ret); + srs_error("gop cache msg failed. ret=%d", ret); return ret; } srs_verbose("cache gop success."); @@ -809,6 +894,7 @@ void SrsSource::on_unpublish() int ret = ERROR_SUCCESS; consumer = new SrsConsumer(this); + consumer->set_queue_size(config->get_queue_length(req->vhost)); consumers.push_back(consumer); if (cache_metadata && (ret = consumer->enqueue(cache_metadata->copy(), sample_rate, frame_rate)) != ERROR_SUCCESS) { diff --git a/trunk/src/core/srs_core_source.hpp b/trunk/src/core/srs_core_source.hpp index 897918c7e..a4567b2b9 100644 --- a/trunk/src/core/srs_core_source.hpp +++ b/trunk/src/core/srs_core_source.hpp @@ -74,6 +74,45 @@ public: virtual int get_time(); }; +/** +* the message queue for the consumer(client), forwarder. +* we limit the size in seconds, drop old messages(the whole gop) if full. +*/ +class SrsMessageQueue +{ +private: + int64_t av_start_time; + int64_t av_end_time; + int queue_size_ms; + std::vector msgs; +public: + SrsMessageQueue(); + virtual ~SrsMessageQueue(); +public: + /** + * set the queue size + * @param queue_size the queue size in seconds. + */ + virtual void set_queue_size(double queue_size); +public: + /** + * enqueue the message, the timestamp always monotonically. + * @param msg, the msg to enqueue, user never free it whatever the return code. + */ + virtual int enqueue(SrsSharedPtrMessage* msg); + /** + * get messages from the queue. + */ + virtual int get_packets(int max_count, SrsSharedPtrMessage**& pmsgs, int& count); +private: + /** + * remove a gop from the front. + * if no iframe found, clear it. + */ + virtual void shrink(); + virtual void clear(); +}; + /** * the consumer for SrsSource, that is a play client. */ @@ -82,11 +121,14 @@ class SrsConsumer private: SrsRtmpJitter* jitter; SrsSource* source; + SrsMessageQueue* queue; std::vector msgs; bool paused; public: SrsConsumer(SrsSource* _source); virtual ~SrsConsumer(); +public: + virtual void set_queue_size(double queue_size); public: /** * get current client time, the last packet time. @@ -111,13 +153,6 @@ public: * when client send the pause message. */ virtual int on_play_client_pause(bool is_pause); -private: - /** - * when paused, shrink the cache queue, - * remove to cache only one gop. - */ - virtual void shrink(); - virtual void clear(); }; /** @@ -218,6 +253,7 @@ public: // interface ISrsReloadHandler public: virtual int on_reload_gop_cache(std::string vhost); + virtual int on_reload_queue_length(std::string vhost); virtual int on_reload_forward(std::string vhost); virtual int on_reload_hls(std::string vhost); virtual int on_reload_transcode(std::string vhost); From c47f07c69a60936b2648df2eb3cf29b7f89cfd51 Mon Sep 17 00:00:00 2001 From: winlin Date: Sun, 15 Dec 2013 19:11:57 +0800 Subject: [PATCH 30/37] refine the consumer, use srs message queue to shrink message when overflow. --- trunk/conf/srs.conf | 2 +- trunk/src/core/srs_core_config.cpp | 2 +- trunk/src/core/srs_core_source.cpp | 20 ++++++++++++++++---- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/trunk/conf/srs.conf b/trunk/conf/srs.conf index 2e1826be2..90d46d8e7 100755 --- a/trunk/conf/srs.conf +++ b/trunk/conf/srs.conf @@ -93,7 +93,7 @@ vhost __defaultVhost__ { vhost dev { enabled on; gop_cache on; - queue_length 30; + queue_length 10; #forward 127.0.0.1:19350; hls { enabled off; diff --git a/trunk/src/core/srs_core_config.cpp b/trunk/src/core/srs_core_config.cpp index fea6d9dd2..3e487d2b5 100644 --- a/trunk/src/core/srs_core_config.cpp +++ b/trunk/src/core/srs_core_config.cpp @@ -1303,7 +1303,7 @@ double SrsConfig::get_queue_length(string vhost) } conf = conf->get("queue_length"); - if (conf || conf->arg0().empty()) { + if (!conf || conf->arg0().empty()) { return SRS_CONF_DEFAULT_QUEUE_LENGTH; } diff --git a/trunk/src/core/srs_core_source.cpp b/trunk/src/core/srs_core_source.cpp index 332f164f0..025b99642 100644 --- a/trunk/src/core/srs_core_source.cpp +++ b/trunk/src/core/srs_core_source.cpp @@ -171,6 +171,10 @@ int SrsMessageQueue::get_packets(int max_count, SrsSharedPtrMessage**& pmsgs, in } else { count = srs_min(max_count, (int)msgs.size()); } + + if (count <= 0) { + return ret; + } pmsgs = new SrsSharedPtrMessage*[count]; @@ -178,6 +182,9 @@ int SrsMessageQueue::get_packets(int max_count, SrsSharedPtrMessage**& pmsgs, in pmsgs[i] = msgs[i]; } + SrsSharedPtrMessage* last = msgs[count - 1]; + av_start_time = last->header.timestamp; + if (count == (int)msgs.size()) { msgs.clear(); } else { @@ -217,15 +224,15 @@ void SrsMessageQueue::shrink() return; } + 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]; srs_freep(msg); } msgs.erase(msgs.begin(), msgs.begin() + iframe_index); - - srs_trace("shrink the cache queue, " - "size=%d, removed=%d", (int)msgs.size(), iframe_index); } void SrsMessageQueue::clear() @@ -893,9 +900,12 @@ void SrsSource::on_unpublish() { int ret = ERROR_SUCCESS; + double queue_size = config->get_queue_length(req->vhost); + consumer = new SrsConsumer(this); - consumer->set_queue_size(config->get_queue_length(req->vhost)); + consumers.push_back(consumer); + consumer->set_queue_size(queue_size); if (cache_metadata && (ret = consumer->enqueue(cache_metadata->copy(), sample_rate, frame_rate)) != ERROR_SUCCESS) { srs_error("dispatch metadata failed. ret=%d", ret); @@ -919,6 +929,8 @@ void SrsSource::on_unpublish() return ret; } + srs_trace("create consumer, queue_size=%.2f, tba=%d, tbv=%d", queue_size, sample_rate, frame_rate); + return ret; } From 5836ffd2d4ed965a50f37fa4d8e15b8ae7b4270c Mon Sep 17 00:00:00 2001 From: winlin Date: Sun, 15 Dec 2013 19:24:29 +0800 Subject: [PATCH 31/37] refine the forwarder, use srs message queue --- trunk/src/core/srs_core_forward.cpp | 83 +++++++++++++++-------------- trunk/src/core/srs_core_forward.hpp | 4 +- trunk/src/core/srs_core_source.hpp | 6 ++- 3 files changed, 49 insertions(+), 44 deletions(-) diff --git a/trunk/src/core/srs_core_forward.cpp b/trunk/src/core/srs_core_forward.cpp index bb2de325e..e4ae5c130 100644 --- a/trunk/src/core/srs_core_forward.cpp +++ b/trunk/src/core/srs_core_forward.cpp @@ -52,22 +52,16 @@ SrsForwarder::SrsForwarder(SrsSource* _source) pthread = new SrsThread(this, SRS_FORWARDER_SLEEP_MS); queue = new SrsMessageQueue(); + jitter = new SrsRtmpJitter(); } SrsForwarder::~SrsForwarder() { on_unpublish(); - // TODO: FIXME: remove it. - std::vector::iterator it; - for (it = msgs.begin(); it != msgs.end(); ++it) { - SrsSharedPtrMessage* msg = *it; - srs_freep(msg); - } - msgs.clear(); - srs_freep(pthread); srs_freep(queue); + srs_freep(jitter); } void SrsForwarder::set_queue_size(double queue_size) @@ -143,7 +137,14 @@ int SrsForwarder::on_meta_data(SrsSharedPtrMessage* metadata) { int ret = ERROR_SUCCESS; - msgs.push_back(metadata); + if ((ret = jitter->correct(metadata, 0, 0)) != ERROR_SUCCESS) { + srs_freep(msg); + return ret; + } + + if ((ret = queue->enqueue(metadata)) != ERROR_SUCCESS) { + return ret; + } return ret; } @@ -152,8 +153,14 @@ int SrsForwarder::on_audio(SrsSharedPtrMessage* msg) { int ret = ERROR_SUCCESS; - // TODO: FIXME: must drop the msgs when server failed. - msgs.push_back(msg); + if ((ret = jitter->correct(msg, 0, 0)) != ERROR_SUCCESS) { + srs_freep(msg); + return ret; + } + + if ((ret = queue->enqueue(msg)) != ERROR_SUCCESS) { + return ret; + } return ret; } @@ -162,8 +169,14 @@ int SrsForwarder::on_video(SrsSharedPtrMessage* msg) { int ret = ERROR_SUCCESS; - // TODO: FIXME: must drop the msgs when server failed. - msgs.push_back(msg); + if ((ret = jitter->correct(msg, 0, 0)) != ERROR_SUCCESS) { + srs_freep(msg); + return ret; + } + + if ((ret = queue->enqueue(msg)) != ERROR_SUCCESS) { + return ret; + } return ret; } @@ -292,11 +305,20 @@ int SrsForwarder::forward() } } + // forward all messages. + int count = 0; + SrsSharedPtrMessage** msgs = NULL; + if ((ret = queue->get_packets(0, &msgs, count)) != ERROR_SUCCESS) { + srs_error("get message to forward failed. ret=%d", ret); + return ret; + } + // ignore when no messages. - int count = (int)msgs.size(); - if (msgs.empty()) { + if (count <= 0) { + srs_verbose("no packets to forward."); continue; } + SrsAutoFree(SrsSharedPtrMessage*, msgs, true); // pithy print pithy_print.elapse(SRS_PULSE_TIMEOUT_MS); @@ -306,36 +328,17 @@ int SrsForwarder::forward() } // all msgs to forward. - int i = 0; - for (i = 0; i < count; i++) { + for (int i = 0; i < count; i++) { SrsSharedPtrMessage* msg = msgs[i]; - msgs[i] = NULL; - - // we erased the sendout messages, the msg must not be NULL. - srs_assert(msg); - ret = client->send_message(msg); - if (ret != ERROR_SUCCESS) { + srs_assert(msg); + msgs[i] = NULL; + + if ((ret = client->send_message(msg)) != ERROR_SUCCESS) { srs_error("forwarder send message to server failed. ret=%d", ret); - - // convert the index to count when error. - i++; - - break; + return ret; } } - - // clear sendout mesages. - if (i < count) { - srs_warn("clear forwarded msg, total=%d, forwarded=%d, ret=%d", count, i, ret); - } else { - srs_info("clear forwarded msg, total=%d, forwarded=%d, ret=%d", count, i, ret); - } - msgs.erase(msgs.begin(), msgs.begin() + i); - - if (ret != ERROR_SUCCESS) { - break; - } } return ret; diff --git a/trunk/src/core/srs_core_forward.hpp b/trunk/src/core/srs_core_forward.hpp index 37cb71269..a2e832cb5 100644 --- a/trunk/src/core/srs_core_forward.hpp +++ b/trunk/src/core/srs_core_forward.hpp @@ -30,13 +30,13 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include -#include #include class SrsSharedPtrMessage; class SrsOnMetaDataPacket; class SrsMessageQueue; +class SrsRtmpJitter; class SrsRtmpClient; class SrsRequest; class SrsSource; @@ -59,8 +59,8 @@ private: private: SrsSource* source; SrsRtmpClient* client; + SrsRtmpJitter* jitter; SrsMessageQueue* queue; - std::vector msgs; public: SrsForwarder(SrsSource* _source); virtual ~SrsForwarder(); diff --git a/trunk/src/core/srs_core_source.hpp b/trunk/src/core/srs_core_source.hpp index a4567b2b9..298b1e65f 100644 --- a/trunk/src/core/srs_core_source.hpp +++ b/trunk/src/core/srs_core_source.hpp @@ -101,7 +101,10 @@ public: */ virtual int enqueue(SrsSharedPtrMessage* msg); /** - * get messages from the queue. + * get packets in consumer queue. + * @pmsgs SrsMessages*[], output the prt array. + * @count the count in array. + * @max_count the max count to dequeue, 0 to dequeue all. */ virtual int get_packets(int max_count, SrsSharedPtrMessage**& pmsgs, int& count); private: @@ -122,7 +125,6 @@ private: SrsRtmpJitter* jitter; SrsSource* source; SrsMessageQueue* queue; - std::vector msgs; bool paused; public: SrsConsumer(SrsSource* _source); From ce4928cef64123711aa1d1f49016e013a9df840d Mon Sep 17 00:00:00 2001 From: winlin Date: Sun, 15 Dec 2013 19:38:39 +0800 Subject: [PATCH 32/37] support set the live queue length(in seconds), drop when full. --- README.md | 2 +- trunk/conf/srs.conf | 2 +- trunk/src/core/srs_core_forward.cpp | 5 +++-- trunk/src/core/srs_core_source.cpp | 8 +++++--- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a62d389e3..73c9a94e3 100755 --- a/README.md +++ b/README.md @@ -212,7 +212,7 @@ usr sys idl wai hiq siq| read writ| recv send| in out | int csw * nginx v1.5.0: 139524 lines
### History -* v0.9, 2013-12-15, drop the old whole gop when live message queue full. +* v0.9, 2013-12-15, support set the live queue length(in seconds), drop when full. * v0.9, 2013-12-15, fix the forwarder reconnect bug, feed it the sequence header. * v0.9, 2013-12-15, support reload the hls/forwarder/transcoder. * v0.9, 2013-12-14, refine the thread model for the retry threads. diff --git a/trunk/conf/srs.conf b/trunk/conf/srs.conf index 90d46d8e7..3a9edda52 100755 --- a/trunk/conf/srs.conf +++ b/trunk/conf/srs.conf @@ -94,7 +94,7 @@ vhost dev { enabled on; gop_cache on; queue_length 10; - #forward 127.0.0.1:19350; + forward 127.0.0.1:19350; hls { enabled off; hls_path ./objs/nginx/html; diff --git a/trunk/src/core/srs_core_forward.cpp b/trunk/src/core/srs_core_forward.cpp index e4ae5c130..73e398343 100644 --- a/trunk/src/core/srs_core_forward.cpp +++ b/trunk/src/core/srs_core_forward.cpp @@ -36,6 +36,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include #include +#include #define SRS_PULSE_TIMEOUT_MS 100 #define SRS_FORWARDER_SLEEP_MS 2000 @@ -138,7 +139,7 @@ int SrsForwarder::on_meta_data(SrsSharedPtrMessage* metadata) int ret = ERROR_SUCCESS; if ((ret = jitter->correct(metadata, 0, 0)) != ERROR_SUCCESS) { - srs_freep(msg); + srs_freep(metadata); return ret; } @@ -308,7 +309,7 @@ int SrsForwarder::forward() // forward all messages. int count = 0; SrsSharedPtrMessage** msgs = NULL; - if ((ret = queue->get_packets(0, &msgs, count)) != ERROR_SUCCESS) { + if ((ret = queue->get_packets(0, msgs, count)) != ERROR_SUCCESS) { srs_error("get message to forward failed. ret=%d", ret); return ret; } diff --git a/trunk/src/core/srs_core_source.cpp b/trunk/src/core/srs_core_source.cpp index 025b99642..3a0f0ce58 100644 --- a/trunk/src/core/srs_core_source.cpp +++ b/trunk/src/core/srs_core_source.cpp @@ -900,11 +900,10 @@ void SrsSource::on_unpublish() { int ret = ERROR_SUCCESS; - double queue_size = config->get_queue_length(req->vhost); - consumer = new SrsConsumer(this); - consumers.push_back(consumer); + + double queue_size = config->get_queue_length(req->vhost); consumer->set_queue_size(queue_size); if (cache_metadata && (ret = consumer->enqueue(cache_metadata->copy(), sample_rate, frame_rate)) != ERROR_SUCCESS) { @@ -959,6 +958,9 @@ int SrsSource::create_forwarders() SrsForwarder* forwarder = new SrsForwarder(this); forwarders.push_back(forwarder); + + double queue_size = config->get_queue_length(req->vhost); + forwarder->set_queue_size(queue_size); if ((ret = forwarder->on_publish(req, forward_server)) != ERROR_SUCCESS) { srs_error("start forwarder failed. " From 27255a3e7a651152c57cf4d2453f5137e0c03525 Mon Sep 17 00:00:00 2001 From: winlin Date: Sun, 15 Dec 2013 19:59:32 +0800 Subject: [PATCH 33/37] refine protocol, use int64_t timestamp for ts and jitter. --- README.md | 1 + trunk/src/core/srs_core_hls.cpp | 10 +++---- trunk/src/core/srs_core_protocol.cpp | 42 +++++++++++++++------------- trunk/src/core/srs_core_protocol.hpp | 5 ++-- trunk/src/core/srs_core_source.cpp | 26 ++++++----------- trunk/src/core/srs_core_source.hpp | 8 ++---- 6 files changed, 41 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 73c9a94e3..4e10b14e5 100755 --- a/README.md +++ b/README.md @@ -212,6 +212,7 @@ usr sys idl wai hiq siq| read writ| recv send| in out | int csw * nginx v1.5.0: 139524 lines
### History +* v0.9, 2013-12-15, refine protocol, use int64_t timestamp for ts and jitter. * v0.9, 2013-12-15, support set the live queue length(in seconds), drop when full. * v0.9, 2013-12-15, fix the forwarder reconnect bug, feed it the sequence header. * v0.9, 2013-12-15, support reload the hls/forwarder/transcoder. diff --git a/trunk/src/core/srs_core_hls.cpp b/trunk/src/core/srs_core_hls.cpp index 76b6df87c..6bc4dfd23 100644 --- a/trunk/src/core/srs_core_hls.cpp +++ b/trunk/src/core/srs_core_hls.cpp @@ -1272,14 +1272,13 @@ int SrsHls::on_audio(SrsSharedPtrMessage* audio) return ret; } - int64_t corrected_time = 0; - if ((ret = jitter->correct(audio, 0, 0, &corrected_time)) != ERROR_SUCCESS) { + if ((ret = jitter->correct(audio, 0, 0)) != ERROR_SUCCESS) { srs_error("rtmp jitter correct audio failed. ret=%d", ret); return ret; } // the pts calc from rtmp/flv header. - int64_t pts = corrected_time * 90; + int64_t pts = audio->header.timestamp * 90; if ((ret = ts_cache->write_audio(codec, muxer, pts, sample)) != ERROR_SUCCESS) { srs_error("ts cache write audio failed. ret=%d", ret); @@ -1315,13 +1314,12 @@ int SrsHls::on_video(SrsSharedPtrMessage* video) return ret; } - int64_t corrected_time = 0; - if ((ret = jitter->correct(video, 0, 0, &corrected_time)) != ERROR_SUCCESS) { + if ((ret = jitter->correct(video, 0, 0)) != ERROR_SUCCESS) { srs_error("rtmp jitter correct video failed. ret=%d", ret); return ret; } - int64_t dts = corrected_time * 90; + int64_t dts = video->header.timestamp * 90; if ((ret = ts_cache->write_video(codec, muxer, dts, sample)) != ERROR_SUCCESS) { srs_error("ts cache write video failed. ret=%d", ret); return ret; diff --git a/trunk/src/core/srs_core_protocol.cpp b/trunk/src/core/srs_core_protocol.cpp index 1b50d18e2..a1e95547b 100644 --- a/trunk/src/core/srs_core_protocol.cpp +++ b/trunk/src/core/srs_core_protocol.cpp @@ -354,7 +354,7 @@ int SrsProtocol::recv_message(SrsCommonMessage** pmsg) } if (msg->size <= 0 || msg->header.payload_length <= 0) { - srs_trace("ignore empty message(type=%d, size=%d, time=%d, sid=%d).", + srs_trace("ignore empty message(type=%d, size=%d, time=%"PRId64", sid=%d).", msg->header.message_type, msg->header.payload_length, msg->header.timestamp, msg->header.stream_id); srs_freep(msg); @@ -405,12 +405,13 @@ int SrsProtocol::send_message(ISrsMessage* msg) // chunk message header, 11 bytes // timestamp, 3bytes, big-endian - if (msg->header.timestamp >= RTMP_EXTENDED_TIMESTAMP) { + u_int32_t timestamp = (u_int32_t)msg->header.timestamp; + if (timestamp >= RTMP_EXTENDED_TIMESTAMP) { *pheader++ = 0xFF; *pheader++ = 0xFF; *pheader++ = 0xFF; } else { - pp = (char*)&msg->header.timestamp; + pp = (char*)×tamp; *pheader++ = pp[2]; *pheader++ = pp[1]; *pheader++ = pp[0]; @@ -433,8 +434,8 @@ int SrsProtocol::send_message(ISrsMessage* msg) *pheader++ = pp[3]; // chunk extended timestamp header, 0 or 4 bytes, big-endian - if(msg->header.timestamp >= RTMP_EXTENDED_TIMESTAMP){ - pp = (char*)&msg->header.timestamp; + if(timestamp >= RTMP_EXTENDED_TIMESTAMP){ + pp = (char*)×tamp; *pheader++ = pp[3]; *pheader++ = pp[2]; *pheader++ = pp[1]; @@ -461,8 +462,9 @@ int SrsProtocol::send_message(ISrsMessage* msg) // must send the extended-timestamp to flash-player. // @see: ngx_rtmp_prepare_message // @see: http://blog.csdn.net/win_lin/article/details/13363699 - if(msg->header.timestamp >= RTMP_EXTENDED_TIMESTAMP){ - pp = (char*)&msg->header.timestamp; + 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]; @@ -702,7 +704,7 @@ int SrsProtocol::recv_interlaced_message(SrsCommonMessage** pmsg) 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=%d, sid=%d)", + 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); } @@ -716,7 +718,7 @@ int SrsProtocol::recv_interlaced_message(SrsCommonMessage** pmsg) return ret; } srs_verbose("read message header success. " - "fmt=%d, mh_size=%d, ext_time=%d, size=%d, message(type=%d, size=%d, time=%d, sid=%d)", + "fmt=%d, mh_size=%d, ext_time=%d, size=%d, message(type=%d, size=%d, time=%"PRId64", sid=%d)", fmt, mh_size, chunk->extended_timestamp, (chunk->msg? chunk->msg->size : 0), chunk->header.message_type, chunk->header.payload_length, chunk->header.timestamp, chunk->header.stream_id); @@ -738,14 +740,14 @@ int SrsProtocol::recv_interlaced_message(SrsCommonMessage** pmsg) // 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=%d, sid=%d)", + 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, 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=%d, sid=%d)", + 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, chunk->header.timestamp, chunk->header.stream_id); @@ -952,16 +954,16 @@ int SrsProtocol::read_message_header(SrsChunkStream* chunk, char fmt, int bh_siz pp[1] = *p++; pp[2] = *p++; pp[3] = *p++; - srs_verbose("header read completed. fmt=%d, mh_size=%d, ext_time=%d, time=%d, payload=%d, type=%d, sid=%d", + srs_verbose("header read completed. fmt=%d, mh_size=%d, ext_time=%d, time=%"PRId64", payload=%d, type=%d, sid=%d", fmt, mh_size, chunk->extended_timestamp, chunk->header.timestamp, chunk->header.payload_length, chunk->header.message_type, chunk->header.stream_id); } else { - srs_verbose("header read completed. fmt=%d, mh_size=%d, ext_time=%d, time=%d, payload=%d, type=%d", + srs_verbose("header read completed. fmt=%d, mh_size=%d, ext_time=%d, time=%"PRId64", payload=%d, type=%d", fmt, mh_size, chunk->extended_timestamp, chunk->header.timestamp, chunk->header.payload_length, chunk->header.message_type); } } else { - srs_verbose("header read completed. fmt=%d, mh_size=%d, ext_time=%d, time=%d", + srs_verbose("header read completed. fmt=%d, mh_size=%d, ext_time=%d, time=%"PRId64"", fmt, mh_size, chunk->extended_timestamp, chunk->header.timestamp); } } else { @@ -986,7 +988,7 @@ int SrsProtocol::read_message_header(SrsChunkStream* chunk, char fmt, int bh_siz // ffmpeg/librtmp may donot send this filed, need to detect the value. // @see also: http://blog.csdn.net/win_lin/article/details/13363699 - int32_t timestamp = 0x00; + u_int32_t timestamp = 0x00; char* pp = (char*)×tamp; pp[3] = *p++; pp[2] = *p++; @@ -995,14 +997,14 @@ int SrsProtocol::read_message_header(SrsChunkStream* chunk, char fmt, int bh_siz // compare to the chunk timestamp, which is set by chunk message header // type 0,1 or 2. - int32_t chunk_timestamp = chunk->header.timestamp; + u_int32_t chunk_timestamp = chunk->header.timestamp; if (chunk_timestamp > RTMP_EXTENDED_TIMESTAMP && chunk_timestamp != timestamp) { mh_size -= 4; srs_verbose("ignore the 4bytes extended timestamp. mh_size=%d", mh_size); } else { chunk->header.timestamp = timestamp; } - srs_verbose("header read ext_time completed. time=%d", chunk->header.timestamp); + srs_verbose("header read ext_time completed. time=%"PRId64"", chunk->header.timestamp); } // valid message @@ -1032,7 +1034,7 @@ int SrsProtocol::read_message_payload(SrsChunkStream* chunk, int bh_size, int mh buffer->erase(bh_size + mh_size); srs_trace("get an empty RTMP " - "message(type=%d, size=%d, time=%d, sid=%d)", chunk->header.message_type, + "message(type=%d, size=%d, time=%"PRId64", sid=%d)", chunk->header.message_type, chunk->header.payload_length, chunk->header.timestamp, chunk->header.stream_id); *pmsg = chunk->msg; @@ -1073,13 +1075,13 @@ int SrsProtocol::read_message_payload(SrsChunkStream* chunk, int bh_size, int mh if (chunk->header.payload_length == chunk->msg->size) { *pmsg = chunk->msg; chunk->msg = NULL; - srs_verbose("get entire RTMP message(type=%d, size=%d, time=%d, sid=%d)", + srs_verbose("get entire 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); return ret; } - srs_verbose("get partial RTMP message(type=%d, size=%d, time=%d, sid=%d), partial size=%d", + srs_verbose("get partial RTMP message(type=%d, size=%d, time=%"PRId64", sid=%d), partial size=%d", chunk->header.message_type, chunk->header.payload_length, chunk->header.timestamp, chunk->header.stream_id, chunk->msg->size); diff --git a/trunk/src/core/srs_core_protocol.hpp b/trunk/src/core/srs_core_protocol.hpp index f9c5db5fe..733b82b31 100644 --- a/trunk/src/core/srs_core_protocol.hpp +++ b/trunk/src/core/srs_core_protocol.hpp @@ -204,8 +204,9 @@ struct SrsMessageHeader * 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. */ - u_int32_t timestamp; + int64_t timestamp; SrsMessageHeader(); virtual ~SrsMessageHeader(); @@ -1126,7 +1127,7 @@ int srs_rtmp_expect_message(SrsProtocol* protocol, SrsCommonMessage** pmsg, T** T* pkt = dynamic_cast(msg->get_packet()); if (!pkt) { delete msg; - srs_trace("drop message(type=%d, size=%d, time=%d, sid=%d).", + srs_trace("drop message(type=%d, size=%d, time=%"PRId64", sid=%d).", msg->header.message_type, msg->header.payload_length, msg->header.timestamp, msg->header.stream_id); continue; diff --git a/trunk/src/core/srs_core_source.cpp b/trunk/src/core/srs_core_source.cpp index 3a0f0ce58..bd79e7921 100644 --- a/trunk/src/core/srs_core_source.cpp +++ b/trunk/src/core/srs_core_source.cpp @@ -38,7 +38,7 @@ using namespace std; #include #define CONST_MAX_JITTER_MS 500 -#define DEFAULT_FRAME_TIME_MS 10 +#define DEFAULT_FRAME_TIME_MS 40 SrsRtmpJitter::SrsRtmpJitter() { @@ -49,19 +49,13 @@ SrsRtmpJitter::~SrsRtmpJitter() { } -// TODO: FIXME: remove the 64bits time, change the timestamp in heaer to 64bits. -int SrsRtmpJitter::correct(SrsSharedPtrMessage* msg, int tba, int tbv, int64_t* corrected_time) +int SrsRtmpJitter::correct(SrsSharedPtrMessage* msg, int tba, int tbv) { int ret = ERROR_SUCCESS; // set to 0 for metadata. if (!msg->header.is_video() && !msg->header.is_audio()) { - if (corrected_time) { - *corrected_time = 0; - } - msg->header.timestamp = 0; - return ret; } @@ -78,16 +72,16 @@ int SrsRtmpJitter::correct(SrsSharedPtrMessage* msg, int tba, int tbv, int64_t* * 3. last_pkt_correct_time: simply add the positive delta, * and enforce the time monotonically. */ - u_int32_t time = msg->header.timestamp; - int32_t delta = time - last_pkt_time; + int64_t time = msg->header.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) { - delta = (int32_t)(delta * 1000.0 / sample_rate); + delta = (int64_t)(delta * 1000.0 / sample_rate); } else if (msg->header.is_video() && frame_rate > 0) { - delta = (int32_t)(delta * 1.0 / frame_rate); + delta = (int64_t)(delta * 1.0 / frame_rate); } else { delta = DEFAULT_FRAME_TIME_MS; } @@ -97,20 +91,16 @@ int SrsRtmpJitter::correct(SrsSharedPtrMessage* msg, int tba, int tbv, int64_t* delta = DEFAULT_FRAME_TIME_MS; } - srs_info("jitter detected, last_pts=%d, pts=%d, diff=%d, last_time=%d, time=%d, diff=%d", + srs_info("jitter detected, last_pts=%"PRId64", pts=%"PRId64", diff=%"PRId64", last_time=%"PRId64", time=%"PRId64", diff=%"PRId64"", last_pkt_time, time, time - last_pkt_time, last_pkt_correct_time, last_pkt_correct_time + delta, delta); } else { - srs_verbose("timestamp no jitter. time=%d, last_pkt=%d, correct_to=%d", + srs_verbose("timestamp no jitter. time=%"PRId64", last_pkt=%"PRId64", correct_to=%"PRId64"", time, last_pkt_time, last_pkt_correct_time + delta); } last_pkt_correct_time = srs_max(0, last_pkt_correct_time + delta); - if (corrected_time) { - *corrected_time = last_pkt_correct_time; - } msg->header.timestamp = last_pkt_correct_time; - last_pkt_time = time; return ret; diff --git a/trunk/src/core/srs_core_source.hpp b/trunk/src/core/srs_core_source.hpp index 298b1e65f..2c008f703 100644 --- a/trunk/src/core/srs_core_source.hpp +++ b/trunk/src/core/srs_core_source.hpp @@ -56,18 +56,16 @@ class SrsEncoder; class SrsRtmpJitter { private: - u_int32_t last_pkt_time; - u_int32_t last_pkt_correct_time; + int64_t last_pkt_time; + int64_t last_pkt_correct_time; public: SrsRtmpJitter(); virtual ~SrsRtmpJitter(); public: /** * detect the time jitter and correct it. - * @param corrected_time output the 64bits time. - * ignore if NULL. */ - virtual int correct(SrsSharedPtrMessage* msg, int tba, int tbv, int64_t* corrected_time = NULL); + virtual int correct(SrsSharedPtrMessage* msg, int tba, int tbv); /** * get current client time, the last packet time. */ From e262147e81d56ed6bb435e9a3262e755c8b6be06 Mon Sep 17 00:00:00 2001 From: winlin Date: Sun, 15 Dec 2013 20:29:18 +0800 Subject: [PATCH 34/37] fix the hls reload bug, feed it the sequence header. --- README.md | 1 + trunk/conf/srs.conf | 2 +- trunk/src/core/srs_core_hls.cpp | 15 +++++++++++---- trunk/src/core/srs_core_hls.hpp | 8 +++++--- trunk/src/core/srs_core_source.cpp | 28 +++++++++++++++++++++++++--- trunk/src/core/srs_core_source.hpp | 4 +++- 6 files changed, 46 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 4e10b14e5..dde6d578c 100755 --- a/README.md +++ b/README.md @@ -212,6 +212,7 @@ usr sys idl wai hiq siq| read writ| recv send| in out | int csw * nginx v1.5.0: 139524 lines
### History +* v0.9, 2013-12-15, fix the hls reload bug, feed it the sequence header. * v0.9, 2013-12-15, refine protocol, use int64_t timestamp for ts and jitter. * v0.9, 2013-12-15, support set the live queue length(in seconds), drop when full. * v0.9, 2013-12-15, fix the forwarder reconnect bug, feed it the sequence header. diff --git a/trunk/conf/srs.conf b/trunk/conf/srs.conf index 3a9edda52..af102c496 100755 --- a/trunk/conf/srs.conf +++ b/trunk/conf/srs.conf @@ -96,7 +96,7 @@ vhost dev { queue_length 10; forward 127.0.0.1:19350; hls { - enabled off; + enabled on; hls_path ./objs/nginx/html; hls_fragment 5; hls_window 30; diff --git a/trunk/src/core/srs_core_hls.cpp b/trunk/src/core/srs_core_hls.cpp index 6bc4dfd23..706fd0081 100644 --- a/trunk/src/core/srs_core_hls.cpp +++ b/trunk/src/core/srs_core_hls.cpp @@ -1109,10 +1109,11 @@ int SrsTSCache::cache_video(SrsCodec* codec, SrsCodecSample* sample) return ret; } -SrsHls::SrsHls() +SrsHls::SrsHls(SrsSource* _source) { hls_enabled = false; + source = _source; codec = new SrsCodec(); sample = new SrsCodecSample(); jitter = new SrsRtmpJitter(); @@ -1171,6 +1172,12 @@ int SrsHls::on_publish(SrsRequest* req) srs_error("m3u8 muxer open segment failed. ret=%d", ret); return ret; } + + // notice the source to get the cached sequence header. + if ((ret = source->on_hls_start()) != ERROR_SUCCESS) { + srs_error("callback source hls start failed. ret=%d", ret); + return ret; + } return ret; } @@ -1195,16 +1202,16 @@ void SrsHls::on_unpublish() hls_enabled = false; } -int SrsHls::on_meta_data(SrsOnMetaDataPacket* metadata) +int SrsHls::on_meta_data(SrsAmf0Object* metadata) { int ret = ERROR_SUCCESS; - if (!metadata || !metadata->metadata) { + if (!metadata) { srs_trace("no metadata persent, hls ignored it."); return ret; } - SrsAmf0Object* obj = metadata->metadata; + SrsAmf0Object* obj = metadata; if (obj->size() <= 0) { srs_trace("no metadata persent, hls ignored it."); return ret; diff --git a/trunk/src/core/srs_core_hls.hpp b/trunk/src/core/srs_core_hls.hpp index f82cbece5..7bc49ade9 100644 --- a/trunk/src/core/srs_core_hls.hpp +++ b/trunk/src/core/srs_core_hls.hpp @@ -34,16 +34,17 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include -class SrsOnMetaDataPacket; class SrsSharedPtrMessage; class SrsCodecSample; class SrsCodecBuffer; class SrsMpegtsFrame; +class SrsAmf0Object; class SrsRtmpJitter; class SrsTSMuxer; class SrsCodec; class SrsRequest; class SrsPithyPrint; +class SrsSource; /** * jitter correct for audio, @@ -207,17 +208,18 @@ private: SrsTSCache* ts_cache; private: bool hls_enabled; + SrsSource* source; SrsCodec* codec; SrsCodecSample* sample; SrsRtmpJitter* jitter; SrsPithyPrint* pithy_print; public: - SrsHls(); + SrsHls(SrsSource* _source); virtual ~SrsHls(); public: virtual int on_publish(SrsRequest* req); virtual void on_unpublish(); - virtual int on_meta_data(SrsOnMetaDataPacket* metadata); + virtual int on_meta_data(SrsAmf0Object* metadata); virtual int on_audio(SrsSharedPtrMessage* audio); virtual int on_video(SrsSharedPtrMessage* video); private: diff --git a/trunk/src/core/srs_core_source.cpp b/trunk/src/core/srs_core_source.cpp index bd79e7921..0143fed2c 100644 --- a/trunk/src/core/srs_core_source.cpp +++ b/trunk/src/core/srs_core_source.cpp @@ -410,7 +410,7 @@ SrsSource::SrsSource(SrsRequest* _req) req = _req->copy(); #ifdef SRS_HLS - hls = new SrsHls(); + hls = new SrsHls(this); #endif #ifdef SRS_FFMPEG encoder = new SrsEncoder(); @@ -553,7 +553,6 @@ int SrsSource::on_reload_hls(string vhost) srs_error("hls publish failed. ret=%d", ret); return ret; } - // TODO: FIXME: must feed it the sequence header. srs_trace("vhost %s hls reload success", vhost.c_str()); #endif @@ -602,6 +601,29 @@ int SrsSource::on_forwarder_start(SrsForwarder* forwarder) return ret; } +int SrsSource::on_hls_start() +{ + int ret = ERROR_SUCCESS; + +#ifdef SRS_HLS + + // feed the hls the metadata/sequence header, + // when reload to enable the hls. + // TODO: maybe need to decode the metadata? + if (cache_sh_video && (ret = hls->on_video(cache_sh_video->copy())) != 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) { + srs_error("hls process audio sequence header message failed. ret=%d", ret); + return ret; + } + +#endif + + return ret; +} + bool SrsSource::can_publish() { return _can_publish; @@ -612,7 +634,7 @@ int SrsSource::on_meta_data(SrsCommonMessage* msg, SrsOnMetaDataPacket* metadata int ret = ERROR_SUCCESS; #ifdef SRS_HLS - if ((ret = hls->on_meta_data(metadata)) != ERROR_SUCCESS) { + if (metadata && (ret = hls->on_meta_data(metadata->metadata)) != ERROR_SUCCESS) { srs_error("hls process onMetaData message failed. ret=%d", ret); return ret; } diff --git a/trunk/src/core/srs_core_source.hpp b/trunk/src/core/srs_core_source.hpp index 2c008f703..405c51e53 100644 --- a/trunk/src/core/srs_core_source.hpp +++ b/trunk/src/core/srs_core_source.hpp @@ -257,9 +257,11 @@ public: virtual int on_reload_forward(std::string vhost); virtual int on_reload_hls(std::string vhost); virtual int on_reload_transcode(std::string vhost); -// for the SrsForwarder to callback to request the sequence headers. public: + // for the SrsForwarder to callback to request the sequence headers. virtual int on_forwarder_start(SrsForwarder* forwarder); + // for the SrsHls to callback to request the sequence headers. + virtual int on_hls_start(); public: virtual bool can_publish(); virtual int on_meta_data(SrsCommonMessage* msg, SrsOnMetaDataPacket* metadata); From 9080c2e559279dbd4f4c1849da913155c26680be Mon Sep 17 00:00:00 2001 From: winlin Date: Sun, 15 Dec 2013 20:36:59 +0800 Subject: [PATCH 35/37] ensure the HLS(ts) is continous when republish stream. --- README.md | 1 + trunk/src/core/srs_core_hls.cpp | 4 ++-- trunk/src/core/srs_core_hls.hpp | 19 ++++++++++++++++++- trunk/src/core/srs_core_source.cpp | 1 - 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index dde6d578c..8740f1cf7 100755 --- a/README.md +++ b/README.md @@ -212,6 +212,7 @@ usr sys idl wai hiq siq| read writ| recv send| in out | int csw * nginx v1.5.0: 139524 lines
### History +* v0.9, 2013-12-15, ensure the HLS(ts) is continous when republish stream. * v0.9, 2013-12-15, fix the hls reload bug, feed it the sequence header. * v0.9, 2013-12-15, refine protocol, use int64_t timestamp for ts and jitter. * v0.9, 2013-12-15, support set the live queue length(in seconds), drop when full. diff --git a/trunk/src/core/srs_core_hls.cpp b/trunk/src/core/srs_core_hls.cpp index 706fd0081..b7182cad5 100644 --- a/trunk/src/core/srs_core_hls.cpp +++ b/trunk/src/core/srs_core_hls.cpp @@ -1332,12 +1332,12 @@ int SrsHls::on_video(SrsSharedPtrMessage* video) return ret; } - _mpegts(); + hls_mux(); return ret; } -void SrsHls::_mpegts() +void SrsHls::hls_mux() { // reportable if (pithy_print->can_print()) { diff --git a/trunk/src/core/srs_core_hls.hpp b/trunk/src/core/srs_core_hls.hpp index 7bc49ade9..9474909f3 100644 --- a/trunk/src/core/srs_core_hls.hpp +++ b/trunk/src/core/srs_core_hls.hpp @@ -217,13 +217,30 @@ public: SrsHls(SrsSource* _source); virtual ~SrsHls(); public: + /** + * publish stream event, continue to write the m3u8, + * for the muxer object not destroyed. + */ virtual int on_publish(SrsRequest* req); + /** + * the unpublish event, only close the muxer, donot destroy the + * muxer, for when we continue to publish, the m3u8 will continue. + */ virtual void on_unpublish(); + /** + * get some information from metadata, it's optinal. + */ virtual int on_meta_data(SrsAmf0Object* metadata); + /** + * mux the audio packets to ts. + */ virtual int on_audio(SrsSharedPtrMessage* audio); + /** + * mux the video packets to ts. + */ virtual int on_video(SrsSharedPtrMessage* video); private: - virtual void _mpegts(); + virtual void hls_mux(); }; #endif diff --git a/trunk/src/core/srs_core_source.cpp b/trunk/src/core/srs_core_source.cpp index 0143fed2c..5cca79543 100644 --- a/trunk/src/core/srs_core_source.cpp +++ b/trunk/src/core/srs_core_source.cpp @@ -546,7 +546,6 @@ int SrsSource::on_reload_hls(string vhost) return ret; } - // TODO: HLS should continue previous sequence and stream. #ifdef SRS_HLS hls->on_unpublish(); if ((ret = hls->on_publish(req)) != ERROR_SUCCESS) { From c4477cd64f4c6124711d64d1879dda70753e6045 Mon Sep 17 00:00:00 2001 From: winlin Date: Tue, 17 Dec 2013 11:30:00 +0800 Subject: [PATCH 36/37] add players to nginx --- README.md | 1 + trunk/auto/depends.sh | 3 +++ trunk/configure | 1 + trunk/research/players/jwplayer5/index.html | 2 +- trunk/research/players/jwplayer6/index.html | 2 +- trunk/research/players/osmf/index.html | 2 +- trunk/research/players/rtmp/index.html | 2 +- 7 files changed, 9 insertions(+), 4 deletions(-) mode change 100644 => 100755 trunk/auto/depends.sh mode change 100644 => 100755 trunk/research/players/jwplayer5/index.html mode change 100644 => 100755 trunk/research/players/jwplayer6/index.html mode change 100644 => 100755 trunk/research/players/osmf/index.html mode change 100644 => 100755 trunk/research/players/rtmp/index.html diff --git a/README.md b/README.md index 8740f1cf7..4a93dc5ec 100755 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ For example, use ffmpeg to publish: step 8: play live stream.
+players: http://demo:80/players
 rtmp url: rtmp://demo:1935/live/livestream
 m3u8 url: http://demo:80/live/livestream.m3u8
 for android: http://demo:80/live/livestream.html
diff --git a/trunk/auto/depends.sh b/trunk/auto/depends.sh
old mode 100644
new mode 100755
index a52470848..31541ad12
--- a/trunk/auto/depends.sh
+++ b/trunk/auto/depends.sh
@@ -252,6 +252,9 @@ if [ $SRS_HLS = YES ]; then
     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.
+    cp research/players ${SRS_OBJS}/nginx/html/ -r
 fi
 
 if [ $SRS_HLS = YES ]; then
diff --git a/trunk/configure b/trunk/configure
index 9d69081a7..698ee8f73 100755
--- a/trunk/configure
+++ b/trunk/configure
@@ -180,6 +180,7 @@ echo "\" make \" to build the srs(simple rtmp server)."
 echo "\" make help \" to get the usage of make"
 if [ $SRS_HLS = YES ]; then
     echo "\" sudo ./objs/nginx/sbin/nginx  \" to start the nginx http server for hls"
+    echo "\" http://demo:80/players \" rtmp players(OSMF/JWPlayer)"
 fi
 if [ $SRS_FFMPEG = YES ]; then
     echo -e "\" ./objs/ffmpeg/bin/ffmpeg  \" is used for live stream transcoding"
diff --git a/trunk/research/players/jwplayer5/index.html b/trunk/research/players/jwplayer5/index.html
old mode 100644
new mode 100755
index 56f5adb3f..aa36e15e2
--- a/trunk/research/players/jwplayer5/index.html
+++ b/trunk/research/players/jwplayer5/index.html
@@ -17,7 +17,7 @@
 
- Url(RTMP/HTTP): + Url(RTMP/HTTP):
diff --git a/trunk/research/players/jwplayer6/index.html b/trunk/research/players/jwplayer6/index.html old mode 100644 new mode 100755 index 96d981931..ce667be99 --- a/trunk/research/players/jwplayer6/index.html +++ b/trunk/research/players/jwplayer6/index.html @@ -17,7 +17,7 @@
- Url(RTMP/HTTP): + Url(RTMP/HTTP):
diff --git a/trunk/research/players/osmf/index.html b/trunk/research/players/osmf/index.html old mode 100644 new mode 100755 index 8619ea125..6b8a73008 --- a/trunk/research/players/osmf/index.html +++ b/trunk/research/players/osmf/index.html @@ -19,7 +19,7 @@ div.control{padding-bottom:10px; background-color:#333333; }
- Url(RTMP/HTTP): + Url(RTMP/HTTP):