diff --git a/.circleci/config.yml b/.circleci/config.yml index 9fa2e06e3a..48d6788504 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,13 +5,13 @@ jobs: - image: ossrs/srs:dev steps: - checkout - - run: cd trunk && ./configure && make + - run: cd trunk && ./configure --with-utest && make test: docker: - image: ossrs/srs:dev steps: - checkout - - run: cd trunk && ./configure --gcov && make && ./objs/srs_utest && bash auto/coverage.sh + - run: cd trunk && ./configure --with-utest --gcov && make && ./objs/srs_utest && bash auto/coverage.sh workflows: version: 2 build_and_test: diff --git a/AUTHORS.txt b/AUTHORS.txt index 352376b94b..40923bb25b 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -55,4 +55,5 @@ CONTRIBUTORS ordered by first contribution. * lam2003 * runner365 * XiaofengWang -* XiaLixin \ No newline at end of file +* XiaLixin +* yanghuiwen diff --git a/README.md b/README.md index 222b112c96..26d194d31c 100755 --- a/README.md +++ b/README.md @@ -13,22 +13,22 @@ SRS is a RTMP/HLS/WebRTC/SRT/GB28181 streaming cluster, high efficiency, stable ## Usage -**Step 1:** Get SRS. +**>>> Step 1:** Get SRS. ``` git clone https://gitee.com/winlinvip/srs.oschina.git srs && cd srs/trunk && git remote set-url origin https://github.com/ossrs/srs.git && git pull ``` -> Note: Repository too large? Please clone from these [mirrors](#mirrors) instead. +> Note: We use [mirrors(gitee)](#mirrors) here, but it's also ok to directly clone by `git clone https://github.com/ossrs/srs.git && cd srs/trunk` -**Step 2:** Build SRS. +**>>> Step 2:** Build SRS. ``` ./configure && make ``` -> Remark: Recommend Centos6 64bits, please read wiki([CN][v3_CN_Build],[EN][v3_EN_Build]). +> Remark: Recommend to use Centos7 64bits, please read wiki([CN][v3_CN_Build],[EN][v3_EN_Build]). > Note: You can also build SRS in docker, please read [docker][docker-dev]. @@ -38,13 +38,16 @@ cd srs/trunk && git remote set-url origin https://github.com/ossrs/srs.git && gi ./objs/srs -c conf/srs.conf ``` -**Whatever**, you can also directly run SRS in [docker][docker-srs3]: +**>>> Whatever**, you can also directly run SRS in [docker][docker-srs3]: ``` -docker run -p 1935:1935 -p 1985:1985 -p 8080:8080 ossrs/srs:3 +docker run -p 1935:1935 -p 1985:1985 -p 8080:8080 \ + registry.cn-hangzhou.aliyuncs.com/ossrs/srs:3 ``` -**From here,** strongly recommend to read bellow wikis: +> Note: Again, we use [ACR](https://cr.console.aliyun.com/) here, you can directly run in docker hub by `docker run -p 1935:1935 -p 1985:1985 -p 8080:8080 ossrs/srs:3` + +**>>> From here,** strongly recommend to read bellow wikis: * Usage: How to delivery RTMP?([CN][v1_CN_SampleRTMP], [EN][v1_EN_SampleRTMP]) * Usage: How to delivery RTMP-Edge Cluster?([CN][v3_CN_SampleRTMPCluster], [EN][v3_EN_SampleRTMPCluster]) @@ -169,6 +172,12 @@ For previous versions, please read: ## V3 changes +* v3.0, 2020-03-29, [3.0 beta4(3.0.139)][r3.0b4] released. 122674 lines. +* v3.0, 2020-03-28, Support multiple OS/Platform build cache. 3.0.139 +* v3.0, 2020-03-28, For [#1250][bug #1250], support macOS, OSX, MacbookPro, Apple Darwin. 3.0.138 +* v3.0, 2020-03-21, For [#1629][bug #1629], fix kickoff FLV client bug. 3.0.137 +* v3.0, 2020-03-21, For [#1619][bug #1619], configure without utest by default. 3.0.136 +* v3.0, 2020-03-21, For [#1651][bug #1651], fix return pnwrite of srs_write_large_iovs. 3.0.135 * v3.0, 2020-03-18, [3.0 beta3(3.0.134)][r3.0b3] released. 122509 lines. * v3.0, 2020-03-12, For [#1635][bug #1635], inotify watch ConfigMap for reload. 3.0.134 * v3.0, 2020-03-12, For [#1635][bug #1635], support auto reaload config by inotify. 3.0.129 @@ -776,6 +785,7 @@ For previous versions, please read: ## Releases +* 2020-03-29, [Release v3.0-b3][r3.0b4], 3.0 beta4, 3.0.139, 122674 lines. * 2020-03-18, [Release v3.0-b3][r3.0b3], 3.0 beta3, 3.0.134, 122509 lines. * 2020-03-05, [Release v3.0-b2][r3.0b2], 3.0 beta2, 3.0.123, 122170 lines. * 2020-02-14, [Release v3.0-b1][r3.0b1], 3.0 beta1, 3.0.117, 121964 lines. @@ -1710,6 +1720,9 @@ Winlin [bug #1594]: https://github.com/ossrs/srs/issues/1594 [bug #1630]: https://github.com/ossrs/srs/issues/1630 [bug #1635]: https://github.com/ossrs/srs/issues/1635 +[bug #1651]: https://github.com/ossrs/srs/issues/1651 +[bug #1619]: https://github.com/ossrs/srs/issues/1619 +[bug #1629]: https://github.com/ossrs/srs/issues/1629 [bug #yyyyyyyyyyyyy]: https://github.com/ossrs/srs/issues/yyyyyyyyyyyyy [bug #1631]: https://github.com/ossrs/srs/issues/1631 @@ -1718,6 +1731,7 @@ Winlin [exo #828]: https://github.com/google/ExoPlayer/pull/828 +[r3.0b4]: https://github.com/ossrs/srs/releases/tag/v3.0-b4 [r3.0b3]: https://github.com/ossrs/srs/releases/tag/v3.0-b3 [r3.0b2]: https://github.com/ossrs/srs/releases/tag/v3.0-b2 [r3.0b1]: https://github.com/ossrs/srs/releases/tag/v3.0-b1 diff --git a/trunk/.gitignore b/trunk/.gitignore index 4e90c9b495..4431df72c2 100644 --- a/trunk/.gitignore +++ b/trunk/.gitignore @@ -40,3 +40,4 @@ srs *.ts *.h264 *.264 +3rdparty/ffmpeg-4.2-fit diff --git a/trunk/3rdparty/st-srs/Makefile b/trunk/3rdparty/st-srs/Makefile index 304d44377c..601d3e5079 100644 --- a/trunk/3rdparty/st-srs/Makefile +++ b/trunk/3rdparty/st-srs/Makefile @@ -128,6 +128,7 @@ OTHER_FLAGS = -Wall endif ifeq ($(OS), DARWIN) +EXTRA_OBJS = $(TARGETDIR)/md_darwin.o LD = cc SFLAGS = -fPIC -fno-common DSO_SUFFIX = dylib @@ -139,8 +140,8 @@ CFLAGS += -arch ppc LDFLAGS += -arch ppc endif ifeq ($(INTEL), yes) -CFLAGS += -arch i386 -arch x86_64 -LDFLAGS += -arch i386 -arch x86_64 +CFLAGS += -arch x86_64 +LDFLAGS += -arch x86_64 endif LDFLAGS += -dynamiclib -install_name /sw/lib/libst.$(MAJOR).$(DSO_SUFFIX) -compatibility_version $(MAJOR) -current_version $(VERSION) OTHER_FLAGS = -Wall @@ -313,7 +314,9 @@ endif # for SRS # disable examples for ubuntu crossbuild failed. # @see https://github.com/winlinvip/simple-rtmp-server/issues/308 +ifeq ($(OS), LINUX) EXAMPLES = +endif ifeq ($(OS), DARWIN) LINKNAME = libst.$(DSO_SUFFIX) @@ -369,10 +372,13 @@ $(HEADER): public.h $(TARGETDIR)/md.o: md.S $(CC) $(CFLAGS) -c $< -o $@ +$(TARGETDIR)/md_darwin.o: md_darwin.S + $(CC) $(CFLAGS) -c $< -o $@ + $(TARGETDIR)/%.o: %.c common.h md.h $(CC) $(CFLAGS) -c $< -o $@ -examples:: +examples: $(SLIBRARY) @echo Making $@ @cd $@; $(MAKE) CC="$(CC)" CFLAGS="$(CFLAGS)" OS="$(OS)" TARGETDIR="$(TARGETDIR)" diff --git a/trunk/3rdparty/st-srs/md.h b/trunk/3rdparty/st-srs/md.h index dc4ef54c90..8c0a222d69 100644 --- a/trunk/3rdparty/st-srs/md.h +++ b/trunk/3rdparty/st-srs/md.h @@ -120,26 +120,30 @@ #define MD_ALWAYS_UNSERIALIZED_ACCEPT #define MD_HAVE_SOCKLEN_T - #define MD_SETJMP(env) _setjmp(env) - #define MD_LONGJMP(env, val) _longjmp(env, val) + #define MD_USE_BUILTIN_SETJMP - #if defined(__ppc__) - #define MD_JB_SP 0 - #elif defined(__i386__) - #define MD_JB_SP 9 - #elif defined(__x86_64__) - #define MD_JB_SP 4 + #if defined(__amd64__) || defined(__x86_64__) + #define JB_SP 12 + #define MD_GET_SP(_t) *((long *)&((_t)->context[JB_SP])) #else #error Unknown CPU architecture #endif - - #define MD_INIT_CONTEXT(_thread, _sp, _main) \ - ST_BEGIN_MACRO \ - if (MD_SETJMP((_thread)->context)) \ - _main(); \ - *((long *)&((_thread)->context[MD_JB_SP])) = (long) (_sp); \ + + #define MD_INIT_CONTEXT(_thread, _sp, _main) \ + ST_BEGIN_MACRO \ + if (MD_SETJMP((_thread)->context)) \ + _main(); \ + MD_GET_SP(_thread) = (long) (_sp); \ ST_END_MACRO + #if defined(MD_USE_BUILTIN_SETJMP) + #define MD_SETJMP(env) _st_md_cxt_save(env) + #define MD_LONGJMP(env, val) _st_md_cxt_restore(env, val) + + extern int _st_md_cxt_save(jmp_buf env); + extern void _st_md_cxt_restore(jmp_buf env, int val); + #endif + #define MD_GET_UTIME() \ struct timeval tv; \ (void) gettimeofday(&tv, NULL); \ diff --git a/trunk/3rdparty/st-srs/md_darwin.S b/trunk/3rdparty/st-srs/md_darwin.S new file mode 100644 index 0000000000..6cd163d441 --- /dev/null +++ b/trunk/3rdparty/st-srs/md_darwin.S @@ -0,0 +1,76 @@ + +/* If user disable the ASM, such as avoiding bugs in ASM, donot compile it. */ +#if !defined(MD_ST_NO_ASM) + +#if defined(__amd64__) || defined(__x86_64__) + + /****************************************************************/ + + /* + * Internal __jmp_buf layout + */ + #define JB_RBX 0 + #define JB_RBP 1 + #define JB_R12 2 /* Backup IP, https://www.cnblogs.com/Five100Miles/p/8458561.html */ + #define JB_R13 3 /* Backup SP, https://www.cnblogs.com/Five100Miles/p/8458561.html */ + #define JB_R14 4 /* Backup LR, https://www.cnblogs.com/Five100Miles/p/8458561.html */ + #define JB_R15 5 /* Backup PC, https://www.cnblogs.com/Five100Miles/p/8458561.html */ + #define JB_RSP 6 + #define JB_PC 7 + + .file "md_darwin.S" + .text + + /* _st_md_cxt_save(__jmp_buf env) */ /* The env is rdi, http://blog.chinaunix.net/uid-20157960-id-1974354.html */ + .globl __st_md_cxt_save + .align 16 + __st_md_cxt_save: + /* + * Save registers. + */ + movq %rbx, (JB_RBX*8)(%rdi) /* Save rbx to env[0], *(int64_t*)(rdi+0)=rbx */ + movq %rbp, (JB_RBP*8)(%rdi) /* Save rbp to env[1], *(int64_t*)(rdi+1)=rbp */ + movq %r12, (JB_R12*8)(%rdi) /* Save r12 to env[2], *(int64_t*)(rdi+2)=r12 */ + movq %r13, (JB_R13*8)(%rdi) /* Save r13 to env[3], *(int64_t*)(rdi+3)=r13 */ + movq %r14, (JB_R14*8)(%rdi) /* Save r14 to env[4], *(int64_t*)(rdi+4)=r14 */ + movq %r15, (JB_R15*8)(%rdi) /* Save r15 to env[5], *(int64_t*)(rdi+5)=r15 */ + /* Save SP */ + leaq 8(%rsp), %rdx /* Save *(int64_t*)(rsp+8) to rdx, https://my.oschina.net/guonaihong/blog/508907 */ + movq %rdx, (JB_RSP*8)(%rdi) /* Save rdx(rsp) to env[6], *(int64_t*)(rdi+6)=rdx */ + /* Save PC we are returning to */ + movq (%rsp), %rax /* Save PC(parent function address) %(rsp) to rax */ + movq %rax, (JB_PC*8)(%rdi) /* Save rax(PC) to env[7], *(int64_t*)(rdi+7)=rax */ + xorq %rax, %rax /* Reset rax to 0 */ + ret + + + /****************************************************************/ + + /* _st_md_cxt_restore(__jmp_buf env, int val) */ /* The env is rdi, val is esi/rsi, http://blog.chinaunix.net/uid-20157960-id-1974354.html */ + .globl __st_md_cxt_restore + .align 16 + __st_md_cxt_restore: + /* + * Restore registers. + */ + movq (JB_RBX*8)(%rdi), %rbx /* Load rbx from env[0] */ + movq (JB_RBP*8)(%rdi), %rbp /* Load rbp from env[1] */ + movq (JB_R12*8)(%rdi), %r12 /* Load r12 from env[2] */ + movq (JB_R13*8)(%rdi), %r13 /* Load r13 from env[3] */ + movq (JB_R14*8)(%rdi), %r14 /* Load r14 from env[4] */ + movq (JB_R15*8)(%rdi), %r15 /* Load r15 from env[5] */ + /* Set return value */ /* The esi is param1 val, the eax is return value */ + test %esi, %esi /* if (!val) { */ + mov $01, %eax /* val=1; */ + cmove %eax, %esi /* } */ + mov %esi, %eax /* return val; */ + movq (JB_PC*8)(%rdi), %rdx /* Load rdx(PC) from env[7] */ + movq (JB_RSP*8)(%rdi), %rsp /* Load rsp from env[6] */ + /* Jump to saved PC */ + jmpq *%rdx /* Jump to rdx(PC) */ + + /****************************************************************/ + +#endif + +#endif diff --git a/trunk/auto/auto_headers.sh b/trunk/auto/auto_headers.sh index b4c04545cd..73afc21960 100755 --- a/trunk/auto/auto_headers.sh +++ b/trunk/auto/auto_headers.sh @@ -143,6 +143,11 @@ if [ $SRS_CROSS_BUILD = YES ]; then else srs_undefine_macro "SRS_AUTO_CROSSBUILD" $SRS_AUTO_HEADERS_H fi +if [ $SRS_OSX = YES ]; then + srs_define_macro "SRS_AUTO_OSX" $SRS_AUTO_HEADERS_H +else + srs_undefine_macro "SRS_AUTO_OSX" $SRS_AUTO_HEADERS_H +fi # prefix echo "" >> $SRS_AUTO_HEADERS_H diff --git a/trunk/auto/coverage.sh b/trunk/auto/coverage.sh index 0196fe9d5e..55cb7ce46f 100644 --- a/trunk/auto/coverage.sh +++ b/trunk/auto/coverage.sh @@ -17,7 +17,7 @@ mkdir -p $workdir && cd $workdir ret=$?; if [[ $ret -ne 0 ]]; then echo "Enter workdir failed, ret=$ret"; exit $ret; fi # Collect all *.gcno and *.gcda to objs/cover. -cd $workdir && (rm -rf src && cp -R ../../src . && cp -R ../src .) +cd $workdir && (rm -rf src && cp -R ../../src . && cp -R ../src/* src/) ret=$?; if [[ $ret -ne 0 ]]; then echo "Collect *.gcno and *.gcda failed, ret=$ret"; exit $ret; fi # Generate *.gcov for coverage. diff --git a/trunk/auto/depends.sh b/trunk/auto/depends.sh index a4c8730dfe..205d87a184 100755 --- a/trunk/auto/depends.sh +++ b/trunk/auto/depends.sh @@ -109,6 +109,7 @@ function Ubuntu_prepare() if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then Ubuntu_prepare; ret=$?; if [[ 0 -ne $ret ]]; then echo "Install tools for ubuntu failed, ret=$ret"; exit $ret; fi fi + ##################################################################################### # for Centos, auto install tools by yum ##################################################################################### @@ -182,14 +183,105 @@ function Centos_prepare() if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then Centos_prepare; ret=$?; if [[ 0 -ne $ret ]]; then echo "Install tools for CentOS failed, ret=$ret"; exit $ret; fi fi + +##################################################################################### +# For OSX, auto install tools by brew +##################################################################################### +OS_IS_OSX=NO +function OSX_prepare() +{ + uname -s|grep Darwin >/dev/null 2>&1 + ret=$?; if [[ 0 -ne $ret ]]; then + if [ $SRS_OSX = YES ]; then + echo "OSX check failed, actual is `uname -s`" + exit 1; + fi + return 0; + fi + + # cross build for arm, install the cross build tool chain. + if [ $SRS_CROSS_BUILD = YES ]; then + echo "embeded(arm/mips) is invalid for OSX" + return 1 + fi + + OS_IS_OSX=YES + echo "OSX detected, install tools if needed" + # requires the osx when os + if [ $OS_IS_OSX = YES ]; then + if [ $SRS_OSX = NO ]; then + echo "OSX detected, must specifies the --osx" + exit 1 + fi + fi + + brew --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then + echo "install brew" + echo "ruby -e \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)\"" + ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi + echo "install brew success" + fi + + gcc --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then + echo "install gcc" + echo "brew install gcc" + brew install gcc; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi + echo "install gcc success" + fi + + g++ --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then + echo "install gcc-c++" + echo "brew install gcc-c++" + brew install gcc-c++; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi + echo "install gcc-c++ success" + fi + + make --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then + echo "install make" + echo "brew install make" + brew install make; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi + echo "install make success" + fi + + patch --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then + echo "install patch" + echo "brew install patch" + brew install patch; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi + echo "install patch success" + fi + + unzip --help >/dev/null 2>&1; ret=$?; if [[ 0 -ne $ret ]]; then + echo "install unzip" + echo "brew install unzip" + brew install unzip; ret=$?; if [[ 0 -ne $ret ]]; then return $ret; fi + echo "install unzip success" + fi + + echo "OSX install tools success" + return 0 +} +# donot prepare tools, for srs-librtmp depends only gcc and g++. +if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then + OSX_prepare; ret=$?; if [[ 0 -ne $ret ]]; then echo "OSX prepare failed, ret=$ret"; exit $ret; fi +fi + ##################################################################################### # for Centos, auto install tools by yum ##################################################################################### # We must use a bash function instead of variable. function sed_utility() { - sed -i "$@" + if [ $OS_IS_OSX = YES ]; then + sed -i '' "$@" + else + sed -i "$@" + fi + ret=$?; if [[ $ret -ne 0 ]]; then - echo "sed -i \"$@\"" + if [ $OS_IS_OSX = YES ]; then + echo "sed -i '' \"$@\"" + else + echo "sed -i \"$@\"" + fi return $ret fi } @@ -204,7 +296,7 @@ SED="sed_utility" && echo "SED is $SED" # directly build on arm/mips, for example, pi or cubie, # export srs-librtmp # others is invalid. -if [[ $OS_IS_UBUNTU = NO && $OS_IS_CENTOS = NO && $SRS_EXPORT_LIBRTMP_PROJECT = NO ]]; then +if [[ $OS_IS_UBUNTU = NO && $OS_IS_CENTOS = NO && $OS_IS_OSX = NO && $SRS_EXPORT_LIBRTMP_PROJECT = NO ]]; then if [[ $SRS_PI = NO && $SRS_CUBIE = NO && $SRS_CROSS_BUILD = NO ]]; then echo "Your OS `uname -s` is not supported." exit 1 @@ -216,29 +308,48 @@ fi ##################################################################################### if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then # check the cross build flag file, if flag changed, need to rebuild the st. - _ST_MAKE=linux-debug && _ST_EXTRA_CFLAGS="-DMD_HAVE_EPOLL" + _ST_MAKE=linux-debug && _ST_EXTRA_CFLAGS="-DMD_HAVE_EPOLL" && _ST_LD=${SRS_TOOL_LD} && _ST_OBJ="LINUX_*" if [[ $SRS_VALGRIND == YES ]]; then _ST_EXTRA_CFLAGS="$_ST_EXTRA_CFLAGS -DMD_VALGRIND" fi + # for osx, use darwin for st, donot use epoll. + if [[ $SRS_OSX == YES ]]; then + _ST_MAKE=darwin-debug && _ST_EXTRA_CFLAGS="-DMD_HAVE_KQUEUE" && _ST_LD=${SRS_TOOL_CC} && _ST_OBJ="DARWIN_*" + fi # Pass the global extra flags. if [[ $SRS_EXTRA_FLAGS != '' ]]; then _ST_EXTRA_CFLAGS="$_ST_EXTRA_CFLAGS $SRS_EXTRA_FLAGS" fi # Patched ST from https://github.com/ossrs/state-threads/tree/srs - if [[ -f ${SRS_OBJS}/st/libst.a ]]; then + if [[ -f ${SRS_OBJS}/${SRS_PLATFORM}/st/libst.a ]]; then echo "The state-threads is ok."; else echo "Building state-threads."; ( - rm -rf ${SRS_OBJS}/st-srs && cd ${SRS_OBJS} && - ln -sf ../3rdparty/st-srs && cd st-srs && - make clean && make ${_ST_MAKE} EXTRA_CFLAGS="${_ST_EXTRA_CFLAGS}" \ - CC=${SRS_TOOL_CC} AR=${SRS_TOOL_AR} LD=${SRS_TOOL_LD} RANDLIB=${SRS_TOOL_RANDLIB} && - cd .. && rm -f st && ln -sf st-srs/obj st + rm -rf ${SRS_OBJS}/${SRS_PLATFORM}/st-srs && mkdir -p ${SRS_OBJS}/${SRS_PLATFORM}/st-srs && + # Create a hidden directory .src + cd ${SRS_OBJS}/${SRS_PLATFORM}/st-srs && ln -sf ../../../3rdparty/st-srs .src && + # Link source files under .src + for file in `(cd .src && find . -maxdepth 1 -type f ! -name '*.o' ! -name '*.d')`; do + ln -sf .src/$file $file; + done && + # Link source files under .src/xxx, the first child dir. + for dir in `(cd .src && find . -maxdepth 1 -type d|grep '\./'|grep -v Linux|grep -v Darwin)`; do + mkdir -p $dir && + for file in `(cd .src/$dir && find . -maxdepth 1 -type f ! -name '*.o' ! -name '*.d')`; do + ln -sf ../.src/$dir/$file $dir/$file; + done; + done && + # Build source code. + make ${_ST_MAKE} EXTRA_CFLAGS="${_ST_EXTRA_CFLAGS}" \ + CC=${SRS_TOOL_CC} AR=${SRS_TOOL_AR} LD=${_ST_LD} RANDLIB=${SRS_TOOL_RANDLIB} && + cd .. && rm -f st && ln -sf st-srs/${_ST_OBJ} st ) fi # check status ret=$?; if [[ $ret -ne 0 ]]; then echo "Build state-threads failed, ret=$ret"; exit $ret; fi + # Always update the links. + (cd ${SRS_OBJS} && rm -rf st && ln -sf ${SRS_PLATFORM}/st-srs/${_ST_OBJ} st) if [ ! -f ${SRS_OBJS}/st/libst.a ]; then echo "Build state-threads static lib failed."; exit -1; fi fi @@ -291,19 +402,19 @@ fi # cherrypy for http hooks callback, CherryPy-3.2.4 ##################################################################################### if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then - if [[ -f ${SRS_OBJS}/CherryPy-3.2.4/setup.py ]]; then + if [[ -f ${SRS_OBJS}/${SRS_PLATFORM}/CherryPy-3.2.4/setup.py ]]; then echo "CherryPy-3.2.4 is ok."; else echo "Installing CherryPy-3.2.4"; ( - rm -rf ${SRS_OBJS}/CherryPy-3.2.4 && cd ${SRS_OBJS} && - unzip -q ../3rdparty/CherryPy-3.2.4.zip && cd CherryPy-3.2.4 && + rm -rf ${SRS_OBJS}/CherryPy-3.2.4 && cd ${SRS_OBJS}/${SRS_PLATFORM} && + unzip -q ../../3rdparty/CherryPy-3.2.4.zip && cd CherryPy-3.2.4 && python setup.py install --user ) fi # check status ret=$?; if [[ $ret -ne 0 ]]; then echo "build CherryPy-3.2.4 failed, ret=$ret"; exit $ret; fi - if [ ! -f ${SRS_OBJS}/CherryPy-3.2.4/setup.py ]; then echo "build CherryPy-3.2.4 failed."; exit -1; fi + if [ ! -f ${SRS_OBJS}/${SRS_PLATFORM}/CherryPy-3.2.4/setup.py ]; then echo "build CherryPy-3.2.4 failed."; exit -1; fi echo "Link players to cherrypy static-dir" rm -rf research/api-server/static-dir/players && @@ -336,21 +447,26 @@ if [[ $SRS_SSL == YES && $SRS_USE_SYS_SSL != YES ]]; then OPENSSL_CONFIG="./Configure linux-armv4" else # If not crossbuild, try to use exists libraries. - if [[ -f /usr/local/lib64/libssl.a && ! -f ${SRS_OBJS}/openssl/lib/libssl.a ]]; then - (mkdir -p ${SRS_OBJS}/openssl/lib && cd ${SRS_OBJS}/openssl/lib && + if [[ -f /usr/local/lib64/libssl.a && ! -f ${SRS_OBJS}/${SRS_PLATFORM}/openssl/lib/libssl.a ]]; then + (mkdir -p ${SRS_OBJS}/${SRS_PLATFORM}/openssl/lib && cd ${SRS_OBJS}/${SRS_PLATFORM}/openssl/lib && ln -sf /usr/local/lib64/libssl.a && ln -sf /usr/local/lib64/libcrypto.a) - (mkdir -p ${SRS_OBJS}/openssl/include && cd ${SRS_OBJS}/openssl/include && + (mkdir -p ${SRS_OBJS}/${SRS_PLATFORM}/openssl/include && cd ${SRS_OBJS}/${SRS_PLATFORM}/openssl/include && ln -sf /usr/local/include/openssl) fi fi + # Which lib we use. + OPENSSL_LIB="openssl-1.1.0e/_release" + if [[ ! -f ${SRS_OBJS}/${SRS_PLATFORM}/${OPENSSL_LIB}/lib/libssl.a ]]; then + OPENSSL_LIB="openssl" + fi # cross build not specified, if exists flag, need to rebuild for no-arm platform. - if [[ -f ${SRS_OBJS}/openssl/lib/libssl.a ]]; then + if [[ -f ${SRS_OBJS}/${SRS_PLATFORM}/openssl/lib/libssl.a ]]; then echo "Openssl-1.1.0e is ok."; else echo "Building openssl-1.1.0e."; ( - rm -rf ${SRS_OBJS}/openssl-1.1.0e && cd ${SRS_OBJS} && - unzip -q ../3rdparty/openssl-1.1.0e.zip && cd openssl-1.1.0e && + rm -rf ${SRS_OBJS}/${SRS_PLATFORM}/openssl-1.1.0e && cd ${SRS_OBJS}/${SRS_PLATFORM} && + unzip -q ../../3rdparty/openssl-1.1.0e.zip && cd openssl-1.1.0e && ${OPENSSL_CONFIG} --prefix=`pwd`/_release $OPENSSL_OPTIONS && make CC=${SRS_TOOL_CC} AR="${SRS_TOOL_AR} -rs" LD=${SRS_TOOL_LD} RANDLIB=${SRS_TOOL_RANDLIB} && make install_sw && cd .. && rm -rf openssl && ln -sf openssl-1.1.0e/_release openssl @@ -358,6 +474,8 @@ if [[ $SRS_SSL == YES && $SRS_USE_SYS_SSL != YES ]]; then fi # check status ret=$?; if [[ $ret -ne 0 ]]; then echo "Build openssl-1.1.0e failed, ret=$ret"; exit $ret; fi + # Always update the links. + (cd ${SRS_OBJS} && rm -rf openssl && ln -sf ${SRS_PLATFORM}/${OPENSSL_LIB} openssl) if [ ! -f ${SRS_OBJS}/openssl/lib/libssl.a ]; then echo "Build openssl-1.1.0e failed."; exit -1; fi fi @@ -365,24 +483,19 @@ fi # live transcoding, ffmpeg-4.1, x264-core157, lame-3.99.5, libaacplus-2.0.2. ##################################################################################### # Always link the ffmpeg tools if exists. -if [[ -f /usr/local/bin/ffmpeg && ! -f ${SRS_OBJS}/ffmpeg/bin/ffmpeg ]]; then - mkdir -p ${SRS_OBJS}/ffmpeg/bin && ln -sf /usr/local/bin/ffmpeg ${SRS_OBJS}/ffmpeg/bin/ffmpeg +if [[ -f /usr/local/bin/ffmpeg && ! -f ${SRS_OBJS}/${SRS_PLATFORM}/ffmpeg/bin/ffmpeg ]]; then + mkdir -p ${SRS_OBJS}/${SRS_PLATFORM}/ffmpeg/bin && + ln -sf /usr/local/bin/ffmpeg ${SRS_OBJS}/${SRS_PLATFORM}/ffmpeg/bin/ffmpeg fi if [ $SRS_FFMPEG_TOOL = YES ]; then - if [[ -f ${SRS_OBJS}/ffmpeg/bin/ffmpeg ]]; then + if [[ -f ${SRS_OBJS}/${SRS_PLATFORM}/ffmpeg/bin/ffmpeg ]]; then echo "ffmpeg-4.1 is ok."; else - echo "build ffmpeg-4.1"; - ( - cd ${SRS_OBJS} && pwd_dir=`pwd` && - rm -rf ffmepg.src && mkdir -p ffmpeg.src && cd ffmpeg.src && - rm -f build_ffmpeg.sh && ln -sf ../../auto/build_ffmpeg.sh && . build_ffmpeg.sh && - cd ${pwd_dir} && rm -rf ffmpeg && ln -sf ffmpeg.src/_release ffmpeg - ) + echo "no ffmpeg found, please use srs-docker or --without-ffmpeg"; + exit -1; fi - # check status - ret=$?; if [[ $ret -ne 0 ]]; then echo "build ffmpeg-4.1 failed, ret=$ret"; exit $ret; fi - if [ ! -f ${SRS_OBJS}/ffmpeg/bin/ffmpeg ]; then echo "build ffmpeg-4.1 failed."; exit -1; fi + # Always update the links. + (cd ${SRS_OBJS} && rm -rf ffmpeg && ln -sf ${SRS_PLATFORM}/ffmpeg) fi ##################################################################################### @@ -430,18 +543,20 @@ fi # build utest code ##################################################################################### if [ $SRS_UTEST = YES ]; then - if [[ -f ${SRS_OBJS}/gtest/include/gtest/gtest.h ]]; then + if [[ -f ${SRS_OBJS}/${SRS_PLATFORM}/gtest/include/gtest/gtest.h ]]; then echo "The gtest-1.6.0 is ok."; else echo "Build gtest-1.6.0"; ( - rm -rf ${SRS_OBJS}/gtest-1.6.0 && cd ${SRS_OBJS} && - unzip -q ../3rdparty/gtest-1.6.0.zip && + rm -rf ${SRS_OBJS}/${SRS_PLATFORM}/gtest-1.6.0 && cd ${SRS_OBJS}/${SRS_PLATFORM} && + unzip -q ../../3rdparty/gtest-1.6.0.zip && rm -rf gtest && ln -sf gtest-1.6.0 gtest ) fi # check status ret=$?; if [[ $ret -ne 0 ]]; then echo "Build gtest-1.6.0 failed, ret=$ret"; exit $ret; fi + # Always update the links. + (cd ${SRS_OBJS} && rm -rf gtest && ln -sf ${SRS_PLATFORM}/gtest-1.6.0 gtest) if [ ! -f ${SRS_OBJS}/gtest/include/gtest/gtest.h ]; then echo "Build gtest-1.6.0 failed."; exit -1; fi fi @@ -449,13 +564,13 @@ fi # build gperf code ##################################################################################### if [ $SRS_GPERF = YES ]; then - if [[ -f ${SRS_OBJS}/gperf/bin/pprof ]]; then + if [[ -f ${SRS_OBJS}/${SRS_PLATFORM}/gperf/bin/pprof ]]; then echo "The gperftools-2.1 is ok."; else echo "Build gperftools-2.1"; ( - rm -rf ${SRS_OBJS}/gperftools-2.1 && cd ${SRS_OBJS} && - unzip -q ../3rdparty/gperftools-2.1.zip && cd gperftools-2.1 && + rm -rf ${SRS_OBJS}/${SRS_PLATFORM}/gperftools-2.1 && cd ${SRS_OBJS}/${SRS_PLATFORM} && + unzip -q ../../3rdparty/gperftools-2.1.zip && cd gperftools-2.1 && ./configure --prefix=`pwd`/_release --enable-frame-pointers && make ${SRS_JOBS} && make install && cd .. && rm -rf gperf && ln -sf gperftools-2.1/_release gperf && rm -rf pprof && ln -sf gperf/bin/pprof pprof @@ -463,10 +578,7 @@ if [ $SRS_GPERF = YES ]; then fi # check status ret=$?; if [[ $ret -ne 0 ]]; then echo "Build gperftools-2.1 failed, ret=$ret"; exit $ret; fi + # Always update the links. + (cd ${SRS_OBJS} && rm -rf pprof && ln -sf ${SRS_PLATFORM}/gperf/bin/pprof pprof) if [ ! -f ${SRS_OBJS}/gperf/bin/pprof ]; then echo "Build gperftools-2.1 failed."; exit -1; fi fi - -##################################################################################### -# generated the test script -##################################################################################### -rm -rf ${SRS_OBJS}/srs.test && ln -sf `pwd`/scripts/srs.test objs/srs.test diff --git a/trunk/auto/options.sh b/trunk/auto/options.sh index ad89f882d1..a200411bb5 100755 --- a/trunk/auto/options.sh +++ b/trunk/auto/options.sh @@ -21,7 +21,7 @@ SRS_NGINX=NO SRS_FFMPEG_TOOL=NO SRS_LIBRTMP=NO SRS_RESEARCH=NO -SRS_UTEST=YES +SRS_UTEST=NO SRS_GPERF=NO # Performance test: tcmalloc SRS_GPERF_MC=NO # Performance test: gperf memory check SRS_GPERF_MD=NO # Performance test: gperf memory defence @@ -108,6 +108,8 @@ SRS_TOOL_AR=ar SRS_TOOL_LD=ld SRS_TOOL_RANDLIB=randlib SRS_EXTRA_FLAGS= +# Set the object files tag name. +SRS_BUILD_TAG= ##################################################################################### # menu @@ -176,6 +178,7 @@ Toolchain options: @see https://github.com/ossrs/srs/issues/1547#issuec --ld= Use linker tool LD, default is ld. --randlib= Use randlib tool RANDLIB, default is randlib. --extra-flags= Set EFLAGS as CFLAGS and CXXFLAGS. Also passed to ST as EXTRA_CFLAGS. + --build-tag= Set the build object directory suffix. Conflicts: 1. --with-gmc vs --with-gmp: @@ -204,10 +207,6 @@ Remark: END } -function ignore_option() { - echo "ignore option \"$option\"" -} - function parse_user_option() { case "$option" in -h) help=yes ;; @@ -266,6 +265,7 @@ function parse_user_option() { --ld) SRS_TOOL_LD=${value} ;; --randlib) SRS_TOOL_RANDLIB=${value} ;; --extra-flags) SRS_EXTRA_FLAGS=${value} ;; + --build-tag) SRS_BUILD_TAG=${value} ;; --x86-x64) SRS_X86_X64=YES ;; --x86-64) SRS_X86_X64=YES ;; @@ -297,16 +297,16 @@ function parse_user_option() { --with-hls) SRS_HLS=YES ;; --with-dvr) SRS_DVR=YES ;; - --without-stream-caster) ignore_option ;; - --without-ingest) ignore_option ;; - --without-ssl) ignore_option ;; - --without-stat) ignore_option ;; - --without-transcode) ignore_option ;; - --without-http-callback) ignore_option ;; - --without-http-server) ignore_option ;; - --without-http-api) ignore_option ;; - --without-hls) ignore_option ;; - --without-dvr) ignore_option ;; + --without-stream-caster) echo "ignore option \"$option\"" ;; + --without-ingest) echo "ignore option \"$option\"" ;; + --without-ssl) echo "ignore option \"$option\"" ;; + --without-stat) echo "ignore option \"$option\"" ;; + --without-transcode) echo "ignore option \"$option\"" ;; + --without-http-callback) echo "ignore option \"$option\"" ;; + --without-http-server) echo "ignore option \"$option\"" ;; + --without-http-api) echo "ignore option \"$option\"" ;; + --without-hls) echo "ignore option \"$option\"" ;; + --without-dvr) echo "ignore option \"$option\"" ;; *) echo "$0: error: invalid option \"$option\"" @@ -397,7 +397,7 @@ function apply_user_presets() { SRS_HDS=YES SRS_LIBRTMP=YES SRS_RESEARCH=NO - SRS_UTEST=YES + SRS_UTEST=NO SRS_STATIC=NO fi @@ -424,7 +424,7 @@ function apply_user_presets() { SRS_HDS=YES SRS_LIBRTMP=YES SRS_RESEARCH=NO - SRS_UTEST=YES + SRS_UTEST=NO SRS_STATIC=NO fi @@ -553,6 +553,7 @@ function regenerate_options() { if [ $SRS_LOG_TRACE = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --log-trace"; fi if [ $SRS_GCOV = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --gcov"; fi if [[ $SRS_EXTRA_FLAGS != '' ]]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --extra-flags=\\\"$SRS_EXTRA_FLAGS\\\""; fi + if [[ $SRS_BUILD_TAG != '' ]]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --build-tag=\\\"$SRS_BUILD_TAG\\\""; fi if [[ $SRS_TOOL_CC != '' ]]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --cc=$SRS_TOOL_CC"; fi if [[ $SRS_TOOL_CXX != '' ]]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --cxx=$SRS_TOOL_CXX"; fi if [[ $SRS_TOOL_AR != '' ]]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --ar=$SRS_TOOL_AR"; fi @@ -575,16 +576,12 @@ function check_option_conflicts() { echo "For crossbuild, must not use default toolchain, cc: $SRS_TOOL_CC, cxx: $SRS_TOOL_CXX, ar: $SRS_TOOL_AR"; exit -1 fi - if [ $SRS_OSX = YES ]; then - echo "We don't support OSX, please use docker https://github.com/ossrs/srs-docker"; exit -1 - fi - if [[ $SRS_NGINX == YES ]]; then - echo "Don't support building NGINX, please use docker https://github.com/ossrs/srs-docker"; exit -1 + echo "Don't support building NGINX, please use docker https://github.com/ossrs/srs-docker"; exit -1; fi if [[ $SRS_FFMPEG_TOOL == YES ]]; then - echo "Don't support building FFMPEG, please use docker https://github.com/ossrs/srs-docker"; exit -1 + echo "Don't support building FFMPEG, please use docker https://github.com/ossrs/srs-docker"; exit -1; fi # TODO: FIXME: check more os. diff --git a/trunk/auto/setup_variables.sh b/trunk/auto/setup_variables.sh index 9d37edbf75..7a39fd1666 100755 --- a/trunk/auto/setup_variables.sh +++ b/trunk/auto/setup_variables.sh @@ -1,3 +1,24 @@ #!/bin/bash # when options parsed, setup some variables, then build the depends. +OS_KERNEL_NAME=$(uname -s) +OS_KERNRL_RELEASE=$(uname -r|awk -F '-' '{print $1}') +OS_PREFIX="Platform" +SRS_PLATFORM="${OS_PREFIX}-${OS_KERNEL_NAME}-${OS_KERNRL_RELEASE}" +if [[ ${SRS_BUILD_TAG} != "" ]]; then + SRS_PLATFORM="${SRS_PLATFORM}-${SRS_BUILD_TAG}" +fi +echo "SRS_WORKDIR: ${SRS_WORKDIR}, SRS_OBJS_DIR: ${SRS_OBJS_DIR}, SRS_OBJS: ${SRS_OBJS}, SRS_PLATFORM: ${SRS_PLATFORM}" + +# For src object files on each platform. +( + mkdir -p ${SRS_OBJS_DIR} && cd ${SRS_OBJS_DIR} && + rm -rf src utest srs srs_utest research include lib srs_hls_ingester srs_mp4_parser && + mkdir -p ${SRS_PLATFORM}/src && ln -sf ${SRS_PLATFORM}/src && + mkdir -p ${SRS_PLATFORM}/utest && ln -sf ${SRS_PLATFORM}/utest && + mkdir -p ${SRS_PLATFORM}/research && ln -sf ${SRS_PLATFORM}/research && + mkdir -p ${SRS_PLATFORM}/include && ln -sf ${SRS_PLATFORM}/include && + mkdir -p ${SRS_PLATFORM}/lib && ln -sf ${SRS_PLATFORM}/lib +) +echo "Fast cleanup, if need to do full cleanup, please use: make clean" + diff --git a/trunk/auto/utest.sh b/trunk/auto/utest.sh index d8cce4a6f5..27bfe06932 100755 --- a/trunk/auto/utest.sh +++ b/trunk/auto/utest.sh @@ -16,13 +16,10 @@ mkdir -p ${SRS_OBJS}/utest # the prefix to generate the objs/utest/Makefile # dirs relative to current dir(objs/utest), it's trunk/objs/utest # trunk of srs, which contains the src dir, relative to objs/utest, it's trunk -SRS_TRUNK_PREFIX=../.. +SRS_TRUNK_PREFIX=../../.. # gest dir, relative to objs/utest, it's trunk/objs/gtest GTEST_DIR=${SRS_TRUNK_PREFIX}/${SRS_OBJS_DIR}/gtest -# the extra defines to compile utest. -EXTRA_DEFINES="" - cat << END > ${FILE} # user must run make the ${SRS_OBJS_DIR}/utest dir # at the same dir of Makefile. diff --git a/trunk/conf/full.conf b/trunk/conf/full.conf index a0b39e7ad1..37f15eaa7d 100644 --- a/trunk/conf/full.conf +++ b/trunk/conf/full.conf @@ -226,64 +226,134 @@ http_server { ############################################################################################# # the streamer cast stream from other protocol to SRS over RTMP. # @see https://github.com/ossrs/srs/tree/develop#stream-architecture + +# MPEGTS over UDP stream_caster { # whether stream caster is enabled. # default: off - enabled off; + enabled on; # the caster type of stream, the casters: # mpegts_over_udp, MPEG-TS over UDP caster. - # rtsp, Real Time Streaming Protocol (RTSP). - # flv, FLV over HTTP by POST. caster mpegts_over_udp; # the output rtmp url. # for mpegts_over_udp caster, the typically output url: # rtmp://127.0.0.1/live/livestream + output rtmp://127.0.0.1/live/livestream; + # the listen port for stream caster. + # for mpegts_over_udp caster, listen at udp port. for example, 8935. + listen 8935; +} + +# RTSP +stream_caster { + # whether stream caster is enabled. + # default: off + enabled on; + # the caster type of stream, the casters: + # rtsp, Real Time Streaming Protocol (RTSP). + caster rtsp; + # the output rtmp url. # for rtsp caster, the typically output url: # rtmp://127.0.0.1/[app]/[stream] # for example, the rtsp url: # rtsp://192.168.1.173:8544/live/livestream.sdp # where the [app] is "live" and [stream] is "livestream", output is: # rtmp://127.0.0.1/live/livestream - # for flv caster, the typically output url: - # rtmp://127.0.0.1/[app]/[stream] - # for example, POST to url: - # http://127.0.0.1:8936/live/livestream.flv - # where the [app] is "live" and [stream] is "livestream", output is: - # rtmp://127.0.0.1/live/livestream - output rtmp://127.0.0.1/live/livestream; + output rtmp://127.0.0.1/[app]/[stream]; # the listen port for stream caster. - # for mpegts_over_udp caster, listen at udp port. for example, 8935. # for rtsp caster, listen at tcp port. for example, 554. - # for flv caster, listen at tcp port. for example, 8936. - # TODO: support listen at <[ip:]port> - listen 8935; + listen 554; # for the rtsp caster, the rtp server local port over udp, # which reply the rtsp setup request message, the port will be used: # [rtp_port_min, rtp_port_max) rtp_port_min 57200; rtp_port_max 57300; } + +# FLV stream_caster { - enabled off; - caster mpegts_over_udp; - output rtmp://127.0.0.1/live/livestream; - listen 8935; -} -stream_caster { - enabled off; - caster rtsp; - output rtmp://127.0.0.1/[app]/[stream]; - listen 554; - rtp_port_min 57200; - rtp_port_max 57300; -} -stream_caster { - enabled off; + # whether stream caster is enabled. + # default: off + enabled on; + # the caster type of stream, the casters: + # flv, FLV over HTTP by POST. caster flv; + # the output rtmp url. + # for flv caster, the typically output url: + # rtmp://127.0.0.1/[app]/[stream] + # for example, POST to url: + # http://127.0.0.1:8936/live/livestream.flv + # where the [app] is "live" and [stream] is "livestream", output is: + # rtmp://127.0.0.1/live/livestream output rtmp://127.0.0.1/[app]/[stream]; + # the listen port for stream caster. + # for flv caster, listen at tcp port. for example, 8936. listen 8936; } +# GB28181 +stream_caster { + # whether stream caster is enabled. + # default: off + enabled on; + # the caster type of stream, the casters: + # gb28181, Push GB28181 to SRS. + caster gb28181; + # the output rtmp url. + # for gb28181 caster, the typically output url: + # rtmp://127.0.0.1/live/[stream] + # where the [stream] is the VideoChannelCodecID. + output rtmp://127.0.0.1/live/[stream]; + # the listen port for stream caster. + # for gb28181 caster, listen at udp port. for example, 9000. + # @remark We can bundle all gb28181 to this port, to reuse this port. + # User can choose to bundle port in API port_mode or SIP invite_port_fixed. + listen 9000; + # If not bundle ports, use specified ports for each stream. + rtp_port_min 58200; + rtp_port_max 58300; + # Whether wait for keyframe then forward to RTMP. + wait_keyframe off; + # Max timeout in seconds for RTP stream, if timeout, RTCP bye and close stream. + # default: 30 + rtp_idle_timeout 30; + # Whether has audio. + # @remark Flash/RTMP only supports 11025 22050 44100 sample rate, if not the audio may corrupt. + # default: off + audio_enable off; + # The exposed IP to receive media stream. + host 192.168.1.3; + + sip { + # Whether enable embeded SIP server. + # default: on + enabled on; + # The SIP listen port. + # default: 5060 + listen 5060; + # The SIP server ID. + # default: 34020000002000000001 + serial 34020000002000000001; + # The SIP server domain. + # default: 3402000000 + realm 3402000000; + # The SIP ACK response timeout in seconds. + # default: 30 + ack_timeout 30; + # The keepalive timeout in seconds. + # default: 120 + keepalive_timeout 120; + # Whether print SIP logs. + print_sip_message off; + # Whether play immediately after registered. + # default: on + auto_play on; + # Whether bundle media stream port. + # default: on + invite_port_fixed on; + } +} + ############################################################################################# # SRT server section ############################################################################################# diff --git a/trunk/conf/push.gb28181.conf b/trunk/conf/push.gb28181.conf new file mode 100644 index 0000000000..a106016c37 --- /dev/null +++ b/trunk/conf/push.gb28181.conf @@ -0,0 +1,99 @@ +# push gb28281 stream to SRS. + +listen 1935; +max_connections 1000; +daemon off; +srs_log_tank console; + +http_api { + enabled on; + listen 1985; +} + +stream_caster { + enabled on; + caster gb28181; + + # 转发流到rtmp服务器地址与端口 + # TODO: https://github.com/ossrs/srs/pull/1679/files#r400875104 + # [stream] is VideoChannelCodecID(视频通道编码ID) + output 127.0.0.1:1935; + + # 接收设备端rtp流的多路复用端口 + listen 9000; + + # rtp接收监听端口范围,最小值 + rtp_port_min 58200; + # rtp接收监听端口范围,最大值 + rtp_port_max 58300; + + # 是否等待关键帧之后,再转发, + # off:不需等待,直接转发 + # on:等第一个关键帧后,再转发 + wait_keyframe off; + + # rtp包空闲等待时间,如果指定时间没有收到任何包 + # rtp监听连接自动停止,发送BYE命令 + rtp_idle_timeout 30; + + # 是否转发音频流 + # 目前只支持aac格式,所以需要设备支持aac格式 + # on:转发音频 + # off:不转发音频,只有视频 + # *注意*!!!:flv 只支持11025 22050 44100 三种 + # 如果设备端没有三种中任何一个,转发时为自动选择一种格式 + # 同时也会将adts的头封装在flv aac raw数据中 + # 这样的话播放器为自动通过adts头自动选择采样频率 + # 像ffplay, vlc都可以,但是flash是没有声音, + # 因为flash,只支持11025 22050 44100 + audio_enable off; + + # 服务器主机号,可以域名或ip地址 + # 也就是设备端将媒体发送的地址,如果是服务器是内外网 + # 需要写外网地址, + # 调用api创建stream session时返回ip地址也是host + # TODO: https://github.com/ossrs/srs/pull/1679/files#r400917594 + host 192.168.1.27; + + sip { + # 是否启用srs内部sip信令 + # 为on信令走srs, off 只转发ps流 + enabled on; + + # sip监听udp端口 + listen 5060; + + # SIP server ID(SIP服务器ID). + # 设备端配置编号需要与该值一致,否则无法注册 + serial 34020000002000000001; + + # SIP server domain(SIP服务器域) + realm 3402000000; + + # 服务端发送ack后,接收回应的超时时间,单位为秒 + # 如果指定时间没有回应,认为失败 + ack_timeout 30; + + # 设备心跳维持时间,如果指定时间内(秒)没有接收一个心跳 + # 认为设备离线 + keepalive_timeout 120; + + # 日志打印是否打印sip信息 + # off:不打印 + # on:打印接收或发送sip命令信息 + # TODO: https://github.com/ossrs/srs/pull/1679/files#r400929300 + print_sip_message off; + + # 注册之后是否自动给设备端发送invite + # on: 是 off 不是,需要通过api控制 + auto_play on; + + # 设备将流发送的端口,是否固定 + # on 发送流到多路复用端口 如9000 + # off 自动从rtp_mix_port - rtp_max_port 之间的值中 + # 选一个可以用的端口 + invite_port_fixed on; + } +} +vhost __defaultVhost__ { +} diff --git a/trunk/configure b/trunk/configure index 8ba25a8baf..117603b792 100755 --- a/trunk/configure +++ b/trunk/configure @@ -26,16 +26,11 @@ BLACK="\\033[0m" # setup variables when options parsed. . auto/setup_variables.sh -# clean the exists, when not export srs-librtmp. -# do this only when the options is ok. -if [[ -f Makefile ]]; then -make clean -fi -# remove makefile +# We don't need to cleanup the exists files. rm -f ${SRS_WORKDIR}/${SRS_MAKEFILE} # create objs -mkdir -p ${SRS_OBJS} +mkdir -p ${SRS_OBJS}/${SRS_PLATFORM} # for export srs-librtmp, change target to it. . auto/generate-srs-librtmp-project.sh @@ -57,7 +52,7 @@ SrsLibrtmpSampleEntry="nossl" if [ $SRS_SSL = YES ]; then SrsLibrtmpSampleEntry="ssl";fi # utest make entry, (cd utest; make) SrsUtestMakeEntry="@echo -e \"ignore utest for it's disabled\"" -if [ $SRS_UTEST = YES ]; then SrsUtestMakeEntry="(cd ${SRS_OBJS_DIR}/utest; \$(MAKE))"; fi +if [ $SRS_UTEST = YES ]; then SrsUtestMakeEntry="(cd ${SRS_OBJS_DIR}/${SRS_PLATFORM}/utest && \$(MAKE))"; fi ##################################################################################### # finger out modules to install. @@ -213,7 +208,7 @@ MODULE_DEPENDS=("CORE" "KERNEL") ModuleLibIncs=(${SRS_OBJS_DIR} ${LibSSLRoot}) MODULE_FILES=("srs_protocol_amf0" "srs_protocol_io" "srs_rtmp_stack" "srs_rtmp_handshake" "srs_protocol_utility" "srs_rtmp_msg_array" "srs_protocol_stream" - "srs_raw_avc" "srs_rtsp_stack" "srs_http_stack" "srs_protocol_kbps" "srs_protocol_json" + "srs_raw_avc" "srs_rtsp_stack" "srs_sip_stack" "srs_http_stack" "srs_protocol_kbps" "srs_protocol_json" "srs_protocol_format") PROTOCOL_INCS="src/protocol"; MODULE_DIR=${PROTOCOL_INCS} . auto/modules.sh PROTOCOL_OBJS="${MODULE_OBJS[@]}" @@ -257,7 +252,7 @@ if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then "srs_app_mpegts_udp" "srs_app_rtsp" "srs_app_listener" "srs_app_async_call" "srs_app_caster_flv" "srs_app_process" "srs_app_ng_exec" "srs_app_hourglass" "srs_app_dash" "srs_app_fragment" "srs_app_dvr" - "srs_app_coworkers" "srs_app_hybrid") + "srs_app_coworkers" "srs_app_hybrid" "srs_app_gb28181" "srs_app_gb28181_sip") DEFINES="" # add each modules for app for SRS_MODULE in ${SRS_MODULES[*]}; do @@ -355,6 +350,11 @@ if [ $SRS_LIBRTMP = YES ]; then MODULE_OBJS="${CORE_OBJS[@]} ${KERNEL_OBJS[@]} ${PROTOCOL_OBJS[@]} ${LIBS_OBJS[@]}" BUILD_KEY="librtmp" LIB_NAME="lib/srs_librtmp" . auto/libs.sh fi +# For utest on mac. +# @see https://github.com/protocolbuffers/protobuf/issues/51#issuecomment-111044468 +if [[ $SRS_OSX == YES ]]; then + EXTRA_DEFINES="-DGTEST_USE_OWN_TR1_TUPLE=1" +fi # # utest, the unit-test cases of srs, base on gtest1.6 if [ $SRS_UTEST = YES ]; then @@ -391,7 +391,9 @@ mv ${SRS_WORKDIR}/${SRS_MAKEFILE} ${SRS_WORKDIR}/${SRS_MAKEFILE}.bk # generate phony header cat << END > ${SRS_WORKDIR}/${SRS_MAKEFILE} -.PHONY: default _default install install-api help clean server srs_ingest_hls librtmp utest _prepare_dir $__mphonys +.PHONY: default _default install install-api help clean destroy server srs_ingest_hls librtmp utest _prepare_dir $__mphonys +.PHONY: clean_srs clean_modules clean_st clean_openssl clean_ffmpeg clean_nginx clean_cherrypy +.PHONY: st # install prefix. SRS_PREFIX=${SRS_PREFIX} @@ -410,24 +412,58 @@ _default: server srs_ingest_hls librtmp utest __modules $__mdefaults @bash objs/_srs_build_summary.sh help: - @echo "Usage: make |||||||" + @echo "Usage: make ||||||||" @echo " help display this help menu" - @echo " clean cleanup project" + @echo " clean cleanup project and all depends" + @echo " destroy Cleanup all files for this platform in ${SRS_OBJS_DIR}/${SRS_PLATFORM}" @echo " server build the srs(simple rtmp server) over st(state-threads)" @echo " librtmp build the client publish/play library, and samples" @echo " utest build the utest for srs" @echo " install install srs to the prefix path" @echo " install-api install srs and api-server to the prefix path" @echo " uninstall uninstall srs from prefix path" + @echo "To clean special module:" + @echo " clean_st Clean depend st-srs in ${SRS_OBJS_DIR}/${SRS_PLATFORM}/st-srs" + @echo " clean_openssl Clean depend openssl in objs" + @echo " clean_ffmpeg Clean depend ffmpeg in objs" @echo "@remark all modules will auto genearted and build" @echo "For example:" @echo " make" @echo " make help" -clean: +doclean: (cd ${SRS_OBJS_DIR} && rm -rf srs srs_utest $__mcleanups) - (cd ${SRS_OBJS_DIR} && rm -rf src include lib) - (cd ${SRS_OBJS_DIR}/utest && rm -rf *.o *.a) + (cd ${SRS_OBJS_DIR} && rm -rf src/* include lib) + (mkdir -p ${SRS_OBJS_DIR}/utest && cd ${SRS_OBJS_DIR}/utest && rm -rf *.o *.a) + (cd research/librtmp && make clean) + (cd research/api-server/static-dir && rm -rf crossdomain.xml forward live players) + +clean: clean_srs clean_modules + @echo "You can clean each some components, see make help" + +destroy: clean_st clean_openssl clean_ffmpeg clean_nginx clean_cherrypy + (cd ${SRS_OBJS_DIR} && rm -rf ${SRS_PLATFORM}) + +clean_srs: + (cd ${SRS_OBJS_DIR} && rm -rf srs srs_utest) + (cd ${SRS_OBJS_DIR}/${SRS_PLATFORM} && rm -rf src include lib utest) + +clean_modules: + (cd ${SRS_OBJS_DIR} && rm -rf $__mdefaults) + +clean_st: + (cd ${SRS_OBJS_DIR}/${SRS_PLATFORM}/st-srs && make clean) + +clean_openssl: + (cd ${SRS_OBJS_DIR}/${SRS_PLATFORM} && rm -rf openssl*) + +clean_ffmpeg: + (cd ${SRS_OBJS_DIR}/${SRS_PLATFORM} && rm -rf ffmpeg) + +clean_nginx: + (cd ${SRS_OBJS_DIR} && rm -rf nginx) + +clean_cherrypy: (cd research/librtmp && make clean) (cd research/api-server/static-dir && rm -rf crossdomain.xml forward live players) @@ -559,7 +595,7 @@ librtmp: server @echo "Building the client publish/play library." \$(MAKE) -f ${SRS_OBJS_DIR}/${SRS_MAKEFILE} librtmp @echo "Building the srs-librtmp example." - (cd research/librtmp; \$(MAKE) EXTRA_CXXFLAGS="${SrsGcov}" ${SrsLibrtmpSampleEntry}) + (cd research/librtmp && \$(MAKE) EXTRA_CXXFLAGS="${SrsGcov}" ${SrsLibrtmpSampleEntry}) END else diff --git a/trunk/scripts/package.sh b/trunk/scripts/package.sh index 6629bbf6e9..ecd2334501 100755 --- a/trunk/scripts/package.sh +++ b/trunk/scripts/package.sh @@ -16,6 +16,7 @@ PI=NO MIPS=NO # EMBEDED=NO +JOBS=1 ################################################################################## ################################################################################## @@ -39,6 +40,7 @@ do --mips) MIPS=YES ;; --arm) ARM=YES ;; --pi) PI=YES ;; + --jobs) JOBS=$value ;; *) echo "$0: error: invalid option \"$option\", @see $0 --help" @@ -56,6 +58,7 @@ if [ $help = yes ]; then --mips for mips cross-build platform, configure/make/package. --pi for pi platform, configure/make/package. --x86-64 alias for --x86-x64. + --jobs Set the configure and make jobs. END exit 0 fi @@ -113,26 +116,26 @@ ok_msg "real os is ${os_name}-${os_major_version} ${os_release} ${os_machine}" # build srs # @see https://github.com/ossrs/srs/wiki/v1_CN_Build -ok_msg "start build srs" +ok_msg "start build srs, ARM: $ARM, MIPS: $MIPS, PI: $PI, X86_64: $X86_X64, JOBS: $JOBS" if [ $ARM = YES ]; then ( cd $work_dir && - ./configure --arm --prefix=$INSTALL && make + ./configure --arm --jobs=$JOBS --prefix=$INSTALL --build-tag=${os_name}${os_major_version} && make ) >> $log 2>&1 elif [ $MIPS = YES ]; then ( cd $work_dir && - ./configure --mips --prefix=$INSTALL && make + ./configure --mips --jobs=$JOBS --prefix=$INSTALL --build-tag=${os_name}${os_major_version} && make ) >> $log 2>&1 elif [ $PI = YES ]; then ( cd $work_dir && - ./configure --pi --prefix=$INSTALL && make + ./configure --pi --jobs=$JOBS --prefix=$INSTALL --build-tag=${os_name}${os_major_version} && make ) >> $log 2>&1 elif [ $X86_X64 = YES ]; then ( cd $work_dir && - ./configure --x86-x64 --prefix=$INSTALL && make + ./configure --x86-x64 --jobs=$JOBS --prefix=$INSTALL --build-tag=${os_name}${os_major_version} && make ) >> $log 2>&1 else failed_msg "invalid option, must be --x86-x64/--arm/--mips/--pi, see --help"; exit 1; diff --git a/trunk/src/app/srs_app_config.cpp b/trunk/src/app/srs_app_config.cpp index f21d464ed0..4b5a4b895f 100644 --- a/trunk/src/app/srs_app_config.cpp +++ b/trunk/src/app/srs_app_config.cpp @@ -275,6 +275,11 @@ bool srs_stream_caster_is_flv(string caster) return caster == "flv"; } +bool srs_stream_caster_is_gb28181(string caster) +{ + return caster == "gb28181"; +} + bool srs_config_apply_filter(SrsConfDirective* dvr_apply, SrsRequest* req) { static bool DEFAULT = true; @@ -2137,7 +2142,30 @@ srs_error_t SrsConfig::global_to_json(SrsJsonObject* obj) sobj->set(sdir->name, sdir->dumps_arg0_to_integer()); } else if (sdir->name == "rtp_port_max") { sobj->set(sdir->name, sdir->dumps_arg0_to_integer()); + } else if (sdir->name == "rtp_idle_timeout") { + sobj->set(sdir->name, sdir->dumps_arg0_to_integer()); + } else if (sdir->name == "ack_timeout") { + sobj->set(sdir->name, sdir->dumps_arg0_to_integer()); + } else if (sdir->name == "keepalive_timeout") { + sobj->set(sdir->name, sdir->dumps_arg0_to_integer()); + } else if (sdir->name == "audio_enable") { + sobj->set(sdir->name, sdir->dumps_arg0_to_boolean()); + } else if (sdir->name == "host") { + sobj->set(sdir->name, sdir->dumps_arg0_to_str()); + } else if (sdir->name == "serial") { + sobj->set(sdir->name, sdir->dumps_arg0_to_str()); + } else if (sdir->name == "realm") { + sobj->set(sdir->name, sdir->dumps_arg0_to_str()); + } else if (sdir->name == "wait_keyframe") { + sobj->set(sdir->name, sdir->dumps_arg0_to_str()); + } else if (sdir->name == "print_sip_message") { + sobj->set(sdir->name, sdir->dumps_arg0_to_str()); + } else if (sdir->name == "invite_port_fixed") { + sobj->set(sdir->name, sdir->dumps_arg0_to_str()); + } else if (sdir->name == "auto_play") { + sobj->set(sdir->name, sdir->dumps_arg0_to_str()); } + } obj->set(dir->name, sobj); } else { @@ -3650,9 +3678,25 @@ srs_error_t SrsConfig::check_normal_config() SrsConfDirective* conf = stream_caster->at(i); string n = conf->name; if (n != "enabled" && n != "caster" && n != "output" - && n != "listen" && n != "rtp_port_min" && n != "rtp_port_max") { + && n != "listen" && n != "rtp_port_min" && n != "rtp_port_max" + && n != "rtp_idle_timeout" && n != "sip" + && n != "audio_enable" && n != "wait_keyframe" + && n != "host") { return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "illegal stream_caster.%s", n.c_str()); } + + if (n == "sip") { + for (int j = 0; j < (int)conf->directives.size(); j++) { + string m = conf->at(j)->name; + if (m != "enabled" && m != "listen" + && m != "ack_timeout" && m != "keepalive_timeout" + && m != "host" && m != "serial" && m != "realm" + && m != "print_sip_message" && m != "auto_play" + && m != "invite_port_fixed") { + return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "illegal stream_caster.%s", m.c_str()); + } + } + } } } @@ -4266,6 +4310,264 @@ int SrsConfig::get_stream_caster_rtp_port_max(SrsConfDirective* conf) return ::atoi(conf->arg0().c_str()); } +srs_utime_t SrsConfig::get_stream_caster_gb28181_rtp_idle_timeout(SrsConfDirective* conf) +{ + static srs_utime_t DEFAULT = 30 * SRS_UTIME_SECONDS; + + if (!conf) { + return DEFAULT; + } + + conf = conf->get("rtp_idle_timeout"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return (srs_utime_t)(::atoi(conf->arg0().c_str()) * SRS_UTIME_SECONDS); +} + +int SrsConfig::get_stream_caster_gb28181_ack_timeout(SrsConfDirective* conf) +{ + static int DEFAULT = 30; + + if (!conf) { + return DEFAULT; + } + + conf = conf->get("sip"); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("ack_timeout"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return ::atoi(conf->arg0().c_str()); +} + +int SrsConfig::get_stream_caster_gb28181_keepalive_timeout(SrsConfDirective* conf) +{ + static int DEFAULT = 120; + + if (!conf) { + return DEFAULT; + } + + conf = conf->get("sip"); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("keepalive_timeout"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return ::atoi(conf->arg0().c_str()); +} + +string SrsConfig::get_stream_caster_gb28181_host(SrsConfDirective* conf) +{ + static string DEFAULT = ""; + + if (!conf) { + return DEFAULT; + } + + conf = conf->get("host"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return conf->arg0(); +} + +string SrsConfig::get_stream_caster_gb28181_serial(SrsConfDirective* conf) +{ + static string DEFAULT = "34020000002000000001"; + + if (!conf) { + return DEFAULT; + } + + conf = conf->get("sip"); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("serial"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return conf->arg0(); +} + +string SrsConfig::get_stream_caster_gb28181_realm(SrsConfDirective* conf) +{ + static string DEFAULT = "3402000000"; + + if (!conf) { + return DEFAULT; + } + + conf = conf->get("sip"); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("realm"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return conf->arg0(); +} + +bool SrsConfig::get_stream_caster_gb28181_audio_enable(SrsConfDirective* conf) +{ + static bool DEFAULT = false; + + if (!conf) { + return DEFAULT; + } + + conf = conf->get("audio_enable"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return SRS_CONF_PERFER_FALSE(conf->arg0()); +} + +bool SrsConfig::get_stream_caster_gb28181_print_sip_message(SrsConfDirective* conf) +{ + static bool DEFAULT = false; + + if (!conf) { + return DEFAULT; + } + + conf = conf->get("sip"); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("print_sip_message"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return SRS_CONF_PERFER_FALSE(conf->arg0()); +} + +bool SrsConfig::get_stream_caster_gb28181_wait_keyframe(SrsConfDirective* conf) +{ + static bool DEFAULT = false; + + if (!conf) { + return DEFAULT; + } + + conf = conf->get("wait_keyframe"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return SRS_CONF_PERFER_FALSE(conf->arg0()); +} + +bool SrsConfig::get_stream_caster_gb28181_sip_enable(SrsConfDirective* conf) +{ + static bool DEFAULT = true; + + if (!conf) { + return DEFAULT; + } + + conf = conf->get("sip"); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("enabled"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return SRS_CONF_PERFER_FALSE(conf->arg0()); + +} + +bool SrsConfig::get_stream_caster_gb28181_sip_auto_play(SrsConfDirective* conf) +{ + static bool DEFAULT = true; + + if (!conf) { + return DEFAULT; + } + + conf = conf->get("sip"); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("auto_play"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return SRS_CONF_PERFER_FALSE(conf->arg0()); + +} + +int SrsConfig::get_stream_caster_gb28181_sip_listen(SrsConfDirective* conf) +{ + static int DEFAULT = 5060; + + if (!conf) { + return DEFAULT; + } + + conf = conf->get("sip"); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("listen"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return ::atoi(conf->arg0().c_str()); + +} + +bool SrsConfig::get_stream_caster_gb28181_sip_invite_port_fixed(SrsConfDirective* conf) +{ + static bool DEFAULT = true; + + if (!conf) { + return DEFAULT; + } + + conf = conf->get("sip"); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("invite_port_fixed"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return SRS_CONF_PERFER_FALSE(conf->arg0()); + +} + + SrsConfDirective* SrsConfig::get_vhost(string vhost, bool try_default_vhost) { srs_assert(root); diff --git a/trunk/src/app/srs_app_config.hpp b/trunk/src/app/srs_app_config.hpp index 103e6f25c0..cdeb861aa0 100644 --- a/trunk/src/app/srs_app_config.hpp +++ b/trunk/src/app/srs_app_config.hpp @@ -119,6 +119,7 @@ extern bool srs_config_dvr_is_plan_session(std::string plan); extern bool srs_stream_caster_is_udp(std::string caster); extern bool srs_stream_caster_is_rtsp(std::string caster); extern bool srs_stream_caster_is_flv(std::string caster); +extern bool srs_stream_caster_is_gb28181(std::string caster); // Whether the dvr_apply active the stream specified by req. extern bool srs_config_apply_filter(SrsConfDirective* dvr_apply, SrsRequest* req); @@ -498,6 +499,21 @@ class SrsConfig virtual int get_stream_caster_rtp_port_min(SrsConfDirective* conf); // Get the max udp port for rtp of stream caster rtsp. virtual int get_stream_caster_rtp_port_max(SrsConfDirective* conf); + + virtual srs_utime_t get_stream_caster_gb28181_rtp_idle_timeout(SrsConfDirective* conf); + virtual int get_stream_caster_gb28181_ack_timeout(SrsConfDirective* conf); + virtual int get_stream_caster_gb28181_keepalive_timeout(SrsConfDirective* conf); + virtual bool get_stream_caster_gb28181_audio_enable(SrsConfDirective* conf); + virtual std::string get_stream_caster_gb28181_host(SrsConfDirective* conf); + virtual std::string get_stream_caster_gb28181_serial(SrsConfDirective* conf); + virtual std::string get_stream_caster_gb28181_realm(SrsConfDirective* conf); + virtual bool get_stream_caster_gb28181_print_sip_message(SrsConfDirective* conf); + virtual bool get_stream_caster_gb28181_wait_keyframe(SrsConfDirective* conf); + virtual bool get_stream_caster_gb28181_sip_enable(SrsConfDirective* conf); + virtual bool get_stream_caster_gb28181_sip_auto_play(SrsConfDirective* conf); + virtual int get_stream_caster_gb28181_sip_listen(SrsConfDirective* conf); + virtual bool get_stream_caster_gb28181_sip_invite_port_fixed(SrsConfDirective* conf); + // vhost specified section public: // Get the vhost directive by vhost name. diff --git a/trunk/src/app/srs_app_gb28181.cpp b/trunk/src/app/srs_app_gb28181.cpp new file mode 100644 index 0000000000..29373ae166 --- /dev/null +++ b/trunk/src/app/srs_app_gb28181.cpp @@ -0,0 +1,1647 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2013-2020 Lixin + * + * 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 + +using namespace std; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +//#define W_PS_FILE +//#define W_VIDEO_FILE +//#define W_AUDIO_FILE + +SrsPsRtpPacket::SrsPsRtpPacket() +{ +} + +SrsPsRtpPacket::~SrsPsRtpPacket() +{ +} + +srs_error_t SrsPsRtpPacket::decode(SrsBuffer* stream) +{ + srs_error_t err = srs_success; + + // 12bytes header + if (!stream->require(12)) { + return srs_error_new(ERROR_RTP_HEADER_CORRUPT, "requires 12 only %d bytes", stream->left()); + } + + int8_t vv = stream->read_1bytes(); + version = (vv >> 6) & 0x03; + padding = (vv >> 5) & 0x01; + extension = (vv >> 4) & 0x01; + csrc_count = vv & 0x0f; + + int8_t mv = stream->read_1bytes(); + marker = (mv >> 7) & 0x01; + payload_type = mv & 0x7f; + + sequence_number = stream->read_2bytes(); + timestamp = stream->read_4bytes(); + ssrc = stream->read_4bytes(); + + // TODO: FIXME: check sequence number. + + // video codec. + if (payload_type == 96) { + // ps stream playload atleast 1bytes content. + if (!stream->require(1)) { + return srs_error_new(ERROR_RTP_TYPE96_CORRUPT, "requires 1 only %d bytes", stream->left()); + } + // append left bytes to payload. + payload->append(stream->data() + stream->pos() , stream->size()-stream->pos()); + } + return err; +} + +//SrsPsRtpListener +SrsPsRtpListener::SrsPsRtpListener(SrsGb28181Config* c, int p, std::string s) +{ + rtp_processor = new SrsGb28181PsRtpProcessor(c, s); + _port = p; + // TODO: support listen at <[ip:]port> + listener = new SrsUdpListener(this, srs_any_address_for_listener(), p); +} + +SrsPsRtpListener::~SrsPsRtpListener() +{ + srs_freep(listener); + srs_freep(rtp_processor); +} + +int SrsPsRtpListener::port() +{ + return _port; +} + +srs_error_t SrsPsRtpListener::listen() +{ + return listener->listen(); +} + +srs_error_t SrsPsRtpListener::on_udp_packet(const sockaddr* from, const int fromlen, char* buf, int nb_buf){ + srs_error_t err = srs_success; + if (rtp_processor){ + err = rtp_processor->on_udp_packet(from, fromlen, buf, nb_buf); + } + return err; +} + +//SrsGb28181RtpMuxService +SrsGb28181RtpMuxService::SrsGb28181RtpMuxService(SrsConfDirective* c) +{ + config = new SrsGb28181Config(c); + rtp_processor = new SrsGb28181PsRtpProcessor(config,""); +} + +SrsGb28181RtpMuxService::~SrsGb28181RtpMuxService() +{ + srs_freep(config); + srs_freep(rtp_processor); +} + +srs_error_t SrsGb28181RtpMuxService::on_udp_packet(const sockaddr* from, const int fromlen, char* buf, int nb_buf){ + srs_error_t err = srs_success; + if (rtp_processor){ + err = rtp_processor->on_udp_packet(from, fromlen, buf, nb_buf); + } + return err; +} + +//SrsGb28181PsRtpProcessor +SrsGb28181PsRtpProcessor::SrsGb28181PsRtpProcessor(SrsGb28181Config* c, std::string id) +{ + config = c; + pprint = SrsPithyPrint::create_caster(); + channel_id = id; +} + +SrsGb28181PsRtpProcessor::~SrsGb28181PsRtpProcessor() +{ + dispose(); + srs_freep(pprint); +} + +void SrsGb28181PsRtpProcessor::dispose() +{ + map::iterator it2; + for (it2 = cache_ps_rtp_packet.begin(); it2 != cache_ps_rtp_packet.end(); ++it2) { + srs_freep(it2->second); + } + cache_ps_rtp_packet.clear(); + + return; +} + +srs_error_t SrsGb28181PsRtpProcessor::on_udp_packet(const sockaddr* from, const int fromlen, char* buf, int nb_buf) +{ + srs_error_t err = srs_success; + bool completed = false; + + pprint->elapse(); + + char address_string[64]; + char port_string[16]; + if (getnameinfo(from, fromlen, + (char*)&address_string, sizeof(address_string), + (char*)&port_string, sizeof(port_string), + NI_NUMERICHOST|NI_NUMERICSERV)){ + return srs_error_new(ERROR_SYSTEM_IP_INVALID, "bad address"); + } + + int peer_port = atoi(port_string); + + if (true) { + SrsBuffer stream(buf, nb_buf); + SrsPsRtpPacket pkt; + + if ((err = pkt.decode(&stream)) != srs_success) { + return srs_error_wrap(err, "ps rtp decode error"); + } + + //TODO: fixme: the same device uses the same SSRC to send with different local ports + + std::stringstream ss; + ss << pkt.ssrc << ":" << pkt.timestamp << ":" << port_string; + std::string pkt_key = ss.str(); + + std::stringstream ss2; + ss2 << pkt.ssrc << ":" << port_string; + std::string pre_pkt_key = ss2.str(); + + if (pre_packet.find(pre_pkt_key) == pre_packet.end()){ + pre_packet[pre_pkt_key] = new SrsPsRtpPacket(); + pre_packet[pre_pkt_key]->copy(&pkt); + } + //cache pkt by ssrc and timestamp + if (cache_ps_rtp_packet.find(pkt_key) == cache_ps_rtp_packet.end()) { + cache_ps_rtp_packet[pkt_key] = new SrsPsRtpPacket(); + } + + //get previous timestamp by ssrc + uint32_t pre_timestamp = pre_packet[pre_pkt_key]->timestamp; + uint32_t pre_sequence_number = pre_packet[pre_pkt_key]->sequence_number; + + //TODO: check sequence number out of order + //it may be out of order, or multiple streaming ssrc are the same + // if (pre_sequence_number > pkt.sequence_number){ + // srs_info("gb28281: ps sequence_number out of order, ssrc=%#x, pre=%u, cur=%u, addr=%s,port=%s", + // pkt.ssrc, pre_sequence_number, pkt.sequence_number, address_string, port_string); + // //return err; + // } + + //copy header to cache + cache_ps_rtp_packet[pkt_key]->copy(&pkt); + //accumulate one frame of data, to payload cache + cache_ps_rtp_packet[pkt_key]->payload->append(pkt.payload); + + //detect whether it is a completed frame + if (pkt.marker) {// rtp maker is true, is a completed frame + completed = true; + }else if (pre_timestamp != pkt.timestamp){ + //current timestamp is different from previous timestamp + //previous timestamp, is a completed frame + std::stringstream ss; + ss << pkt.ssrc << ":" << pre_timestamp << ":" << port_string; + pkt_key = ss.str(); + if (cache_ps_rtp_packet.find(pkt_key) != cache_ps_rtp_packet.end()) { + completed = true; + } + } + + if (pprint->can_print()) { + srs_trace("<- " SRS_CONSTS_LOG_STREAM_CASTER " gb28181: client_id %s, ps rtp packet %dB, age=%d, vt=%d/%u, sts=%u/%u/%#x, paylod=%dB", + channel_id.c_str(), nb_buf, pprint->age(), pkt.version, pkt.payload_type, pkt.sequence_number, pkt.timestamp, pkt.ssrc, + pkt.payload->length() + ); + } + + //current packet becomes previous packet + srs_freep(pre_packet[pre_pkt_key]); + pre_packet[pre_pkt_key] = new SrsPsRtpPacket(); + pre_packet[pre_pkt_key]->copy(&pkt);; + + if (!completed){ + return err; + } + + //process completed frame data + //clear processed one ps frame + //on completed frame data rtp packet in muxer enqueue + map::iterator key = cache_ps_rtp_packet.find(pkt_key); + if(key != cache_ps_rtp_packet.end()) + { + SrsGb28181RtmpMuxer* muxer = NULL; + + //First, search according to the channel_id. Otherwise, search according to the SSRC. + //Some channel_id are created by RTP pool, which are different ports. + //No channel_id are created by multiplexing ports, which are the same port + if (!channel_id.empty()){ + muxer = _srs_gb28181->fetch_rtmpmuxer(channel_id); + }else { + muxer = _srs_gb28181->fetch_rtmpmuxer_by_ssrc(pkt.ssrc); + + } + + if (muxer){ + //TODO: fixme: the same device uses the same SSRC to send with different local ports + //record the first peer port + muxer->set_channel_peer_port(peer_port); + muxer->set_channel_peer_ip(address_string); + //not the first peer port's non processing + if (muxer->channel_peer_port() != peer_port){ + srs_warn("<- " SRS_CONSTS_LOG_STREAM_CASTER " gb28181: client_id %s, ssrc=%#x, first peer_port=%d cur peer_port=%d", + muxer->get_channel_id().c_str(), pkt.ssrc, muxer->channel_peer_port(), peer_port); + srs_freep(key->second); + }else { + //put it in queue, wait for conn to process, and then free + muxer->ps_packet_enqueue(key->second); + } + }else{ + //no connection process it, discarded + srs_freep(key->second); + } + cache_ps_rtp_packet.erase(pkt_key); + } + } + return err; +} + +//ISrsPsStreamHander ps stream raw video/audio hander interface +ISrsPsStreamHander::ISrsPsStreamHander() +{ +} + +ISrsPsStreamHander::~ISrsPsStreamHander() +{ +} + +//SrsPsStreamDemixer ps stream parse to h264/aac +SrsPsStreamDemixer::SrsPsStreamDemixer(ISrsPsStreamHander *h, std::string id, bool a, bool k) +{ + hander = h; + audio_enable = a; + wait_first_keyframe = k; + channel_id = id; +} + +SrsPsStreamDemixer::~SrsPsStreamDemixer() +{ +} + +bool SrsPsStreamDemixer::can_send_ps_av_packet(){ + if (!wait_first_keyframe) + return true; + + if (first_keyframe_flag) + return true; + + return false; +} + +int64_t SrsPsStreamDemixer::parse_ps_timestamp(const uint8_t* p) +{ + unsigned long b; + //total 33 bits + unsigned long val, val2, val3; + + //1st byte, 5、6、7 bit + b = *p++; + val = (b & 0x0e); + + //2 byte, all bit + b = (*(p++)) << 8; + //3 bytes 1--7 bit + b += *(p++); + val2 = (b & 0xfffe) >> 1; + + //4 byte, all bit + b = (*(p++)) << 8; + //5 byte 1--7 bit + b += *(p++); + val3 = (b & 0xfffe) >> 1; + + //<32--val--30> <29----val2----15> <14----val3----0> + val = (val << 29) | (val2 << 15) | val3; + return val; +} + + +srs_error_t SrsPsStreamDemixer::on_ps_stream(char* ps_data, int ps_size, uint32_t timestamp, uint32_t ssrc) +{ + srs_error_t err = srs_success; + + int complete_len = 0; + int incomplete_len = ps_size; + char *next_ps_pack = ps_data; + + SrsSimpleStream video_stream; + SrsSimpleStream audio_stream; + uint64_t audio_pts = 0; + uint64_t video_pts = 0; + int pse_index = 0; + +#ifdef W_PS_FILE + if (!ps_fw.is_open()) { + std::string filename = "test_ps_" + channel_id + ".mpg"; + ps_fw.open(filename.c_str()); + } + ps_fw.write(ps_data, ps_size, NULL); +#endif + + while(incomplete_len >= sizeof(SrsPsPacketStartCode)) + { + if (next_ps_pack + && next_ps_pack[0] == (char)0x00 + && next_ps_pack[1] == (char)0x00 + && next_ps_pack[2] == (char)0x01 + && next_ps_pack[3] == (char)0xBA) + { + //ps header + SrsPsPacketHeader *head = (SrsPsPacketHeader *)next_ps_pack; + unsigned char pack_stuffing_length = head->stuffing_length & 0x07; + + next_ps_pack = next_ps_pack + sizeof(SrsPsPacketHeader) + pack_stuffing_length; + complete_len = complete_len + sizeof(SrsPsPacketHeader) + pack_stuffing_length; + incomplete_len = ps_size - complete_len; + } + else if(next_ps_pack + && next_ps_pack[0] == (char)0x00 + && next_ps_pack[1] == (char)0x00 + && next_ps_pack[2] == (char)0x01 + && next_ps_pack[3] == (char)0xBB) + { + //ps system header + SrsPsPacketBBHeader *bbhead=(SrsPsPacketBBHeader *)(next_ps_pack); + int bbheaderlen = htons(bbhead->length); + next_ps_pack = next_ps_pack + sizeof(SrsPsPacketBBHeader) + bbheaderlen; + complete_len = complete_len + sizeof(SrsPsPacketBBHeader) + bbheaderlen; + incomplete_len = ps_size - complete_len; + + first_keyframe_flag = true; + } + else if(next_ps_pack + && next_ps_pack[0] == (char)0x00 + && next_ps_pack[1] == (char)0x00 + && next_ps_pack[2] == (char)0x01 + && next_ps_pack[3] == (char)0xBC) + { + //program stream map + + SrsPsMapPacket* psmap_pack = (SrsPsMapPacket*)next_ps_pack; + + psmap_pack->length = htons(psmap_pack->length); + + next_ps_pack = next_ps_pack + psmap_pack->length + sizeof(SrsPsMapPacket); + complete_len = complete_len + psmap_pack->length + sizeof(SrsPsMapPacket); + incomplete_len = ps_size - complete_len; + + } + else if(next_ps_pack + && next_ps_pack[0] == (char)0x00 + && next_ps_pack[1] == (char)0x00 + && next_ps_pack[2] == (char)0x01 + && next_ps_pack[3] == (char)0xE0) + { + //pse video stream + SrsPsePacket* pse_pack = (SrsPsePacket*)next_ps_pack; + + unsigned char pts_dts_flags = (pse_pack->info[0] & 0xF0) >> 6; + //in a frame of data, pts is obtained from the first PSE packet + if (pse_index == 0 && pts_dts_flags > 0) { + video_pts = parse_ps_timestamp((unsigned char*)next_ps_pack + 9); + srs_info("gb28181: ps stream video ts=%u pkt_ts=%u", pts, timestamp); + } + pse_index +=1; + + int packlength = htons(pse_pack->length); + int payloadlen = packlength - 2 - 1 - pse_pack->stuffing_length; + + next_ps_pack = next_ps_pack + 9 + pse_pack->stuffing_length; + complete_len = complete_len + 9 + pse_pack->stuffing_length; + + video_stream.append(next_ps_pack, payloadlen); + +#ifdef W_VIDEO_FILE + if (!video_fw.is_open()) { + std::string filename = "test_video_" + channel_id + ".h264"; + video_fw.open(filename.c_str()); + } + video_fw.write(next_ps_pack, payloadlen, NULL); +#endif + + next_ps_pack = next_ps_pack + payloadlen; + complete_len = complete_len + payloadlen; + incomplete_len = ps_size - complete_len; + } + else if (next_ps_pack + && next_ps_pack[0] == (char)0x00 + && next_ps_pack[1] == (char)0x00 + && next_ps_pack[2] == (char)0x01 + && next_ps_pack[3] == (char)0xBD) + { + //private stream + + SrsPsePacket* pse_pack = (SrsPsePacket*)next_ps_pack; + + int packlength = htons(pse_pack->length); + int payload_len = packlength - 2 - 1 - pse_pack->stuffing_length; + + next_ps_pack = next_ps_pack + payload_len + 9 + pse_pack->stuffing_length; + complete_len = complete_len + (payload_len + 9 + pse_pack->stuffing_length); + incomplete_len = ps_size - complete_len; + } + else if (next_ps_pack + && next_ps_pack[0] == (char)0x00 + && next_ps_pack[1] == (char)0x00 + && next_ps_pack[2] == (char)0x01 + && next_ps_pack[3] == (char)0xC0) + { + //audio stream + + SrsPsePacket* pse_pack = (SrsPsePacket*)next_ps_pack; + + unsigned char pts_dts_flags = (pse_pack->info[0] & 0xF0) >> 6; + if (pts_dts_flags > 0 ) { + audio_pts = parse_ps_timestamp((unsigned char*)next_ps_pack + 9); + srs_info("gb28181: ps stream video ts=%u pkt_ts=%u", audio_pts, timestamp); + } + + int packlength = htons(pse_pack->length); + int payload_len = packlength - 2 - 1 - pse_pack->stuffing_length; + next_ps_pack = next_ps_pack + 9 + pse_pack->stuffing_length; + + audio_stream.append(next_ps_pack, payload_len); + +#ifdef W_AUDIO_FILE + if (!audio_fw.is_open()) { + std::string filename = "test_audio_" + channel_id + ".aac"; + audio_fw.open(filename.c_str()); + } + audio_fw.write(next_ps_pack, payload_len, NULL); +#endif + + next_ps_pack = next_ps_pack + payload_len; + complete_len = complete_len + (payload_len + 9 + pse_pack->stuffing_length); + incomplete_len = ps_size - complete_len; + + if (hander && audio_enable && audio_stream.length() && can_send_ps_av_packet()) { + if ((err = hander->on_rtp_audio(&audio_stream, audio_pts)) != srs_success) { + return srs_error_wrap(err, "process ps audio packet"); + } + } + } + else + { + srs_trace("gb28181: client_id %s, unkonw ps data %02x %02x %02x %02x\n", + channel_id.c_str(), next_ps_pack[0], next_ps_pack[1], next_ps_pack[2], next_ps_pack[3]); + break; + } + } + + if (complete_len != ps_size){ + srs_trace("gb28181: client_id %s decode ps packet error! ps_size=%d complete=%d \n", + channel_id.c_str(), ps_size, complete_len); + }else if (hander && video_stream.length() && can_send_ps_av_packet()) { + if ((err = hander->on_rtp_video(&video_stream, video_pts)) != srs_success) { + return srs_error_wrap(err, "process ps video packet"); + } + } + + return err; +} + + +//Gb28181 Config +SrsGb28181Config::SrsGb28181Config(SrsConfDirective* c) +{ + // TODO: FIXME: support reload. + host = _srs_config->get_stream_caster_gb28181_host(c); + output = _srs_config->get_stream_caster_output(c); + rtp_mux_port = _srs_config->get_stream_caster_listen(c); + rtp_port_min = _srs_config->get_stream_caster_rtp_port_min(c); + rtp_port_max = _srs_config->get_stream_caster_rtp_port_max(c); + rtp_idle_timeout = _srs_config->get_stream_caster_gb28181_rtp_idle_timeout(c); + + wait_keyframe = _srs_config->get_stream_caster_gb28181_wait_keyframe(c); + audio_enable = _srs_config->get_stream_caster_gb28181_audio_enable(c); + + //sip config + sip_enable = _srs_config->get_stream_caster_gb28181_sip_enable(c); + sip_port = _srs_config->get_stream_caster_gb28181_sip_listen(c); + sip_realm = _srs_config->get_stream_caster_gb28181_realm(c); + sip_serial = _srs_config->get_stream_caster_gb28181_serial(c); + sip_auto_play = _srs_config->get_stream_caster_gb28181_sip_auto_play(c); + sip_ack_timeout = _srs_config->get_stream_caster_gb28181_ack_timeout(c); + sip_keepalive_timeout = _srs_config->get_stream_caster_gb28181_keepalive_timeout(c); + print_sip_message = _srs_config->get_stream_caster_gb28181_print_sip_message(c); + sip_invite_port_fixed = _srs_config->get_stream_caster_gb28181_sip_invite_port_fixed(c); +} + +SrsGb28181Config::~SrsGb28181Config() +{ + +} + + +//SrsGb28181RtmpMuxer gb28181 rtmp muxer, process ps stream to rtmp +SrsGb28181RtmpMuxer::SrsGb28181RtmpMuxer(SrsGb28181Manger* c, std::string id, bool a, bool k) +{ + channel_id = id; + gb28181_manger = c; + channel = new SrsGb28181StreamChannel(); + + pprint = SrsPithyPrint::create_caster(); + trd = new SrsSTCoroutine("gb28181rtmpmuxer", this); + + sdk = NULL; + vjitter = new SrsRtspJitter(); + ajitter = new SrsRtspJitter(); + + avc = new SrsRawH264Stream(); + aac = new SrsRawAacStream(); + + ps_demixer = new SrsPsStreamDemixer(this, id, a, k); + wait_ps_queue = srs_cond_new(); + + h264_sps_changed = false; + h264_pps_changed = false; + h264_sps_pps_sent = false; + + stream_idle_timeout = -1; + recv_stream_time = 0; + + _rtmp_url = ""; +} + +SrsGb28181RtmpMuxer::~SrsGb28181RtmpMuxer() +{ + close(); + destroy(); + srs_cond_destroy(wait_ps_queue); + + srs_freep(channel); + srs_freep(ps_demixer); + srs_freep(trd); + srs_freep(sdk); + srs_freep(vjitter); + srs_freep(ajitter); + srs_freep(pprint); +} + +srs_error_t SrsGb28181RtmpMuxer::serve() +{ + srs_error_t err = srs_success; + + if ((err = trd->start()) != srs_success) { + return srs_error_wrap(err, "gb28181rtmpmuxer"); + } + + return err; +} + +std::string SrsGb28181RtmpMuxer::remote_ip() +{ + return ""; +} + +std::string SrsGb28181RtmpMuxer::get_channel_id() +{ + return channel_id; +} + +void SrsGb28181RtmpMuxer::copy_channel(SrsGb28181StreamChannel *s) +{ + channel->copy(s); +} + +SrsGb28181StreamChannel SrsGb28181RtmpMuxer::get_channel() +{ + return *channel; +} + +void SrsGb28181RtmpMuxer::set_channel_peer_ip(std::string ip) +{ + if (channel->get_rtp_peer_ip().empty()){ + channel->set_rtp_peer_ip(ip); + } +} + +void SrsGb28181RtmpMuxer::set_channel_peer_port(int port) +{ + if (channel->get_rtp_peer_port() == 0){ + channel->set_rtp_peer_port(port); + } +} + +int SrsGb28181RtmpMuxer::channel_peer_port() +{ + return channel->get_rtp_peer_port(); +} + +std::string SrsGb28181RtmpMuxer::channel_peer_ip() +{ + return channel->get_rtp_peer_ip(); +} + +void SrsGb28181RtmpMuxer::set_rtmp_url(std::string url) +{ + _rtmp_url = url; +} +std::string SrsGb28181RtmpMuxer::rtmp_url() +{ + return _rtmp_url; +} + + +void SrsGb28181RtmpMuxer::destroy() +{ + while(!ps_queue.empty()){ + SrsPsRtpPacket* pkt = ps_queue.front(); + ps_queue.pop(); + //must be free pkt + srs_freep(pkt); + } +} + +srs_error_t SrsGb28181RtmpMuxer::do_cycle() +{ + srs_error_t err = srs_success; + recv_stream_time = srs_get_system_time(); + + //consume ps stream, and check status + while (true) { + + pprint->elapse(); + + if ((err = trd->pull()) != srs_success) { + return srs_error_wrap(err, "gb28181 rtmp muxer cycle"); + } + + //demix ps to h264/aac, to rtmp + while(!ps_queue.empty()){ + SrsPsRtpPacket* pkt = ps_queue.front(); + if (pkt){ + if ((err = ps_demixer->on_ps_stream(pkt->payload->bytes(), + pkt->payload->length(), pkt->timestamp, pkt->ssrc)) != srs_success){ + srs_warn("gb28181: demix ps stream error:%s", srs_error_desc(err).c_str()); + srs_freep(err); + }; + } + ps_queue.pop(); + //must be free pkt + srs_freep(pkt); + } + + if (pprint->can_print()) { + srs_trace("gb28181: client id=%s, rtmp muxer is alive", channel_id.c_str()); + } + + srs_utime_t now = srs_get_system_time(); + srs_utime_t duration = now - recv_stream_time; + + SrsGb28181Config config = gb28181_manger->get_gb28181_config(); + if (duration > config.rtp_idle_timeout){ + srs_trace("gb28181: client id=%s, stream idle timeout, stop!!!", channel_id.c_str()); + break; + } + + srs_cond_timedwait(wait_ps_queue, 5 * SRS_UTIME_MILLISECONDS); + //srs_usleep(1000 * 5); + } + + return err; +} + + +void SrsGb28181RtmpMuxer::stop() +{ + if (trd){ + trd->interrupt(); + } + //stop rtmp publish + close(); +} + +void SrsGb28181RtmpMuxer::ps_packet_enqueue(SrsPsRtpPacket *pkt) +{ + srs_assert(pkt); + + //prevent consumers from being unable to process data + //and accumulating in the queue + uint32_t size = ps_queue.size(); + if (size > 100){ + srs_warn("gb28181: rtmpmuxer too much queue data, need to clear!!!"); + while(ps_queue.empty()) { + SrsPsRtpPacket* pkt = ps_queue.front(); + ps_queue.pop(); + srs_freep(pkt); + } + } + + ps_queue.push(pkt); + srs_cond_signal(wait_ps_queue); +} + +srs_error_t SrsGb28181RtmpMuxer::cycle() +{ + // serve the rtmp muxer. + srs_error_t err = do_cycle(); + + gb28181_manger->stop_rtp_listen(channel_id); + + gb28181_manger->remove(this); + srs_trace("gb28181: client id=%s rtmp muxer is remove", channel_id.c_str()); + + if (err == srs_success) { + srs_trace("client finished."); + } else if (srs_is_client_gracefully_close(err)) { + srs_warn("client disconnect peer. code=%d", srs_error_code(err)); + srs_freep(err); + } + + return err; +} + +srs_error_t SrsGb28181RtmpMuxer::on_rtp_video(SrsSimpleStream *stream, int64_t fpts) +{ + srs_error_t err = srs_success; + + // ensure rtmp connected. + if ((err = connect()) != srs_success) { + //after the connection fails, need to clear flag + //and send the av header again next time + h264_sps = ""; + h264_pps = ""; + aac_specific_config = ""; + return srs_error_wrap(err, "connect"); + } + + if ((err = vjitter->correct(fpts)) != srs_success) { + return srs_error_wrap(err, "jitter"); + } + + // ts tbn to flv tbn. + uint32_t dts = (uint32_t)(fpts / 90); + uint32_t pts = (uint32_t)(fpts / 90); + srs_info("gb28181rtmpmuxer: on_rtp_video dts=%u", dts); + + recv_stream_time = srs_get_system_time(); + + SrsBuffer *avs = new SrsBuffer(stream->bytes(), stream->length()); + SrsAutoFree(SrsBuffer, avs); + // send each frame. + while (!avs->empty()) { + char* frame = NULL; + int frame_size = 0; + if ((err = avc->annexb_demux(avs, &frame, &frame_size)) != srs_success) { + return srs_error_wrap(err, "demux annexb"); + } + + // 5bits, 7.3.1 NAL unit syntax, + // ISO_IEC_14496-10-AVC-2003.pdf, page 44. + // 7: SPS, 8: PPS, 5: I Frame, 1: P Frame + SrsAvcNaluType nal_unit_type = (SrsAvcNaluType)(frame[0] & 0x1f); + + // ignore the nalu type sei(6) aud(9) + if (nal_unit_type == SrsAvcNaluTypeAccessUnitDelimiter || + nal_unit_type == SrsAvcNaluTypeSEI) { + continue; + } + + // for sps + if (avc->is_sps(frame, frame_size)) { + std::string sps; + if ((err = avc->sps_demux(frame, frame_size, sps)) != srs_success) { + return srs_error_wrap(err, "demux sps"); + } + + if (h264_sps == sps) { + continue; + } + h264_sps_changed = true; + h264_sps = sps; + + if ((err = write_h264_sps_pps(dts, pts)) != srs_success) { + return srs_error_wrap(err, "write sps/pps"); + } + continue; + } + + // for pps + if (avc->is_pps(frame, frame_size)) { + std::string pps; + if ((err = avc->pps_demux(frame, frame_size, pps)) != srs_success) { + return srs_error_wrap(err, "demux pps"); + } + + if (h264_pps == pps) { + continue; + } + h264_pps_changed = true; + h264_pps = pps; + + if ((err = write_h264_sps_pps(dts, pts)) != srs_success) { + return srs_error_wrap(err, "write sps/pps"); + } + continue; + } + + // ibp frame. + srs_info("gb28181: demux avc ibp frame size=%d, dts=%d", frame_size, dts); + if ((err = write_h264_ipb_frame(frame, frame_size, dts, pts)) != srs_success) { + return srs_error_wrap(err, "write frame"); + } + } + + return err; +} + +srs_error_t SrsGb28181RtmpMuxer::on_rtp_audio(SrsSimpleStream* stream, int64_t fdts) +{ + srs_error_t err = srs_success; + + // ensure rtmp connected. + if ((err = connect()) != srs_success) { + //after the connection fails, need to clear flag + //and send the av header again next time + h264_sps = ""; + h264_pps = ""; + aac_specific_config = ""; + return srs_error_wrap(err, "connect"); + } + + if ((err = ajitter->correct(fdts)) != srs_success) { + return srs_error_wrap(err, "jitter"); + } + + recv_stream_time = srs_get_system_time(); + + uint32_t dts = (uint32_t)(fdts / 90); + + // send each frame. + SrsBuffer *avs = new SrsBuffer(stream->bytes(), stream->length()); + SrsAutoFree(SrsBuffer, avs); + if (!avs->empty()) { + char* frame = NULL; + int frame_size = 0; + SrsRawAacStreamCodec codec; + if ((err = aac->adts_demux(avs, &frame, &frame_size, codec)) != srs_success) { + return srs_error_wrap(err, "demux adts"); + } + + if (frame_size <= 0) { + return err; + } + + bool send_adts = false; + static int srs_aac_srates[] = { + 96000, 88200, 64000, 48000, + 44100, 32000, 24000, 22050, + 16000, 12000, 11025, 8000, + 7350, 0, 0, 0 + }; + switch (srs_aac_srates[codec.sampling_frequency_index]) { + case 11025: + codec.sound_rate = SrsAudioSampleRate11025; + break; + case 22050: + codec.sound_rate = SrsAudioSampleRate22050; + break; + case 44100: + codec.sound_rate = SrsAudioSampleRate44100; + break; + default: + send_adts = true; //raw with adts + break; + }; + + std::string sh; + if ((err = aac->mux_sequence_header(&codec, sh)) != srs_success) { + return srs_error_wrap(err, "mux sequence header"); + } + + if (aac_specific_config != sh){ + std::string sh; + if ((err = aac->mux_sequence_header(&codec, sh)) != srs_success) { + return srs_error_wrap(err, "mux sequence header"); + } + aac_specific_config = sh; + codec.aac_packet_type = 0; + if ((err = write_audio_raw_frame((char*)sh.data(), (int)sh.length(), &codec, dts)) != srs_success) { + return srs_error_wrap(err, "write raw audio frame"); + } + } + + codec.aac_packet_type = 1; + if (send_adts) { // audio raw data. with adts header + if ((err = write_audio_raw_frame(stream->bytes(), stream->length(), &codec, dts)) != srs_success) { + return srs_error_wrap(err, "write audio raw frame"); + } + }else { // audio raw data. without adts header + if ((err = write_audio_raw_frame(frame, frame_size, &codec, dts)) != srs_success) { + return srs_error_wrap(err, "write audio raw frame"); + } + } + }//end if (!avs->empty()) + + return err; +} + +srs_error_t SrsGb28181RtmpMuxer::write_h264_sps_pps(uint32_t dts, uint32_t pts) +{ + srs_error_t err = srs_success; + + if (!h264_sps_changed || !h264_pps_changed) { + return err; + } + + // h264 raw to h264 packet. + std::string sh; + if ((err = avc->mux_sequence_header(h264_sps, h264_pps, dts, pts, sh)) != srs_success) { + return srs_error_wrap(err, "mux sequence header"); + } + + // h264 packet to flv packet. + int8_t frame_type = SrsVideoAvcFrameTypeKeyFrame; + int8_t avc_packet_type = SrsVideoAvcFrameTraitSequenceHeader; + char* flv = NULL; + int nb_flv = 0; + if ((err = avc->mux_avc2flv(sh, frame_type, avc_packet_type, dts, pts, &flv, &nb_flv)) != srs_success) { + return srs_error_wrap(err, "mux avc to flv"); + } + + // the timestamp in rtmp message header is dts. + uint32_t timestamp = dts; + if ((err = rtmp_write_packet(SrsFrameTypeVideo, timestamp, flv, nb_flv)) != srs_success) { + return srs_error_wrap(err, "write packet"); + } + + // reset sps and pps. + h264_sps_changed = false; + h264_pps_changed = false; + h264_sps_pps_sent = true; + + return err; +} + +srs_error_t SrsGb28181RtmpMuxer::write_h264_ipb_frame(char* frame, int frame_size, uint32_t dts, uint32_t pts) +{ + srs_error_t err = srs_success; + + // 5bits, 7.3.1 NAL unit syntax, + // ISO_IEC_14496-10-AVC-2003.pdf, page 44. + // 7: SPS, 8: PPS, 5: I Frame, 1: P Frame + SrsAvcNaluType nal_unit_type = (SrsAvcNaluType)(frame[0] & 0x1f); + + // for IDR frame, the frame is keyframe. + SrsVideoAvcFrameType frame_type = SrsVideoAvcFrameTypeInterFrame; + if (nal_unit_type == SrsAvcNaluTypeIDR) { + frame_type = SrsVideoAvcFrameTypeKeyFrame; + } + + std::string ibp; + if ((err = avc->mux_ipb_frame(frame, frame_size, ibp)) != srs_success) { + return srs_error_wrap(err, "mux ibp frame"); + } + + int8_t avc_packet_type = SrsVideoAvcFrameTraitNALU; + char* flv = NULL; + int nb_flv = 0; + if ((err = avc->mux_avc2flv(ibp, frame_type, avc_packet_type, dts, pts, &flv, &nb_flv)) != srs_success) { + return srs_error_wrap(err, "mux avc to flv"); + } + + // the timestamp in rtmp message header is dts. + uint32_t timestamp = dts; + return rtmp_write_packet(SrsFrameTypeVideo, timestamp, flv, nb_flv); +} + +srs_error_t SrsGb28181RtmpMuxer::write_audio_raw_frame(char* frame, int frame_size, SrsRawAacStreamCodec* codec, uint32_t dts) +{ + srs_error_t err = srs_success; + + char* data = NULL; + int size = 0; + if ((err = aac->mux_aac2flv(frame, frame_size, codec, dts, &data, &size)) != srs_success) { + return srs_error_wrap(err, "mux aac to flv"); + } + + return rtmp_write_packet(SrsFrameTypeAudio, dts, data, size); +} + +srs_error_t SrsGb28181RtmpMuxer::rtmp_write_packet(char type, uint32_t timestamp, char* data, int size) +{ + srs_error_t err = srs_success; + + if ((err = connect()) != srs_success) { + return srs_error_wrap(err, "connect"); + } + + SrsSharedPtrMessage* msg = NULL; + + if ((err = srs_rtmp_create_msg(type, timestamp, data, size, sdk->sid(), &msg)) != srs_success) { + return srs_error_wrap(err, "create message"); + } + srs_assert(msg); + + // send out encoded msg. + if ((err = sdk->send_and_free_message(msg)) != srs_success) { + close(); + return srs_error_wrap(err, "write message"); + } + + return err; +} + +srs_error_t SrsGb28181RtmpMuxer::connect() +{ + srs_error_t err = srs_success; + + // Ignore when connected. + if (sdk) { + return err; + } + + // generate rtmp url to connect to. + std::string url = _rtmp_url; + + // connect host. + srs_utime_t cto = SRS_CONSTS_RTMP_TIMEOUT; + srs_utime_t sto = SRS_CONSTS_RTMP_PULSE; + sdk = new SrsSimpleRtmpClient(url, cto, sto); + + srs_trace("gb28181: rtmp connect url=%s", url.c_str()); + if ((err = sdk->connect()) != srs_success) { + close(); + return srs_error_wrap(err, "connect %s failed, cto=%dms, sto=%dms.", url.c_str(), srsu2msi(cto), srsu2msi(sto)); + } + + // publish. + if ((err = sdk->publish(SRS_CONSTS_RTMP_PROTOCOL_CHUNK_SIZE)) != srs_success) { + close(); + return srs_error_wrap(err, "publish %s failed", url.c_str()); + } + + return err; +} + +void SrsGb28181RtmpMuxer::close() +{ + srs_freep(sdk); +} + +void SrsGb28181RtmpMuxer::rtmp_close(){ + close(); +} + +SrsGb28181StreamChannel::SrsGb28181StreamChannel(){ + channel_id = ""; + port_mode = ""; + app = ""; + stream = ""; + ip = ""; + rtp_port = 0; + rtmp_port = 0; + ssrc = 0; + rtp_peer_port = 0; + rtp_peer_ip = ""; +} + +SrsGb28181StreamChannel::~SrsGb28181StreamChannel() +{ + +} + +void SrsGb28181StreamChannel::copy(const SrsGb28181StreamChannel *s){ + channel_id = s->get_channel_id(); + port_mode = s->get_port_mode(); + app = s->get_app(); + stream = s->get_stream(); + + ip = s->get_ip(); + rtp_port = s->get_rtp_port(); + rtmp_port = s->get_rtmp_port(); + ssrc = s->get_ssrc(); + + rtp_peer_ip = s->get_rtp_peer_ip(); + rtp_peer_port = s->get_rtp_peer_port(); +} + +void SrsGb28181StreamChannel::dumps(SrsJsonObject* obj) +{ + obj->set("id", SrsJsonAny::str(channel_id.c_str())); + obj->set("ip", SrsJsonAny::str(ip.c_str())); + obj->set("rtmp_port", SrsJsonAny::integer(rtmp_port)); + obj->set("app", SrsJsonAny::str(app.c_str())); + obj->set("stream", SrsJsonAny::str(stream.c_str())); + + obj->set("ssrc", SrsJsonAny::integer(ssrc)); + obj->set("rtp_port", SrsJsonAny::integer(rtp_port)); + obj->set("port_mode", SrsJsonAny::str(port_mode.c_str())); + obj->set("rtp_peer_port", SrsJsonAny::integer(rtp_peer_port)); + obj->set("rtp_peer_ip", SrsJsonAny::str(rtp_peer_ip.c_str())); +} + + +//Global Singleton instance, init in SrsServer +SrsGb28181Manger* _srs_gb28181 = NULL; + +//SrsGb28181Manger +SrsGb28181Manger::SrsGb28181Manger(SrsConfDirective* c) +{ + // TODO: FIXME: support reload. + config = new SrsGb28181Config(c); + manager = new SrsCoroutineManager(); +} + +SrsGb28181Manger::~SrsGb28181Manger() +{ + used_ports.clear(); + + srs_freep(manager); + srs_freep(config); + + destroy(); +} + +srs_error_t SrsGb28181Manger::initialize() +{ + srs_error_t err = srs_success; + if ((err = manager->start()) != srs_success) { + return srs_error_wrap(err, "start manager"); + } + + return err; +} + +SrsGb28181Config SrsGb28181Manger::get_gb28181_config() +{ + return *config; +} + +void SrsGb28181Manger::alloc_port(int* pport) +{ + // use a pair of port. + for (int i = config->rtp_port_min; i < config->rtp_port_max - 1; i += 2) { + if (!used_ports[i]) { + used_ports[i] = true; + used_ports[i + 1] = true; + *pport = i; + break; + } + } + srs_info("gb28181: alloc port=%d-%d", *pport, *pport + 1); +} + +void SrsGb28181Manger::free_port(int lpmin, int lpmax) +{ + for (int i = lpmin; i < lpmax; i++) { + used_ports[i] = false; + } + srs_trace("gb28181: free rtp port=%d-%d", lpmin, lpmax); +} + +uint32_t SrsGb28181Manger::hash_code(std::string str) +{ + uint32_t h = 0; + int len = str.length(); + + if (h == 0) { + int off = 0; + const char *val = str.c_str(); + + for (int i = 0; i < len; i++) { + h = 31 * h + val[off++]; + } + } + return h; +} + +uint32_t SrsGb28181Manger::generate_ssrc(std::string id) +{ + srand(uint(time(0))); + // TODO: SSRC rules can be customized, + //uint8_t index = uint8_t(rand() % (0x0F - 0x01 + 1) + 0x01); + //uint32_t ssrc = 0x00FFFFF0 & (hash_code(id) << 4) | index; + uint32_t ssrc = 0x00FFFFFF & (hash_code(id)); + srs_trace("gb28181: generate ssrc id=%s, ssrc=%u", id.c_str(), ssrc); + return ssrc; +} + +srs_error_t SrsGb28181Manger::fetch_or_create_rtmpmuxer(std::string id, SrsGb28181RtmpMuxer** gb28181) +{ + srs_error_t err = srs_success; + + SrsGb28181RtmpMuxer* muxer = NULL; + if ((muxer = fetch_rtmpmuxer(id)) != NULL) { + *gb28181 = muxer; + return err; + } + + muxer = new SrsGb28181RtmpMuxer(this, id, config->audio_enable, config->wait_keyframe); + if ((err = muxer->serve()) != srs_success) { + return srs_error_wrap(err, "gb28281: rtmp muxer serve %s", id.c_str()); + } + rtmpmuxers[id] = muxer; + *gb28181 = muxer; + + return err; +} + +SrsGb28181RtmpMuxer* SrsGb28181Manger::fetch_rtmpmuxer(std::string id) +{ + SrsGb28181RtmpMuxer* muxer = NULL; + + if (rtmpmuxers.find(id) == rtmpmuxers.end()) { + return NULL; + } + + muxer = rtmpmuxers[id]; + return muxer; +} + +SrsGb28181RtmpMuxer* SrsGb28181Manger::fetch_rtmpmuxer_by_ssrc(uint32_t ssrc) +{ + SrsGb28181RtmpMuxer* muxer = NULL; + if (rtmpmuxers_ssrc.find(ssrc) == rtmpmuxers_ssrc.end()) { + return NULL; + } + + muxer = rtmpmuxers_ssrc[ssrc]; + return muxer; +} + +void SrsGb28181Manger::rtmpmuxer_map_by_ssrc(SrsGb28181RtmpMuxer*muxer, uint32_t ssrc) +{ + if (rtmpmuxers_ssrc.find(ssrc) == rtmpmuxers_ssrc.end()) { + rtmpmuxers_ssrc[ssrc] = muxer; + } +} + +void SrsGb28181Manger::rtmpmuxer_unmap_by_ssrc(uint32_t ssrc) +{ + std::map::iterator it = rtmpmuxers_ssrc.find(ssrc); + if (it != rtmpmuxers_ssrc.end()) { + rtmpmuxers_ssrc.erase(it); + } +} + +void SrsGb28181Manger::destroy() +{ + //destory ps rtp listen + std::map::iterator it; + for (it = rtp_pool.begin(); it != rtp_pool.end(); ++it) { + SrsPsRtpListener* listener = it->second; + srs_freep(listener); + } + rtp_pool.clear(); + + //destory gb28181 muxer + std::map::iterator it2; + for (it2 = rtmpmuxers.begin(); it2 != rtmpmuxers.end(); ++it2) { + SrsGb28181RtmpMuxer* muxer = it2->second; + SrsGb28181StreamChannel sess = muxer->get_channel(); + rtmpmuxer_unmap_by_ssrc(sess.get_ssrc()); + manager->remove(muxer); + } + rtmpmuxers.clear(); +} + +void SrsGb28181Manger::remove(SrsGb28181RtmpMuxer* muxer) +{ + std::string id = muxer->get_channel_id(); + + map::iterator it = rtmpmuxers.find(id); + if (it != rtmpmuxers.end()) { + SrsGb28181RtmpMuxer* muxer = it->second; + SrsGb28181StreamChannel sess = muxer->get_channel(); + rtmpmuxer_unmap_by_ssrc(sess.get_ssrc()); + rtmpmuxers.erase(it); + } + manager->remove(muxer); +} + + +srs_error_t SrsGb28181Manger::start_ps_rtp_listen(std::string id, int port) +{ + srs_error_t err = srs_success; + if (port == config->rtp_mux_port) { + return srs_error_wrap(err, "start rtp listen port is mux port"); + } + + map::iterator key = rtmpmuxers.find(id); + if (key == rtmpmuxers.end()){ + return srs_error_wrap(err, "start rtp listen port rtmp muxer is null"); + } + + if (rtp_pool.find(port) == rtp_pool.end()) + { + SrsPsRtpListener* rtp = new SrsPsRtpListener(this->config, port, id); + rtp_pool[port] = rtp; + if ((err = rtp_pool[port]->listen()) != srs_success) { + stop_rtp_listen(id); + return srs_error_wrap(err, "rtp listen"); + } + + srs_trace("gb28181: start rtp ps stream over server-port=%d", port); + } + + return err; +} + +void SrsGb28181Manger::stop_rtp_listen(std::string id) +{ + srs_error_t err = srs_success; + + map::iterator it = rtmpmuxers.find(id); + if (it == rtmpmuxers.end()){ + return; + } + + SrsGb28181RtmpMuxer* muxer = it->second; + SrsGb28181StreamChannel sess = muxer->get_channel(); + + int port = sess.get_rtp_port(); + if (port == config->rtp_mux_port) { + return; + } + + map::iterator it2 = rtp_pool.find(port); + if (it2 != rtp_pool.end()){ + srs_freep(it2->second); + rtp_pool.erase(it2); + } + + free_port(port, port+1); +} + +//api +uint32_t SrsGb28181Manger::create_stream_channel(SrsGb28181StreamChannel *channel) +{ + srs_assert(channel); + + std::string id = channel->get_channel_id(); + SrsGb28181RtmpMuxer *muxer = NULL; + + muxer = fetch_rtmpmuxer(id); + if (muxer){ + SrsGb28181StreamChannel s = muxer->get_channel(); + channel->copy(&s); + //return ERROR_GB28181_SESSION_IS_EXIST; + return ERROR_SUCCESS; + } + + if (channel->get_stream().empty()){ + channel->set_stream("[stream]"); + } + + if (channel->get_app().empty()){ + channel->set_stream("[app]"); + } + + if (channel->get_port_mode().empty()){ + channel->set_port_mode(RTP_PORT_MODE_FIXED); + } + + //create on rtmp muxer, gb28281 stream to rtmp + srs_error_t err = srs_success; + if ((err = fetch_or_create_rtmpmuxer(id, &muxer)) != srs_success){ + srs_warn("gb28181: create rtmp muxer error, %s", srs_error_desc(err).c_str()); + srs_freep(err); + return ERROR_GB28281_CREATER_RTMPMUXER_FAILED; + } + + //Start RTP listening port, receive gb28181 stream, + //fixed is mux port, + //random is random allocation port + int rtp_port = 0; + std::string port_mode = channel->get_port_mode(); + + if (port_mode == RTP_PORT_MODE_RANDOM){ + alloc_port(&rtp_port); + if (rtp_port <= 0){ + return ERROR_GB28181_RTP_PORT_FULL; + } + srs_error_t err = srs_success; + if ((err = start_ps_rtp_listen(id, rtp_port)) != srs_success){ + srs_warn("gb28181: start ps rtp listen error, %s", srs_error_desc(err).c_str()); + srs_freep(err); + free_port(rtp_port, rtp_port + 1); + return ERROR_GB28281_CREATER_RTMPMUXER_FAILED; + } + } + else if(port_mode == RTP_PORT_MODE_FIXED) { + rtp_port = config->rtp_mux_port; + } + else{ + return ERROR_GB28181_PORT_MODE_INVALID; + } + + //Generate SSRC according to the hash code, + //of the string value of the id + uint32_t ssrc = generate_ssrc(id); + rtmpmuxer_map_by_ssrc(muxer, ssrc); + + //Generate RTMP push stream address, + std::string app = channel->get_app(); + std::string stream = channel->get_stream(); + app = srs_string_replace(app, "[app]", "live"); + stream = srs_string_replace(stream, "[stream]", id); + + std::string url = "rtmp://" + config->output + "/" + app + "/" + stream; + int rtmp_port; + if (true) { + std::string schema, host, vhost, param, _app, _stream; + srs_discovery_tc_url(url, schema, host, vhost, _app, _stream, rtmp_port, param); + url = srs_generate_rtmp_url(host, rtmp_port, "", "", app, stream, ""); + std::stringstream ss; + ss << ssrc; + url = srs_string_replace(url, "[ssrc]", ss.str()); + url = srs_path_build_timestamp(url); + } + muxer->set_rtmp_url(url); + srs_trace("gb28181: create new stream channel id:%s rtmp url=%s", id.c_str(), muxer->rtmp_url().c_str()); + + //generate the value returned to the api response + channel->set_app(app); + channel->set_stream(stream); + channel->set_rtp_port(rtp_port); + channel->set_rtmp_port(rtmp_port); + channel->set_ip(config->host); + channel->set_ssrc(ssrc); + + muxer->copy_channel(channel); + + return ERROR_SUCCESS; +} + +uint32_t SrsGb28181Manger::delete_stream_channel(std::string id) +{ + //notify the device to stop streaming + //if an internal sip service controlled channel + notify_sip_bye(id); + + SrsGb28181RtmpMuxer *muxer = fetch_rtmpmuxer(id); + if (muxer){ + stop_rtp_listen(id); + muxer->stop(); + return ERROR_SUCCESS; + }else { + return ERROR_GB28181_SESSION_IS_NOTEXIST; + } +} + + +uint32_t SrsGb28181Manger::queue_stream_channel(std::string id, SrsJsonArray* arr) +{ + if (!id.empty()){ + SrsGb28181RtmpMuxer *muxer = fetch_rtmpmuxer(id); + if (!muxer){ + return ERROR_GB28181_SESSION_IS_NOTEXIST; + } + SrsJsonObject* obj = SrsJsonAny::object(); + arr->append(obj); + muxer->get_channel().dumps(obj); + }else { + std::map::iterator it2; + for (it2 = rtmpmuxers.begin(); it2 != rtmpmuxers.end(); ++it2) { + SrsGb28181RtmpMuxer* muxer = it2->second; + SrsJsonObject* obj = SrsJsonAny::object(); + arr->append(obj); + muxer->get_channel().dumps(obj); + } + } + + return ERROR_SUCCESS; +} + +uint32_t SrsGb28181Manger::notify_sip_invite(std::string id, std::string ip, int port, uint32_t ssrc) +{ + if (!sip_service){ + return ERROR_GB28181_SIP_NOT_RUN; + } + + //if RTMP Muxer does not exist, you need to create + SrsGb28181RtmpMuxer *muxer = fetch_rtmpmuxer(id); + + if (!muxer){ + //if there is an invalid parameter, the channel will be created automatically + if (ip.empty() || port == 0 || ssrc == 0){ + //channel not exist + SrsGb28181StreamChannel channel; + channel.set_channel_id(id); + channel.set_app("live"); + channel.set_stream(id); + int code = create_stream_channel(&channel); + if (code != ERROR_SUCCESS){ + return code; + } + + ip = channel.get_ip(); + port = channel.get_rtp_port(); + ssrc = channel.get_ssrc(); + } + }else { + //channel exit, use channel config + SrsGb28181StreamChannel channel = muxer->get_channel(); + ip = channel.get_ip(); + port = channel.get_rtp_port(); + ssrc = channel.get_ssrc(); + } + + SrsSipRequest req; + req.sip_auth_id = id; + return sip_service->send_invite(&req, ip, port, ssrc); + +} + +uint32_t SrsGb28181Manger::notify_sip_bye(std::string id) +{ + if (!sip_service){ + return ERROR_GB28181_SIP_NOT_RUN; + } + + SrsGb28181RtmpMuxer *muxer = fetch_rtmpmuxer(id); + if (muxer){ + muxer->rtmp_close(); + } + + SrsSipRequest req; + req.sip_auth_id = id; + return sip_service->send_bye(&req); +} + +uint32_t SrsGb28181Manger::notify_sip_raw_data(std::string id, std::string data) +{ + if (!sip_service){ + return ERROR_GB28181_SIP_NOT_RUN; + } + + SrsSipRequest req; + req.sip_auth_id = id; + return sip_service->send_sip_raw_data(&req, data); + +} + +uint32_t SrsGb28181Manger::notify_sip_unregister(std::string id) +{ + if (!sip_service){ + return ERROR_GB28181_SIP_NOT_RUN; + } + + delete_stream_channel(id); + sip_service->remove_session(id); + return ERROR_SUCCESS; +} diff --git a/trunk/src/app/srs_app_gb28181.hpp b/trunk/src/app/srs_app_gb28181.hpp new file mode 100644 index 0000000000..812f390fbf --- /dev/null +++ b/trunk/src/app/srs_app_gb28181.hpp @@ -0,0 +1,438 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2013-2020 Lixin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef SRS_APP_GB28181_HPP +#define SRS_APP_GB28181_HPP + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RTP_PORT_MODE_FIXED "fixed" +#define RTP_PORT_MODE_RANDOM "random" + +class SrsConfDirective; +class SrsRtpPacket; +class SrsRtmpClient; +class SrsRawH264Stream; +class SrsRawAacStream; +struct SrsRawAacStreamCodec; +class SrsSharedPtrMessage; +class SrsAudioFrame; +class SrsSimpleStream; +class SrsPithyPrint; +class SrsSimpleRtmpClient; +class SrsSipStack; +class SrsGb28181Manger; +class SrsRtspJitter; +class SrsSipRequest; +class SrsGb28181RtmpMuxer; +class SrsGb28181Config; +class SrsGb28181PsRtpProcessor; +class SrsGb28181SipService; +class SrsGb28181StreamChannel; + +//ps rtp header packet parse +class SrsPsRtpPacket: public SrsRtpPacket +{ +public: + SrsPsRtpPacket(); + virtual ~SrsPsRtpPacket(); +public: + virtual srs_error_t decode(SrsBuffer* stream); +}; + +//randomly assigned ports receive gb28281 device streams +class SrsPsRtpListener: public ISrsUdpHandler +{ +private: + SrsUdpListener* listener; + SrsGb28181PsRtpProcessor* rtp_processor; + int _port; +public: + SrsPsRtpListener(SrsGb28181Config* c, int p, std::string s); + virtual ~SrsPsRtpListener(); +public: + virtual int port(); + virtual srs_error_t listen(); +// Interface ISrsUdpHandler +public: + virtual srs_error_t on_udp_packet(const sockaddr* from, const int fromlen, char* buf, int nb_buf); +}; + +//multiplexing service, single port receiving all gb28281 device streams +class SrsGb28181RtpMuxService : public ISrsUdpHandler +{ +private: + SrsGb28181Config *config; + SrsGb28181PsRtpProcessor *rtp_processor; +public: + SrsGb28181RtpMuxService(SrsConfDirective* c); + virtual ~SrsGb28181RtpMuxService(); + + // Interface ISrsUdpHandler +public: + virtual srs_error_t on_udp_packet(const sockaddr* from, const int fromlen, char* buf, int nb_buf); +}; + + +//process gb28281 RTP package, generate a completed PS stream data, +//call the PS stream parser, parse the original video and audio +class SrsGb28181PsRtpProcessor: public ISrsUdpHandler +{ +private: + SrsPithyPrint* pprint; + SrsGb28181Config* config; + std::map cache_ps_rtp_packet; + std::map pre_packet; + std::string channel_id; + bool auto_create_channel; +public: + SrsGb28181PsRtpProcessor(SrsGb28181Config* c, std::string sid); + virtual ~SrsGb28181PsRtpProcessor(); +private: + bool can_send_ps_av_packet(); + void dispose(); +// Interface ISrsUdpHandler +public: + virtual srs_error_t on_udp_packet(const sockaddr* from, const int fromlen, char* buf, int nb_buf); +}; + +//ps stream processing parsing interface +class ISrsPsStreamHander +{ +public: + ISrsPsStreamHander(); + virtual ~ISrsPsStreamHander(); +public: + virtual srs_error_t on_rtp_video(SrsSimpleStream* stream, int64_t dts)=0; + virtual srs_error_t on_rtp_audio(SrsSimpleStream* stream, int64_t dts)=0; +}; + +//analysis of PS stream and +//extraction of H264 raw data and audio data +//then process the flow through PS stream hander, +//such as RTMP multiplexer, and composited into RTMP av stream +class SrsPsStreamDemixer +{ +public: + // gb28181 program stream struct define + struct SrsPsPacketStartCode + { + uint8_t start_code[3]; + uint8_t stream_id[1]; + }; + + struct SrsPsPacketHeader + { + SrsPsPacketStartCode start;// 4 + uint8_t info[9]; + uint8_t stuffing_length; + }; + + struct SrsPsPacketBBHeader + { + SrsPsPacketStartCode start; + uint16_t length; + }; + + struct SrsPsePacket + { + SrsPsPacketStartCode start; + uint16_t length; + uint8_t info[2]; + uint8_t stuffing_length; + }; + + struct SrsPsMapPacket + { + SrsPsPacketStartCode start; + uint16_t length; + }; + +private: + SrsFileWriter ps_fw; + SrsFileWriter video_fw; + SrsFileWriter audio_fw; + + bool first_keyframe_flag; + bool wait_first_keyframe; + bool audio_enable; + std::string channel_id; + + ISrsPsStreamHander *hander; +public: + SrsPsStreamDemixer(ISrsPsStreamHander *h, std::string sid, bool a, bool k); + virtual ~SrsPsStreamDemixer(); +private: + bool can_send_ps_av_packet(); +public: + int64_t parse_ps_timestamp(const uint8_t* p); + virtual srs_error_t on_ps_stream(char* ps_data, int ps_size, uint32_t timestamp, uint32_t ssrc); +}; + + +//RTMP multiplexer, which processes the raw H264 / AAC, +//then publish it to RTMP server +class SrsGb28181RtmpMuxer : public ISrsCoroutineHandler, + public ISrsConnection, public ISrsPsStreamHander +{ +private: + SrsPithyPrint* pprint; + SrsGb28181StreamChannel *channel; + int stream_idle_timeout; + srs_utime_t recv_stream_time; +private: + std::string channel_id; + std::string _rtmp_url; + std::string video_ssrc; + std::string audio_ssrc; + int audio_sample_rate; + int audio_channel; + + SrsGb28181Manger* gb28181_manger; + SrsCoroutine* trd; + SrsPsStreamDemixer* ps_demixer; + srs_cond_t wait_ps_queue; + + SrsSimpleRtmpClient* sdk; + SrsRtspJitter* vjitter; + SrsRtspJitter* ajitter; + + SrsRawH264Stream* avc; + std::string h264_sps; + std::string h264_pps; + bool h264_sps_changed; + bool h264_pps_changed; + bool h264_sps_pps_sent; + + SrsRawAacStream* aac; + std::string aac_specific_config; + +public: + std::queue ps_queue; + +public: + SrsGb28181RtmpMuxer(SrsGb28181Manger* m, std::string id, bool a, bool k); + virtual ~SrsGb28181RtmpMuxer(); + +public: + virtual srs_error_t serve(); + virtual void stop(); + + virtual std::string get_channel_id(); + virtual void ps_packet_enqueue(SrsPsRtpPacket *pkt); + virtual void copy_channel(SrsGb28181StreamChannel *s); + virtual void set_channel_peer_ip(std::string ip); + virtual void set_channel_peer_port(int port); + virtual int channel_peer_port(); + virtual std::string channel_peer_ip(); + virtual void set_rtmp_url(std::string url); + virtual std::string rtmp_url(); + virtual SrsGb28181StreamChannel get_channel(); + +private: + virtual srs_error_t do_cycle(); + virtual void destroy(); + +// Interface ISrsOneCycleThreadHandler +public: + virtual srs_error_t cycle(); + virtual std::string remote_ip(); +public: + virtual srs_error_t on_rtp_video(SrsSimpleStream* stream, int64_t dts); + virtual srs_error_t on_rtp_audio(SrsSimpleStream* stream, int64_t dts); +private: + virtual srs_error_t write_h264_sps_pps(uint32_t dts, uint32_t pts); + virtual srs_error_t write_h264_ipb_frame(char* frame, int frame_size, uint32_t dts, uint32_t pts); + virtual srs_error_t write_audio_raw_frame(char* frame, int frame_size, SrsRawAacStreamCodec* codec, uint32_t dts); + virtual srs_error_t rtmp_write_packet(char type, uint32_t timestamp, char* data, int size); +private: + // Connect to RTMP server. + virtual srs_error_t connect(); + // Close the connection to RTMP server. + virtual void close(); +public: + virtual void rtmp_close(); +}; + +//system parameter configuration of gb28281 module, +//read file from configuration file to generate +class SrsGb28181Config +{ +public: + std::string host; + srs_utime_t rtp_idle_timeout; + bool audio_enable; + bool wait_keyframe; + std::string output; + int rtp_port_min; + int rtp_port_max; + int rtp_mux_port; + + //sip config + int sip_port; + std::string sip_serial; + std::string sip_realm; + bool sip_enable; + int sip_ack_timeout; + int sip_keepalive_timeout; + bool print_sip_message; + bool sip_auto_play; + bool sip_invite_port_fixed; + +public: + SrsGb28181Config(SrsConfDirective* c); + virtual ~SrsGb28181Config(); +}; + +class SrsGb28181StreamChannel +{ +private: + std::string channel_id; + std::string port_mode; + std::string app; + std::string stream; + + std::string ip; + int rtp_port; + int rtmp_port; + uint32_t ssrc; + + //send rtp stream client local port + int rtp_peer_port; + //send rtp stream client local ip + std::string rtp_peer_ip; + +public: + SrsGb28181StreamChannel(); + virtual ~SrsGb28181StreamChannel(); + + std::string get_channel_id() const { return channel_id; } + std::string get_port_mode() const { return port_mode; } + std::string get_app() const { return app; } + std::string get_stream() const { return stream; } + std::string get_ip() const { return ip; } + int get_rtp_port() const { return rtp_port; } + int get_rtmp_port() const { return rtmp_port; } + uint32_t get_ssrc() const { return ssrc; } + uint32_t get_rtp_peer_port() const { return rtp_peer_port; } + std::string get_rtp_peer_ip() const { return rtp_peer_ip; } + + void set_channel_id(const std::string &i) { channel_id = i; } + void set_port_mode(const std::string &p) { port_mode = p; } + void set_app(const std::string &a) { app = a; } + void set_stream(const std::string &s) { stream = s; } + void set_ip(const std::string &i) { ip = i; } + void set_rtp_port( const int &p) { rtp_port = p; } + void set_rtmp_port( const int &p) { rtmp_port = p; } + void set_ssrc( const int &s) { ssrc = s;} + void set_rtp_peer_ip( const std::string &p) { rtp_peer_ip = p; } + void set_rtp_peer_port( const int &s) { rtp_peer_port = s;} + + void copy(const SrsGb28181StreamChannel *s); + void dumps(SrsJsonObject* obj); + +}; + +// Global singleton instance. +extern SrsGb28181Manger* _srs_gb28181; + +//gb28181 module management, management of all RTMP multiplexers, +//random assignment of RTP listeners, and external control interfaces +class SrsGb28181Manger +{ +private: + SrsGb28181Config *config; + // The key: port, value: whether used. + std::map used_ports; + std::map rtp_pool; + std::map rtmpmuxers_ssrc; + std::map rtmpmuxers; + SrsCoroutineManager* manager; + + SrsGb28181SipService* sip_service; + +public: + SrsGb28181Manger(SrsConfDirective* c); + virtual ~SrsGb28181Manger(); + +public: + srs_error_t fetch_or_create_rtmpmuxer(std::string id, SrsGb28181RtmpMuxer** gb28181); + SrsGb28181RtmpMuxer* fetch_rtmpmuxer(std::string id); + SrsGb28181RtmpMuxer* fetch_rtmpmuxer_by_ssrc(uint32_t ssrc); + void rtmpmuxer_map_by_ssrc(SrsGb28181RtmpMuxer*muxer, uint32_t ssrc); + void rtmpmuxer_unmap_by_ssrc(uint32_t ssrc); + uint32_t generate_ssrc(std::string id); + uint32_t hash_code(std::string str); + + void set_sip_service(SrsGb28181SipService *s) { sip_service = s; } + SrsGb28181SipService* get_sip_service() { return sip_service; } + +public: + //stream channel api + uint32_t create_stream_channel(SrsGb28181StreamChannel *channel); + uint32_t delete_stream_channel(std::string id); + uint32_t queue_stream_channel(std::string id, SrsJsonArray* arr); + //sip api + uint32_t notify_sip_invite(std::string id, std::string ip, int port, uint32_t ssrc); + uint32_t notify_sip_bye(std::string id); + uint32_t notify_sip_raw_data(std::string id, std::string data); + uint32_t notify_sip_unregister(std::string id); + +private: + void destroy(); + +public: + // Alloc a rtp port from local ports pool. + // @param pport output the rtp port. + void alloc_port(int* pport); + // Free the alloced rtp port. + void free_port(int lpmin, int lpmax); + srs_error_t initialize(); + + SrsGb28181Config get_gb28181_config(); + srs_error_t start_ps_rtp_listen(std::string id, int port); + void stop_rtp_listen(std::string id); + +public: + void remove(SrsGb28181RtmpMuxer* conn); + +}; + +#endif + diff --git a/trunk/src/app/srs_app_gb28181_sip.cpp b/trunk/src/app/srs_app_gb28181_sip.cpp new file mode 100644 index 0000000000..72a55685cf --- /dev/null +++ b/trunk/src/app/srs_app_gb28181_sip.cpp @@ -0,0 +1,471 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2013-2020 Lixin + * + * 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 + +using namespace std; + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +SrsGb28181SipSession::SrsGb28181SipSession(SrsGb28181SipService *c, SrsSipRequest* r) +{ + caster = c; + req = new SrsSipRequest(); + req->copy(r); + + _register_status = SrsGb28181SipSessionUnkonw; + _alive_status = SrsGb28181SipSessionUnkonw; + _invite_status = SrsGb28181SipSessionUnkonw; + _register_time = 0; + _alive_time = 0; + _invite_time = 0; + _recv_rtp_time = 0; + _reg_expires = 0; + + _peer_ip = ""; + _peer_port = 0; + + _from = NULL; + _fromlen = 0; +} + +SrsGb28181SipSession::~SrsGb28181SipSession() +{ + srs_freep(req); +} + +//gb28181 sip Service +SrsGb28181SipService::SrsGb28181SipService(SrsConfDirective* c) +{ + // TODO: FIXME: support reload. + config = new SrsGb28181Config(c); + sip = new SrsSipStack(); + + if (_srs_gb28181){ + _srs_gb28181->set_sip_service(this); + } +} + +SrsGb28181SipService::~SrsGb28181SipService() +{ + destroy(); + srs_freep(sip); + srs_freep(config); +} + +void SrsGb28181SipService::set_stfd(srs_netfd_t fd) +{ + lfd = fd; +} + +srs_error_t SrsGb28181SipService::on_udp_packet(const sockaddr* from, const int fromlen, char* buf, int nb_buf) +{ + char address_string[64]; + char port_string[16]; + if(getnameinfo(from, fromlen, + (char*)&address_string, sizeof(address_string), + (char*)&port_string, sizeof(port_string), + NI_NUMERICHOST|NI_NUMERICSERV)) { + return srs_error_new(ERROR_SYSTEM_IP_INVALID, "bad address"); + } + std::string peer_ip = std::string(address_string); + int peer_port = atoi(port_string); + + srs_error_t err = on_udp_sip(peer_ip, peer_port, buf, nb_buf, (sockaddr*)from, fromlen); + if (err != srs_success) { + return srs_error_wrap(err, "process udp"); + } + return err; +} + +srs_error_t SrsGb28181SipService::on_udp_sip(string peer_ip, int peer_port, + char* buf, int nb_buf, sockaddr* from, const int fromlen) +{ + srs_error_t err = srs_success; + + if (config->print_sip_message) + { + srs_trace("gb28181: request peer_ip=%s, peer_port=%d nbbuf=%d", peer_ip.c_str(), peer_port, nb_buf); + srs_trace("gb28181: request recv message=%s", buf); + } + + if (nb_buf < 10) { + return err; + } + + SrsSipRequest* req = NULL; + + if ((err = sip->parse_request(&req, buf, nb_buf)) != srs_success) { + return srs_error_wrap(err, "parse sip request"); + } + + if (config->print_sip_message) + { + srs_trace("gb28181: %s method=%s, uri=%s, version=%s ", + req->get_cmdtype_str().c_str(), req->method.c_str(), req->uri.c_str(), req->version.c_str()); + srs_trace("gb28281: request client id=%s", req->sip_auth_id.c_str()); + } + + req->peer_ip = peer_ip; + req->peer_port = peer_port; + SrsAutoFree(SrsSipRequest, req); + + std::string session_id = req->sip_auth_id; + + if (req->is_register()) { + std::vector serial = srs_string_split(srs_string_replace(req->uri,"sip:", ""), "@"); + if (serial.at(0) != config->sip_serial){ + srs_trace("gb28181: client:%s request serial and server serial inconformity(%s:%s)", + req->sip_auth_id.c_str(), serial.at(0).c_str(), config->sip_serial.c_str()); + return err; + } + + srs_trace("gb28181: request peer_ip=%s, peer_port=%d", peer_ip.c_str(), peer_port, nb_buf); + srs_trace("gb28181: request %s method=%s, uri=%s, version=%s ", + req->get_cmdtype_str().c_str(), req->method.c_str(), req->uri.c_str(), req->version.c_str()); + srs_trace("gb28281: request client id=%s", req->sip_auth_id.c_str()); + + SrsGb28181SipSession* sip_session = create_sip_session(req); + if (!sip_session) { + srs_trace("gb28181: create sip session faild:%s", req->uri.c_str()); + return err; + } + + send_status(req, from, fromlen); + sip_session->set_register_status(SrsGb28181SipSessionRegisterOk); + sip_session->set_register_time(srs_get_system_time()); + sip_session->set_reg_expires(req->expires); + sip_session->set_sockaddr(from); + sip_session->set_sockaddr_len(fromlen); + sip_session->set_peer_ip(peer_ip); + sip_session->set_peer_port(peer_port); + }else if (req->is_message()) { + SrsGb28181SipSession* sip_session = fetch(session_id); + if (!sip_session || sip_session->register_status() == SrsGb28181SipSessionUnkonw){ + srs_trace("gb28181: %s client not registered", req->sip_auth_id.c_str()); + return err; + } + + //reponse status + send_status(req, from, fromlen); + sip_session->set_register_status(SrsGb28181SipSessionRegisterOk); + sip_session->set_register_time(srs_get_system_time()); + sip_session->set_alive_status(SrsGb28181SipSessionAliveOk); + sip_session->set_alive_time(srs_get_system_time()); + sip_session->set_sockaddr(from); + sip_session->set_sockaddr_len(fromlen); + sip_session->set_peer_port(peer_port); + sip_session->set_peer_ip(peer_ip); + + //send invite, play client av + //start ps rtp listen, recv ps stream + if (config->sip_auto_play && sip_session->register_status() == SrsGb28181SipSessionRegisterOk && + sip_session->alive_status() == SrsGb28181SipSessionAliveOk && + sip_session->invite_status() == SrsGb28181SipSessionUnkonw) + { + //stop the possible stream and push a new stream + //send_bye(req, from, fromlen); + + SrsGb28181StreamChannel ch; + ch.set_channel_id(session_id); + ch.set_ip(config->host); + ch.set_stream(session_id); + ch.set_app("live"); + if (config->sip_invite_port_fixed){ + ch.set_port_mode(RTP_PORT_MODE_FIXED); + }else { + ch.set_port_mode(RTP_PORT_MODE_RANDOM); + } + + int code = _srs_gb28181->create_stream_channel(&ch); + if (code == ERROR_SUCCESS){ + code = send_invite(req, ch.get_ip(), + ch.get_rtp_port(), ch.get_ssrc()); + } + + if (code == ERROR_SUCCESS){ + sip_session->set_invite_status(SrsGb28181SipSessionTrying); + sip_session->set_invite_time(srs_get_system_time()); + } + + } + }else if (req->is_invite()) { + SrsGb28181SipSession* sip_session = fetch(session_id); + + srs_trace("gb28181: request peer_ip=%s, peer_port=%d", peer_ip.c_str(), peer_port, nb_buf); + srs_trace("gb28181: request %s method=%s, uri=%s, version=%s ", + req->get_cmdtype_str().c_str(), req->method.c_str(), req->uri.c_str(), req->version.c_str()); + srs_trace("gb28281: request client id=%s", req->sip_auth_id.c_str()); + + if (!sip_session){ + send_bye(req); + srs_trace("gb28181: %s client not registered", req->sip_auth_id.c_str()); + return err; + } + + if (sip_session->register_status() == SrsGb28181SipSessionUnkonw || + sip_session->alive_status() == SrsGb28181SipSessionUnkonw) { + srs_trace("gb28181: %s client not registered or not alive", req->sip_auth_id.c_str()); + return err; + } + + if (req->cmdtype == SrsSipCmdRespone && req->status == "200") { + srs_trace("gb28181: INVITE response %s client status=%s", req->sip_auth_id.c_str(), req->status.c_str()); + send_ack(req, from, fromlen); + sip_session->set_invite_status(SrsGb28181SipSessionInviteOk); + sip_session->set_invite_time(srs_get_system_time()); + //Record tag and branch, which are required by the 'bye' command, + sip_session->set_request(req); + }else{ + sip_session->set_invite_status(SrsGb28181SipSessionUnkonw); + sip_session->set_invite_time(0); + } + }else if (req->is_bye()) { + srs_trace("gb28181: request peer_ip=%s, peer_port=%d", peer_ip.c_str(), peer_port, nb_buf); + srs_trace("gb28181: request %s method=%s, uri=%s, version=%s ", + req->get_cmdtype_str().c_str(), req->method.c_str(), req->uri.c_str(), req->version.c_str()); + srs_trace("gb28281: request client id=%s", req->sip_auth_id.c_str()); + + SrsGb28181SipSession* sip_session = fetch(session_id); + send_status(req, from, fromlen); + + if (!sip_session){ + srs_trace("gb28181: %s client not registered", req->sip_auth_id.c_str()); + return err; + } + + sip_session->set_invite_status(SrsGb28181SipSessionBye); + sip_session->set_invite_time(0); + + }else{ + srs_trace("gb28181: ingor request method=%s", req->method.c_str()); + } + + return err; +} + +int SrsGb28181SipService::send_message(sockaddr* from, int fromlen, std::stringstream& ss) +{ + std::string str = ss.str(); + if (config->print_sip_message) + srs_trace("gb28181: send_message:%s", str.c_str()); + srs_assert(!str.empty()); + + int ret = srs_sendto(lfd, (char*)str.c_str(), (int)str.length(), from, fromlen, SRS_UTIME_NO_TIMEOUT); + if (ret <= 0){ + srs_trace("gb28181: send_message falid (%d)", ret); + } + + return ret; +} + + +int SrsGb28181SipService::send_ack(SrsSipRequest *req, sockaddr *f, int l) +{ + srs_assert(req); + + std::stringstream ss; + + req->host = config->host; + req->host_port = config->sip_port; + req->realm = config->sip_realm; + req->serial = config->sip_serial; + + sip->resp_ack(ss, req); + return send_message(f, l, ss); +} + +int SrsGb28181SipService::send_status(SrsSipRequest *req, sockaddr *f, int l) +{ + srs_assert(req); + + std::stringstream ss; + + req->host = config->host; + req->host_port = config->sip_port; + req->realm = config->sip_realm; + req->serial = config->sip_serial; + + sip->resp_status(ss, req); + return send_message(f, l, ss); +} + + +int SrsGb28181SipService::send_invite(SrsSipRequest *req, string ip, int port, uint32_t ssrc) +{ + srs_assert(req); + + SrsGb28181SipSession *sip_session = fetch(req->sip_auth_id); + + if (!sip_session){ + return ERROR_GB28181_SESSION_IS_NOTEXIST; + } + + //if you are inviting or succeed in invite, + //you cannot invite again. you need to 'bye' and try again + if (sip_session->invite_status() == SrsGb28181SipSessionTrying || + sip_session->invite_status() == SrsGb28181SipSessionInviteOk){ + return ERROR_GB28281_SIP_IS_INVITING; + } + + req->host = config->host; + req->host_port = config->sip_port; + req->realm = config->sip_realm; + req->serial = config->sip_serial; + + std::stringstream ss; + sip->req_invite(ss, req, ip, port, ssrc); + + if (send_message(sip_session->sockaddr_from(), sip_session->sockaddr_fromlen(), ss) <= 0) + { + return ERROR_GB28281_SIP_INVITE_FAILED; + } + + sip_session->set_invite_status(SrsGb28181SipSessionTrying); + + return ERROR_SUCCESS; + +} + +int SrsGb28181SipService::send_bye(SrsSipRequest *req) +{ + srs_assert(req); + + SrsGb28181SipSession *sip_session = fetch(req->sip_auth_id); + + if (!sip_session){ + return ERROR_GB28181_SESSION_IS_NOTEXIST; + } + + //prame branch, from_tag, to_tag, call_id, + //The parameter of 'bye' must be the same as 'invite' + SrsSipRequest r = sip_session->request(); + req->copy(&r); + + req->host = config->host; + req->host_port = config->sip_port; + req->realm = config->sip_realm; + req->serial = config->sip_serial; + + //get protocol stack + std::stringstream ss; + sip->req_bye(ss, req); + + if (send_message(sip_session->sockaddr_from(), sip_session->sockaddr_fromlen(), ss) <= 0) + { + return ERROR_GB28281_SIP_BYE_FAILED; + } + + return ERROR_SUCCESS; + + +} + + +int SrsGb28181SipService::send_sip_raw_data(SrsSipRequest *req, std::string data) +{ + srs_assert(req); + + SrsGb28181SipSession *sip_session = fetch(req->sip_auth_id); + + if (!sip_session){ + return ERROR_GB28181_SESSION_IS_NOTEXIST; + } + + std::stringstream ss; + ss << data; + + if (send_message(sip_session->sockaddr_from(), sip_session->sockaddr_fromlen(), ss) <= 0) + { + return ERROR_GB28281_SIP_BYE_FAILED; + } + + return ERROR_SUCCESS; +} + +SrsGb28181SipSession* SrsGb28181SipService::create_sip_session(SrsSipRequest *req) +{ + SrsGb28181SipSession *sess = NULL; + + std::map::iterator it = sessions.find(req->sip_auth_id); + if (it == sessions.end()){ + sess = new SrsGb28181SipSession(this, req); + }else{ + return it->second; + } + + sessions[req->sip_auth_id] = sess; + return sess; +} + +SrsGb28181SipSession* SrsGb28181SipService::fetch(std::string sid) +{ + std::map::iterator it = sessions.find(sid); + if (it == sessions.end()){ + return NULL; + }else{ + return it->second; + } +} + +void SrsGb28181SipService::remove_session(std::string sid) +{ + std::map::iterator it = sessions.find(sid); + if (it != sessions.end()){ + srs_freep(it->second); + sessions.erase(it); + } +} + + +void SrsGb28181SipService::destroy() +{ + //destory all sip session + std::map::iterator it; + for (it = sessions.begin(); it != sessions.end(); ++it) { + srs_freep(it->second); + } + sessions.clear(); +} + + diff --git a/trunk/src/app/srs_app_gb28181_sip.hpp b/trunk/src/app/srs_app_gb28181_sip.hpp new file mode 100644 index 0000000000..9206e66c91 --- /dev/null +++ b/trunk/src/app/srs_app_gb28181_sip.hpp @@ -0,0 +1,161 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2013-2020 Lixin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef SRS_APP_GB28181_SIP_HPP +#define SRS_APP_GB28181_SIP_HPP + +#include + +#include +#include +#include + +#include +#include +#include + + +class SrsConfDirective; +class SrsSipRequest; +class SrsGb28181Config; +class SrsSipStack; +class SrsGb28181SipService; + +enum SrsGb28281SipSessionStatusType{ + SrsGb28181SipSessionUnkonw = 0, + SrsGb28181SipSessionRegisterOk = 1, + SrsGb28181SipSessionAliveOk = 2, + SrsGb28181SipSessionInviteOk = 3, + SrsGb28181SipSessionTrying = 4, + SrsGb28181SipSessionBye = 5, +}; + +class SrsGb28181SipSession +{ +private: + //SrsSipRequest *req; + SrsGb28181SipService *caster; + std::string session_id; +private: + SrsGb28281SipSessionStatusType _register_status; + SrsGb28281SipSessionStatusType _alive_status; + SrsGb28281SipSessionStatusType _invite_status; + srs_utime_t _register_time; + srs_utime_t _alive_time; + srs_utime_t _invite_time; + srs_utime_t _recv_rtp_time; + int _reg_expires; + + std::string _peer_ip; + int _peer_port; + + sockaddr *_from; + int _fromlen; + SrsSipRequest *req; + +public: + void set_register_status(SrsGb28281SipSessionStatusType s) { _register_status = s;} + void set_alive_status(SrsGb28281SipSessionStatusType s) { _alive_status = s;} + void set_invite_status(SrsGb28281SipSessionStatusType s) { _invite_status = s;} + void set_register_time(srs_utime_t t) { _register_time = t;} + void set_alive_time(srs_utime_t t) { _alive_time = t;} + void set_invite_time(srs_utime_t t) { _invite_time = t;} + void set_recv_rtp_time(srs_utime_t t) { _recv_rtp_time = t;} + void set_reg_expires(int e) { _reg_expires = e;} + void set_peer_ip(std::string i) { _peer_ip = i;} + void set_peer_port(int o) { _peer_port = o;} + void set_sockaddr(sockaddr *f) { _from = f;} + void set_sockaddr_len(int l) { _fromlen = l;} + void set_request(SrsSipRequest *r) { req->copy(r);} + + SrsGb28281SipSessionStatusType register_status() { return _register_status;} + SrsGb28281SipSessionStatusType alive_status() { return _alive_status;} + SrsGb28281SipSessionStatusType invite_status() { return _invite_status;} + srs_utime_t register_time() { return _register_time;} + srs_utime_t alive_time() { return _alive_time;} + srs_utime_t invite_time() { return _invite_time;} + srs_utime_t recv_rtp_time() { return _recv_rtp_time;} + int reg_expires() { return _reg_expires;} + std::string peer_ip() { return _peer_ip;} + int peer_port() { return _peer_port;} + sockaddr* sockaddr_from() { return _from;} + int sockaddr_fromlen() { return _fromlen;} + SrsSipRequest request() { return *req;} + +public: + SrsGb28181SipSession(SrsGb28181SipService *c, SrsSipRequest* r); + virtual ~SrsGb28181SipSession(); + +}; + +class SrsGb28181SipService : public ISrsUdpHandler +{ +private: + SrsSipStack *sip; + SrsGb28181Config *config; + srs_netfd_t lfd; + + std::map sessions; +public: + SrsGb28181SipService(SrsConfDirective* c); + virtual ~SrsGb28181SipService(); + + // Interface ISrsUdpHandler +public: + virtual srs_error_t on_udp_packet(const sockaddr* from, const int fromlen, char* buf, int nb_buf); + virtual void set_stfd(srs_netfd_t fd); +private: + void destroy(); + srs_error_t on_udp_sip(std::string host, int port, char* buf, int nb_buf, sockaddr* from, int fromlen); +public: + int send_message(sockaddr* f, int l, std::stringstream& ss); + + int send_ack(SrsSipRequest *req, sockaddr *f, int l); + int send_status(SrsSipRequest *req, sockaddr *f, int l); + + int send_invite(SrsSipRequest *req, std::string ip, int port, uint32_t ssrc); + int send_bye(SrsSipRequest *req); + + // The SIP command is transmitted through HTTP API, + // and the body content is transmitted to the device, + // mainly for testing and debugging, For example, here is HTTP body: + // BYE sip:34020000001320000003@3402000000 SIP/2.0 + // Via: SIP/2.0/UDP 39.100.155.146:15063;rport;branch=z9hG4bK34205410 + // From: ;tag=512355410 + // To: ;tag=680367414 + // Call-ID: 200003304 + // CSeq: 21 BYE + // Max-Forwards: 70 + // User-Agent: SRS/4.0.4(Leo) + // Content-Length: 0 + // + // + int send_sip_raw_data(SrsSipRequest *req, std::string data); + + SrsGb28181SipSession* create_sip_session(SrsSipRequest *req); + SrsGb28181SipSession* fetch(std::string id); + void remove_session(std::string id); +}; + +#endif + diff --git a/trunk/src/app/srs_app_http_api.cpp b/trunk/src/app/srs_app_http_api.cpp index 5355068eb3..d2174d08bf 100644 --- a/trunk/src/app/srs_app_http_api.cpp +++ b/trunk/src/app/srs_app_http_api.cpp @@ -1301,6 +1301,131 @@ srs_error_t SrsGoApiError::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage return srs_api_response_code(w, r, 100); } +SrsGoApiGb28181::SrsGoApiGb28181() +{ +} + +SrsGoApiGb28181::~SrsGoApiGb28181() +{ +} + +srs_error_t SrsGoApiGb28181::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r) +{ + SrsJsonObject* obj = SrsJsonAny::object(); + SrsAutoFree(SrsJsonObject, obj); + + obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); + SrsJsonObject* data = SrsJsonAny::object(); + obj->set("data", data); + + string id = r->query_get("id"); + string action = r->query_get("action"); + string vhost = r->query_get("vhost"); + string app = r->query_get("app"); + string stream = r->query_get("stream"); + //fixed, random + string port_mode = r->query_get("port_mode"); + + if (_srs_gb28181) { + if(action == "create_channel"){ + if (id.empty()){ + return srs_api_response_code(w, r, ERROR_GB28181_VALUE_EMPTY); + } + + SrsGb28181StreamChannel channel; + channel.set_channel_id(id); + channel.set_app(app); + channel.set_stream(stream); + channel.set_port_mode(port_mode); + + uint32_t code = _srs_gb28181->create_stream_channel(&channel); + if (code != ERROR_SUCCESS) { + return srs_api_response_code(w, r, code); + } + + data->set("query", SrsJsonAny::object() + ->set("id", SrsJsonAny::str(channel.get_channel_id().c_str())) + ->set("ip", SrsJsonAny::str(channel.get_ip().c_str())) + ->set("rtmp_port", SrsJsonAny::integer(channel.get_rtmp_port())) + ->set("app", SrsJsonAny::str(channel.get_app().c_str())) + ->set("stream", SrsJsonAny::str(channel.get_stream().c_str())) + ->set("rtp_port", SrsJsonAny::integer(channel.get_rtp_port())) + ->set("ssrc", SrsJsonAny::integer(channel.get_ssrc()))); + return srs_api_response(w, r, obj->dumps()); + + } + else if(action == "delete_channel"){ + if (id.empty()){ + return srs_api_response_code(w, r, ERROR_GB28181_VALUE_EMPTY); + } + + uint32_t code = _srs_gb28181->delete_stream_channel(id); + return srs_api_response_code(w, r, code); + } + else if(action == "query_channel") { + SrsJsonArray* arr = SrsJsonAny::array(); + data->set("channels", arr); + + uint32_t code = _srs_gb28181->queue_stream_channel(id, arr); + if (code != ERROR_SUCCESS) { + return srs_api_response_code(w, r, code); + } + + return srs_api_response(w, r, obj->dumps()); + } + else if(action == "sip_invite"){ + if (id.empty()){ + return srs_api_response_code(w, r, ERROR_GB28181_VALUE_EMPTY); + } + + string ssrc = r->query_get("ssrc"); + string rtp_port = r->query_get("rtp_port"); + string ip = r->query_get("ip"); + + int _port = strtoul(rtp_port.c_str(), NULL, 10); + uint32_t _ssrc = (uint32_t)(strtoul(ssrc.c_str(), NULL, 10)); + + + + int code = _srs_gb28181->notify_sip_invite(id, ip, _port, _ssrc); + return srs_api_response_code(w, r, code); + } + else if(action == "sip_bye"){ + if (id.empty()){ + return srs_api_response_code(w, r, ERROR_GB28181_VALUE_EMPTY); + } + + int code = _srs_gb28181->notify_sip_bye(id); + return srs_api_response_code(w, r, code); + } + else if(action == "sip_raw_data"){ + if (id.empty()){ + return srs_api_response_code(w, r, ERROR_GB28181_VALUE_EMPTY); + } + + std::string body; + r->body_read_all(body); + int code = _srs_gb28181->notify_sip_raw_data(id, body); + return srs_api_response_code(w, r, code); + } + else if(action == "sip_unregister"){ + if (id.empty()){ + return srs_api_response_code(w, r, ERROR_GB28181_VALUE_EMPTY); + } + + int code = _srs_gb28181->notify_sip_unregister(id); + return srs_api_response_code(w, r, code); + } + else + { + return srs_api_response_code(w, r, ERROR_GB28181_ACTION_INVALID); + } + + }else { + return srs_api_response_code(w, r, ERROR_GB28181_SERVER_NOT_RUN); + } +} + SrsHttpApi::SrsHttpApi(IConnectionManager* cm, srs_netfd_t fd, SrsHttpServeMux* m, string cip) : SrsConnection(cm, fd, cip) { diff --git a/trunk/src/app/srs_app_http_api.hpp b/trunk/src/app/srs_app_http_api.hpp index 5957ff2f3f..7713d9816b 100644 --- a/trunk/src/app/srs_app_http_api.hpp +++ b/trunk/src/app/srs_app_http_api.hpp @@ -210,6 +210,16 @@ class SrsGoApiError : public ISrsHttpHandler virtual srs_error_t serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r); }; + +class SrsGoApiGb28181 : public ISrsHttpHandler +{ +public: + SrsGoApiGb28181(); + virtual ~SrsGoApiGb28181(); +public: + virtual srs_error_t serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r); +}; + class SrsHttpApi : virtual public SrsConnection, virtual public ISrsReloadHandler { private: diff --git a/trunk/src/app/srs_app_http_conn.cpp b/trunk/src/app/srs_app_http_conn.cpp index fc1831aa49..8f9429cbc1 100644 --- a/trunk/src/app/srs_app_http_conn.cpp +++ b/trunk/src/app/srs_app_http_conn.cpp @@ -198,19 +198,32 @@ srs_error_t SrsResponseOnlyHttpConn::pop_message(ISrsHttpMessage** preq) srs_error_t err = srs_success; SrsStSocket skt; - + if ((err = skt.initialize(stfd)) != srs_success) { return srs_error_wrap(err, "init socket"); } - - if ((err = parser->parse_message(&skt, preq)) != srs_success) { - return srs_error_wrap(err, "parse message"); + + // Check user interrupt by interval. + skt.set_recv_timeout(3 * SRS_UTIME_SECONDS); + + // drop all request body. + char body[4096]; + while (true) { + if ((err = trd->pull()) != srs_success) { + return srs_error_wrap(err, "timeout"); + } + + if ((err = skt.read(body, 4096, NULL)) != srs_success) { + // Because we use timeout to check trd state, so we should ignore any timeout. + if (srs_error_code(err) == ERROR_SOCKET_TIMEOUT) { + srs_freep(err); + continue; + } + + return srs_error_wrap(err, "read response"); + } } - // Attach owner connection to message. - SrsHttpMessage* hreq = (SrsHttpMessage*)(*preq); - hreq->set_connection(this); - return err; } @@ -219,12 +232,12 @@ srs_error_t SrsResponseOnlyHttpConn::on_got_http_message(ISrsHttpMessage* msg) srs_error_t err = srs_success; ISrsHttpResponseReader* br = msg->body_reader(); - + // when not specified the content length, ignore. if (msg->content_length() == -1) { return err; } - + // drop all request body. char body[4096]; while (!br->eof()) { @@ -236,6 +249,11 @@ srs_error_t SrsResponseOnlyHttpConn::on_got_http_message(ISrsHttpMessage* msg) return err; } +void SrsResponseOnlyHttpConn::expire() +{ + SrsHttpConn::expire(); +} + SrsHttpServer::SrsHttpServer(SrsServer* svr) { server = svr; diff --git a/trunk/src/app/srs_app_http_conn.hpp b/trunk/src/app/srs_app_http_conn.hpp index f3de6659e2..7529cfc5b7 100644 --- a/trunk/src/app/srs_app_http_conn.hpp +++ b/trunk/src/app/srs_app_http_conn.hpp @@ -101,6 +101,9 @@ class SrsResponseOnlyHttpConn : public SrsHttpConn virtual srs_error_t pop_message(ISrsHttpMessage** preq); public: virtual srs_error_t on_got_http_message(ISrsHttpMessage* msg); +public: + // Set connection to expired. + virtual void expire(); }; // The http server, use http stream or static server to serve requests. diff --git a/trunk/src/app/srs_app_http_stream.cpp b/trunk/src/app/srs_app_http_stream.cpp index fb7bd54b36..815561e1e8 100755 --- a/trunk/src/app/srs_app_http_stream.cpp +++ b/trunk/src/app/srs_app_http_stream.cpp @@ -592,10 +592,15 @@ srs_error_t SrsLiveStream::do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMess SrsAutoFree(SrsPithyPrint, pprint); SrsMessageArray msgs(SRS_PERF_MW_MSGS); + + // Use receive thread to accept the close event to avoid FD leak. + // @see https://github.com/ossrs/srs/issues/636#issuecomment-298208427 + SrsHttpMessage* hr = dynamic_cast(r); + SrsResponseOnlyHttpConn* hc = dynamic_cast(hr->connection()); // update the statistic when source disconveried. SrsStatistic* stat = SrsStatistic::instance(); - if ((err = stat->on_client(_srs_context->get_id(), req, NULL, SrsRtmpConnPlay)) != srs_success) { + if ((err = stat->on_client(_srs_context->get_id(), req, hc, SrsRtmpConnPlay)) != srs_success) { return srs_error_wrap(err, "stat on client"); } @@ -613,11 +618,6 @@ srs_error_t SrsLiveStream::do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMess } SrsFlvStreamEncoder* ffe = dynamic_cast(enc); - - // Use receive thread to accept the close event to avoid FD leak. - // @see https://github.com/ossrs/srs/issues/636#issuecomment-298208427 - SrsHttpMessage* hr = dynamic_cast(r); - SrsResponseOnlyHttpConn* hc = dynamic_cast(hr->connection()); // Set the socket options for transport. bool tcp_nodelay = _srs_config->get_tcp_nodelay(req->vhost); diff --git a/trunk/src/app/srs_app_listener.cpp b/trunk/src/app/srs_app_listener.cpp index b33b4f6f8e..0634e2855b 100755 --- a/trunk/src/app/srs_app_listener.cpp +++ b/trunk/src/app/srs_app_listener.cpp @@ -59,6 +59,13 @@ srs_error_t ISrsUdpHandler::on_stfd_change(srs_netfd_t /*fd*/) return srs_success; } +void ISrsUdpHandler::set_stfd(srs_netfd_t /*fd*/) +{ + +} + + + ISrsTcpHandler::ISrsTcpHandler() { } @@ -104,6 +111,8 @@ srs_error_t SrsUdpListener::listen() if ((err = srs_udp_listen(ip, port, &lfd)) != srs_success) { return srs_error_wrap(err, "listen %s:%d", ip.c_str(), port); } + + handler->set_stfd(lfd); srs_freep(trd); trd = new SrsSTCoroutine("udp", this); @@ -206,4 +215,3 @@ srs_error_t SrsTcpListener::cycle() return err; } - diff --git a/trunk/src/app/srs_app_listener.hpp b/trunk/src/app/srs_app_listener.hpp index d7d930e91c..5f112ae121 100644 --- a/trunk/src/app/srs_app_listener.hpp +++ b/trunk/src/app/srs_app_listener.hpp @@ -43,6 +43,8 @@ class ISrsUdpHandler // When fd changed, for instance, reload the listen port, // notify the handler and user can do something. virtual srs_error_t on_stfd_change(srs_netfd_t fd); + + virtual void set_stfd(srs_netfd_t fd); public: // When udp listener got a udp packet, notice server to process it. // @param type, the client type, used to create concrete connection, diff --git a/trunk/src/app/srs_app_server.cpp b/trunk/src/app/srs_app_server.cpp index e4fdf4db23..4f2676cb40 100644 --- a/trunk/src/app/srs_app_server.cpp +++ b/trunk/src/app/srs_app_server.cpp @@ -30,7 +30,9 @@ #include #include #include +#ifndef SRS_AUTO_OSX #include +#endif using namespace std; #include @@ -52,6 +54,8 @@ using namespace std; #include #include #include +#include +#include // system interval in srs_utime_t, // all resolution times should be times togother, @@ -109,6 +113,10 @@ std::string srs_listener_type2string(SrsListenerType type) return "RTSP"; case SrsListenerFlv: return "HTTP-FLV"; + case SrsListenerGb28181Sip: + return "GB28181-SIP over UDP"; + case SrsListenerGb28181RtpMux: + return "GB28181-Stream over RTP"; default: return "UNKONWN"; } @@ -301,7 +309,9 @@ srs_error_t SrsUdpStreamListener::listen(string i, int p) // the caller already ensure the type is ok, // we just assert here for unknown stream caster. - srs_assert(type == SrsListenerMpegTsOverUdp); + srs_assert(type == SrsListenerMpegTsOverUdp + || type == SrsListenerGb28181Sip + || type == SrsListenerGb28181RtpMux); ip = i; port = p; @@ -339,6 +349,26 @@ SrsUdpCasterListener::~SrsUdpCasterListener() srs_freep(caster); } + +SrsGb28181Listener::SrsGb28181Listener(SrsServer* svr, SrsListenerType t, SrsConfDirective* c) : SrsUdpStreamListener(svr, t, NULL) +{ + // the caller already ensure the type is ok, + // we just assert here for unknown stream caster. + srs_assert(type == SrsListenerGb28181Sip + ||type == SrsListenerGb28181RtpMux); + + if (type == SrsListenerGb28181Sip) { + caster = new SrsGb28181SipService(c); + }else if(type == SrsListenerGb28181RtpMux){ + caster = new SrsGb28181RtpMuxService(c); + } +} + +SrsGb28181Listener::~SrsGb28181Listener() +{ + srs_freep(caster); +} + SrsSignalManager* SrsSignalManager::instance = NULL; SrsSignalManager::SrsSignalManager(SrsServer* s) @@ -481,6 +511,7 @@ srs_error_t SrsInotifyWorker::start() { srs_error_t err = srs_success; +#ifndef SRS_AUTO_OSX // Whether enable auto reload config. bool auto_reload = _srs_config->inotify_auto_reload(); if (!auto_reload && _srs_in_docker && _srs_config->auto_reload_for_docker()) { @@ -551,6 +582,7 @@ srs_error_t SrsInotifyWorker::start() if ((err = trd->start()) != srs_success) { return srs_error_wrap(err, "inotify"); } +#endif return err; } @@ -559,6 +591,7 @@ srs_error_t SrsInotifyWorker::cycle() { srs_error_t err = srs_success; +#ifndef SRS_AUTO_OSX string config_path = _srs_config->config(); string config_file = srs_path_basename(config_path); string k8s_file = "..data"; @@ -598,6 +631,7 @@ srs_error_t SrsInotifyWorker::cycle() srs_usleep(3000 * SRS_UTIME_MILLISECONDS); } +#endif return err; } @@ -657,6 +691,9 @@ void SrsServer::destroy() srs_freep(signal_manager); srs_freep(conn_manager); + + //free global gb28281 manager + srs_freep(_srs_gb28181); } void SrsServer::dispose() @@ -761,7 +798,7 @@ srs_error_t SrsServer::initialize(ISrsServerCycle* ch) srs_error_t SrsServer::initialize_st() { srs_error_t err = srs_success; - + // @remark, st alloc segment use mmap, which only support 32757 threads, // if need to support more, for instance, 100k threads, define the macro MALLOC_STACK. // TODO: FIXME: maybe can use "sysctl vm.max_map_count" to refine. @@ -947,6 +984,10 @@ srs_error_t SrsServer::http_handle() if ((err = http_api_mux->handle("/api/v1/clusters", new SrsGoApiClusters())) != srs_success) { return srs_error_wrap(err, "handle raw"); } + + if ((err = http_api_mux->handle("/api/v1/gb28181", new SrsGoApiGb28181())) != srs_success) { + return srs_error_wrap(err, "handle raw"); + } // test the request info. if ((err = http_api_mux->handle("/api/v1/tests/requests", new SrsGoApiRequests())) != srs_success) { @@ -1301,6 +1342,30 @@ srs_error_t SrsServer::listen_http_stream() return err; } +srs_error_t SrsServer::listen_gb28281_sip(SrsConfDirective* stream_caster) +{ + srs_error_t err = srs_success; + + SrsListener* sip_listener = NULL; + sip_listener = new SrsGb28181Listener(this, SrsListenerGb28181Sip, stream_caster); + + int port = _srs_config->get_stream_caster_gb28181_sip_listen(stream_caster); + if (port <= 0) { + return srs_error_new(ERROR_STREAM_CASTER_PORT, "invalid sip port=%d", port); + } + + srs_assert(sip_listener != NULL); + + listeners.push_back(sip_listener); + + // TODO: support listen at <[ip:]port> + if ((err = sip_listener->listen(srs_any_address_for_listener(), port)) != srs_success) { + return srs_error_wrap(err, "listen at %d", port); + } + + return err; +} + srs_error_t SrsServer::listen_stream_caster() { srs_error_t err = srs_success; @@ -1325,18 +1390,34 @@ srs_error_t SrsServer::listen_stream_caster() listener = new SrsRtspListener(this, SrsListenerRtsp, stream_caster); } else if (srs_stream_caster_is_flv(caster)) { listener = new SrsHttpFlvListener(this, SrsListenerFlv, stream_caster); + } else if (srs_stream_caster_is_gb28181(caster)) { + //init global gb28281 manger + if (_srs_gb28181 == NULL){ + _srs_gb28181 = new SrsGb28181Manger(stream_caster); + if ((err = _srs_gb28181->initialize()) != srs_success){ + return err; + } + } + + //sip listener + if (_srs_config->get_stream_caster_gb28181_sip_enable(stream_caster)){ + if ((err = listen_gb28281_sip(stream_caster)) != srs_success){ + return err; + } + } + + //gb28281 stream listener + listener = new SrsGb28181Listener(this, SrsListenerGb28181RtpMux, stream_caster); } else { return srs_error_new(ERROR_STREAM_CASTER_ENGINE, "invalid caster %s", caster.c_str()); } srs_assert(listener != NULL); listeners.push_back(listener); - int port = _srs_config->get_stream_caster_listen(stream_caster); if (port <= 0) { return srs_error_new(ERROR_STREAM_CASTER_PORT, "invalid port=%d", port); } - // TODO: support listen at <[ip:]port> if ((err = listener->listen(srs_any_address_for_listener(), port)) != srs_success) { return srs_error_wrap(err, "listen at %d", port); diff --git a/trunk/src/app/srs_app_server.hpp b/trunk/src/app/srs_app_server.hpp index 56fa945dff..62766e26d5 100644 --- a/trunk/src/app/srs_app_server.hpp +++ b/trunk/src/app/srs_app_server.hpp @@ -36,6 +36,8 @@ #include #include #include +#include +#include class SrsServer; class SrsConnection; @@ -52,6 +54,8 @@ class SrsTcpListener; class SrsAppCasterFlv; class SrsRtspCaster; class SrsCoroutineManager; +class SrsGb28181Caster; + // The listener type for server to identify the connection, // that is, use different type to process the connection. @@ -69,6 +73,10 @@ enum SrsListenerType SrsListenerRtsp = 4, // TCP stream, FLV stream over HTTP. SrsListenerFlv = 5, + // UDP stream, gb28181 ps stream over rtp, + SrsListenerGb28181RtpMux = 6, + // UDP gb28181 sip server + SrsListenerGb28181Sip = 7, }; // A common tcp listener, for RTMP/HTTP server. @@ -156,6 +164,14 @@ class SrsUdpCasterListener : public SrsUdpStreamListener virtual ~SrsUdpCasterListener(); }; +// A UDP gb28181 listener, for sip and rtp stream mux server. +class SrsGb28181Listener : public SrsUdpStreamListener +{ +public: + SrsGb28181Listener(SrsServer* svr, SrsListenerType t, SrsConfDirective* c); + virtual ~SrsGb28181Listener(); +}; + // Convert signal to io, // @see: st-1.9/docs/notes.html class SrsSignalManager : public ISrsCoroutineHandler @@ -303,6 +319,7 @@ class SrsServer : virtual public ISrsReloadHandler, virtual public ISrsSourceHan virtual srs_error_t listen_http_api(); virtual srs_error_t listen_http_stream(); virtual srs_error_t listen_stream_caster(); + virtual srs_error_t listen_gb28281_sip(SrsConfDirective* c); // Close the listeners for specified type, // Remove the listen object from manager. virtual void close_listeners(SrsListenerType type); diff --git a/trunk/src/app/srs_app_utility.cpp b/trunk/src/app/srs_app_utility.cpp index 04ece71840..3a7ab640d8 100644 --- a/trunk/src/app/srs_app_utility.cpp +++ b/trunk/src/app/srs_app_utility.cpp @@ -35,6 +35,9 @@ #include #include #include +#ifdef SRS_AUTO_OSX +#include +#endif using namespace std; #include @@ -326,6 +329,7 @@ SrsProcSystemStat* srs_get_system_proc_stat() bool get_proc_system_stat(SrsProcSystemStat& r) { +#ifndef SRS_AUTO_OSX FILE* f = fopen("/proc/stat", "r"); if (f == NULL) { srs_warn("open system cpu stat failed, ignore"); @@ -355,6 +359,7 @@ bool get_proc_system_stat(SrsProcSystemStat& r) } fclose(f); +#endif r.ok = true; @@ -363,6 +368,7 @@ bool get_proc_system_stat(SrsProcSystemStat& r) bool get_proc_self_stat(SrsProcSelfStat& r) { +#ifndef SRS_AUTO_OSX FILE* f = fopen("/proc/self/stat", "r"); if (f == NULL) { srs_warn("open self cpu stat failed, ignore"); @@ -389,6 +395,7 @@ bool get_proc_self_stat(SrsProcSelfStat& r) &r.guest_time, &r.cguest_time); fclose(f); +#endif r.ok = true; @@ -484,6 +491,7 @@ SrsDiskStat* srs_get_disk_stat() bool srs_get_disk_vmstat_stat(SrsDiskStat& r) { +#ifndef SRS_AUTO_OSX FILE* f = fopen("/proc/vmstat", "r"); if (f == NULL) { srs_warn("open vmstat failed, ignore"); @@ -503,6 +511,7 @@ bool srs_get_disk_vmstat_stat(SrsDiskStat& r) } fclose(f); +#endif r.ok = true; @@ -513,13 +522,14 @@ bool srs_get_disk_diskstats_stat(SrsDiskStat& r) { r.ok = true; r.sample_time = srsu2ms(srs_get_system_time()); - + +#ifndef SRS_AUTO_OSX // if disabled, ignore all devices. SrsConfDirective* conf = _srs_config->get_stats_disk_device(); if (conf == NULL) { return true; } - + FILE* f = fopen("/proc/diskstats", "r"); if (f == NULL) { srs_warn("open vmstat failed, ignore"); @@ -584,6 +594,7 @@ bool srs_get_disk_diskstats_stat(SrsDiskStat& r) } fclose(f); +#endif r.ok = true; @@ -675,7 +686,8 @@ SrsMemInfo* srs_get_meminfo() void srs_update_meminfo() { SrsMemInfo& r = _srs_system_meminfo; - + +#ifndef SRS_AUTO_OSX FILE* f = fopen("/proc/meminfo", "r"); if (f == NULL) { srs_warn("open meminfo failed, ignore"); @@ -701,6 +713,7 @@ void srs_update_meminfo() } fclose(f); +#endif r.sample_time = srsu2ms(srs_get_system_time()); r.MemActive = r.MemTotal - r.MemFree; @@ -767,7 +780,8 @@ void srs_update_platform_info() SrsPlatformInfo& r = _srs_system_platform_info; r.srs_startup_time = srsu2ms(srs_get_system_startup_time()); - + +#ifndef SRS_AUTO_OSX if (true) { FILE* f = fopen("/proc/uptime", "r"); if (f == NULL) { @@ -796,7 +810,44 @@ void srs_update_platform_info() fclose(f); } - +#else + // man 3 sysctl + if (true) { + struct timeval tv; + size_t len = sizeof(timeval); + + int mib[2]; + mib[0] = CTL_KERN; + mib[1] = KERN_BOOTTIME; + if (sysctl(mib, 2, &tv, &len, NULL, 0) < 0) { + srs_warn("sysctl boottime failed, ignore"); + return; + } + + time_t bsec = tv.tv_sec; + time_t csec = ::time(NULL); + r.os_uptime = difftime(csec, bsec); + } + + // man 3 sysctl + if (true) { + struct loadavg la; + size_t len = sizeof(loadavg); + + int mib[2]; + mib[0] = CTL_VM; + mib[1] = VM_LOADAVG; + if (sysctl(mib, 2, &la, &len, NULL, 0) < 0) { + srs_warn("sysctl loadavg failed, ignore"); + return; + } + + r.load_one_minutes = (double)la.ldavg[0] / la.fscale; + r.load_five_minutes = (double)la.ldavg[1] / la.fscale; + r.load_fifteen_minutes = (double)la.ldavg[2] / la.fscale; + } +#endif + r.ok = true; } @@ -842,6 +893,7 @@ int srs_get_network_devices_count() void srs_update_network_devices() { +#ifndef SRS_AUTO_OSX if (true) { FILE* f = fopen("/proc/net/dev", "r"); if (f == NULL) { @@ -878,6 +930,7 @@ void srs_update_network_devices() fclose(f); } +#endif } SrsNetworkRtmpServer::SrsNetworkRtmpServer() @@ -924,7 +977,8 @@ void srs_update_rtmp_server(int nb_conn, SrsKbps* kbps) int nb_tcp_total = 0; int nb_tcp_mem = 0; int nb_udp4 = 0; - + +#ifndef SRS_AUTO_OSX if (true) { FILE* f = fopen("/proc/net/sockstat", "r"); if (f == NULL) { @@ -954,9 +1008,20 @@ void srs_update_rtmp_server(int nb_conn, SrsKbps* kbps) fclose(f); } - +#else + // TODO: FIXME: impelments it. + nb_socks = 0; + nb_tcp4_hashed = 0; + nb_tcp_orphans = 0; + nb_tcp_tws = 0; + nb_tcp_total = 0; + nb_tcp_mem = 0; + nb_udp4 = 0; +#endif + int nb_tcp_estab = 0; - + +#ifndef SRS_AUTO_OSX if (true) { FILE* f = fopen("/proc/net/snmp", "r"); if (f == NULL) { @@ -986,6 +1051,7 @@ void srs_update_rtmp_server(int nb_conn, SrsKbps* kbps) fclose(f); } +#endif // @see: https://github.com/shemminger/iproute2/blob/master/misc/ss.c // TODO: FIXME: ignore the slabstat, @see: get_slabstat() diff --git a/trunk/src/core/srs_core_version3.hpp b/trunk/src/core/srs_core_version3.hpp index 5ff02aa5a8..c732e77bea 100644 --- a/trunk/src/core/srs_core_version3.hpp +++ b/trunk/src/core/srs_core_version3.hpp @@ -24,6 +24,6 @@ #ifndef SRS_CORE_VERSION3_HPP #define SRS_CORE_VERSION3_HPP -#define SRS_VERSION3_REVISION 134 +#define SRS_VERSION3_REVISION 139 #endif diff --git a/trunk/src/kernel/srs_kernel_error.hpp b/trunk/src/kernel/srs_kernel_error.hpp index 558bde4056..331e7541e3 100644 --- a/trunk/src/kernel/srs_kernel_error.hpp +++ b/trunk/src/kernel/srs_kernel_error.hpp @@ -325,6 +325,22 @@ #define ERROR_BASE64_DECODE 4039 #define ERROR_HTTP_STREAM_EOF 4040 +/////////////////////////////////////////////////////// +// GB28181 API error. +/////////////////////////////////////////////////////// +#define ERROR_GB28181_SERVER_NOT_RUN 6000 +#define ERROR_GB28181_SESSION_IS_EXIST 6001 +#define ERROR_GB28181_SESSION_IS_NOTEXIST 6002 +#define ERROR_GB28181_RTP_PORT_FULL 6003 +#define ERROR_GB28181_PORT_MODE_INVALID 6004 +#define ERROR_GB28181_VALUE_EMPTY 6005 +#define ERROR_GB28181_ACTION_INVALID 6006 +#define ERROR_GB28181_SIP_NOT_RUN 6007 +#define ERROR_GB28281_SIP_INVITE_FAILED 6008 +#define ERROR_GB28281_SIP_BYE_FAILED 6009 +#define ERROR_GB28281_SIP_IS_INVITING 6010 +#define ERROR_GB28281_CREATER_RTMPMUXER_FAILED 6011 + /////////////////////////////////////////////////////// // HTTP API error. /////////////////////////////////////////////////////// diff --git a/trunk/src/kernel/srs_kernel_utility.cpp b/trunk/src/kernel/srs_kernel_utility.cpp index 5fe03a1290..1d91b72bd5 100644 --- a/trunk/src/kernel/srs_kernel_utility.cpp +++ b/trunk/src/kernel/srs_kernel_utility.cpp @@ -124,7 +124,9 @@ srs_utime_t srs_get_system_startup_time() } // For utest to mock it. +#ifndef SRS_AUTO_OSX _srs_gettimeofday_t _srs_gettimeofday = ::gettimeofday; +#endif srs_utime_t srs_update_system_time() { diff --git a/trunk/src/kernel/srs_kernel_utility.hpp b/trunk/src/kernel/srs_kernel_utility.hpp index 04d3862987..c63937430d 100644 --- a/trunk/src/kernel/srs_kernel_utility.hpp +++ b/trunk/src/kernel/srs_kernel_utility.hpp @@ -166,7 +166,11 @@ extern int srs_chunk_header_c3(int perfer_cid, uint32_t timestamp, char* cache, // For utest to mock it. #include -typedef int (*_srs_gettimeofday_t)(struct timeval* tv, struct timezone* tz); +#ifdef SRS_AUTO_OSX + #define _srs_gettimeofday gettimeofday +#else + typedef int (*_srs_gettimeofday_t) (struct timeval* tv, struct timezone* tz); +#endif #endif diff --git a/trunk/src/main/srs_main_server.cpp b/trunk/src/main/srs_main_server.cpp index 126ee6f16f..358c1a8f08 100644 --- a/trunk/src/main/srs_main_server.cpp +++ b/trunk/src/main/srs_main_server.cpp @@ -127,8 +127,8 @@ srs_error_t do_main(int argc, char** argv) srs_trace("%s, %s", RTMP_SIG_SRS_SERVER, RTMP_SIG_SRS_LICENSE); srs_trace("authors: %s", RTMP_SIG_SRS_AUTHORS); srs_trace("contributors: %s", SRS_AUTO_CONSTRIBUTORS); - srs_trace("cwd=%s, work_dir=%s, build: %s, configure: %s, uname: %s", - _srs_config->cwd().c_str(), cwd.c_str(), SRS_AUTO_BUILD_DATE, SRS_AUTO_USER_CONFIGURE, SRS_AUTO_UNAME); + srs_trace("cwd=%s, work_dir=%s, build: %s, configure: %s, uname: %s, osx: %d", + _srs_config->cwd().c_str(), cwd.c_str(), SRS_AUTO_BUILD_DATE, SRS_AUTO_USER_CONFIGURE, SRS_AUTO_UNAME, SRS_AUTO_OSX_BOOL); srs_trace("configure detail: " SRS_AUTO_CONFIGURE); #ifdef SRS_AUTO_EMBEDED_TOOL_CHAIN srs_trace("crossbuild tool chain: " SRS_AUTO_EMBEDED_TOOL_CHAIN); diff --git a/trunk/src/protocol/srs_protocol_utility.cpp b/trunk/src/protocol/srs_protocol_utility.cpp index 251d69854f..57e65ecf09 100644 --- a/trunk/src/protocol/srs_protocol_utility.cpp +++ b/trunk/src/protocol/srs_protocol_utility.cpp @@ -336,21 +336,25 @@ srs_error_t srs_write_large_iovs(ISrsProtocolReadWriter* skt, iovec* iovs, int s #endif // send in a time. - if (size < limits) { + if (size <= limits) { if ((err = skt->writev(iovs, size, pnwrite)) != srs_success) { return srs_error_wrap(err, "writev"); } return err; } - + // send in multiple times. int cur_iov = 0; + ssize_t nwrite = 0; while (cur_iov < size) { int cur_count = srs_min(limits, size - cur_iov); - if ((err = skt->writev(iovs + cur_iov, cur_count, pnwrite)) != srs_success) { + if ((err = skt->writev(iovs + cur_iov, cur_count, &nwrite)) != srs_success) { return srs_error_wrap(err, "writev"); } cur_iov += cur_count; + if (pnwrite) { + *pnwrite += nwrite; + } } return err; diff --git a/trunk/src/protocol/srs_sip_stack.cpp b/trunk/src/protocol/srs_sip_stack.cpp new file mode 100644 index 0000000000..e24e743c24 --- /dev/null +++ b/trunk/src/protocol/srs_sip_stack.cpp @@ -0,0 +1,776 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2013-2020 Lixin + * + * 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 + +#if !defined(SRS_EXPORT_LIBRTMP) + +#include +#include +#include +#include + +using namespace std; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +unsigned int srs_sip_random(int min,int max) +{ + srand(int(time(0))); + return rand() % (max - min + 1) + min; +} + +std::string srs_sip_get_form_to_uri(std::string msg) +{ + //;tag=536961166 + //sip:34020000002000000001@3402000000 + + size_t pos = msg.find("<"); + if (pos == string::npos) { + return msg; + } + + msg = msg.substr(pos+1); + + size_t pos2 = msg.find(">"); + if (pos2 == string::npos) { + return msg; + } + + msg = msg.substr(0, pos2); + return msg; +} + +std::string srs_sip_get_utc_date() +{ + // clock time + timeval tv; + if (gettimeofday(&tv, NULL) == -1) { + return ""; + } + + // to calendar time + struct tm* tm; + if ((tm = gmtime(&tv.tv_sec)) == NULL) { + return ""; + } + + //Date: 2020-03-21T14:20:57.638 + std::string utc_date = ""; + char buffer[25] = {0}; + snprintf(buffer, 25, + "%d-%02d-%02dT%02d:%02d:%02d.%03d", + 1900 + tm->tm_year, 1 + tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, (int)(tv.tv_usec / 1000)); + utc_date = buffer; + return utc_date; +} + + +std::string srs_sip_get_param(std::string msg, std::string param) +{ + std::vector vec_params = srs_string_split(msg, ";"); + + size_t min_pos = string::npos; + for (vector::iterator it = vec_params.begin(); it != vec_params.end(); ++it) { + string value = *it; + + size_t pos = value.find(param); + if (pos == string::npos) { + continue; + } + + std::vector v_pram = srs_string_split(value, "="); + + if (v_pram.size() > 0) { + return v_pram.at(1); + } + } + return ""; +} + +SrsSipRequest::SrsSipRequest() +{ + seq = 0; + content_length = 0; + sdp = NULL; + transport = NULL; + + method = ""; + uri = "";; + version = "";; + seq = 0; + content_type = ""; + content_length = 0; + call_id = ""; + from = ""; + to = ""; + via = ""; + from_tag = ""; + to_tag = ""; + contact = ""; + user_agent = ""; + branch = ""; + status = ""; + expires = 3600; + max_forwards = 70; + www_authenticate = ""; + authorization = ""; + cmdtype = SrsSipCmdRequest; + + host = "127.0.0.1";; + host_port = 5060; + + serial = "";; + realm = "";; + + sip_auth_id = ""; + sip_auth_pwd = ""; + sip_username = ""; + peer_ip = ""; + peer_port = 0; +} + +SrsSipRequest::~SrsSipRequest() +{ + srs_freep(sdp); + srs_freep(transport); +} + +bool SrsSipRequest::is_register() +{ + return method == SRS_SIP_METHOD_REGISTER; +} + +bool SrsSipRequest::is_invite() +{ + return method == SRS_SIP_METHOD_INVITE; +} + +bool SrsSipRequest::is_ack() +{ + return method == SRS_SIP_METHOD_ACK; +} + +bool SrsSipRequest::is_message() +{ + return method == SRS_SIP_METHOD_MESSAGE; +} + +bool SrsSipRequest::is_bye() +{ + return method == SRS_SIP_METHOD_BYE; +} + +std::string SrsSipRequest::get_cmdtype_str() +{ + switch(cmdtype) { + case SrsSipCmdRequest: + return "request"; + case SrsSipCmdRespone: + return "respone"; + } + + return ""; +} + +void SrsSipRequest::copy(SrsSipRequest* src) +{ + if (!src){ + return; + } + + method = src->method; + uri = src->uri; + version = src->version; + seq = src->seq; + content_type = src->content_type; + content_length = src->content_length; + call_id = src->call_id; + from = src->from; + to = src->to; + via = src->via; + from_tag = src->from_tag; + to_tag = src->to_tag; + contact = src->contact; + user_agent = src->user_agent; + branch = src->branch; + status = src->status; + expires = src->expires; + max_forwards = src->max_forwards; + www_authenticate = src->www_authenticate; + authorization = src->authorization; + cmdtype = src->cmdtype; + + host = src->host; + host_port = src->host_port; + + serial = src->serial; + realm = src->realm; + + sip_auth_id = src->sip_auth_id; + sip_auth_pwd = src->sip_auth_pwd; + sip_username = src->sip_username; + peer_ip = src->peer_ip; + peer_port = src->peer_port; +} + +SrsSipStack::SrsSipStack() +{ + buf = new SrsSimpleStream(); +} + +SrsSipStack::~SrsSipStack() +{ + srs_freep(buf); +} + +srs_error_t SrsSipStack::parse_request(SrsSipRequest** preq, const char* recv_msg, int nb_len) +{ + srs_error_t err = srs_success; + + SrsSipRequest* req = new SrsSipRequest(); + if ((err = do_parse_request(req, recv_msg)) != srs_success) { + srs_freep(req); + return srs_error_wrap(err, "recv message"); + } + + *preq = req; + + return err; +} + +srs_error_t SrsSipStack::do_parse_request(SrsSipRequest* req, const char* recv_msg) +{ + srs_error_t err = srs_success; + + std::vector header_body = srs_string_split(recv_msg, SRS_RTSP_CRLFCRLF); + std::string header = header_body.at(0); + std::string body = ""; + + if (header_body.size() > 1){ + body = header_body.at(1); + } + + srs_info("sip: header=%s\n", header.c_str()); + srs_info("sip: body=%s\n", body.c_str()); + + // parse one by one. + char* start = (char*)header.c_str(); + char* end = start + header.size(); + char* p = start; + char* newline_start = start; + std::string firstline = ""; + while (p < end) { + if (p[0] == '\r' && p[1] == '\n'){ + p +=2; + int linelen = (int)(p - newline_start); + std::string oneline(newline_start, linelen); + newline_start = p; + + if (firstline == ""){ + firstline = oneline; + srs_info("sip: first line=%s", firstline.c_str()); + }else{ + size_t pos = oneline.find(":"); + if (pos != string::npos){ + if (pos != 0) { + //ex: CSeq: 100 MESSAGE header is 'CSeq:',content is '100 MESSAGE' + std::string head = oneline.substr(0, pos+1); + std::string content = oneline.substr(pos+1, oneline.length()-pos-1); + content = srs_string_replace(content, "\r\n", ""); + content = srs_string_trim_start(content, " "); + char *phead = (char*)head.c_str(); + + if (!strcasecmp(phead, "call-id:")) { + std::vector vec_callid = srs_string_split(content, " "); + req->call_id = vec_callid.at(0); + } + else if (!strcasecmp(phead, "contact:")) { + req->contact = content; + } + else if (!strcasecmp(phead, "content-encoding:")) { + srs_trace("sip: message head %s content=%s", phead, content.c_str()); + } + else if (!strcasecmp(phead, "content-length:")) { + req->content_length = strtoul(content.c_str(), NULL, 10); + } + else if (!strcasecmp(phead, "content-type:")) { + req->content_type = content; + } + else if (!strcasecmp(phead, "cseq:")) { + std::vector vec_seq = srs_string_split(content, " "); + req->seq = strtoul(vec_seq.at(0).c_str(), NULL, 10); + req->method = vec_seq.at(1); + } + else if (!strcasecmp(phead, "from:")) { + content = srs_string_replace(content, "sip:", ""); + req->from = srs_sip_get_form_to_uri(content.c_str()); + if (srs_string_contains(content, "tag")) { + req->from_tag = srs_sip_get_param(content.c_str(), "tag"); + } + } + else if (!strcasecmp(phead, "to:")) { + content = srs_string_replace(content, "sip:", ""); + req->to = srs_sip_get_form_to_uri(content.c_str()); + if (srs_string_contains(content, "tag")) { + req->to_tag = srs_sip_get_param(content.c_str(), "tag"); + } + } + else if (!strcasecmp(phead, "via:")) { + std::vector vec_seq = srs_string_split(content, ";"); + req->via = content; + req->branch = srs_sip_get_param(content.c_str(), "branch"); + } + else if (!strcasecmp(phead, "expires:")){ + req->expires = strtoul(content.c_str(), NULL, 10); + } + else if (!strcasecmp(phead, "user-agent:")){ + req->user_agent = content; + } + else if (!strcasecmp(phead, "max-forwards:")){ + req->max_forwards = strtoul(content.c_str(), NULL, 10); + } + else if (!strcasecmp(phead, "www-authenticate:")){ + req->www_authenticate = content; + } + else if (!strcasecmp(phead, "authorization:")){ + req->authorization = content; + } + else { + //TODO: fixme + srs_trace("sip: unkonw message head %s content=%s", phead, content.c_str()); + } + } + } + } + }else{ + p++; + } + } + + std::vector method_uri_ver = srs_string_split(firstline, " "); + //respone first line text:SIP/2.0 200 OK + if (!strcasecmp(method_uri_ver.at(0).c_str(), "sip/2.0")) { + req->cmdtype = SrsSipCmdRespone; + //req->method= vec_seq.at(1); + req->status = method_uri_ver.at(1); + req->version = method_uri_ver.at(0); + req->uri = req->from; + + vector str = srs_string_split(req->to, "@"); + req->sip_auth_id = srs_string_replace(str.at(0), "sip:", ""); + + }else {//request first line text :MESSAGE sip:34020000002000000001@3402000000 SIP/2.0 + req->cmdtype = SrsSipCmdRequest; + req->method= method_uri_ver.at(0); + req->uri = method_uri_ver.at(1); + req->version = method_uri_ver.at(2); + + vector str = srs_string_split(req->from, "@"); + req->sip_auth_id = srs_string_replace(str.at(0), "sip:", ""); + } + + req->sip_username = req->sip_auth_id; + + srs_info("sip: method=%s uri=%s version=%s cmdtype=%s", + req->method.c_str(), req->uri.c_str(), req->version.c_str(), req->get_cmdtype_str().c_str()); + srs_info("via=%s", req->via.c_str()); + srs_info("via_branch=%s", req->branch.c_str()); + srs_info("cseq=%d", req->seq); + srs_info("contact=%s", req->contact.c_str()); + srs_info("from=%s", req->from.c_str()); + srs_info("to=%s", req->to.c_str()); + srs_info("callid=%s", req->call_id.c_str()); + srs_info("status=%s", req->status.c_str()); + srs_info("from_tag=%s", req->from_tag.c_str()); + srs_info("to_tag=%s", req->to_tag.c_str()); + srs_info("sip_auth_id=%s", req->sip_auth_id.c_str()); + + return err; +} + +void SrsSipStack::resp_keepalive(std::stringstream& ss, SrsSipRequest *req){ + ss << SRS_SIP_VERSION <<" 200 OK" << SRS_RTSP_CRLF + << "Via: " << SRS_SIP_VERSION << "/UDP " << req->host << ":" << req->host_port << ";branch=" << req->branch << SRS_RTSP_CRLF + << "From: from.c_str() << ">;tag=" << req->from_tag << SRS_RTSP_CRLF + << "To: to.c_str() << ">\r\n" + << "Call-ID: " << req->call_id << SRS_RTSP_CRLF + << "CSeq: " << req->seq << " " << req->method << SRS_RTSP_CRLF + << "Contact: "<< req->contact << SRS_RTSP_CRLF + << "Max-Forwards: 70" << SRS_RTSP_CRLF + << "User-Agent: "<< SRS_SIP_USER_AGENT << SRS_RTSP_CRLF + << "Content-Length: 0" << SRS_RTSP_CRLFCRLF; +} + +void SrsSipStack::resp_status(stringstream& ss, SrsSipRequest *req) +{ + if (req->method == "REGISTER"){ + /* + //request: sip-agent-----REGISTER------->sip-server + REGISTER sip:34020000002000000001@3402000000 SIP/2.0 + Via: SIP/2.0/UDP 192.168.137.11:5060;rport;branch=z9hG4bK1371463273 + From: ;tag=2043466181 + To: + Call-ID: 1011047669 + CSeq: 1 REGISTER + Contact: + Max-Forwards: 70 + User-Agent: IP Camera + Expires: 3600 + Content-Length: 0 + + //response: sip-agent<-----200 OK--------sip-server + SIP/2.0 200 OK + Via: SIP/2.0/UDP 192.168.137.11:5060;rport;branch=z9hG4bK1371463273 + From: + To: + CSeq: 1 REGISTER + Call-ID: 1011047669 + Contact: + User-Agent: SRS/4.0.4(Leo) + Expires: 3600 + Content-Length: 0 + + */ + if (req->authorization.empty()){ + //TODO: fixme supoort 401 + //return req_401_unauthorized(ss, req); + } + + ss << SRS_SIP_VERSION <<" 200 OK" << SRS_RTSP_CRLF + << "Via: " << req->via << SRS_RTSP_CRLF + << "From: from << ">" << SRS_RTSP_CRLF + << "To: to << ">" << SRS_RTSP_CRLF + << "CSeq: "<< req->seq << " " << req->method << SRS_RTSP_CRLF + << "Call-ID: " << req->call_id << SRS_RTSP_CRLF + << "Contact: " << req->contact << SRS_RTSP_CRLF + << "User-Agent: " << SRS_SIP_USER_AGENT << SRS_RTSP_CRLF + << "Expires: " << req->expires << SRS_RTSP_CRLF + << "Content-Length: 0" << SRS_RTSP_CRLFCRLF; + }else{ + /* + //request: sip-agnet-------MESSAGE------->sip-server + MESSAGE sip:34020000002000000001@3402000000 SIP/2.0 + Via: SIP/2.0/UDP 192.168.137.11:5060;rport;branch=z9hG4bK1066375804 + From: ;tag=1925919231 + To: + Call-ID: 1185236415 + CSeq: 20 MESSAGE + Content-Type: Application/MANSCDP+xml + Max-Forwards: 70 + User-Agent: IP Camera + Content-Length: 175 + + + + Keepalive + 1 + 34020000001320000003 + OK + + + + //response: sip-agent------200 OK --------> sip-server + SIP/2.0 200 OK + Via: SIP/2.0/UDP 192.168.137.11:5060;rport;branch=z9hG4bK1066375804 + From: + To: + CSeq: 20 MESSAGE + Call-ID: 1185236415 + User-Agent: SRS/4.0.4(Leo) + Content-Length: 0 + + */ + + ss << SRS_SIP_VERSION <<" 200 OK" << SRS_RTSP_CRLF + << "Via: " << req->via << SRS_RTSP_CRLF + << "From: from << ">" << SRS_RTSP_CRLF + << "To: to << ">" << SRS_RTSP_CRLF + << "CSeq: "<< req->seq << " " << req->method << SRS_RTSP_CRLF + << "Call-ID: " << req->call_id << SRS_RTSP_CRLF + << "User-Agent: " << SRS_SIP_USER_AGENT << SRS_RTSP_CRLF + << "Content-Length: 0" << SRS_RTSP_CRLFCRLF; + } + +} + +void SrsSipStack::req_invite(stringstream& ss, SrsSipRequest *req, string ip, int port, uint32_t ssrc) +{ + /* + //request: sip-agent <-------INVITE------ sip-server + INVITE sip:34020000001320000003@3402000000 SIP/2.0 + Via: SIP/2.0/UDP 39.100.155.146:15063;rport;branch=z9hG4bK34208805 + From: ;tag=512358805 + To: + Call-ID: 200008805 + CSeq: 20 INVITE + Content-Type: Application/SDP + Contact: + Max-Forwards: 70 + User-Agent: SRS/4.0.4(Leo) + Subject: 34020000001320000003:630886,34020000002000000001:0 + Content-Length: 164 + + v=0 + o=34020000001320000003 0 0 IN IP4 39.100.155.146 + s=Play + c=IN IP4 39.100.155.146 + t=0 0 + m=video 9000 RTP/AVP 96 + a=recvonly + a=rtpmap:96 PS/90000 + y=630886 + //response: sip-agent --------100 Trying--------> sip-server + SIP/2.0 100 Trying + Via: SIP/2.0/UDP 39.100.155.146:15063;rport=15063;branch=z9hG4bK34208805 + From: ;tag=512358805 + To: + Call-ID: 200008805 + CSeq: 20 INVITE + User-Agent: IP Camera + Content-Length: 0 + + //response: sip-agent -------200 OK--------> sip-server + SIP/2.0 200 OK + Via: SIP/2.0/UDP 39.100.155.146:15063;rport=15063;branch=z9hG4bK34208805 + From: ;tag=512358805 + To: ;tag=1083111311 + Call-ID: 200008805 + CSeq: 20 INVITE + Contact: + Content-Type: application/sdp + User-Agent: IP Camera + Content-Length: 263 + + v=0 + o=34020000001320000003 1073 1073 IN IP4 192.168.137.11 + s=Play + c=IN IP4 192.168.137.11 + t=0 0 + m=video 15060 RTP/AVP 96 + a=setup:active + a=sendonly + a=rtpmap:96 PS/90000 + a=username:34020000001320000003 + a=password:12345678 + a=filesize:0 + y=0000630886 + f= + //request: sip-agent <------ ACK ------- sip-server + ACK sip:34020000001320000003@3402000000 SIP/2.0 + Via: SIP/2.0/UDP 39.100.155.146:15063;rport;branch=z9hG4bK34208805 + From: ;tag=512358805 + To: + Call-ID: 200008805 + CSeq: 20 ACK + Max-Forwards: 70 + User-Agent: SRS/4.0.4(Leo) + Content-Length: 0 + */ + + std::stringstream sdp; + sdp << "v=0" << SRS_RTSP_CRLF + << "o=" << req->sip_auth_id << " 0 0 IN IP4 " << ip << SRS_RTSP_CRLF + << "s=Play" << SRS_RTSP_CRLF + << "c=IN IP4 " << ip << SRS_RTSP_CRLF + << "t=0 0" << SRS_RTSP_CRLF + //TODO 97 98 99 current no support + //<< "m=video " << port <<" RTP/AVP 96 97 98 99" << SRS_RTSP_CRLF + << "m=video " << port <<" RTP/AVP 96" << SRS_RTSP_CRLF + << "a=recvonly" << SRS_RTSP_CRLF + << "a=rtpmap:96 PS/90000" << SRS_RTSP_CRLF + //TODO: current no support + //<< "a=rtpmap:97 MPEG4/90000" << SRS_RTSP_CRLF + //<< "a=rtpmap:98 H264/90000" << SRS_RTSP_CRLF + //<< "a=rtpmap:99 H265/90000" << SRS_RTSP_CRLF + //<< "a=streamMode:MAIN\r\n" + //<< "a=filesize:0\r\n" + << "y=" << ssrc << SRS_RTSP_CRLF; + + + int rand = srs_sip_random(1000, 9999); + std::stringstream from, to, uri, branch, from_tag; + //"INVITE sip:34020000001320000001@3402000000 SIP/2.0\r\n + uri << "sip:" << req->sip_auth_id << "@" << req->realm; + //From: ;tag=500485%d\r\n + from << req->serial << "@" << req->host << ":" << req->host_port; + to << req->sip_auth_id << "@" << req->realm; + + req->from = from.str(); + req->to = to.str(); + req->uri = uri.str(); + + branch << "z9hG4bK3420" << rand; + from_tag << "51235" << rand; + req->branch = branch.str(); + req->from_tag = from_tag.str(); + + ss << "INVITE " << req->uri << " " << SRS_SIP_VERSION << SRS_RTSP_CRLF + << "Via: " << SRS_SIP_VERSION << "/UDP "<< req->host << ":" << req->host_port << ";rport;branch=" << req->branch << SRS_RTSP_CRLF + << "From: from << ">;tag=" << req->from_tag << SRS_RTSP_CRLF + << "To: to << ">" << SRS_RTSP_CRLF + << "Call-ID: 20000" << rand <to << ">" << SRS_RTSP_CRLF + << "Max-Forwards: 70" << " \r\n" + << "User-Agent: " << SRS_SIP_USER_AGENT <sip_auth_id << ":" << ssrc << "," << req->serial << ":0" << SRS_RTSP_CRLF + << "Content-Length: " << sdp.str().length() << SRS_RTSP_CRLFCRLF + << sdp.str(); +} + + +void SrsSipStack::req_401_unauthorized(std::stringstream& ss, SrsSipRequest *req) +{ + /* sip-agent <-----401 Unauthorized ------ sip-server + SIP/2.0 401 Unauthorized + Via: SIP/2.0/UDP 192.168.137.92:5061;rport=61378;received=192.168.1.13;branch=z9hG4bK802519080 + From: ;tag=611442989 + To: ;tag=102092689 + CSeq: 1 REGISTER + Call-ID: 1650345118 + User-Agent: LiveGBS v200228 + Contact: + Content-Length: 0 + WWW-Authenticate: Digest realm="3402000000",qop="auth",nonce="f1da98bd160f3e2efe954c6eedf5f75a" + */ + + ss << SRS_SIP_VERSION <<" 401 Unauthorized" << SRS_RTSP_CRLF + //<< "Via: " << req->via << SRS_RTSP_CRLF + << "Via: " << req->via << ";rport=" << req->peer_port << ";received=" << req->peer_ip << ";branch=" << req->branch << SRS_RTSP_CRLF + << "From: from << ">" << SRS_RTSP_CRLF + << "To: to << ">" << SRS_RTSP_CRLF + << "CSeq: "<< req->seq << " " << req->method << SRS_RTSP_CRLF + << "Call-ID: " << req->call_id << SRS_RTSP_CRLF + << "Contact: " << req->contact << SRS_RTSP_CRLF + << "User-Agent: " << SRS_SIP_USER_AGENT << SRS_RTSP_CRLF + << "Content-Length: 0" << SRS_RTSP_CRLF + << "WWW-Authenticate: Digest realm=\"3402000000\",qop=\"auth\",nonce=\"f1da98bd160f3e2efe954c6eedf5f75a\"" << SRS_RTSP_CRLFCRLF; + return; +} + +void SrsSipStack::resp_ack(std::stringstream& ss, SrsSipRequest *req){ + /* + //request: sip-agent <------ ACK ------- sip-server + ACK sip:34020000001320000003@3402000000 SIP/2.0 + Via: SIP/2.0/UDP 39.100.155.146:15063;rport;branch=z9hG4bK34208805 + From: ;tag=512358805 + To: + Call-ID: 200008805 + CSeq: 20 ACK + Max-Forwards: 70 + User-Agent: SRS/4.0.4(Leo) + Content-Length: 0 + */ + + ss << "ACK " << "sip:" << req->sip_auth_id << "@" << req->realm << " "<< SRS_SIP_VERSION << SRS_RTSP_CRLF + << "Via: " << SRS_SIP_VERSION << "/UDP " << req->host << ":" << req->host_port << ";rport;branch=" << req->branch << SRS_RTSP_CRLF + << "From: serial << "@" << req->host + ":" << req->host_port << ">;tag=" << req->from_tag << SRS_RTSP_CRLF + << "To: sip_auth_id << "@" << req->realm << ">\r\n" + << "Call-ID: " << req->call_id << SRS_RTSP_CRLF + << "CSeq: " << req->seq << " ACK"<< SRS_RTSP_CRLF + << "Max-Forwards: 70" << SRS_RTSP_CRLF + << "User-Agent: "<< SRS_SIP_USER_AGENT << SRS_RTSP_CRLF + << "Content-Length: 0" << SRS_RTSP_CRLFCRLF; +} + +void SrsSipStack::req_bye(std::stringstream& ss, SrsSipRequest *req) +{ + /* + //request: sip-agent <------BYE------ sip-server + BYE sip:34020000001320000003@3402000000 SIP/2.0 + Via: SIP/2.0/UDP 39.100.155.146:15063;rport;branch=z9hG4bK34208805 + From: ;tag=512358805 + To: ;tag=1083111311 + Call-ID: 200008805 + CSeq: 79 BYE + Max-Forwards: 70 + User-Agent: SRS/4.0.4(Leo) + Content-Length: 0 + + //response: sip-agent ------200 OK ------> sip-server + SIP/2.0 200 OK + Via: SIP/2.0/UDP 39.100.155.146:15063;rport=15063;branch=z9hG4bK34208805 + From: ;tag=512358805 + To: ;tag=1083111311 + Call-ID: 200008805 + CSeq: 79 BYE + User-Agent: IP Camera + Content-Length: 0 + + */ + + std::stringstream from, to, uri; + uri << "sip:" << req->sip_auth_id << "@" << req->realm; + from << req->serial << "@" << req->realm; + to << req->sip_auth_id << "@" << req->realm; + + req->from = from.str(); + req->to = to.str(); + req->uri = uri.str(); + + string to_tag, from_tag, branch; + + if (req->branch.empty()){ + branch = ""; + }else { + branch = ";branch=" + req->branch; + } + + if (req->from_tag.empty()){ + from_tag = ""; + }else { + from_tag = ";tag=" + req->from_tag; + } + + if (req->to_tag.empty()){ + to_tag = ""; + }else { + to_tag = ";tag=" + req->to_tag; + } + + int seq = srs_sip_random(22, 99); + ss << "BYE " << req->uri << " "<< SRS_SIP_VERSION << SRS_RTSP_CRLF + << "Via: "<< SRS_SIP_VERSION << "/UDP "<< req->host << ":" << req->host_port << ";rport" << branch << SRS_RTSP_CRLF + << "From: from << ">" << from_tag << SRS_RTSP_CRLF + << "To: to << ">" << to_tag << SRS_RTSP_CRLF + << "Call-ID: " << req->call_id << SRS_RTSP_CRLF + << "CSeq: "<< seq <<" BYE" << SRS_RTSP_CRLF + << "Max-Forwards: 70" << SRS_RTSP_CRLF + << "User-Agent: " << SRS_SIP_USER_AGENT << SRS_RTSP_CRLF + << "Content-Length: 0" << SRS_RTSP_CRLFCRLF; + +} + +#endif + diff --git a/trunk/src/protocol/srs_sip_stack.hpp b/trunk/src/protocol/srs_sip_stack.hpp new file mode 100644 index 0000000000..006ecbba32 --- /dev/null +++ b/trunk/src/protocol/srs_sip_stack.hpp @@ -0,0 +1,147 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2013-2020 Lixin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef SRS_PROTOCOL_SIP_HPP +#define SRS_PROTOCOL_SIP_HPP + +#include + +#if !defined(SRS_EXPORT_LIBRTMP) + +#include +#include + +#include +#include + +class SrsBuffer; +class SrsSimpleStream; +class SrsAudioFrame; + +// SIP methods +#define SRS_SIP_METHOD_REGISTER "REGISTER" +#define SRS_SIP_METHOD_MESSAGE "MESSAGE" +#define SRS_SIP_METHOD_INVITE "INVITE" +#define SRS_SIP_METHOD_ACK "ACK" +#define SRS_SIP_METHOD_BYE "BYE" + +// SIP-Version +#define SRS_SIP_VERSION "SIP/2.0" +#define SRS_SIP_USER_AGENT RTMP_SIG_SRS_SERVER + + +enum SrsSipCmdType{ + SrsSipCmdRequest=0, + SrsSipCmdRespone=1 +}; + +class SrsSipRequest +{ +public: + //sip header member + std::string method; + std::string uri; + std::string version; + std::string status; + + std::string via; + std::string from; + std::string to; + std::string from_tag; + std::string to_tag; + std::string branch; + + std::string call_id; + long seq; + + std::string contact; + std::string user_agent; + + std::string content_type; + long content_length; + + long expires; + int max_forwards; + + std::string www_authenticate; + std::string authorization; + +public: + std::string serial; + std::string realm; + std::string sip_auth_id; + std::string sip_auth_pwd; + std::string sip_username; + std::string peer_ip; + int peer_port; + std::string host; + int host_port; + SrsSipCmdType cmdtype; + +public: + SrsRtspSdp* sdp; + SrsRtspTransport* transport; +public: + SrsSipRequest(); + virtual ~SrsSipRequest(); +public: + virtual bool is_register(); + virtual bool is_invite(); + virtual bool is_message(); + virtual bool is_ack(); + virtual bool is_bye(); + + virtual void copy(SrsSipRequest* src); +public: + virtual std::string get_cmdtype_str(); +}; + +// The gb28181 sip protocol stack. +class SrsSipStack +{ +private: + // The cached bytes buffer. + SrsSimpleStream* buf; +public: + SrsSipStack(); + virtual ~SrsSipStack(); +public: + virtual srs_error_t parse_request(SrsSipRequest** preq, const char *recv_msg, int nb_buf); +protected: + virtual srs_error_t do_parse_request(SrsSipRequest* req, const char *recv_msg); + +public: + virtual void resp_status(std::stringstream& ss, SrsSipRequest *req); + virtual void resp_keepalive(std::stringstream& ss, SrsSipRequest *req); + virtual void resp_ack(std::stringstream& ss, SrsSipRequest *req); + + virtual void req_invite(std::stringstream& ss, SrsSipRequest *req, std::string ip, int port, uint32_t ssrc); + virtual void req_bye(std::stringstream& ss, SrsSipRequest *req); + virtual void req_401_unauthorized(std::stringstream& ss, SrsSipRequest *req); + +}; + +#endif + +#endif + diff --git a/trunk/src/service/srs_service_log.cpp b/trunk/src/service/srs_service_log.cpp index af5cec2ff6..4bc0a0c2eb 100644 --- a/trunk/src/service/srs_service_log.cpp +++ b/trunk/src/service/srs_service_log.cpp @@ -46,7 +46,7 @@ int SrsThreadContext::generate_id() static int id = 0; if (id == 0) { - id = (100 + ((int)(int64_t)this)%1000); + id = (100 + ((uint32_t)(int64_t)this)%1000); } int gid = id++; diff --git a/trunk/src/service/srs_service_st.cpp b/trunk/src/service/srs_service_st.cpp index f63cd42794..27d860efb5 100644 --- a/trunk/src/service/srs_service_st.cpp +++ b/trunk/src/service/srs_service_st.cpp @@ -397,6 +397,11 @@ int srs_recvfrom(srs_netfd_t stfd, void *buf, int len, struct sockaddr *from, in return st_recvfrom((st_netfd_t)stfd, buf, len, from, fromlen, (st_utime_t)timeout); } +int srs_sendto(srs_netfd_t stfd, void *msg, int len, struct sockaddr *to, int tolen, srs_utime_t timeout) +{ + return st_sendto((st_netfd_t)stfd, (const void*)msg, len, (const struct sockaddr *)to, tolen, (st_utime_t)timeout); +} + srs_netfd_t srs_accept(srs_netfd_t stfd, struct sockaddr *addr, int *addrlen, srs_utime_t timeout) { return (srs_netfd_t)st_accept((st_netfd_t)stfd, addr, addrlen, (st_utime_t)timeout); diff --git a/trunk/src/service/srs_service_st.hpp b/trunk/src/service/srs_service_st.hpp index 510b9ba8ac..234b3a9a8c 100644 --- a/trunk/src/service/srs_service_st.hpp +++ b/trunk/src/service/srs_service_st.hpp @@ -88,6 +88,7 @@ extern srs_netfd_t srs_netfd_open_socket(int osfd); extern srs_netfd_t srs_netfd_open(int osfd); extern int srs_recvfrom(srs_netfd_t stfd, void *buf, int len, struct sockaddr *from, int *fromlen, srs_utime_t timeout); +extern int srs_sendto(srs_netfd_t stfd, void *msg, int len, struct sockaddr *to, int tolen, srs_utime_t timeout); extern srs_netfd_t srs_accept(srs_netfd_t stfd, struct sockaddr *addr, int *addrlen, srs_utime_t timeout); diff --git a/trunk/src/utest/srs_utest_kernel.cpp b/trunk/src/utest/srs_utest_kernel.cpp index acfdeeff03..9f1c6cf18c 100644 --- a/trunk/src/utest/srs_utest_kernel.cpp +++ b/trunk/src/utest/srs_utest_kernel.cpp @@ -4208,6 +4208,7 @@ VOID TEST(KernelUtilityTest, CoverBitsBufferAll) } } +#ifndef SRS_AUTO_OSX extern _srs_gettimeofday_t _srs_gettimeofday; int mock_gettimeofday(struct timeval* /*tp*/, struct timezone* /*tzp*/) { return -1; @@ -4238,6 +4239,7 @@ VOID TEST(KernelUtilityTest, CoverTimeSpecial) EXPECT_TRUE(-1 == srs_update_system_time()); } } +#endif extern int64_t _srs_system_time_startup_time; extern int64_t _srs_system_time_us_cache; diff --git a/trunk/src/utest/srs_utest_protocol.cpp b/trunk/src/utest/srs_utest_protocol.cpp index 0e952cf68a..2535b6fd2a 100644 --- a/trunk/src/utest/srs_utest_protocol.cpp +++ b/trunk/src/utest/srs_utest_protocol.cpp @@ -230,8 +230,6 @@ srs_error_t MockBufferIO::writev(const iovec *iov, int iov_size, ssize_t* nwrite total += writen; } - sbytes += total; - if (nwrite) { *nwrite = total; } @@ -6412,3 +6410,65 @@ VOID TEST(ProtocolKbpsTest, RAWStatistic) } } +VOID TEST(ProtocolKbpsTest, WriteLargeIOVs) +{ + srs_error_t err; + + if (true) { + iovec iovs[1]; + iovs[0].iov_base = (char*)"Hello"; + iovs[0].iov_len = 5; + + MockBufferIO io; + ssize_t nn = 0; + HELPER_EXPECT_SUCCESS(srs_write_large_iovs(&io, iovs, 1, &nn)); + EXPECT_EQ(5, nn); + EXPECT_EQ(5, io.sbytes); + } + + if (true) { + iovec iovs[1024]; + int nn_iovs = (int)(sizeof(iovs)/sizeof(iovec)); + for (int i = 0; i < nn_iovs; i++) { + iovs[i].iov_base = (char*)"Hello"; + iovs[i].iov_len = 5; + } + + MockBufferIO io; + ssize_t nn = 0; + HELPER_EXPECT_SUCCESS(srs_write_large_iovs(&io, iovs, nn_iovs, &nn)); + EXPECT_EQ(5 * nn_iovs, nn); + EXPECT_EQ(5 * nn_iovs, io.sbytes); + } + + if (true) { + iovec iovs[1025]; + int nn_iovs = (int)(sizeof(iovs)/sizeof(iovec)); + for (int i = 0; i < nn_iovs; i++) { + iovs[i].iov_base = (char*)"Hello"; + iovs[i].iov_len = 5; + } + + MockBufferIO io; + ssize_t nn = 0; + HELPER_EXPECT_SUCCESS(srs_write_large_iovs(&io, iovs, nn_iovs, &nn)); + EXPECT_EQ(5 * nn_iovs, nn); + EXPECT_EQ(5 * nn_iovs, io.sbytes); + } + + if (true) { + iovec iovs[4096]; + int nn_iovs = (int)(sizeof(iovs)/sizeof(iovec)); + for (int i = 0; i < nn_iovs; i++) { + iovs[i].iov_base = (char*)"Hello"; + iovs[i].iov_len = 5; + } + + MockBufferIO io; + ssize_t nn = 0; + HELPER_EXPECT_SUCCESS(srs_write_large_iovs(&io, iovs, nn_iovs, &nn)); + EXPECT_EQ(5 * nn_iovs, nn); + EXPECT_EQ(5 * nn_iovs, io.sbytes); + } +} +