diff --git a/trunk/auto/auto_headers.sh b/trunk/auto/auto_headers.sh index d77325395..00e7b9b04 100755 --- a/trunk/auto/auto_headers.sh +++ b/trunk/auto/auto_headers.sh @@ -11,11 +11,11 @@ echo "#ifndef SRS_AUTO_HEADER_HPP" >> $SRS_AUTO_HEADERS_H echo "#define SRS_AUTO_HEADER_HPP" >> $SRS_AUTO_HEADERS_H echo "" >> $SRS_AUTO_HEADERS_H -echo "#define SRS_AUTO_BUILD_TS \"`date +%s`\"" >> $SRS_AUTO_HEADERS_H -echo "#define SRS_AUTO_BUILD_DATE \"`date \"+%Y-%m-%d %H:%M:%S\"`\"" >> $SRS_AUTO_HEADERS_H -echo "#define SRS_AUTO_UNAME \"`uname -a`\"" >> $SRS_AUTO_HEADERS_H -echo "#define SRS_AUTO_USER_CONFIGURE \"${SRS_AUTO_USER_CONFIGURE}\"" >> $SRS_AUTO_HEADERS_H -echo "#define SRS_AUTO_CONFIGURE \"${SRS_AUTO_CONFIGURE}\"" >> $SRS_AUTO_HEADERS_H +echo "#define SRS_BUILD_TS \"`date +%s`\"" >> $SRS_AUTO_HEADERS_H +echo "#define SRS_BUILD_DATE \"`date \"+%Y-%m-%d %H:%M:%S\"`\"" >> $SRS_AUTO_HEADERS_H +echo "#define SRS_UNAME \"`uname -a`\"" >> $SRS_AUTO_HEADERS_H +echo "#define SRS_USER_CONFIGURE \"${SRS_AUTO_USER_CONFIGURE}\"" >> $SRS_AUTO_HEADERS_H +echo "#define SRS_CONFIGURE \"${SRS_AUTO_CONFIGURE}\"" >> $SRS_AUTO_HEADERS_H echo "" >> $SRS_AUTO_HEADERS_H function srs_define_macro() @@ -49,11 +49,7 @@ fi if [ $SRS_CUBIE = YES ]; then srs_define_macro "SRS_CUBIE" $SRS_AUTO_HEADERS_H fi -if [ $SRS_EXPORT_LIBRTMP_PROJECT != NO ]; then - echo "#define SRS_EXPORT_LIBRTMP" >> $SRS_AUTO_HEADERS_H -else - echo "#undef SRS_EXPORT_LIBRTMP" >> $SRS_AUTO_HEADERS_H -fi +echo "#undef SRS_EXPORT_LIBRTMP" >> $SRS_AUTO_HEADERS_H echo "" >> $SRS_AUTO_HEADERS_H @@ -62,73 +58,103 @@ echo "" >> $SRS_AUTO_HEADERS_H ##################################################################################### # auto headers in depends. if [ $SRS_HDS = YES ]; then - srs_define_macro "SRS_AUTO_HDS" $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_HDS" $SRS_AUTO_HEADERS_H else - srs_undefine_macro "SRS_AUTO_HDS" $SRS_AUTO_HEADERS_H + srs_undefine_macro "SRS_HDS" $SRS_AUTO_HEADERS_H fi if [ $SRS_SRT = YES ]; then - srs_define_macro "SRS_AUTO_SRT" $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_SRT" $SRS_AUTO_HEADERS_H else - srs_undefine_macro "SRS_AUTO_SRT" $SRS_AUTO_HEADERS_H + srs_undefine_macro "SRS_SRT" $SRS_AUTO_HEADERS_H +fi + +if [ $SRS_CXX11 = YES ]; then + srs_define_macro "SRS_CXX11" $SRS_AUTO_HEADERS_H +else + srs_undefine_macro "SRS_CXX11" $SRS_AUTO_HEADERS_H +fi + +if [ $SRS_CXX14 = YES ]; then + srs_define_macro "SRS_CXX14" $SRS_AUTO_HEADERS_H +else + srs_undefine_macro "SRS_CXX14" $SRS_AUTO_HEADERS_H fi if [ $SRS_RTC = YES ]; then - srs_define_macro "SRS_AUTO_RTC" $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_RTC" $SRS_AUTO_HEADERS_H else - srs_undefine_macro "SRS_AUTO_RTC" $SRS_AUTO_HEADERS_H + srs_undefine_macro "SRS_RTC" $SRS_AUTO_HEADERS_H +fi + +if [ $SRS_FFMPEG_FIT = YES ]; then + srs_define_macro "SRS_FFMPEG_FIT" $SRS_AUTO_HEADERS_H +else + srs_undefine_macro "SRS_FFMPEG_FIT" $SRS_AUTO_HEADERS_H +fi + +if [ $SRS_SIMULATOR = YES ]; then + srs_define_macro "SRS_SIMULATOR" $SRS_AUTO_HEADERS_H +else + srs_undefine_macro "SRS_SIMULATOR" $SRS_AUTO_HEADERS_H fi if [ $SRS_GB28181 = YES ]; then - srs_define_macro "SRS_AUTO_GB28181" $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_GB28181" $SRS_AUTO_HEADERS_H else - srs_undefine_macro "SRS_AUTO_GB28181" $SRS_AUTO_HEADERS_H + srs_undefine_macro "SRS_GB28181" $SRS_AUTO_HEADERS_H +fi + +if [ $SRS_HTTPS = YES ]; then + srs_define_macro "SRS_HTTPS" $SRS_AUTO_HEADERS_H +else + srs_undefine_macro "SRS_HTTPS" $SRS_AUTO_HEADERS_H fi if [ $SRS_MEM_WATCH = YES ]; then - srs_define_macro "SRS_AUTO_MEM_WATCH" $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_MEM_WATCH" $SRS_AUTO_HEADERS_H else - srs_undefine_macro "SRS_AUTO_MEM_WATCH" $SRS_AUTO_HEADERS_H + srs_undefine_macro "SRS_MEM_WATCH" $SRS_AUTO_HEADERS_H fi if [ $SRS_UTEST = YES ]; then - srs_define_macro "SRS_AUTO_UTEST" $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_UTEST" $SRS_AUTO_HEADERS_H else - srs_undefine_macro "SRS_AUTO_UTEST" $SRS_AUTO_HEADERS_H + srs_undefine_macro "SRS_UTEST" $SRS_AUTO_HEADERS_H fi # whatever the FFMPEG tools, if transcode and ingest specified, # srs always compile the FFMPEG tool stub which used to start the FFMPEG process. if [ $SRS_FFMPEG_STUB = YES ]; then - srs_define_macro "SRS_AUTO_FFMPEG_STUB" $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_FFMPEG_STUB" $SRS_AUTO_HEADERS_H else - srs_undefine_macro "SRS_AUTO_FFMPEG_STUB" $SRS_AUTO_HEADERS_H + srs_undefine_macro "SRS_FFMPEG_STUB" $SRS_AUTO_HEADERS_H fi if [ $SRS_GPERF = YES ]; then - srs_define_macro "SRS_AUTO_GPERF" $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_GPERF" $SRS_AUTO_HEADERS_H else - srs_undefine_macro "SRS_AUTO_GPERF" $SRS_AUTO_HEADERS_H + srs_undefine_macro "SRS_GPERF" $SRS_AUTO_HEADERS_H fi if [ $SRS_GPERF_MC = YES ]; then - srs_define_macro "SRS_AUTO_GPERF_MC" $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_GPERF_MC" $SRS_AUTO_HEADERS_H else - srs_undefine_macro "SRS_AUTO_GPERF_MC" $SRS_AUTO_HEADERS_H + srs_undefine_macro "SRS_GPERF_MC" $SRS_AUTO_HEADERS_H fi if [ $SRS_GPERF_MD = YES ]; then - srs_define_macro "SRS_AUTO_GPERF_MD" $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_GPERF_MD" $SRS_AUTO_HEADERS_H else - srs_undefine_macro "SRS_AUTO_GPERF_MD" $SRS_AUTO_HEADERS_H + srs_undefine_macro "SRS_GPERF_MD" $SRS_AUTO_HEADERS_H fi if [ $SRS_GPERF_MP = YES ]; then - srs_define_macro "SRS_AUTO_GPERF_MP" $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_GPERF_MP" $SRS_AUTO_HEADERS_H else - srs_undefine_macro "SRS_AUTO_GPERF_MP" $SRS_AUTO_HEADERS_H + srs_undefine_macro "SRS_GPERF_MP" $SRS_AUTO_HEADERS_H fi if [ $SRS_GPERF_CP = YES ]; then - srs_define_macro "SRS_AUTO_GPERF_CP" $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_GPERF_CP" $SRS_AUTO_HEADERS_H else - srs_undefine_macro "SRS_AUTO_GPERF_CP" $SRS_AUTO_HEADERS_H + srs_undefine_macro "SRS_GPERF_CP" $SRS_AUTO_HEADERS_H fi ##################################################################################### @@ -136,50 +162,40 @@ fi ##################################################################################### # for log level compile settings if [ $SRS_LOG_VERBOSE = YES ]; then - srs_define_macro "SRS_AUTO_VERBOSE" $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_VERBOSE" $SRS_AUTO_HEADERS_H else - srs_undefine_macro "SRS_AUTO_VERBOSE" $SRS_AUTO_HEADERS_H + srs_undefine_macro "SRS_VERBOSE" $SRS_AUTO_HEADERS_H fi if [ $SRS_LOG_INFO = YES ]; then - srs_define_macro "SRS_AUTO_INFO" $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_INFO" $SRS_AUTO_HEADERS_H else - srs_undefine_macro "SRS_AUTO_INFO" $SRS_AUTO_HEADERS_H + srs_undefine_macro "SRS_INFO" $SRS_AUTO_HEADERS_H fi if [ $SRS_LOG_TRACE = YES ]; then - srs_define_macro "SRS_AUTO_TRACE" $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_TRACE" $SRS_AUTO_HEADERS_H else - srs_undefine_macro "SRS_AUTO_TRACE" $SRS_AUTO_HEADERS_H + srs_undefine_macro "SRS_TRACE" $SRS_AUTO_HEADERS_H fi if [ $SRS_CROSS_BUILD = YES ]; then - srs_define_macro "SRS_AUTO_CROSSBUILD" $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_CROSSBUILD" $SRS_AUTO_HEADERS_H else - srs_undefine_macro "SRS_AUTO_CROSSBUILD" $SRS_AUTO_HEADERS_H + srs_undefine_macro "SRS_CROSSBUILD" $SRS_AUTO_HEADERS_H fi if [ $SRS_OSX = YES ]; then - srs_define_macro "SRS_AUTO_OSX" $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_OSX" $SRS_AUTO_HEADERS_H else - srs_undefine_macro "SRS_AUTO_OSX" $SRS_AUTO_HEADERS_H -fi -if [ $SRS_SENDMMSG = YES ]; then - srs_define_macro "SRS_AUTO_SENDMMSG" $SRS_AUTO_HEADERS_H -else - srs_undefine_macro "SRS_AUTO_SENDMMSG" $SRS_AUTO_HEADERS_H -fi -if [ $SRS_HAS_SENDMMSG = YES ]; then - srs_define_macro "SRS_AUTO_HAS_SENDMMSG" $SRS_AUTO_HEADERS_H -else - srs_undefine_macro "SRS_AUTO_HAS_SENDMMSG" $SRS_AUTO_HEADERS_H + srs_undefine_macro "SRS_OSX" $SRS_AUTO_HEADERS_H fi if [ $SRS_DEBUG = YES ]; then - srs_define_macro "SRS_AUTO_DEBUG" $SRS_AUTO_HEADERS_H + srs_define_macro "SRS_DEBUG" $SRS_AUTO_HEADERS_H else - srs_undefine_macro "SRS_AUTO_DEBUG" $SRS_AUTO_HEADERS_H + srs_undefine_macro "SRS_DEBUG" $SRS_AUTO_HEADERS_H fi # prefix echo "" >> $SRS_AUTO_HEADERS_H -echo "#define SRS_AUTO_PREFIX \"${SRS_PREFIX}\"" >> $SRS_AUTO_HEADERS_H +echo "#define SRS_PREFIX \"${SRS_PREFIX}\"" >> $SRS_AUTO_HEADERS_H echo "" >> $SRS_AUTO_HEADERS_H @@ -188,13 +204,13 @@ echo "" >> $SRS_AUTO_HEADERS_H ##################################################################################### if [[ -f ../AUTHORS.txt ]]; then SRS_CONSTRIBUTORS=`cat ../AUTHORS.txt|grep "*"|awk '{print $2}'` - echo "#define SRS_AUTO_CONSTRIBUTORS \"\\" >> $SRS_AUTO_HEADERS_H + echo "#define SRS_CONSTRIBUTORS \"\\" >> $SRS_AUTO_HEADERS_H for CONTRIBUTOR in $SRS_CONSTRIBUTORS; do echo "${CONTRIBUTOR} \\" >> $SRS_AUTO_HEADERS_H done echo "\"" >> $SRS_AUTO_HEADERS_H else - echo "#define SRS_AUTO_CONSTRIBUTORS \"ossrs\"" >> $SRS_AUTO_HEADERS_H + echo "#define SRS_CONSTRIBUTORS \"ossrs\"" >> $SRS_AUTO_HEADERS_H fi # new empty line to auto headers file. diff --git a/trunk/auto/options.sh b/trunk/auto/options.sh index 745b45e29..eff8e917f 100755 --- a/trunk/auto/options.sh +++ b/trunk/auto/options.sh @@ -19,8 +19,9 @@ SRS_HDS=NO SRS_SRT=NO SRS_RTC=YES SRS_GB28181=NO +SRS_CXX11=NO +SRS_CXX14=NO SRS_NGINX=NO -SRS_FFMPEG_TOOL=NO SRS_LIBRTMP=NO SRS_RESEARCH=NO SRS_UTEST=NO @@ -34,6 +35,8 @@ SRS_GPROF=NO # Performance test: gprof SRS_STREAM_CASTER=YES SRS_INGEST=YES SRS_SSL=YES +SRS_SSL_1_0=NO +SRS_HTTPS=YES SRS_STAT=YES SRS_TRANSCODE=YES SRS_HTTP_CALLBACK=YES @@ -42,10 +45,15 @@ SRS_HTTP_API=YES SRS_HTTP_CORE=YES SRS_HLS=YES SRS_DVR=YES +SRS_CHERRYPY=YES # ################################################################ -# libraries +# FFmpeg stub is the stub code in SRS for ingester or encoder. SRS_FFMPEG_STUB=NO +# FFmpeg tool is the binary for FFmpeg tool, to exec ingest or transcode. +SRS_FFMPEG_TOOL=NO +# FFmpeg fit is the source code for RTC, to transcode audio or video in SRS. +SRS_FFMPEG_FIT=RESERVED # arguments SRS_PREFIX=/usr/local/srs SRS_JOBS=1 @@ -60,7 +68,7 @@ SRS_GCOV=NO # always enable the warn/error level. SRS_LOG_VERBOSE=NO SRS_LOG_INFO=NO -SRS_LOG_TRACE=NO +SRS_LOG_TRACE=YES # ################################################################ # experts @@ -80,6 +88,8 @@ SRS_VALGRIND=NO SRS_BUILD_TAG= # Whether do "make clean" when configure. SRS_CLEAN=YES +# Whether enable RTC simulate API. +SRS_SIMULATOR=NO # ################################################################ # presets @@ -118,10 +128,9 @@ SRS_EXTRA_FLAGS= # ##################################################################################### # Performance optimize. -SRS_NASM=YES -SRS_SRTP_ASM=YES -SRS_SENDMMSG=YES -SRS_HAS_SENDMMSG=YES +SRS_NASM=NO +SRS_SRTP_ASM=NO +SRS_SENDMMSG=NO SRS_DEBUG=NO ##################################################################################### @@ -133,37 +142,30 @@ function show_help() { Presets: --x86-64, --x86-x64 [default] For x86/x64 cpu, common pc and servers. --arm Enable crossbuild for ARM, should also set bellow toolchain options. - --mips Enable crossbuild for MIPS + --osx Enable build for OSX/Darwin AppleOS. Features: -h, --help Print this message and exit 0. - --with-ssl Enable rtmp complex handshake, requires openssl-devel installed. - --with-hds Enable hds streaming, mux RTMP to F4M/F4V files. - --with-stream-caster Enable stream caster to serve other stream over other protocol. - --with-stat Enable the data statistic, for http api. - --with-librtmp Enable srs-librtmp, library for client. - --with-research Build the research tools. - --with-utest Build the utest for SRS. - --with-srt Build the SRT support for SRS. - --with-rtc Build the WebRTC support for SRS. - --with-gb28181 Build the GB28181 support for SRS. - - --without-ssl Disable rtmp complex handshake. - --without-hds Disable hds, the adobe http dynamic streaming. - --without-stream-caster Disable stream caster, only listen and serve RTMP/HTTP. - --without-stat Disable the data statistic feature. - --without-librtmp Disable srs-librtmp, library for client. - --without-research Disable the research tools. - --without-utest Disable the utest for SRS. - --without-srt Disable the SRT support for SRS. - --without-rtc Disable the WebRTC support for SRS. - --without-gb28181 Disable the GB28181 support for SRS. + --ssl=on|off Whether build the rtmp complex handshake, requires openssl-devel installed. + --https=on|off Whether enable HTTPS client and server. Default: off + --hds=on|off Whether build the hds streaming, mux RTMP to F4M/F4V files. + --stream-caster=on|off Whether build the stream caster to serve other stream over other protocol. + --stat=on|off Whether build the the data statistic, for http api. + --librtmp=on|off Whether build the srs-librtmp, library for client. + --research=on|off Whether build the research tools. + --cherrypy=on|off Whether install CherryPy for demo api-server. + --utest=on|off Whether build the utest for SRS. + --srt=on|off Whether build the SRT support for SRS. + --rtc=on|off Whether build the WebRTC support for SRS. + --gb28181=on|off Whether build the GB28181 support for SRS. + --cxx11=on|off Whether enable the C++11 support for SRS. + --cxx14=on|off Whether enable the C++14 support for SRS. + --ffmpeg-fit=on|off Whether enable the FFmpeg fit(source code) for SRS. --prefix= The absolute installation path for srs. Default: $SRS_PREFIX - --static Whether add '-static' to link options. - --gcov Whether enable the GCOV compiler options. - --debug Whether enable the debug code, may hurt performance. + --gcov=on|off Whether enable the GCOV compiler options. + --debug=on|off Whether enable the debug code, may hurt performance. --jobs[=N] Allow N jobs at once; infinite jobs with no arg. Used for make in the configure, for example, to make ffmpeg. --log-verbose Whether enable the log verbose level. default: no. @@ -171,32 +173,21 @@ Features: --log-trace Whether enable the log trace level. default: yes. Performance: @see https://blog.csdn.net/win_lin/article/details/53503869 - --with-valgrind Support valgrind for memory check. - --with-gperf Build SRS with gperf tools(no gmd/gmc/gmp/gcp, with tcmalloc only). - --with-gmc Build memory check for SRS with gperf tools. - --with-gmd Build memory defense(corrupt memory) for SRS with gperf tools. - --with-gmp Build memory profile for SRS with gperf tools. - --with-gcp Build cpu profile for SRS with gperf tools. - --with-gprof Build SRS with gprof(GNU profile tool). + --valgrind=on|off Whether build valgrind for memory check. + --gperf=on|off Whether build SRS with gperf tools(no gmd/gmc/gmp/gcp, with tcmalloc only). + --gmc=on|off Whether build memory check for SRS with gperf tools. + --gmd=on|off Whether build memory defense(corrupt memory) for SRS with gperf tools. + --gmp=on|off Whether build memory profile for SRS with gperf tools. + --gcp=on|off Whether build cpu profile for SRS with gperf tools. + --gprof=on|off Whether build SRS with gprof(GNU profile tool). - --without-valgrind Do not support valgrind for memory check. - --without-gperf Do not build SRS with gperf tools(without tcmalloc and gmd/gmc/gmp/gcp). - --without-gmc Do not build memory check for SRS with gperf tools. - --without-gmd Do not build memory defense for SRS with gperf tools. - --without-gmp Do not build memory profile for SRS with gperf tools. - --without-gcp Do not build cpu profile for SRS with gperf tools. - --without-gprof Do not build srs with gprof(GNU profile tool). - - --with-nasm Build FFMPEG for RTC with nasm support. - --without-nasm Build FFMPEG for RTC without nasm support, for CentOS6 nasm is too old. - --with-srtp-nasm Build SRTP with ASM(openssl-asm) support, requires RTC and openssl-1.0.*. - --without-srtp-nasm Disable SRTP ASM support. - --with-sendmmsg Enable UDP sendmmsg support. @see http://man7.org/linux/man-pages/man2/sendmmsg.2.html - --without-sendmmsg Disable UDP sendmmsg support. + --nasm=on|off Whether build FFMPEG for RTC with nasm support. + --srtp-nasm=on|off Whether build SRTP with ASM(openssl-asm) support, requires RTC and openssl-1.0.*. + --sendmmsg=on|off Whether enable UDP sendmmsg support. Default: off. @see http://man7.org/linux/man-pages/man2/sendmmsg.2.html Toolchain options: @see https://github.com/ossrs/srs/issues/1547#issuecomment-576078411 + --static Whether add '-static' to link options. --arm Enable crossbuild for ARM. - --mips Enable crossbuild for MIPS. --cc= Use c compiler CC, default is gcc. --cxx= Use c++ compiler CXX, default is g++. --ar= Use archive tool AR, default is ar. @@ -213,14 +204,12 @@ Conflicts: The complex tools not available for arm. Experts: - --use-sys-ssl Do not compile ssl, use system ssl(-lssl) if required. + --sys-ssl=on|off Do not compile ssl, use system ssl(-lssl) if required. --use-shared-st Use link shared libraries for ST which uses MPL license. --use-shared-srt Use link shared libraries for SRT which uses MPL license. - --export-librtmp-project= Export srs-librtmp to specified project in path. - --export-librtmp-single= Export srs-librtmp to a single file(.h+.cpp) in path. - --build-tag= Set the build object directory suffix. - --with-clean Configure SRS and do make clean if possible. - --without-clean Configure SRS and never make clean even possible.. + --build-tag= Set the build object directory suffix. + --clean=on|off Whether do 'make clean' when configure. + --simulator=on|off Whether enable RTC network simulator. Default: off Workflow: 1. Apply "Presets". if not specified, use default preset. @@ -239,55 +228,6 @@ function parse_user_option() { -h) help=yes ;; --help) help=yes ;; - --with-ssl) SRS_SSL=YES ;; - --with-hds) SRS_HDS=YES ;; - --with-nginx) SRS_NGINX=YES ;; - --with-ffmpeg) SRS_FFMPEG_TOOL=YES ;; - --with-transcode) SRS_TRANSCODE=YES ;; - --with-ingest) SRS_INGEST=YES ;; - --with-stat) SRS_STAT=YES ;; - --with-stream-caster) SRS_STREAM_CASTER=YES ;; - --with-librtmp) SRS_LIBRTMP=YES ;; - --with-research) SRS_RESEARCH=YES ;; - --with-utest) SRS_UTEST=YES ;; - --with-srt) SRS_SRT=YES ;; - --with-rtc) SRS_RTC=YES ;; - --with-gb28181) SRS_GB28181=YES ;; - --with-nasm) SRS_NASM=YES ;; - --with-srtp-nasm) SRS_SRTP_ASM=YES ;; - --with-sendmmsg) SRS_SENDMMSG=YES ;; - --with-clean) SRS_CLEAN=YES ;; - --with-gperf) SRS_GPERF=YES ;; - --with-gmc) SRS_GPERF_MC=YES ;; - --with-gmd) SRS_GPERF_MD=YES ;; - --with-gmp) SRS_GPERF_MP=YES ;; - --with-gcp) SRS_GPERF_CP=YES ;; - --with-gprof) SRS_GPROF=YES ;; - --with-arm-ubuntu12) SRS_CROSS_BUILD=YES ;; - --with-mips-ubuntu12) SRS_CROSS_BUILD=YES ;; - - --without-hds) SRS_HDS=NO ;; - --without-nginx) SRS_NGINX=NO ;; - --without-ffmpeg) SRS_FFMPEG_TOOL=NO ;; - --without-librtmp) SRS_LIBRTMP=NO ;; - --without-research) SRS_RESEARCH=NO ;; - --without-utest) SRS_UTEST=NO ;; - --without-srt) SRS_SRT=NO ;; - --without-rtc) SRS_RTC=NO ;; - --without-gb28181) SRS_GB28181=NO ;; - --without-nasm) SRS_NASM=NO ;; - --without-srtp-nasm) SRS_SRTP_ASM=NO ;; - --without-sendmmsg) SRS_SENDMMSG=NO ;; - --without-clean) SRS_CLEAN=NO ;; - --without-gperf) SRS_GPERF=NO ;; - --without-gmc) SRS_GPERF_MC=NO ;; - --without-gmd) SRS_GPERF_MD=NO ;; - --without-gmp) SRS_GPERF_MP=NO ;; - --without-gcp) SRS_GPERF_CP=NO ;; - --without-gprof) SRS_GPROF=NO ;; - --without-arm-ubuntu12) SRS_CROSS_BUILD=NO ;; - --without-mips-ubuntu12) SRS_CROSS_BUILD=NO ;; - --jobs) SRS_JOBS=${value} ;; --prefix) SRS_PREFIX=${value} ;; --static) SRS_STATIC=YES ;; @@ -320,33 +260,153 @@ function parse_user_option() { --disable-all) SRS_DISABLE_ALL=YES ;; --pure-rtmp) SRS_PURE_RTMP=YES ;; --full) SRS_ENABLE_ALL=YES ;; - - --use-sys-ssl) SRS_USE_SYS_SSL=YES ;; - --use-shared-st) SRS_SHARED_ST=YES ;; - --use-shared-srt) SRS_SHARED_SRT=YES ;; --memory-watch) SRS_MEM_WATCH=YES ;; --export-librtmp-project) SRS_EXPORT_LIBRTMP_PROJECT=${value} ;; --export-librtmp-single) SRS_EXPORT_LIBRTMP_SINGLE=${value} ;; + + --sendmmsg) if [[ $value == off ]]; then SRS_SENDMMSG=NO; else SRS_SENDMMSG=YES; fi ;; + + --without-srtp-nasm) SRS_SRTP_ASM=NO ;; + --with-srtp-nasm) SRS_SRTP_ASM=YES ;; + --srtp-nasm) if [[ $value == off ]]; then SRS_SRTP_ASM=NO; else SRS_SRTP_ASM=YES; fi ;; + + --without-nasm) SRS_NASM=NO ;; + --with-nasm) SRS_NASM=YES ;; + --nasm) if [[ $value == off ]]; then SRS_NASM=NO; else SRS_NASM=YES; fi ;; + + --with-ssl) SRS_SSL=YES ;; + --ssl) if [[ $value == off ]]; then SRS_SSL=NO; else SRS_SSL=YES; fi ;; + --https) if [[ $value == off ]]; then SRS_HTTPS=NO; else SRS_HTTPS=YES; fi ;; + --ssl-1-0) if [[ $value == off ]]; then SRS_SSL_1_0=NO; else SRS_SSL_1_0=YES; fi ;; + + --with-hds) SRS_HDS=YES ;; + --without-hds) SRS_HDS=NO ;; + --hds) if [[ $value == off ]]; then SRS_HDS=NO; else SRS_HDS=YES; fi ;; + + --with-nginx) SRS_NGINX=YES ;; + --without-nginx) SRS_NGINX=NO ;; + --nginx) if [[ $value == off ]]; then SRS_NGINX=NO; else SRS_NGINX=YES; fi ;; + + --with-ffmpeg) SRS_FFMPEG_TOOL=YES ;; + --without-ffmpeg) SRS_FFMPEG_TOOL=NO ;; + --ffmpeg-tool) if [[ $value == off ]]; then SRS_FFMPEG_TOOL=NO; else SRS_FFMPEG_TOOL=YES; fi ;; + + --with-transcode) SRS_TRANSCODE=YES ;; + --without-transcode) echo "ignore option \"$option\"" ;; + --transcode) if [[ $value == off ]]; then SRS_TRANSCODE=NO; else SRS_TRANSCODE=YES; fi ;; + + --with-ingest) SRS_INGEST=YES ;; + --without-ingest) echo "ignore option \"$option\"" ;; + --ingest) if [[ $value == off ]]; then SRS_INGEST=NO; else SRS_INGEST=YES; fi ;; + + --with-stat) SRS_STAT=YES ;; + --without-stat) echo "ignore option \"$option\"" ;; + --stat) if [[ $value == off ]]; then SRS_STAT=NO; else SRS_STAT=YES; fi ;; + + --with-stream-caster) SRS_STREAM_CASTER=YES ;; + --without-stream-caster) echo "ignore option \"$option\"" ;; + --stream-caster) if [[ $value == off ]]; then SRS_STREAM_CASTER=NO; else SRS_STREAM_CASTER=YES; fi ;; + + --with-librtmp) SRS_LIBRTMP=YES ;; + --without-librtmp) SRS_LIBRTMP=NO ;; + --librtmp) if [[ $value == off ]]; then SRS_LIBRTMP=NO; else SRS_LIBRTMP=YES; fi ;; + + --with-research) SRS_RESEARCH=YES ;; + --without-research) SRS_RESEARCH=NO ;; + --research) if [[ $value == off ]]; then SRS_RESEARCH=NO; else SRS_RESEARCH=YES; fi ;; + + --with-utest) SRS_UTEST=YES ;; + --without-utest) SRS_UTEST=NO ;; + --utest) if [[ $value == off ]]; then SRS_UTEST=NO; else SRS_UTEST=YES; fi ;; + --cherrypy) if [[ $value == off ]]; then SRS_CHERRYPY=NO; else SRS_CHERRYPY=YES; fi ;; + + --with-srt) SRS_SRT=YES ;; + --without-srt) SRS_SRT=NO ;; + --srt) if [[ $value == off ]]; then SRS_SRT=NO; else SRS_SRT=YES; fi ;; + + --with-rtc) SRS_RTC=YES ;; + --without-rtc) SRS_RTC=NO ;; + --rtc) if [[ $value == off ]]; then SRS_RTC=NO; else SRS_RTC=YES; fi ;; + --simulator) if [[ $value == off ]]; then SRS_SIMULATOR=NO; else SRS_SIMULATOR=YES; fi ;; + + --with-gb28181) SRS_GB28181=YES ;; + --without-gb28181) SRS_GB28181=NO ;; + --gb28181) if [[ $value == off ]]; then SRS_GB28181=NO; else SRS_GB28181=YES; fi ;; + + --cxx11) if [[ $value == off ]]; then SRS_CXX11=NO; else SRS_CXX11=YES; fi ;; + --cxx14) if [[ $value == off ]]; then SRS_CXX14=NO; else SRS_CXX14=YES; fi ;; + --ffmpeg-fit) if [[ $value == off ]]; then SRS_FFMPEG_FIT=NO; else SRS_FFMPEG_FIT=YES; fi ;; + + --with-clean) SRS_CLEAN=YES ;; + --without-clean) SRS_CLEAN=NO ;; + --clean) if [[ $value == off ]]; then SRS_CLEAN=NO; else SRS_CLEAN=YES; fi ;; + + --with-gperf) SRS_GPERF=YES ;; + --without-gperf) SRS_GPERF=NO ;; + --gperf) if [[ $value == off ]]; then SRS_GPERF=NO; else SRS_GPERF=YES; fi ;; + + --with-gmc) SRS_GPERF_MC=YES ;; + --without-gmc) SRS_GPERF_MC=NO ;; + --gmc) if [[ $value == off ]]; then SRS_GPERF_MC=NO; else SRS_GPERF_MC=YES; fi ;; + + --with-gmd) SRS_GPERF_MD=YES ;; + --without-gmd) SRS_GPERF_MD=NO ;; + --gmd) if [[ $value == off ]]; then SRS_GPERF_MD=NO; else SRS_GPERF_MD=YES; fi ;; + + --with-gmp) SRS_GPERF_MP=YES ;; + --without-gmp) SRS_GPERF_MP=NO ;; + --gmp) if [[ $value == off ]]; then SRS_GPERF_MP=NO; else SRS_GPERF_MP=YES; fi ;; + + --with-gcp) SRS_GPERF_CP=YES ;; + --without-gcp) SRS_GPERF_CP=NO ;; + --gcp) if [[ $value == off ]]; then SRS_GPERF_CP=NO; else SRS_GPERF_CP=YES; fi ;; + + --with-gprof) SRS_GPROF=YES ;; + --without-gprof) SRS_GPROF=NO ;; + --gprof) if [[ $value == off ]]; then SRS_GPROF=NO; else SRS_GPROF=YES; fi ;; + + --with-arm-ubuntu12) SRS_CROSS_BUILD=YES ;; + --without-arm-ubuntu12) SRS_CROSS_BUILD=NO ;; + --arm-ubuntu12) if [[ $value == off ]]; then SRS_CROSS_BUILD=NO; else SRS_CROSS_BUILD=YES; fi ;; + + --with-mips-ubuntu12) SRS_CROSS_BUILD=YES ;; + --without-mips-ubuntu12) SRS_CROSS_BUILD=NO ;; + --mips-ubuntu12) if [[ $value == off ]]; then SRS_CROSS_BUILD=NO; else SRS_CROSS_BUILD=YES; fi ;; + + --use-sys-ssl) SRS_USE_SYS_SSL=YES ;; + --without-ssl) echo "ignore option \"$option\"" ;; + --sys-ssl) if [[ $value == off ]]; then SRS_USE_SYS_SSL=NO; else SRS_USE_SYS_SSL=YES; fi ;; + + --use-shared-st) SRS_SHARED_ST=YES ;; + --shared-st) if [[ $value == off ]]; then SRS_SHARED_ST=NO; else SRS_SHARED_ST=YES; fi ;; + + --use-shared-srt) SRS_SHARED_SRT=YES ;; + --shared-srt) if [[ $value == off ]]; then SRS_SHARED_SRT=NO; else SRS_SHARED_SRT=YES; fi ;; + --with-valgrind) SRS_VALGRIND=YES ;; --without-valgrind) SRS_VALGRIND=NO ;; + --valgrind) if [[ $value == off ]]; then SRS_VALGRIND=NO; else SRS_VALGRIND=YES; fi ;; --with-http-callback) SRS_HTTP_CALLBACK=YES ;; - --with-http-api) SRS_HTTP_API=YES ;; - --with-http-server) SRS_HTTP_SERVER=YES ;; - --with-hls) SRS_HLS=YES ;; - --with-dvr) SRS_DVR=YES ;; - - --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\"" ;; + --http-callback) if [[ $value == off ]]; then SRS_HTTP_CALLBACK=NO; else SRS_HTTP_CALLBACK=YES; fi ;; + + --with-http-api) SRS_HTTP_API=YES ;; --without-http-api) echo "ignore option \"$option\"" ;; + --http-api) if [[ $value == off ]]; then SRS_HTTP_API=NO; else SRS_HTTP_API=YES; fi ;; + + --with-http-server) SRS_HTTP_SERVER=YES ;; + --without-http-server) echo "ignore option \"$option\"" ;; + --http-server) if [[ $value == off ]]; then SRS_HTTP_SERVER=NO; else SRS_HTTP_SERVER=YES; fi ;; + + --with-hls) SRS_HLS=YES ;; --without-hls) echo "ignore option \"$option\"" ;; + --hls) if [[ $value == off ]]; then SRS_HLS=NO; else SRS_HLS=YES; fi ;; + + --with-dvr) SRS_DVR=YES ;; --without-dvr) echo "ignore option \"$option\"" ;; + --dvr) if [[ $value == off ]]; then SRS_DVR=NO; else SRS_DVR=YES; fi ;; *) echo "$0: error: invalid option \"$option\"" @@ -382,12 +442,7 @@ if [ $help = yes ]; then exit 0 fi -function apply_user_presets() { - # always set the log level for all presets. - SRS_LOG_VERBOSE=NO - SRS_LOG_INFO=NO - SRS_LOG_TRACE=YES - +function apply_detail_options() { # set default preset if not specifies if [[ $SRS_PURE_RTMP == NO && $SRS_FAST == NO && $SRS_DISABLE_ALL == NO && $SRS_ENABLE_ALL == NO && \ $SRS_DEV == NO && $SRS_FAST_DEV == NO && $SRS_DEMO == NO && $SRS_PI == NO && $SRS_CUBIE == NO && \ @@ -396,116 +451,16 @@ function apply_user_presets() { SRS_X86_X64=YES; opt="--x86-x64 $opt"; fi - # all disabled. - if [ $SRS_DISABLE_ALL = YES ]; then - SRS_HDS=NO - SRS_LIBRTMP=NO - SRS_RESEARCH=NO - SRS_UTEST=NO - SRS_STATIC=NO + # Enable c++11 for SRT. + if [[ $SRS_SRT == YES ]]; then + SRS_CXX11=YES fi - # all enabled. - if [ $SRS_ENABLE_ALL = YES ]; then - SRS_HDS=YES - SRS_LIBRTMP=YES - SRS_RESEARCH=YES - SRS_UTEST=YES - SRS_STATIC=NO + # Enable FFmpeg fit for RTC to trancode audio from AAC to OPUS, if user has't disabled it. + if [[ $SRS_RTC == YES && $SRS_FFMPEG_FIT == RESERVED ]]; then + SRS_FFMPEG_FIT=YES fi - # only rtmp vp6 - if [ $SRS_FAST = YES ]; then - SRS_HDS=NO - SRS_LIBRTMP=NO - SRS_RESEARCH=NO - SRS_UTEST=NO - SRS_STATIC=NO - fi - - # only ssl for RTMP with complex handshake. - if [ $SRS_PURE_RTMP = YES ]; then - SRS_HDS=NO - SRS_LIBRTMP=NO - SRS_RESEARCH=NO - SRS_UTEST=NO - SRS_STATIC=NO - fi - - # defaults for x86/x64 - if [ $SRS_X86_X64 = YES ]; then - SRS_HDS=YES - SRS_LIBRTMP=YES - SRS_RESEARCH=NO - SRS_UTEST=NO - SRS_STATIC=NO - fi - - # if dev specified, open features if possible. - if [ $SRS_DEV = YES ]; then - SRS_HDS=YES - SRS_LIBRTMP=YES - SRS_RESEARCH=YES - SRS_UTEST=YES - SRS_STATIC=NO - fi - - # if fast dev specified, open main server features. - if [ $SRS_FAST_DEV = YES ]; then - SRS_HDS=YES - SRS_LIBRTMP=NO - SRS_RESEARCH=NO - SRS_UTEST=NO - SRS_STATIC=NO - fi - - # for srs demo - if [ $SRS_DEMO = YES ]; then - SRS_HDS=YES - SRS_LIBRTMP=YES - SRS_RESEARCH=NO - SRS_UTEST=NO - SRS_STATIC=NO - fi - - # if raspberry-pi specified, open ssl/hls/static features - if [ $SRS_PI = YES ]; then - SRS_HDS=YES - SRS_LIBRTMP=YES - SRS_RESEARCH=NO - SRS_UTEST=NO - SRS_STATIC=NO - fi - - # if cubieboard specified, open features except ffmpeg/nginx. - if [ $SRS_CUBIE = YES ]; then - SRS_HDS=YES - SRS_LIBRTMP=YES - SRS_RESEARCH=NO - SRS_UTEST=NO - SRS_STATIC=NO - fi - - # if crossbuild, disable research and librtmp. - if [[ $SRS_CROSS_BUILD == YES ]]; then - SRS_LIBRTMP=NO - SRS_RESEARCH=NO - SRS_UTEST=NO - SRS_STATIC=NO - fi -} -apply_user_presets - -##################################################################################### -# parse detail feature options -##################################################################################### -for option -do - parse_user_option_to_value_and_option - parse_user_option -done - -function apply_user_detail_options() { # if transcode/ingest specified, requires the ffmpeg stub classes. SRS_FFMPEG_STUB=NO if [ $SRS_TRANSCODE = YES ]; then SRS_FFMPEG_STUB=YES; fi @@ -533,28 +488,24 @@ function apply_user_detail_options() { # if specified export single file, export project first. if [ $SRS_EXPORT_LIBRTMP_SINGLE != NO ]; then - SRS_EXPORT_LIBRTMP_PROJECT=$SRS_EXPORT_LIBRTMP_SINGLE + echo "Warning: Ingore --export-librtmp-single" + SRS_EXPORT_LIBRTMP_SINGLE=NO fi - + # disable almost all features for export srs-librtmp. if [ $SRS_EXPORT_LIBRTMP_PROJECT != NO ]; then - SRS_HDS=NO - SRS_SSL=NO - SRS_TRANSCODE=NO - SRS_HTTP_CALLBACK=NO - SRS_INGEST=NO - SRS_STAT=NO - SRS_STREAM_CASTER=NO - SRS_LIBRTMP=YES - SRS_RESEARCH=YES - SRS_UTEST=NO - SRS_GPERF=NO - SRS_GPERF_MC=NO - SRS_GPERF_MD=NO - SRS_GPERF_MP=NO - SRS_GPERF_CP=NO - SRS_GPROF=NO - SRS_STATIC=NO + echo "Warning: Ingore --export-librtmp-project" + SRS_EXPORT_LIBRTMP_PROJECT=NO + fi + + if [[ $SRS_LIBRTMP != NO ]]; then + echo "Warning: Ingore --librtmp" + SRS_LIBRTMP=NO + fi + + if [[ $SRS_RESEARCH != NO ]]; then + echo "Warning: Ingore --research" + SRS_RESEARCH=NO fi if [[ $SRS_SRTP_ASM == YES && $SRS_RTC == NO ]]; then @@ -567,74 +518,72 @@ function apply_user_detail_options() { SRS_SRTP_ASM=NO fi - # Detect whether has sendmmsg. - # @see http://man7.org/linux/man-pages/man2/sendmmsg.2.html - mkdir -p ${SRS_OBJS} && - echo " #include " > ${SRS_OBJS}/_tmp_sendmmsg_detect.c - echo " int main(int argc, char** argv) { " >> ${SRS_OBJS}/_tmp_sendmmsg_detect.c - echo " struct mmsghdr hdr; " >> ${SRS_OBJS}/_tmp_sendmmsg_detect.c - echo " hdr.msg_len = 0; " >> ${SRS_OBJS}/_tmp_sendmmsg_detect.c - echo " return 0; " >> ${SRS_OBJS}/_tmp_sendmmsg_detect.c - echo " } " >> ${SRS_OBJS}/_tmp_sendmmsg_detect.c - ${SRS_TOOL_CC} -c ${SRS_OBJS}/_tmp_sendmmsg_detect.c -D_GNU_SOURCE -o /dev/null >/dev/null 2>&1 - ret=$?; rm -f ${SRS_OBJS}/_tmp_sendmmsg_detect.c; - if [[ $ret -ne 0 ]]; then - SRS_HAS_SENDMMSG=NO - if [[ $SRS_SENDMMSG == YES ]]; then - echo "Disable UDP sendmmsg automatically" - SRS_SENDMMSG=NO - fi + # Which openssl we choose, openssl-1.0.* for SRTP with ASM, others we use openssl-1.1.* + if [[ $SRS_SRTP_ASM == YES && $SRS_SSL_1_0 == NO ]]; then + echo "Use openssl-1.0 for SRTP ASM." + SRS_SSL_1_0=YES + fi + + if [[ $SRS_OSX == YES && $SRS_SENDMMSG == YES ]]; then + echo "Disable sendmmsg for OSX" + SRS_SENDMMSG=NO fi } -apply_user_detail_options +apply_detail_options function regenerate_options() { # save all config options to macro to write to auto headers file SRS_AUTO_USER_CONFIGURE=`echo $opt` # regenerate the options for default values. SRS_AUTO_CONFIGURE="--prefix=${SRS_PREFIX}" - if [ $SRS_HLS = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-hls"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-hls"; fi - if [ $SRS_HDS = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-hds"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-hds"; fi - if [ $SRS_DVR = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-dvr"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-dvr"; fi - if [ $SRS_SSL = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-ssl"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-ssl"; fi - if [ $SRS_TRANSCODE = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-transcode"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-transcode"; fi - if [ $SRS_INGEST = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-ingest"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-ingest"; fi - if [ $SRS_STAT = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-stat"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-stat"; fi - if [ $SRS_HTTP_CALLBACK = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-http-callback"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-http-callback"; fi - if [ $SRS_HTTP_SERVER = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-http-server"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-http-server"; fi - if [ $SRS_STREAM_CASTER = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-stream-caster"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-stream-caster"; fi - if [ $SRS_HTTP_API = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-http-api"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-http-api"; fi - if [ $SRS_LIBRTMP = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-librtmp"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-librtmp"; fi - if [ $SRS_RESEARCH = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-research"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-research"; fi - if [ $SRS_UTEST = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-utest"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-utest"; fi - if [ $SRS_SRT = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-srt"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-srt"; fi - if [ $SRS_RTC = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-rtc"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-rtc"; fi - if [ $SRS_GB28181 = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-gb28181"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-gb28181"; fi - if [ $SRS_NASM = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-nasm"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-nasm"; fi - if [ $SRS_SRTP_ASM = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-srtp-nasm"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-srtp-nasm"; fi - if [ $SRS_SENDMMSG = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-sendmmsg"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-sendmmsg"; fi - if [ $SRS_CLEAN = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-clean"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-clean"; fi - if [ $SRS_GPERF = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-gperf"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-gperf"; fi - if [ $SRS_GPERF_MC = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-gmc"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-gmc"; fi - if [ $SRS_GPERF_MD = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-gmd"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-gmd"; fi - if [ $SRS_GPERF_MP = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-gmp"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-gmp"; fi - if [ $SRS_GPERF_CP = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-gcp"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-gcp"; fi - if [ $SRS_GPROF = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --with-gprof"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --without-gprof"; fi - if [ $SRS_STATIC = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --static"; fi - if [ $SRS_SHARED_ST = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --use-shared-st"; fi - if [ $SRS_SHARED_SRT = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --use-shared-srt"; fi - if [ $SRS_LOG_VERBOSE = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --log-verbose"; fi - if [ $SRS_LOG_INFO = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --log-info"; fi - 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_DEBUG = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --debug"; 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 - if [[ $SRS_TOOL_LD != '' ]]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --ld=$SRS_TOOL_LD"; fi - if [[ $SRS_TOOL_RANDLIB != '' ]]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --randlib=$SRS_TOOL_RANDLIB"; fi + if [ $SRS_HLS = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --hls=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --hls=off"; fi + if [ $SRS_HDS = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --hds=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --hds=off"; fi + if [ $SRS_DVR = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --dvr=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --dvr=off"; fi + if [ $SRS_SSL = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --ssl=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --ssl=off"; fi + if [ $SRS_HTTPS = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --https=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --https=off"; fi + if [ $SRS_SSL_1_0 = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --ssl-1-0=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --ssl-1-0=off"; fi + if [ $SRS_USE_SYS_SSL = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --sys-ssl=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --sys-ssl=off"; fi + if [ $SRS_TRANSCODE = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --transcode=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --transcode=off"; fi + if [ $SRS_INGEST = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --ingest=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --ingest=off"; fi + if [ $SRS_STAT = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --stat=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --stat=off"; fi + if [ $SRS_HTTP_CALLBACK = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --http-callback=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --http-callback=off"; fi + if [ $SRS_HTTP_SERVER = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --http-server=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --http-server=off"; fi + if [ $SRS_STREAM_CASTER = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --stream-caster=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --stream-caster=off"; fi + if [ $SRS_HTTP_API = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --http-api=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --http-api=off"; fi + if [ $SRS_UTEST = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --utest=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --utest=off"; fi + if [ $SRS_CHERRYPY = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --cherrypy=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --cherrypy=off"; fi + if [ $SRS_SRT = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --srt=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --srt=off"; fi + if [ $SRS_RTC = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --rtc=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --rtc=off"; fi + if [ $SRS_SIMULATOR = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --simulator=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --simulator=off"; fi + if [ $SRS_GB28181 = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --gb28181=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --gb28181=off"; fi + if [ $SRS_CXX11 = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --cxx11=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --cxx11=off"; fi + if [ $SRS_CXX14 = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --cxx14=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --cxx14=off"; fi + if [ $SRS_FFMPEG_FIT = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --ffmpeg-fit=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --ffmpeg-fit=off"; fi + if [ $SRS_NASM = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --nasm=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --nasm=off"; fi + if [ $SRS_SRTP_ASM = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --srtp-nasm=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --srtp-nasm=off"; fi + if [ $SRS_SENDMMSG = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --sendmmsg=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --sendmmsg=off"; fi + if [ $SRS_CLEAN = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --clean=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --clean=off"; fi + if [ $SRS_GPERF = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --gperf=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --gperf=off"; fi + if [ $SRS_GPERF_MC = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --gmc=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --gmc=off"; fi + if [ $SRS_GPERF_MD = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --gmd=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --gmd=off"; fi + if [ $SRS_GPERF_MP = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --gmp=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --gmp=off"; fi + if [ $SRS_GPERF_CP = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --gcp=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --gcp=off"; fi + if [ $SRS_GPROF = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --gprof=on"; else SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --gprof=off"; fi + if [ $SRS_STATIC = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --static"; fi + if [ $SRS_SHARED_ST = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --use-shared-st"; fi + if [ $SRS_SHARED_SRT = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --use-shared-srt"; fi + if [ $SRS_LOG_VERBOSE = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --log-verbose"; fi + if [ $SRS_LOG_INFO = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --log-info"; fi + 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_DEBUG = YES ]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --debug"; 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 + if [[ $SRS_TOOL_LD != '' ]]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --ld=$SRS_TOOL_LD"; fi + if [[ $SRS_TOOL_RANDLIB != '' ]]; then SRS_AUTO_CONFIGURE="${SRS_AUTO_CONFIGURE} --randlib=$SRS_TOOL_RANDLIB"; fi echo "User config: $SRS_AUTO_USER_CONFIGURE" echo "Detail config: ${SRS_AUTO_CONFIGURE}" } @@ -649,17 +598,13 @@ function check_option_conflicts() { fi if [[ $SRS_CROSS_BUILD == YES && ($SRS_TOOL_CC == 'gcc' || $SRS_TOOL_CXX == 'g++' || $SRS_TOOL_AR == 'ar') ]]; then - echo "For crossbuild, must not use default toolchain, cc: $SRS_TOOL_CC, cxx: $SRS_TOOL_CXX, ar: $SRS_TOOL_AR"; exit -1 + echo "Warning: For crossbuild, must not use default toolchain, cc: $SRS_TOOL_CC, cxx: $SRS_TOOL_CXX, ar: $SRS_TOOL_AR" + SRS_CROSS_BUILD=NO fi if [[ $SRS_NGINX == YES ]]; then - echo "Don't support building NGINX, please use docker https://github.com/ossrs/srs-docker"; exit -1; - fi - - # For OSX, recommend to use DTrace, https://blog.csdn.net/win_lin/article/details/53503869 - if [[ $SRS_OSX == YES && $SRS_GPROF == YES ]]; then - echo "Tool gprof for OSX is unavailable, please use dtrace, read https://blog.csdn.net/win_lin/article/details/53503869" - exit -1 + echo "Warning: Don't support building NGINX, please use docker https://github.com/ossrs/srs-docker" + SRS_NGINX=NO fi # TODO: FIXME: check more os. @@ -692,8 +637,6 @@ function check_option_conflicts() { if [ $SRS_HDS = RESERVED ]; then echo "you must specifies the hds, see: ./configure --help"; __check_ok=NO; fi if [ $SRS_SSL = RESERVED ]; then echo "you must specifies the ssl, see: ./configure --help"; __check_ok=NO; fi if [ $SRS_STREAM_CASTER = RESERVED ]; then echo "you must specifies the stream-caster, see: ./configure --help"; __check_ok=NO; fi - if [ $SRS_LIBRTMP = RESERVED ]; then echo "you must specifies the librtmp, see: ./configure --help"; __check_ok=NO; fi - if [ $SRS_RESEARCH = RESERVED ]; then echo "you must specifies the research, see: ./configure --help"; __check_ok=NO; fi if [ $SRS_UTEST = RESERVED ]; then echo "you must specifies the utest, see: ./configure --help"; __check_ok=NO; fi if [ $SRS_GPERF = RESERVED ]; then echo "you must specifies the gperf, see: ./configure --help"; __check_ok=NO; fi if [ $SRS_GPERF_MC = RESERVED ]; then echo "you must specifies the gperf-mc, see: ./configure --help"; __check_ok=NO; fi diff --git a/trunk/configure b/trunk/configure index ed76da4d5..5bfb54a0d 100755 --- a/trunk/configure +++ b/trunk/configure @@ -32,9 +32,6 @@ rm -f ${SRS_WORKDIR}/${SRS_MAKEFILE} # create objs mkdir -p ${SRS_OBJS}/${SRS_PLATFORM} -# for export srs-librtmp, change target to it. -. auto/generate-srs-librtmp-project.sh - # apply user options. . auto/depends.sh @@ -47,9 +44,6 @@ mkdir -p ${SRS_OBJS}/${SRS_PLATFORM} # ubuntu echo in Makefile cannot display color, use bash instead SRS_BUILD_SUMMARY="_srs_build_summary.sh" -# srs-librtmp sample entry -SrsLibrtmpSampleEntry="nossl" -if [ $SRS_SSL = YES ]; then SrsLibrtmpSampleEntry="ssl";fi # utest make entry, (cd utest; make) SrsUtestMakeEntry="@echo -e \"ignore utest for it's disabled\"" if [ $SRS_UTEST = YES ]; then SrsUtestMakeEntry="(cd ${SRS_OBJS_DIR}/${SRS_PLATFORM}/utest && \$(MAKE))"; fi @@ -90,15 +84,14 @@ END # enable gdb debug GDBDebug=" -g -O0" # the warning level. -WarnLevel=" -Wall" +WarnLevel=" -Wall -Wno-deprecated-declarations" # the compile standard. CppStd="-ansi" -if [[ $SRS_SRT == YES ]]; then +if [[ $SRS_CXX11 == YES ]]; then CppStd="-std=c++11" fi -# for library compile -if [[ $SRS_EXPORT_LIBRTMP_PROJECT == YES ]]; then - LibraryCompile=" -fPIC" +if [[ $SRS_CXX14 == YES ]]; then + CppStd="-std=c++14" fi # performance of gprof SrsGprof=""; SrsGprofLink=""; if [ $SRS_GPROF = YES ]; then SrsGprof=" -pg -lc_p"; SrsGprofLink=" -pg"; fi @@ -129,7 +122,7 @@ ARFLAGS = -rs LINK = ${SRS_TOOL_CXX} CXXFLAGS = ${CXXFLAGS} -.PHONY: default srs srs_ingest_hls librtmp +.PHONY: default srs srs_ingest_hls default: @@ -143,20 +136,24 @@ END # st(state-threads) the basic network library for SRS. LibSTRoot="${SRS_OBJS_DIR}/st"; LibSTfile="${LibSTRoot}/libst.a" if [[ $SRS_SHARED_ST == YES ]]; then LibSTfile="-lst"; fi + # srtp if [[ $SRS_RTC == YES ]]; then LibSrtpRoot="${SRS_OBJS_DIR}/srtp2/include"; LibSrtpFile="${SRS_OBJS_DIR}/srtp2/lib/libsrtp2.a" fi + # FFMPEG for WebRTC transcoding, such as aac to opus. -if [[ $SRS_RTC == YES ]]; then +if [[ $SRS_FFMPEG_FIT == YES ]]; then LibFfmpegRoot="${SRS_OBJS_DIR}/ffmpeg/include"; LibFfmpegFile="${SRS_OBJS_DIR}/ffmpeg/lib/libavcodec.a ${SRS_OBJS_DIR}/ffmpeg/lib/libswresample.a ${SRS_OBJS_DIR}/ffmpeg/lib/libavutil.a" LibFfmpegRoot="${LibFfmpegRoot} ${SRS_OBJS_DIR}/opus/include"; LibFfmpegFile="${LibFfmpegFile} ${SRS_OBJS_DIR}/opus/lib/libopus.a" fi + # openssl-1.1.0e, for the RTMP complex handshake. LibSSLRoot="";LibSSLfile="" if [[ $SRS_SSL == YES && $SRS_USE_SYS_SSL == NO ]]; then LibSSLRoot="${SRS_OBJS_DIR}/openssl/include"; LibSSLfile="${SRS_OBJS_DIR}/openssl/lib/libssl.a ${SRS_OBJS_DIR}/openssl/lib/libcrypto.a"; fi + # gperftools-2.1, for mem check and mem/cpu profile LibGperfRoot=""; LibGperfFile="" if [ $SRS_GPERF = YES ]; then @@ -165,30 +162,37 @@ fi if [ $SRS_GPERF_MD = YES ]; then LibGperfFile="${SRS_OBJS_DIR}/gperf/lib/libtcmalloc_debug.a"; fi + # srt code path if [[ $SRS_SRT == YES ]]; then LibSRTRoot="${SRS_WORKDIR}/src/srt"; LibSRTfile="${SRS_OBJS_DIR}/srt/lib/libsrt.a" if [[ $SRS_SHARED_SRT == YES ]]; then LibSRTfile="-lsrt"; fi fi + # the link options, always use static link SrsLinkOptions="-ldl"; if [[ $SRS_SRT == YES || $SRS_RTC == YES ]]; then SrsLinkOptions="${SrsLinkOptions} -lpthread"; fi + if [[ $SRS_SSL == YES && $SRS_USE_SYS_SSL == YES ]]; then SrsLinkOptions="${SrsLinkOptions} -lssl -lcrypto"; fi -# if static specified, add static -# TODO: FIXME: remove static. + +# Static link the c++ libraries, for user who build SRS by a new version of gcc, +# so we need to link the c++ libraries staticly but not all. +# @see https://stackoverflow.com/a/26107550 if [ $SRS_STATIC = YES ]; then - SrsLinkOptions="${SrsLinkOptions} -static"; + SrsLinkOptions="${SrsLinkOptions} -static-libstdc++"; fi + # For coverage. if [[ $SRS_GCOV == YES ]]; then SrsLinkOptions="${SrsLinkOptions} ${SrsGcov}"; fi + # For FFMPEG/RTC. -if [[ $SRS_RTC == YES && $SRS_NASM == NO ]]; then +if [[ $SRS_RTC == YES && $SRS_NASM == NO && $SRS_OSX == NO ]]; then SrsLinkOptions="${SrsLinkOptions} -lrt"; fi @@ -213,7 +217,7 @@ MODULE_FILES=("srs_kernel_error" "srs_kernel_log" "srs_kernel_buffer" "srs_kernel_consts" "srs_kernel_aac" "srs_kernel_mp3" "srs_kernel_ts" "srs_kernel_stream" "srs_kernel_balance" "srs_kernel_mp4" "srs_kernel_file") if [[ $SRS_RTC == YES ]]; then - MODULE_FILES+=("srs_kernel_rtp") + MODULE_FILES+=("srs_kernel_rtc_rtp" "srs_kernel_rtc_rtcp") fi KERNEL_INCS="src/kernel"; MODULE_DIR=${KERNEL_INCS} . auto/modules.sh KERNEL_OBJS="${MODULE_OBJS[@]}" @@ -221,13 +225,21 @@ KERNEL_OBJS="${MODULE_OBJS[@]}" #RTMP/HTTP/Raw Protocol, depends on core/kernel, provides rtmp/htttp protocol features. MODULE_ID="PROTOCOL" MODULE_DEPENDS=("CORE" "KERNEL") -ModuleLibIncs=(${SRS_OBJS_DIR} ${LibSSLRoot}) +ModuleLibIncs=(${SRS_OBJS_DIR} ${LibSTRoot} ${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_sip_stack" "srs_http_stack" "srs_protocol_kbps" "srs_protocol_json" - "srs_protocol_format") + "srs_raw_avc" "srs_rtsp_stack" "srs_http_stack" "srs_protocol_kbps" "srs_protocol_json" + "srs_protocol_format" "srs_service_log" "srs_service_st" "srs_service_http_client" "srs_service_http_conn" + "srs_service_rtmp_conn" "srs_service_utility" "srs_service_conn") if [[ $SRS_RTC == YES ]]; then - MODULE_FILES+=("srs_stun_stack") + MODULE_FILES+=("srs_rtc_stun_stack") + ModuleLibIncs+=(${LibSrtpRoot}) +fi +if [[ $SRS_GB28181 == YES ]]; then + MODULE_FILES+=("srs_sip_stack") +fi +if [[ $SRS_FFMPEG_FIT == YES ]]; then + ModuleLibIncs+=("${LibFfmpegRoot[*]}") fi PROTOCOL_INCS="src/protocol"; MODULE_DIR=${PROTOCOL_INCS} . auto/modules.sh PROTOCOL_OBJS="${MODULE_OBJS[@]}" @@ -235,167 +247,154 @@ PROTOCOL_OBJS="${MODULE_OBJS[@]}" #srt protocol features. if [ $SRS_SRT = YES ]; then MODULE_ID="SRT" - MODULE_DEPENDS=("CORE" "KERNEL" "PROTOCOL" "SERVICE" "APP") + MODULE_DEPENDS=("CORE" "KERNEL" "PROTOCOL" "APP") ModuleLibIncs=(${SRS_OBJS_DIR}) MODULE_FILES=("srt_server" "srt_handle" "srt_conn" "srt_to_rtmp" "ts_demux" "srt_data") SRT_INCS=${LibSRTRoot}; MODULE_DIR=${LibSRTRoot} . auto/modules.sh SRT_OBJS="${MODULE_OBJS[@]}" fi -# -#Service Module, for both Server and Client Modules. -if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then - MODULE_ID="SERVICE" - MODULE_DEPENDS=("CORE" "KERNEL" "PROTOCOL") - ModuleLibIncs=(${LibSTRoot} ${SRS_OBJS_DIR} ${LibSSLRoot}) - if [[ $SRS_RTC == YES ]]; then - ModuleLibIncs+=("${LibFfmpegRoot[*]}" ${LibSrtpRoot}) - fi - MODULE_FILES=("srs_service_log" "srs_service_st" "srs_service_http_client" - "srs_service_http_conn" "srs_service_rtmp_conn" "srs_service_utility" - "srs_service_conn") - DEFINES="" - SERVICE_INCS="src/service"; MODULE_DIR=${SERVICE_INCS} . auto/modules.sh - SERVICE_OBJS="${MODULE_OBJS[@]}" -fi # #App Module, for SRS server only. -if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then - MODULE_ID="APP" - MODULE_DEPENDS=("CORE" "KERNEL" "PROTOCOL" "SERVICE") - ModuleLibIncs=(${LibSTRoot} ${SRS_OBJS_DIR} ${LibSSLRoot} ${LibGperfRoot}) - if [[ $SRS_RTC == YES ]]; then - ModuleLibIncs+=("${LibFfmpegRoot[*]}" ${LibSrtpRoot}) - fi - MODULE_FILES=("srs_app_server" "srs_app_conn" "srs_app_rtmp_conn" "srs_app_source" - "srs_app_refer" "srs_app_hls" "srs_app_forward" "srs_app_encoder" "srs_app_http_stream" - "srs_app_thread" "srs_app_bandwidth" "srs_app_st" "srs_app_log" "srs_app_config" - "srs_app_pithy_print" "srs_app_reload" "srs_app_http_api" "srs_app_http_conn" "srs_app_http_hooks" - "srs_app_ingest" "srs_app_ffmpeg" "srs_app_utility" "srs_app_edge" - "srs_app_heartbeat" "srs_app_empty" "srs_app_http_client" "srs_app_http_static" - "srs_app_recv_thread" "srs_app_security" "srs_app_statistic" "srs_app_hds" - "srs_app_mpegts_udp" "srs_app_rtsp" "srs_app_listener" "srs_app_async_call" - "srs_app_caster_flv" "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") - if [[ $SRS_RTC == YES ]]; then - MODULE_FILES+=("srs_app_rtc" "srs_app_rtc_conn" "srs_app_dtls" "srs_app_audio_recode" "srs_app_sdp") - fi - if [[ $SRS_GB28181 == YES ]]; then - MODULE_FILES+=("srs_app_gb28181" "srs_app_gb28181_sip") - fi - DEFINES="" - # add each modules for app - for SRS_MODULE in ${SRS_MODULES[*]}; do - . $SRS_MODULE/config - MODULE_FILES+=("${SRS_MODULE_APP[*]}") - DEFINES="${DEFINES} ${SRS_MODULE_DEFINES}" - done - APP_INCS="src/app"; MODULE_DIR=${APP_INCS} . auto/modules.sh - APP_OBJS="${MODULE_OBJS[@]}" +MODULE_ID="APP" +MODULE_DEPENDS=("CORE" "KERNEL" "PROTOCOL") +ModuleLibIncs=(${SRS_OBJS_DIR} ${LibSTRoot} ${LibSSLRoot}) +if [ $SRS_GPERF = YES ]; then + ModuleLibIncs+=(${LibGperfRoot}) fi -# -#LIBS Module, build libsrs.a for static link. -MODULE_ID="LIBS" -MODULE_DEPENDS=("CORE" "KERNEL" "PROTOCOL") -ModuleLibIncs=(${SRS_OBJS_DIR}) -MODULE_FILES=("srs_librtmp" "srs_lib_simple_socket" "srs_lib_bandwidth") -LIBS_INCS="src/libs"; MODULE_DIR=${LIBS_INCS} . auto/modules.sh -LIBS_OBJS="${MODULE_OBJS[@]}" +if [[ $SRS_RTC == YES ]]; then + ModuleLibIncs+=(${LibSrtpRoot}) +fi +if [[ $SRS_FFMPEG_FIT == YES ]]; then + ModuleLibIncs+=("${LibFfmpegRoot[*]}") +fi +MODULE_FILES=("srs_app_server" "srs_app_conn" "srs_app_rtmp_conn" "srs_app_source" + "srs_app_refer" "srs_app_hls" "srs_app_forward" "srs_app_encoder" "srs_app_http_stream" + "srs_app_bandwidth" "srs_app_st" "srs_app_log" "srs_app_config" + "srs_app_pithy_print" "srs_app_reload" "srs_app_http_api" "srs_app_http_conn" "srs_app_http_hooks" + "srs_app_ingest" "srs_app_ffmpeg" "srs_app_utility" "srs_app_edge" + "srs_app_heartbeat" "srs_app_empty" "srs_app_http_client" "srs_app_http_static" + "srs_app_recv_thread" "srs_app_security" "srs_app_statistic" "srs_app_hds" + "srs_app_mpegts_udp" "srs_app_rtsp" "srs_app_listener" "srs_app_async_call" + "srs_app_caster_flv" "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") +if [[ $SRS_RTC == YES ]]; then + MODULE_FILES+=("srs_app_rtc_conn" "srs_app_rtc_dtls" "srs_app_rtc_sdp" + "srs_app_rtc_queue" "srs_app_rtc_server" "srs_app_rtc_source" "srs_app_rtc_api") +fi +if [[ $SRS_FFMPEG_FIT == YES ]]; then + MODULE_FILES+=("srs_app_rtc_codec") +fi +if [[ $SRS_GB28181 == YES ]]; then + MODULE_FILES+=("srs_app_gb28181" "srs_app_gb28181_sip" "srs_app_gb28181_jitbuffer") +fi +DEFINES="" +# add each modules for app +for SRS_MODULE in ${SRS_MODULES[*]}; do + . $SRS_MODULE/config + MODULE_FILES+=("${SRS_MODULE_APP[*]}") + DEFINES="${DEFINES} ${SRS_MODULE_DEFINES}" +done +APP_INCS="src/app"; MODULE_DIR=${APP_INCS} . auto/modules.sh +APP_OBJS="${MODULE_OBJS[@]}" # #Server Module, for SRS only. -if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then - MODULE_ID="SERVER" - MODULE_DEPENDS=("CORE" "KERNEL" "PROTOCOL" "SERVICE" "APP") - if [[ $SRS_SRT == YES ]]; then - MODULE_DEPENDS+=("SRT") - fi - ModuleLibIncs=(${LibSTRoot} ${SRS_OBJS_DIR} ${LibGperfRoot} ${LibSSLRoot}) - if [[ $SRS_RTC == YES ]]; then - ModuleLibIncs+=("${LibFfmpegRoot[*]}" ${LibSrtpRoot}) - fi - if [[ $SRS_SRT == YES ]]; then - ModuleLibIncs+=("${LibSRTRoot[*]}") - fi - MODULE_FILES=("srs_main_server") - SERVER_INCS="src/main"; MODULE_DIR=${SERVER_INCS} . auto/modules.sh - SERVER_OBJS="${MODULE_OBJS[@]}" +MODULE_ID="SERVER" +MODULE_DEPENDS=("CORE" "KERNEL" "PROTOCOL" "APP") +if [[ $SRS_SRT == YES ]]; then + MODULE_DEPENDS+=("SRT") fi +ModuleLibIncs=(${SRS_OBJS_DIR} ${LibSTRoot} ${LibGperfRoot} ${LibSSLRoot}) +if [[ $SRS_RTC == YES ]]; then + ModuleLibIncs+=(${LibSrtpRoot}) +fi +if [[ $SRS_FFMPEG_FIT == YES ]]; then + ModuleLibIncs+=("${LibFfmpegRoot[*]}") +fi +if [[ $SRS_SRT == YES ]]; then + ModuleLibIncs+=("${LibSRTRoot[*]}") +fi +MODULE_FILES=("srs_main_server") +SERVER_INCS="src/main"; MODULE_DIR=${SERVER_INCS} . auto/modules.sh +SERVER_OBJS="${MODULE_OBJS[@]}" # #Main Module, for app from modules. -if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then - MODULE_ID="MAIN" - MODULE_DEPENDS=("CORE" "KERNEL" "PROTOCOL" "SERVICE") - ModuleLibIncs=(${LibSTRoot} ${SRS_OBJS_DIR} ${LibGperfRoot} ${LibSSLRoot}) - if [[ $SRS_RTC == YES ]]; then - ModuleLibIncs+=("${LibFfmpegRoot[*]}" ${LibSrtpRoot}) - fi - MODULE_FILES=() - DEFINES="" - # add each modules for main - for SRS_MODULE in ${SRS_MODULES[*]}; do - . $SRS_MODULE/config - MODULE_FILES+=("${SRS_MODULE_MAIN[*]}") - DEFINES="${DEFINES} ${SRS_MODULE_DEFINES}" - done - MAIN_INCS="src/main"; MODULE_DIR=${MAIN_INCS} . auto/modules.sh - MAIN_OBJS="${MODULE_OBJS[@]}" +MODULE_ID="MAIN" +MODULE_DEPENDS=("CORE" "KERNEL" "PROTOCOL") +ModuleLibIncs=(${SRS_OBJS_DIR} ${LibSTRoot} ${LibGperfRoot} ${LibSSLRoot}) +if [[ $SRS_RTC == YES ]]; then + ModuleLibIncs+=(${LibSrtpRoot}) fi +if [[ $SRS_FFMPEG_FIT == YES ]]; then + ModuleLibIncs+=("${LibFfmpegRoot[*]}") +fi +MODULE_FILES=() +DEFINES="" +# add each modules for main +for SRS_MODULE in ${SRS_MODULES[*]}; do + . $SRS_MODULE/config + MODULE_FILES+=("${SRS_MODULE_MAIN[*]}") + DEFINES="${DEFINES} ${SRS_MODULE_DEFINES}" +done +MAIN_INCS="src/main"; MODULE_DIR=${MAIN_INCS} . auto/modules.sh +MAIN_OBJS="${MODULE_OBJS[@]}" ##################################################################################### # Binaries, main entrances, link the module and its depends modules, # then link to a binary, for example, objs/srs # -# Disable SRS application for exporting librtmp. -if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then - # all main entrances - MAIN_ENTRANCES=("srs_main_server") - for SRS_MODULE in ${SRS_MODULES[*]}; do - . $SRS_MODULE/config - MAIN_ENTRANCES+=("${SRS_MODULE_MAIN[*]}") - done - # - # all depends libraries - ModuleLibFiles=(${LibSTfile} ${LibSSLfile} ${LibGperfFile}) - if [[ $SRS_RTC == YES ]]; then - ModuleLibFiles+=("${LibFfmpegFile[*]}" ${LibSrtpFile}) - fi - if [[ $SRS_SRT == YES ]]; then - ModuleLibFiles+=("${LibSRTfile[*]}") - fi - # all depends objects - MODULE_OBJS="${CORE_OBJS[@]} ${KERNEL_OBJS[@]} ${PROTOCOL_OBJS[@]} ${SERVICE_OBJS[@]} ${APP_OBJS[@]} ${SERVER_OBJS[@]}" - ModuleLibIncs=(${LibSTRoot} ${SRS_OBJS_DIR} ${LibGperfRoot} ${LibSSLRoot}) - if [[ $SRS_RTC == YES ]]; then - ModuleLibIncs+=("${LibFfmpegRoot[*]}" ${LibSrtpRoot}) - fi - if [[ $SRS_SRT == YES ]]; then - MODULE_OBJS="${MODULE_OBJS} ${SRT_OBJS[@]}" - fi - LINK_OPTIONS="${SrsLinkOptions}${SrsGprofLink}${SrsGperfLink}" - # - # srs: srs(simple rtmp server) over st(state-threads) - BUILD_KEY="srs" APP_MAIN="srs_main_server" APP_NAME="srs" . auto/apps.sh - # - # For modules, without the app module. - MODULE_OBJS="${CORE_OBJS[@]} ${KERNEL_OBJS[@]} ${PROTOCOL_OBJS[@]} ${SERVICE_OBJS[@]} ${MAIN_OBJS[@]}" - ModuleLibFiles=(${LibSTfile} ${LibSSLfile} ${LibGperfFile}) - if [[ $SRS_RTC == YES ]]; then - ModuleLibFiles+=("${LibFfmpegFile[*]}" ${LibSrtpFile}) - fi - # - for SRS_MODULE in ${SRS_MODULES[*]}; do - . $SRS_MODULE/config - # no SRS_MODULE_MAIN - if [[ 0 -eq ${#SRS_MODULE_MAIN[@]} ]]; then continue; fi - BUILD_KEY="$SRS_MODULE_NAME" APP_MAIN="${SRS_MODULE_MAIN[0]}" APP_NAME="$SRS_MODULE_NAME" . auto/apps.sh - done +# all main entrances +MAIN_ENTRANCES=("srs_main_server") +for SRS_MODULE in ${SRS_MODULES[*]}; do + . $SRS_MODULE/config + MAIN_ENTRANCES+=("${SRS_MODULE_MAIN[*]}") +done +# +# all depends libraries +ModuleLibFiles=(${LibSTfile} ${LibSSLfile} ${LibGperfFile}) +if [[ $SRS_RTC == YES ]]; then + ModuleLibFiles+=(${LibSrtpFile}) fi -# srs librtmp -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 +if [[ $SRS_FFMPEG_FIT == YES ]]; then + ModuleLibFiles+=("${LibFfmpegFile[*]}") fi +if [[ $SRS_SRT == YES ]]; then + ModuleLibFiles+=("${LibSRTfile[*]}") +fi +# all depends objects +MODULE_OBJS="${CORE_OBJS[@]} ${KERNEL_OBJS[@]} ${PROTOCOL_OBJS[@]} ${APP_OBJS[@]} ${SERVER_OBJS[@]}" +ModuleLibIncs=(${SRS_OBJS_DIR} ${LibSTRoot} ${LibGperfRoot} ${LibSSLRoot}) +if [[ $SRS_RTC == YES ]]; then + ModuleLibIncs+=(${LibSrtpRoot}) +fi +if [[ $SRS_FFMPEG_FIT == YES ]]; then + ModuleLibIncs+=("${LibFfmpegRoot[*]}") +fi +if [[ $SRS_SRT == YES ]]; then + MODULE_OBJS="${MODULE_OBJS} ${SRT_OBJS[@]}" +fi +LINK_OPTIONS="${SrsLinkOptions}${SrsGprofLink}${SrsGperfLink}" +# +# srs: srs(simple rtmp server) over st(state-threads) +BUILD_KEY="srs" APP_MAIN="srs_main_server" APP_NAME="srs" . auto/apps.sh +# +# For modules, without the app module. +MODULE_OBJS="${CORE_OBJS[@]} ${KERNEL_OBJS[@]} ${PROTOCOL_OBJS[@]} ${MAIN_OBJS[@]}" +ModuleLibFiles=(${LibSTfile} ${LibSSLfile} ${LibGperfFile}) +if [[ $SRS_RTC == YES ]]; then + ModuleLibFiles+=(${LibSrtpFile}) +fi +if [[ $SRS_FFMPEG_FIT == YES ]]; then + ModuleLibFiles+=("${LibFfmpegFile[*]}") +fi +# +for SRS_MODULE in ${SRS_MODULES[*]}; do + . $SRS_MODULE/config + # no SRS_MODULE_MAIN + if [[ 0 -eq ${#SRS_MODULE_MAIN[@]} ]]; then continue; fi + BUILD_KEY="$SRS_MODULE_NAME" APP_MAIN="${SRS_MODULE_MAIN[0]}" APP_NAME="$SRS_MODULE_NAME" . auto/apps.sh +done # For utest on mac. # @see https://github.com/protocolbuffers/protobuf/issues/51#issuecomment-111044468 if [[ $SRS_OSX == YES ]]; then @@ -406,26 +405,32 @@ fi if [ $SRS_UTEST = YES ]; then MODULE_FILES=("srs_utest" "srs_utest_amf0" "srs_utest_protocol" "srs_utest_kernel" "srs_utest_core" "srs_utest_config" "srs_utest_rtmp" "srs_utest_http" "srs_utest_avc" "srs_utest_reload" - "srs_utest_mp4" "srs_utest_service" "srs_utest_app") + "srs_utest_mp4" "srs_utest_service" "srs_utest_app" "srs_utest_rtc") ModuleLibIncs=(${SRS_OBJS_DIR} ${LibSTRoot} ${LibSSLRoot}) if [[ $SRS_RTC == YES ]]; then - ModuleLibIncs+=("${LibFfmpegRoot[*]}" ${LibSrtpRoot}) + ModuleLibIncs+=(${LibSrtpRoot}) + fi + if [[ $SRS_FFMPEG_FIT == YES ]]; then + ModuleLibIncs+=("${LibFfmpegRoot[*]}") fi if [[ $SRS_SRT == YES ]]; then ModuleLibIncs+=("${LibSRTRoot[*]}") fi ModuleLibFiles=(${LibSTfile} ${LibSSLfile}) if [[ $SRS_RTC == YES ]]; then - ModuleLibFiles+=("${LibFfmpegFile[*]}" ${LibSrtpFile}) + ModuleLibFiles+=(${LibSrtpFile}) + fi + if [[ $SRS_FFMPEG_FIT == YES ]]; then + ModuleLibFiles+=("${LibFfmpegFile[*]}") fi if [[ $SRS_SRT == YES ]]; then ModuleLibFiles+=("${LibSRTfile[*]}") fi - MODULE_DEPENDS=("CORE" "KERNEL" "PROTOCOL" "SERVICE" "APP") + MODULE_DEPENDS=("CORE" "KERNEL" "PROTOCOL" "APP") if [[ $SRS_SRT == YES ]]; then MODULE_DEPENDS+=("SRT") fi - MODULE_OBJS="${CORE_OBJS[@]} ${KERNEL_OBJS[@]} ${PROTOCOL_OBJS[@]} ${SERVICE_OBJS[@]} ${APP_OBJS[@]} ${SRT_OBJS[@]}" + MODULE_OBJS="${CORE_OBJS[@]} ${KERNEL_OBJS[@]} ${PROTOCOL_OBJS[@]} ${APP_OBJS[@]} ${SRT_OBJS[@]}" LINK_OPTIONS="-lpthread ${SrsLinkOptions}" MODULE_DIR="src/utest" APP_NAME="srs_utest" . auto/utest.sh fi @@ -443,7 +448,7 @@ 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 destroy server srs_ingest_hls librtmp utest _prepare_dir $__mphonys +.PHONY: default _default install install-api help clean destroy server srs_ingest_hls utest _prepare_dir $__mphonys .PHONY: clean_srs clean_modules clean_openssl clean_nginx clean_cherrypy clean_srtp2 clean_opus clean_ffmpeg clean_st .PHONY: st ffmpeg @@ -457,19 +462,16 @@ default: END # the real entry for all platform: -# the server, librtmp and utest -# where the bellow will check and disable some entry by only echo. cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE} -_default: server srs_ingest_hls librtmp utest __modules $__mdefaults +_default: server srs_ingest_hls 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 and all depends" @echo " destroy Cleanup all files for this platform in ${SRS_OBJS_DIR}/${SRS_PLATFORM}" @echo " server Build the srs and other modules in main" - @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" @@ -491,7 +493,6 @@ doclean: (cd ${SRS_OBJS_DIR} && rm -rf srs srs_utest $__mcleanups) (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 @@ -501,13 +502,13 @@ destroy: (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 include/* lib/*) - (cd ${SRS_OBJS_DIR}/${SRS_PLATFORM} && find src -name "*.o" -delete) - (cd ${SRS_OBJS_DIR}/${SRS_PLATFORM} && find utest -name "*.o" -delete) + @(cd ${SRS_OBJS_DIR} && rm -rf srs srs_utest) + @(cd ${SRS_OBJS_DIR}/${SRS_PLATFORM} && rm -rf include/* lib/*) + @(cd ${SRS_OBJS_DIR}/${SRS_PLATFORM} && find src -name "*.o" -delete) + @(cd ${SRS_OBJS_DIR}/${SRS_PLATFORM} && find utest -name "*.o" -delete) clean_modules: - (cd ${SRS_OBJS_DIR} && rm -rf $__mdefaults) + @(cd ${SRS_OBJS_DIR} && rm -rf $__mdefaults) clean_openssl: (cd ${SRS_OBJS_DIR}/${SRS_PLATFORM} && rm -rf openssl*) @@ -533,7 +534,6 @@ 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) st: @@ -556,56 +556,25 @@ for MMF in ${__makefiles[*]}; do done echo "" >> ${SRS_WORKDIR}/${SRS_MAKEFILE} -# if export librtmp, donot build the srs server. -if [ $SRS_EXPORT_LIBRTMP_PROJECT != NO ]; then - cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE} -server: _prepare_dir - @echo "Ingore srs(simple rtmp server) for srs-librtmp" - -END -else - cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE} +cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE} server: _prepare_dir @echo "Build the srs(simple rtmp server) over ST(state-threads)" \$(MAKE) -f ${SRS_OBJS_DIR}/${SRS_MAKEFILE} srs END -fi # generate all modules entry for SRS_MODULE in ${SRS_MODULES[*]}; do . $SRS_MODULE/config - # if export librtmp, donot build the bravo-ingest. - if [ $SRS_EXPORT_LIBRTMP_PROJECT != NO ]; then - cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE} -$SRS_MODULE_NAME: _prepare_dir server - @echo "Ingore the $SRS_MODULE_NAME for srs-librtmp" - -END - else - cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE} + cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE} $SRS_MODULE_NAME: _prepare_dir server @echo "Build the $SRS_MODULE_NAME over SRS" \$(MAKE) -f ${SRS_OBJS_DIR}/${SRS_MAKEFILE} $SRS_MODULE_NAME END - fi done -# disable install entry for srs-librtmp -if [ $SRS_EXPORT_LIBRTMP_PROJECT != NO ]; then - cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE} -uninstall: - @echo "Disable uninstall for srs-librtmp" - -install-api: - @echo "Disable install-api for srs-librtmp" - -install: - @echo "Disable install for srs-librtmp" - -END -else - cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE} +# install entry +cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE} uninstall: @echo "rmdir \$(SRS_PREFIX)" @rm -rf \$(SRS_PREFIX) @@ -665,25 +634,6 @@ install: @echo "@see: https://github.com/ossrs/srs/wiki/v3_CN_LinuxService" END -fi - -# generate srs-librtmp entry -if [ $SRS_LIBRTMP = YES ]; then - cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE} -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}) - -END -else - cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE} -librtmp: server - @echo "Ignore srs-librtmp for it's disabled." - -END -fi if [ $SRS_UTEST = YES ]; then cat << END >> ${SRS_WORKDIR}/${SRS_MAKEFILE} @@ -717,173 +667,144 @@ echo 'Configure ok! ' # when configure success, prepare build ##################################################################################### # create objs/logs for ffmpeg to write log. -if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then - mkdir -p ${SRS_OBJS}/logs -fi +mkdir -p ${SRS_OBJS}/logs ##################################################################################### # configure summary ##################################################################################### # summary -if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then - echo "" - echo "Configure summary:" - echo " ${SRS_AUTO_USER_CONFIGURE}" - echo " ${SRS_AUTO_CONFIGURE}" - if [ $SRS_HLS = YES ]; then - echo -e "${GREEN}HLS is enabled.${BLACK}" - else - echo -e "${YELLOW}Warning: HLS is disabled.${BLACK}" - fi - if [ $SRS_STREAM_CASTER = YES ]; then - echo -e "${YELLOW}Experiment: StreamCaster is enabled.${BLACK}" - else - echo -e "${GREEN}Note: StreamCaster is disabled.${BLACK}" - fi - if [ $SRS_HDS = YES ]; then - echo -e "${YELLOW}Experiment: HDS is enabled.${BLACK}" - else - echo -e "${GREEN}Warning: HDS is disabled.${BLACK}" - fi - if [ $SRS_SRT = YES ]; then - echo -e "${YELLOW}Experiment: SRT is enabled. https://github.com/ossrs/srs/issues/1147${BLACK}" - else - echo -e "${GREEN}Warning: SRT is disabled.${BLACK}" - fi - if [ $SRS_RTC = YES ]; then - echo -e "${YELLOW}Experiment: RTC is enabled. https://github.com/ossrs/srs/issues/307${BLACK}" - else - echo -e "${GREEN}Warning: RTC is disabled.${BLACK}" - fi - if [ $SRS_DVR = YES ]; then - echo -e "${GREEN}DVR is enabled.${BLACK}" - else - echo -e "${YELLOW}Warning: DVR is disabled.${BLACK}" - fi - if [ $SRS_SSL = YES ]; then - echo -e "${GREEN}RTMP complex handshake is enabled${BLACK}" - else - echo -e "${YELLOW}Warning: RTMP complex handshake is disabled, flash cann't play h264/aac.${BLACK}" - fi - if [ $SRS_TRANSCODE = YES ]; then - echo -e "${GREEN}The transcoding is enabled${BLACK}" - else - echo -e "${YELLOW}Warning: The transcoding is disabled.${BLACK}" - fi - if [ $SRS_INGEST = YES ]; then - echo -e "${GREEN}The ingesting is enabled.${BLACK}" - else - echo -e "${YELLOW}Warning: The ingesting is disabled.${BLACK}" - fi - if [ $SRS_HTTP_CALLBACK = YES ]; then - echo -e "${GREEN}The http-callback is enabled${BLACK}" - else - echo -e "${YELLOW}Warning: The http-callback is disabled.${BLACK}" - fi - if [ $SRS_HTTP_SERVER = YES ]; then - echo -e "${GREEN}Embeded HTTP server for HTTP-FLV/HLS is enabled.${BLACK}" - else - echo -e "${YELLOW}Warning: Embeded HTTP server is disabled, HTTP-FLV is disabled, please use nginx to delivery HLS.${BLACK}" - fi - if [ $SRS_HTTP_API = YES ]; then - echo -e "${GREEN}The HTTP API is enabled${BLACK}" - else - echo -e "${YELLOW}Warning: The HTTP API is disabled.${BLACK}" - fi - if [ $SRS_LIBRTMP = YES ]; then - echo -e "${GREEN}The client-side srs-librtmp is enabled.${BLACK}" - else - echo -e "${YELLOW}Note: The client-side srs-librtmp is disabled.${BLACK}" - fi - if [ $SRS_RESEARCH = YES ]; then - echo -e "${GREEN}The research tools are enabled.${BLACK}" - else - echo -e "${GREEN}Note: The research tools are disabled.${BLACK}" - fi - if [ $SRS_UTEST = YES ]; then - echo -e "${GREEN}The utests are enabled.${BLACK}" - else - echo -e "${YELLOW}Note: The utests are disabled.${BLACK}" - fi - if [ $SRS_GPERF = YES ]; then - echo -e "${GREEN}The gperf(tcmalloc) is enabled.${BLACK}" - else - echo -e "${GREEN}Note: The gperf(tcmalloc) is disabled.${BLACK}" - fi - if [ $SRS_GPERF_MC = YES ]; then - echo -e "${YELLOW}The gmc(gperf memory check) is enabled, performance may suffer.${BLACK}" - else - echo -e "${GREEN}Note: The gmc(gperf memory check) is disabled.${BLACK}" - fi - if [ $SRS_GPERF_MD = YES ]; then - echo -e "${YELLOW}The gmd(gperf memory defense) is enabled, performance may suffer.${BLACK}" - else - echo -e "${GREEN}Note: The gmd(gperf memory defense) is disabled.${BLACK}" - fi - if [ $SRS_GPERF_MP = YES ]; then - echo -e "${YELLOW}The gmp(gperf memory profile) is enabled, performance may suffer.${BLACK}" - else - echo -e "${GREEN}Note: The gmp(gperf memory profile) is disabled.${BLACK}" - fi - if [ $SRS_GPERF_CP = YES ]; then - echo -e "${YELLOW}The gcp(gperf cpu profile) is enabled, performance may suffer.${BLACK}" - else - echo -e "${GREEN}Note: The gcp(gperf cpu profile) is disabled.${BLACK}" - fi - if [ $SRS_GPROF = YES ]; then - echo -e "${YELLOW}The gprof(GNU profile tool) is enabled, performance may suffer.${BLACK}" - else - echo -e "${GREEN}Note: The gprof(GNU profile tool) is disabled.${BLACK}" - fi - if [ $SRS_VALGRIND = YES ]; then - echo -e "${GREEN}The valgrind is enabled.${BLACK}" - else - echo -e "${GREEN}Note: The valgrind is disabled.${BLACK}" - fi - # add each modules for application - for SRS_MODULE in ${SRS_MODULES[*]}; do - echo -e "${GREEN}Enable module: $SRS_MODULE${BLACK}" - done +echo "" +echo "Configure summary:" +echo " ${SRS_AUTO_USER_CONFIGURE}" +echo " ${SRS_AUTO_CONFIGURE}" +if [ $SRS_HLS = YES ]; then + echo -e "${GREEN}HLS is enabled.${BLACK}" +else + echo -e "${YELLOW}Warning: HLS is disabled.${BLACK}" fi +if [ $SRS_STREAM_CASTER = YES ]; then + echo -e "${YELLOW}Experiment: StreamCaster is enabled.${BLACK}" +else + echo -e "${GREEN}Note: StreamCaster is disabled.${BLACK}" +fi +if [ $SRS_HDS = YES ]; then + echo -e "${YELLOW}Experiment: HDS is enabled.${BLACK}" +else + echo -e "${GREEN}Warning: HDS is disabled.${BLACK}" +fi +if [ $SRS_SRT = YES ]; then + echo -e "${YELLOW}Experiment: SRT is enabled. https://github.com/ossrs/srs/issues/1147${BLACK}" +else + echo -e "${GREEN}Warning: SRT is disabled.${BLACK}" +fi +if [ $SRS_RTC = YES ]; then + echo -e "${YELLOW}Experiment: RTC is enabled. https://github.com/ossrs/srs/issues/307${BLACK}" +else + echo -e "${GREEN}Warning: RTC is disabled.${BLACK}" +fi +if [ $SRS_HTTPS = YES ]; then + echo -e "${YELLOW}Experiment: HTTPS is enabled. https://github.com/ossrs/srs/issues/1657${BLACK}" +else + echo -e "${GREEN}Warning: HTTPS is disabled.${BLACK}" +fi +if [ $SRS_DVR = YES ]; then + echo -e "${GREEN}DVR is enabled.${BLACK}" +else + echo -e "${YELLOW}Warning: DVR is disabled.${BLACK}" +fi +if [ $SRS_SSL = YES ]; then + echo -e "${GREEN}RTMP complex handshake is enabled${BLACK}" +else + echo -e "${YELLOW}Warning: RTMP complex handshake is disabled, flash cann't play h264/aac.${BLACK}" +fi +if [ $SRS_TRANSCODE = YES ]; then + echo -e "${GREEN}The transcoding is enabled${BLACK}" +else + echo -e "${YELLOW}Warning: The transcoding is disabled.${BLACK}" +fi +if [ $SRS_INGEST = YES ]; then + echo -e "${GREEN}The ingesting is enabled.${BLACK}" +else + echo -e "${YELLOW}Warning: The ingesting is disabled.${BLACK}" +fi +if [ $SRS_HTTP_CALLBACK = YES ]; then + echo -e "${GREEN}The http-callback is enabled${BLACK}" +else + echo -e "${YELLOW}Warning: The http-callback is disabled.${BLACK}" +fi +if [ $SRS_HTTP_SERVER = YES ]; then + echo -e "${GREEN}Embeded HTTP server for HTTP-FLV/HLS is enabled.${BLACK}" +else + echo -e "${YELLOW}Warning: Embeded HTTP server is disabled, HTTP-FLV is disabled, please use nginx to delivery HLS.${BLACK}" +fi +if [ $SRS_HTTP_API = YES ]; then + echo -e "${GREEN}The HTTP API is enabled${BLACK}" +else + echo -e "${YELLOW}Warning: The HTTP API is disabled.${BLACK}" +fi +if [ $SRS_UTEST = YES ]; then + echo -e "${GREEN}The utests are enabled.${BLACK}" +else + echo -e "${YELLOW}Note: The utests are disabled.${BLACK}" +fi +if [ $SRS_GPERF = YES ]; then + echo -e "${GREEN}The gperf(tcmalloc) is enabled.${BLACK}" +else + echo -e "${GREEN}Note: The gperf(tcmalloc) is disabled.${BLACK}" +fi +if [ $SRS_GPERF_MC = YES ]; then + echo -e "${YELLOW}The gmc(gperf memory check) is enabled, performance may suffer.${BLACK}" +else + echo -e "${GREEN}Note: The gmc(gperf memory check) is disabled.${BLACK}" +fi +if [ $SRS_GPERF_MD = YES ]; then + echo -e "${YELLOW}The gmd(gperf memory defense) is enabled, performance may suffer.${BLACK}" +else + echo -e "${GREEN}Note: The gmd(gperf memory defense) is disabled.${BLACK}" +fi +if [ $SRS_GPERF_MP = YES ]; then + echo -e "${YELLOW}The gmp(gperf memory profile) is enabled, performance may suffer.${BLACK}" +else + echo -e "${GREEN}Note: The gmp(gperf memory profile) is disabled.${BLACK}" +fi +if [ $SRS_GPERF_CP = YES ]; then + echo -e "${YELLOW}The gcp(gperf cpu profile) is enabled, performance may suffer.${BLACK}" +else + echo -e "${GREEN}Note: The gcp(gperf cpu profile) is disabled.${BLACK}" +fi +if [ $SRS_GPROF = YES ]; then + echo -e "${YELLOW}The gprof(GNU profile tool) is enabled, performance may suffer.${BLACK}" +else + echo -e "${GREEN}Note: The gprof(GNU profile tool) is disabled.${BLACK}" +fi +if [ $SRS_VALGRIND = YES ]; then + echo -e "${GREEN}The valgrind is enabled.${BLACK}" +else + echo -e "${GREEN}Note: The valgrind is disabled.${BLACK}" +fi +# add each modules for application +for SRS_MODULE in ${SRS_MODULES[*]}; do + echo -e "${GREEN}Enable module: $SRS_MODULE${BLACK}" +done ##################################################################################### # Do cleanup when configure done. ##################################################################################### if [[ $SRS_CLEAN == YES && -f Makefile ]]; then - echo "Do full cleanup, you can disable it by: --without-clean" + echo "Do full cleanup, you can disable it by: --clean=off" make clean fi ##################################################################################### # next step ##################################################################################### -if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then - echo "" - echo "You can run 3rdparty applications:" - if [ $SRS_HTTP_CALLBACK = YES ]; then - echo -e "\" python ./research/api-server/server.py 8085 \" to start the api-server" - fi - echo "" - echo "You can build SRS:" - echo "\" make \" to build the srs(simple rtmp server)." - echo "\" make help \" to get the usage of make" -else - # for srs-librtmp single file, - # package the whole project to srs_librtmp.h and srs_librtmp.cpp - if [ $SRS_EXPORT_LIBRTMP_SINGLE != NO ]; then - echo "Packaging the whole project to srs_librtmp.h and srs_librtmp.cpp" - . $SRS_EXPORT_LIBRTMP_SINGLE/auto/generate-srs-librtmp-single.sh - echo -e "${GREEN}Please use the srs-librtmp files: ${BLACK}" - echo -e "${GREEN} $SRS_EXPORT_LIBRTMP_PROJECT/srs_librtmp.h ${BLACK}" - echo -e "${GREEN} $SRS_EXPORT_LIBRTMP_PROJECT/srs_librtmp.cpp ${BLACK}" - echo -e "${GREEN} $SRS_EXPORT_LIBRTMP_PROJECT/example.c ${BLACK}" - echo -e "${GREEN}To compile the example: ${BLACK}" - echo -e "${GREEN} cd $SRS_EXPORT_LIBRTMP_PROJECT && $SRS_SINGLE_LIBRTMP_COMPILE ${BLACK}" - # for srs-librtmp project. - else - echo -e "${GREEN}Please use the srs-librtmp project: ${BLACK}" - echo -e "${GREEN} cd $SRS_EXPORT_LIBRTMP_PROJECT && make ${BLACK}" - fi - # Change to experiment. - echo -e "${YELLOW}Warning: Notice srs-librtmp is deprecated and maybe removed in future.${BLACK}" +echo "" +echo "You can run 3rdparty applications:" +if [ $SRS_HTTP_CALLBACK = YES ]; then + echo -e "\" python ./research/api-server/server.py 8085 \" to start the api-server" fi +echo "" +echo "You can build SRS:" +echo "\" make \" to build the srs(simple rtmp server)." +echo "\" make help \" to get the usage of make" + diff --git a/trunk/src/app/srs_app_async_call.hpp b/trunk/src/app/srs_app_async_call.hpp index 7cc183f4a..b05fda21a 100644 --- a/trunk/src/app/srs_app_async_call.hpp +++ b/trunk/src/app/srs_app_async_call.hpp @@ -29,7 +29,7 @@ #include #include -#include +#include // The async call for http hooks, for the http hooks will switch st-thread, // so we must use isolate thread to avoid the thread corrupt, diff --git a/trunk/src/app/srs_app_bandwidth.cpp b/trunk/src/app/srs_app_bandwidth.cpp index f89cabe03..5fd77c7db 100644 --- a/trunk/src/app/srs_app_bandwidth.cpp +++ b/trunk/src/app/srs_app_bandwidth.cpp @@ -91,7 +91,7 @@ bool _bandwidth_is_stopped_publish(SrsBandwidthPacket* pkt) { return pkt->is_stopped_publish(); } -srs_error_t _srs_expect_bandwidth_packet(SrsRtmpServer* rtmp, _CheckPacketType pfn) +srs_error_t srs_expect_bandwidth_packet(SrsRtmpServer* rtmp, _CheckPacketType pfn) { srs_error_t err = srs_success; @@ -151,7 +151,7 @@ srs_error_t SrsBandwidth::bandwidth_check(SrsRtmpServer* rtmp, ISrsProtocolStati // reject the connection in the interval window. if (last_check_time > 0 && time_now - last_check_time < interval) { _rtmp->response_connect_reject(_req, "bandcheck rejected"); - return srs_error_new(ERROR_SYSTEM_BANDWIDTH_DENIED, "reject, last_check=%" PRId64 ", now=%" PRId64 ", interval=%d", last_check_time, time_now, interval); + return srs_error_new(ERROR_SYSTEM_BANDWIDTH_DENIED, "reject, last_check=%" PRId64 ", now=%" PRId64 ", interval=%" PRId64 "", last_check_time, time_now, interval); } // accept and do bandwidth check. @@ -241,7 +241,7 @@ srs_error_t SrsBandwidth::play_start(SrsBandwidthSample* sample, SrsKbpsLimit* l } } - if ((err = _srs_expect_bandwidth_packet(_rtmp, _bandwidth_is_starting_play)) != srs_success) { + if ((err = srs_expect_bandwidth_packet(_rtmp, _bandwidth_is_starting_play)) != srs_success) { return srs_error_wrap(err, "expect bandwidth"); } @@ -304,7 +304,7 @@ srs_error_t SrsBandwidth::play_stop(SrsBandwidthSample* sample, SrsKbpsLimit* /* } } - if ((err = _srs_expect_bandwidth_packet(_rtmp, _bandwidth_is_stopped_play)) != srs_success) { + if ((err = srs_expect_bandwidth_packet(_rtmp, _bandwidth_is_stopped_play)) != srs_success) { return srs_error_wrap(err, "expect bandwidth"); } @@ -328,7 +328,7 @@ srs_error_t SrsBandwidth::publish_start(SrsBandwidthSample* sample, SrsKbpsLimit } } - if ((err = _srs_expect_bandwidth_packet(_rtmp, _bandwidth_is_starting_publish)) != srs_success) { + if ((err = srs_expect_bandwidth_packet(_rtmp, _bandwidth_is_starting_publish)) != srs_success) { return srs_error_wrap(err, "expect packet"); } @@ -388,7 +388,7 @@ srs_error_t SrsBandwidth::publish_stop(SrsBandwidthSample* sample, SrsKbpsLimit* // we just ignore the packet and send the bandwidth test data. bool is_flash = (_req->swfUrl != ""); if (!is_flash) { - if ((err = _srs_expect_bandwidth_packet(_rtmp, _bandwidth_is_stopped_publish)) != srs_success) { + if ((err = srs_expect_bandwidth_packet(_rtmp, _bandwidth_is_stopped_publish)) != srs_success) { return srs_error_wrap(err, "expect bandwidth"); } } @@ -422,7 +422,7 @@ srs_error_t SrsBandwidth::do_final(SrsBandwidthSample& play_sample, SrsBandwidth bool is_flash = (_req->swfUrl != ""); if (!is_flash) { // ignore any error. - err = _srs_expect_bandwidth_packet(_rtmp, _bandwidth_is_final); + err = srs_expect_bandwidth_packet(_rtmp, _bandwidth_is_final); srs_error_reset(err); } diff --git a/trunk/src/app/srs_app_caster_flv.cpp b/trunk/src/app/srs_app_caster_flv.cpp index d6f3150f7..7d86fe434 100644 --- a/trunk/src/app/srs_app_caster_flv.cpp +++ b/trunk/src/app/srs_app_caster_flv.cpp @@ -49,7 +49,7 @@ SrsAppCasterFlv::SrsAppCasterFlv(SrsConfDirective* c) { http_mux = new SrsHttpServeMux(); output = _srs_config->get_stream_caster_output(c); - manager = new SrsCoroutineManager(); + manager = new SrsResourceManager("CFLV"); } SrsAppCasterFlv::~SrsAppCasterFlv() @@ -77,12 +77,15 @@ srs_error_t SrsAppCasterFlv::on_tcp_client(srs_netfd_t stfd) { srs_error_t err = srs_success; - string ip = srs_get_peer_ip(srs_netfd_fileno(stfd)); + int fd = srs_netfd_fileno(stfd); + string ip = srs_get_peer_ip(fd); + int port = srs_get_peer_port(fd); + if (ip.empty() && !_srs_config->empty_ip_ok()) { srs_warn("empty ip for fd=%d", srs_netfd_fileno(stfd)); } - SrsHttpConn* conn = new SrsDynamicHttpConn(this, stfd, http_mux, ip); + ISrsStartableConneciton* conn = new SrsDynamicHttpConn(this, stfd, http_mux, ip, port); conns.push_back(conn); if ((err = conn->start()) != srs_success) { @@ -92,16 +95,16 @@ srs_error_t SrsAppCasterFlv::on_tcp_client(srs_netfd_t stfd) return err; } -void SrsAppCasterFlv::remove(ISrsConnection* c) +void SrsAppCasterFlv::remove(ISrsResource* c) { - SrsConnection* conn = dynamic_cast(c); + ISrsStartableConneciton* conn = dynamic_cast(c); - std::vector::iterator it; + std::vector::iterator it; if ((it = std::find(conns.begin(), conns.end(), conn)) != conns.end()) { conns.erase(it); } - // fixbug: SrsHttpConn for CasterFlv is not freed, which could cause memory leak + // fixbug: ISrsStartableConneciton for CasterFlv is not freed, which could cause memory leak // so, free conn which is not managed by SrsServer->conns; // @see: https://github.com/ossrs/srs/issues/826 manager->remove(c); @@ -138,30 +141,38 @@ srs_error_t SrsAppCasterFlv::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessa return err; } -SrsDynamicHttpConn::SrsDynamicHttpConn(IConnectionManager* cm, srs_netfd_t fd, SrsHttpServeMux* m, string cip) -: SrsHttpConn(cm, fd, m, cip) +SrsDynamicHttpConn::SrsDynamicHttpConn(ISrsResourceManager* cm, srs_netfd_t fd, SrsHttpServeMux* m, string cip, int cport) { + // Create a identify for this client. + _srs_context->set_id(_srs_context->generate_id()); + + manager = cm; sdk = NULL; pprint = SrsPithyPrint::create_caster(); + skt = new SrsTcpConnection(fd); + conn = new SrsHttpConn(this, skt, m, cip, cport); + ip = cip; + port = cport; + + _srs_config->subscribe(this); } SrsDynamicHttpConn::~SrsDynamicHttpConn() { + _srs_config->unsubscribe(this); + + srs_freep(conn); + srs_freep(skt); srs_freep(sdk); srs_freep(pprint); } -srs_error_t SrsDynamicHttpConn::on_got_http_message(ISrsHttpMessage* msg) -{ - return srs_success; -} - srs_error_t SrsDynamicHttpConn::proxy(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, std::string o) { srs_error_t err = srs_success; output = o; - srs_trace("flv: proxy %s to %s", r->uri().c_str(), output.c_str()); + srs_trace("flv: proxy %s:%d %s to %s", ip.c_str(), port, r->uri().c_str(), output.c_str()); char* buffer = new char[SRS_HTTP_FLV_STREAM_BUFFER]; SrsAutoFreeA(char, buffer); @@ -247,6 +258,72 @@ srs_error_t SrsDynamicHttpConn::do_proxy(ISrsHttpResponseReader* rr, SrsFlvDecod return err; } +srs_error_t SrsDynamicHttpConn::on_reload_http_stream_crossdomain() +{ + bool v = _srs_config->get_http_stream_crossdomain(); + return conn->set_crossdomain_enabled(v); +} + +srs_error_t SrsDynamicHttpConn::on_start() +{ + return srs_success; +} + +srs_error_t SrsDynamicHttpConn::on_http_message(ISrsHttpMessage* r, SrsHttpResponseWriter* w) +{ + return srs_success; +} + +srs_error_t SrsDynamicHttpConn::on_message_done(ISrsHttpMessage* r, SrsHttpResponseWriter* w) +{ + return srs_success; +} + +srs_error_t SrsDynamicHttpConn::on_conn_done(srs_error_t r0) +{ + // Because we use manager to manage this object, + // not the http connection object, so we must remove it here. + manager->remove(this); + + return r0; +} + +std::string SrsDynamicHttpConn::desc() +{ + return "DHttpConn"; +} + +std::string SrsDynamicHttpConn::remote_ip() +{ + return conn->remote_ip(); +} + +const SrsContextId& SrsDynamicHttpConn::get_id() +{ + return conn->get_id(); +} + +srs_error_t SrsDynamicHttpConn::start() +{ + srs_error_t err = srs_success; + + bool v = _srs_config->get_http_stream_crossdomain(); + if ((err = conn->set_crossdomain_enabled(v)) != srs_success) { + return srs_error_wrap(err, "set cors=%d", v); + } + + if ((err = skt->initialize()) != srs_success) { + return srs_error_wrap(err, "init socket"); + } + + return conn->start(); +} + +void SrsDynamicHttpConn::remark(int64_t* in, int64_t* out) +{ + conn->remark(in, out); +} + SrsHttpFileReader::SrsHttpFileReader(ISrsHttpResponseReader* h) { http = h; diff --git a/trunk/src/app/srs_app_caster_flv.hpp b/trunk/src/app/srs_app_caster_flv.hpp index 9a251d32c..1a964ca58 100644 --- a/trunk/src/app/srs_app_caster_flv.hpp +++ b/trunk/src/app/srs_app_caster_flv.hpp @@ -40,7 +40,7 @@ class SrsFlvDecoder; class SrsTcpClient; class SrsSimpleRtmpClient; -#include +#include #include #include #include @@ -48,13 +48,13 @@ class SrsSimpleRtmpClient; // The stream caster for flv stream over HTTP POST. class SrsAppCasterFlv : virtual public ISrsTcpHandler - , virtual public IConnectionManager, virtual public ISrsHttpHandler + , virtual public ISrsResourceManager, virtual public ISrsHttpHandler { private: std::string output; SrsHttpServeMux* http_mux; - std::vector conns; - SrsCoroutineManager* manager; + std::vector conns; + SrsResourceManager* manager; public: SrsAppCasterFlv(SrsConfDirective* c); virtual ~SrsAppCasterFlv(); @@ -63,30 +63,60 @@ public: // Interface ISrsTcpHandler public: virtual srs_error_t on_tcp_client(srs_netfd_t stfd); -// Interface IConnectionManager +// Interface ISrsResourceManager public: - virtual void remove(ISrsConnection* c); + virtual void remove(ISrsResource* c); // Interface ISrsHttpHandler public: virtual srs_error_t serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r); }; // The dynamic http connection, never drop the body. -class SrsDynamicHttpConn : public SrsHttpConn +class SrsDynamicHttpConn : virtual public ISrsStartableConneciton, virtual public ISrsHttpConnOwner + , virtual public ISrsReloadHandler { private: + // The manager object to manage the connection. + ISrsResourceManager* manager; std::string output; SrsPithyPrint* pprint; SrsSimpleRtmpClient* sdk; + SrsTcpConnection* skt; + SrsHttpConn* conn; +private: + // The ip and port of client. + std::string ip; + int port; public: - SrsDynamicHttpConn(IConnectionManager* cm, srs_netfd_t fd, SrsHttpServeMux* m, std::string cip); + SrsDynamicHttpConn(ISrsResourceManager* cm, srs_netfd_t fd, SrsHttpServeMux* m, std::string cip, int port); virtual ~SrsDynamicHttpConn(); -public: - virtual srs_error_t on_got_http_message(ISrsHttpMessage* msg); public: virtual srs_error_t proxy(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, std::string o); private: virtual srs_error_t do_proxy(ISrsHttpResponseReader* rr, SrsFlvDecoder* dec); +// Extract APIs from SrsTcpConnection. +// Interface ISrsReloadHandler +public: + virtual srs_error_t on_reload_http_stream_crossdomain(); +// Interface ISrsHttpConnOwner. +public: + virtual srs_error_t on_start(); + virtual srs_error_t on_http_message(ISrsHttpMessage* r, SrsHttpResponseWriter* w); + virtual srs_error_t on_message_done(ISrsHttpMessage* r, SrsHttpResponseWriter* w); + virtual srs_error_t on_conn_done(srs_error_t r0); +// Interface ISrsResource. +public: + virtual std::string desc(); +// Interface ISrsConnection. +public: + virtual std::string remote_ip(); + virtual const SrsContextId& get_id(); +// Interface ISrsStartable +public: + virtual srs_error_t start(); +// Interface ISrsKbpsDelta +public: + virtual void remark(int64_t* in, int64_t* out); }; // The http wrapper for file reader, to read http post stream like a file. diff --git a/trunk/src/app/srs_app_config.cpp b/trunk/src/app/srs_app_config.cpp index 01db3abc0..bbd0ab46c 100644 --- a/trunk/src/app/srs_app_config.cpp +++ b/trunk/src/app/srs_app_config.cpp @@ -57,7 +57,7 @@ using namespace std; #include #include -using namespace _srs_internal; +using namespace srs_internal; // @global the version to identify the core. const char* _srs_version = "XCORE-" RTMP_SIG_SRS_SERVER; @@ -92,7 +92,7 @@ bool is_common_space(char ch) return (ch == ' ' || ch == '\t' || ch == SRS_CR || ch == SRS_LF); } -namespace _srs_internal +namespace srs_internal { SrsConfigBuffer::SrsConfigBuffer() { @@ -130,7 +130,7 @@ namespace _srs_internal // read total content from file. ssize_t nread = 0; if ((err = reader.read(start, filesize, &nread)) != srs_success) { - return srs_error_wrap(err, "read %d only %d bytes", filesize, nread); + return srs_error_wrap(err, "read %d only %d bytes", filesize, (int)nread); } return err; @@ -298,7 +298,7 @@ bool srs_config_apply_filter(SrsConfDirective* dvr_apply, SrsRequest* req) } string id = req->app + "/" + req->stream; - if (::find(args.begin(), args.end(), id) != args.end()) { + if (std::find(args.begin(), args.end(), id) != args.end()) { return true; } @@ -620,6 +620,7 @@ srs_error_t srs_config_dumps_engine(SrsConfDirective* dir, SrsJsonObject* engine SrsConfDirective::SrsConfDirective() { + conf_line = 0; } SrsConfDirective::~SrsConfDirective() @@ -785,7 +786,7 @@ SrsConfDirective* SrsConfDirective::set_arg0(string a0) void SrsConfDirective::remove(SrsConfDirective* v) { std::vector::iterator it; - if ((it = ::find(directives.begin(), directives.end(), v)) != directives.end()) { + if ((it = std::find(directives.begin(), directives.end(), v)) != directives.end()) { directives.erase(it); } } @@ -1140,6 +1141,7 @@ SrsConfig::SrsConfig() show_help = false; show_version = false; test_conf = false; + show_signature = false; root = new SrsConfDirective(); root->conf_line = 0; @@ -1468,9 +1470,6 @@ srs_error_t SrsConfig::reload_conf(SrsConfig* conf) root = conf->root; conf->root = NULL; - // merge config. - std::vector::iterator it; - // never support reload: // daemon // @@ -1988,9 +1987,8 @@ srs_error_t SrsConfig::parse_options(int argc, char** argv) return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "no log file"); } if (get_log_tank_file()) { - srs_trace("write log to file %s", log_filename.c_str()); - srs_trace("you can: tailf %s", log_filename.c_str()); - srs_trace("@see: %s", SRS_WIKI_URL_LOG); + srs_trace("you can check log by: tail -f %s (@see %s)", log_filename.c_str(), SRS_WIKI_URL_LOG); + srs_trace("please check SRS by: ./etc/init.d/srs status"); } else { srs_trace("write log to console"); } @@ -2189,6 +2187,8 @@ srs_error_t SrsConfig::global_to_json(SrsJsonObject* obj) 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 == "jitterbuffer_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 == "wait_keyframe") { @@ -2243,8 +2243,11 @@ srs_error_t SrsConfig::global_to_json(SrsJsonObject* obj) SrsJsonObject* sobj = SrsJsonAny::object(); sobjs->set(dir->arg0(), sobj); - SrsStatisticVhost* svhost = stat->find_vhost(dir->arg0()); - sobj->set("id", SrsJsonAny::integer(svhost? (double)svhost->id : 0)); + SrsStatisticVhost* svhost = stat->find_vhost_by_name(dir->arg0()); + if (!svhost) { + continue; + } + sobj->set("id", SrsJsonAny::str(svhost->id.c_str())); sobj->set("name", dir->dumps_arg0_to_str()); sobj->set("enabled", SrsJsonAny::boolean(get_vhost_enabled(dir->arg0()))); @@ -2368,9 +2371,12 @@ srs_error_t SrsConfig::vhost_to_json(SrsConfDirective* vhost, SrsJsonObject* obj // always present in vhost. SrsStatistic* stat = SrsStatistic::instance(); - SrsStatisticVhost* svhost = stat->find_vhost(vhost->arg0()); - obj->set("id", SrsJsonAny::integer(svhost? (double)svhost->id : 0)); - + SrsStatisticVhost* svhost = stat->find_vhost_by_name(vhost->arg0()); + if (!svhost) { + return err; + } + obj->set("id", SrsJsonAny::str(svhost->id.c_str())); + obj->set("name", vhost->dumps_arg0_to_str()); obj->set("enabled", SrsJsonAny::boolean(get_vhost_enabled(vhost))); @@ -3185,7 +3191,7 @@ srs_error_t SrsConfig::raw_enable_dvr(string vhost, string stream, bool& applied conf->args.clear(); } - if (::find(conf->args.begin(), conf->args.end(), stream) == conf->args.end()) { + if (std::find(conf->args.begin(), conf->args.end(), stream) == conf->args.end()) { conf->args.push_back(stream); } @@ -3209,7 +3215,7 @@ srs_error_t SrsConfig::raw_disable_dvr(string vhost, string stream, bool& applie std::vector::iterator it; - if ((it = ::find(conf->args.begin(), conf->args.end(), stream)) != conf->args.end()) { + if ((it = std::find(conf->args.begin(), conf->args.end(), stream)) != conf->args.end()) { conf->args.erase(it); } @@ -3584,7 +3590,7 @@ srs_error_t SrsConfig::check_normal_config() for (int i = 0; conf && i < (int)conf->directives.size(); i++) { SrsConfDirective* obj = conf->at(i); string n = obj->name; - if (n != "enabled" && n != "listen" && n != "crossdomain" && n != "raw_api") { + if (n != "enabled" && n != "listen" && n != "crossdomain" && n != "raw_api" && n != "https") { return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "illegal http_api.%s", n.c_str()); } @@ -3602,7 +3608,7 @@ srs_error_t SrsConfig::check_normal_config() SrsConfDirective* conf = root->get("http_server"); for (int i = 0; conf && i < (int)conf->directives.size(); i++) { string n = conf->at(i)->name; - if (n != "enabled" && n != "listen" && n != "dir" && n != "crossdomain") { + if (n != "enabled" && n != "listen" && n != "dir" && n != "crossdomain" && n != "https") { return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "illegal http_stream.%s", n.c_str()); } } @@ -3644,8 +3650,8 @@ srs_error_t SrsConfig::check_normal_config() for (int i = 0; conf && i < (int)conf->directives.size(); i++) { string n = conf->at(i)->name; if (n != "enabled" && n != "listen" && n != "dir" && n != "candidate" && n != "ecdsa" - && n != "sendmmsg" && n != "encrypt" && n != "reuseport" && n != "gso" && n != "merge_nalus" - && n != "padding" && n != "perf_stat" && n != "queue_length") { + && n != "encrypt" && n != "reuseport" && n != "merge_nalus" && n != "perf_stat" && n != "black_hole" + && n != "ip_family") { return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "illegal rtc_server.%s", n.c_str()); } } @@ -3662,7 +3668,7 @@ srs_error_t SrsConfig::check_normal_config() for (int i = 0; i < (int)listens.size(); i++) { string port = listens[i]; if (port.empty() || ::atoi(port.c_str()) <= 0) { - return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "listen.port=%d is invalid", port.c_str()); + return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "listen.port=%s is invalid", port.c_str()); } } } @@ -3682,13 +3688,15 @@ srs_error_t SrsConfig::check_normal_config() return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "invalid stats.network=%d", get_stats_network()); } if (true) { - vector ips = srs_get_local_ips(); + vector ips = srs_get_local_ips(); int index = get_stats_network(); if (index >= (int)ips.size()) { return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "invalid stats.network=%d of %d", index, (int)ips.size()); } - srs_warn("stats network use index=%d, ip=%s", index, ips.at(index).c_str()); + + SrsIPAddress* addr = ips.at(index); + srs_warn("stats network use index=%d, ip=%s, ifname=%s", index, addr->ip.c_str(), addr->ifname.c_str()); } if (true) { SrsConfDirective* conf = get_stats_disk_device(); @@ -3727,9 +3735,8 @@ srs_error_t SrsConfig::check_normal_config() return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "log file is empty"); } if (get_log_tank_file()) { - srs_trace("write log to file %s", log_filename.c_str()); - srs_trace("you can: tailf %s", log_filename.c_str()); - srs_trace("@see: %s", SRS_WIKI_URL_LOG); + srs_trace("you can check log by: tail -f %s (@see %s)", log_filename.c_str(), SRS_WIKI_URL_LOG); + srs_trace("please check SRS by: ./etc/init.d/srs status"); } else { srs_trace("write log to console"); } @@ -3745,9 +3752,9 @@ 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 != "tcp_enable" && n != "rtp_port_min" && n != "rtp_port_max" && n != "rtp_idle_timeout" && n != "sip" - && n != "audio_enable" && n != "wait_keyframe" + && n != "audio_enable" && n != "wait_keyframe" && n != "jitterbuffer_enable" && n != "host" && n != "auto_create_channel") { return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "illegal stream_caster.%s", n.c_str()); } @@ -3782,7 +3789,8 @@ srs_error_t SrsConfig::check_normal_config() && n != "play" && n != "publish" && n != "cluster" && n != "security" && n != "http_remux" && n != "dash" && n != "http_static" && n != "hds" && n != "exec" - && n != "in_ack_size" && n != "out_ack_size" && n != "rtc") { + && n != "in_ack_size" && n != "out_ack_size" && n != "rtc" && n != "nack" + && n != "twcc") { return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "illegal vhost.%s", n.c_str()); } // for each sub directives of vhost. @@ -3932,7 +3940,8 @@ srs_error_t SrsConfig::check_normal_config() } else if (n == "rtc") { for (int j = 0; j < (int)conf->directives.size(); j++) { string m = conf->at(j)->name; - if (m != "enabled" && m != "bframe" && m != "aac" && m != "stun_timeout" && m != "stun_strict_check") { + if (m != "enabled" && m != "bframe" && m != "aac" && m != "stun_timeout" && m != "stun_strict_check" + && m != "dtls_role" && m != "dtls_version" && m != "drop_for_pt") { return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "illegal vhost.rtc.%s of %s", m.c_str(), vhost->arg0().c_str()); } } @@ -4367,6 +4376,22 @@ int SrsConfig::get_stream_caster_listen(SrsConfDirective* conf) return ::atoi(conf->arg0().c_str()); } +bool SrsConfig::get_stream_caster_tcp_enable(SrsConfDirective* conf) +{ + static bool DEFAULT = false; + + if (!conf) { + return DEFAULT; + } + + conf = conf->get("tcp_enable"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return SRS_CONF_PERFER_FALSE(conf->arg0()); +} + int SrsConfig::get_stream_caster_rtp_port_min(SrsConfDirective* conf) { static int DEFAULT = 0; @@ -4538,9 +4563,25 @@ bool SrsConfig::get_stream_caster_gb28181_audio_enable(SrsConfDirective* conf) return SRS_CONF_PERFER_FALSE(conf->arg0()); } +bool SrsConfig::get_stream_caster_gb28181_jitterbuffer_enable(SrsConfDirective* conf) +{ + static bool DEFAULT = true; + + if (!conf) { + return DEFAULT; + } + + conf = conf->get("jitterbuffer_enable"); + 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; + static bool DEFAULT = true; if (!conf) { return DEFAULT; @@ -4679,7 +4720,7 @@ srs_utime_t SrsConfig::get_stream_caster_gb28181_sip_query_catalog_interval(SrsC return (srs_utime_t)(::atoi(conf->arg0().c_str()) * SRS_UTIME_SECONDS); } -int SrsConfig::get_rtc_server_enabled() +bool SrsConfig::get_rtc_server_enabled() { SrsConfDirective* conf = root->get("rtc_server"); return get_rtc_server_enabled(conf); @@ -4742,7 +4783,24 @@ std::string SrsConfig::get_rtc_server_candidates() return DEFAULT; } - return (conf->arg0().c_str()); + return conf->arg0(); +} + +std::string SrsConfig::get_rtc_server_ip_family() +{ + static string DEFAULT = "ipv4"; + + SrsConfDirective* conf = root->get("rtc_server"); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("ip_family"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return conf->arg0(); } bool SrsConfig::get_rtc_server_ecdsa() @@ -4779,35 +4837,15 @@ bool SrsConfig::get_rtc_server_encrypt() return SRS_CONF_PERFER_TRUE(conf->arg0()); } -int SrsConfig::get_rtc_server_sendmmsg() -{ -#if !defined(SRS_AUTO_HAS_SENDMMSG) || !defined(SRS_AUTO_SENDMMSG) - return 1; -#else - static int DEFAULT = 256; - - SrsConfDirective* conf = root->get("rtc_server"); - if (!conf) { - return DEFAULT; - } - - conf = conf->get("sendmmsg"); - if (!conf || conf->arg0().empty()) { - return DEFAULT; - } - - int v = ::atoi(conf->arg0().c_str()); - return srs_max(1, v); -#endif -} - int SrsConfig::get_rtc_server_reuseport() { int v = get_rtc_server_reuseport2(); #if !defined(SO_REUSEPORT) - srs_warn("REUSEPORT not supported, reset %d to %d", reuseport, DEFAULT); - v = 1 + if (v > 1) { + srs_warn("REUSEPORT not supported, reset %d to %d", reuseport, DEFAULT); + v = 1 + } #endif return v; @@ -4815,7 +4853,7 @@ int SrsConfig::get_rtc_server_reuseport() int SrsConfig::get_rtc_server_reuseport2() { - static int DEFAULT = 4; + static int DEFAULT = 1; SrsConfDirective* conf = root->get("rtc_server"); if (!conf) { @@ -4832,7 +4870,7 @@ int SrsConfig::get_rtc_server_reuseport2() bool SrsConfig::get_rtc_server_merge_nalus() { - static int DEFAULT = true; + static int DEFAULT = false; SrsConfDirective* conf = root->get("rtc_server"); if (!conf) { @@ -4847,68 +4885,6 @@ bool SrsConfig::get_rtc_server_merge_nalus() return SRS_CONF_PERFER_TRUE(conf->arg0()); } -bool SrsConfig::get_rtc_server_gso() -{ - bool v = get_rtc_server_gso2(); - - bool gso_disabled = false; -#if !defined(__linux__) - gso_disabled = true; - if (v) { - srs_warn("GSO is disabled, for Linux 4.18+ only"); - } -#elif LINUX_VERSION_CODE < KERNEL_VERSION(4,18,0) - if (v) { - utsname un = {0}; - int r0 = uname(&un); - if (r0 || strcmp(un.release, "4.18.0") < 0) { - gso_disabled = true; - srs_warn("GSO is disabled, for Linux 4.18+ only, r0=%d, kernel=%s", r0, un.release); - } - } -#endif - - if (v && gso_disabled) { - v = false; - } - - return v; -} - -bool SrsConfig::get_rtc_server_gso2() -{ - static int DEFAULT = true; - - SrsConfDirective* conf = root->get("rtc_server"); - if (!conf) { - return DEFAULT; - } - - conf = conf->get("gso"); - if (!conf || conf->arg0().empty()) { - return DEFAULT; - } - - return SRS_CONF_PERFER_TRUE(conf->arg0()); -} - -int SrsConfig::get_rtc_server_padding() -{ - static int DEFAULT = 127; - - SrsConfDirective* conf = root->get("rtc_server"); - if (!conf) { - return DEFAULT; - } - - conf = conf->get("padding"); - if (!conf || conf->arg0().empty()) { - return DEFAULT; - } - - return srs_min(127, ::atoi(conf->arg0().c_str())); -} - bool SrsConfig::get_rtc_server_perf_stat() { static bool DEFAULT = true; @@ -4926,21 +4902,48 @@ bool SrsConfig::get_rtc_server_perf_stat() return SRS_CONF_PERFER_TRUE(conf->arg0()); } -int SrsConfig::get_rtc_server_queue_length() +bool SrsConfig::get_rtc_server_black_hole() { - static int DEFAULT = 2000; + static bool DEFAULT = false; SrsConfDirective* conf = root->get("rtc_server"); if (!conf) { return DEFAULT; } - conf = conf->get("queue_length"); + conf = conf->get("black_hole"); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("enabled"); if (!conf || conf->arg0().empty()) { return DEFAULT; } - return ::atoi(conf->arg0().c_str()); + return SRS_CONF_PERFER_FALSE(conf->arg0()); +} + +std::string SrsConfig::get_rtc_server_black_hole_addr() +{ + static string DEFAULT = ""; + + SrsConfDirective* conf = root->get("rtc_server"); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("black_hole"); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("addr"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return conf->arg0(); } SrsConfDirective* SrsConfig::get_rtc(string vhost) @@ -5039,6 +5042,103 @@ bool SrsConfig::get_rtc_stun_strict_check(string vhost) return SRS_CONF_PERFER_FALSE(conf->arg0()); } +std::string SrsConfig::get_rtc_dtls_role(string vhost) +{ + static std::string DEFAULT = "passive"; + + SrsConfDirective* conf = get_rtc(vhost); + + if (!conf) { + return DEFAULT; + } + + conf = conf->get("dtls_role"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return conf->arg0(); +} + +std::string SrsConfig::get_rtc_dtls_version(string vhost) +{ + static std::string DEFAULT = "auto"; + + SrsConfDirective* conf = get_rtc(vhost); + + if (!conf) { + return DEFAULT; + } + + conf = conf->get("dtls_version"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return conf->arg0(); +} + +int SrsConfig::get_rtc_drop_for_pt(string vhost) +{ + static int DEFAULT = 0; + + SrsConfDirective* conf = get_vhost(vhost); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("drop_for_pt"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return ::atoi(conf->arg0().c_str()); +} + +bool SrsConfig::get_rtc_nack_enabled(string vhost) +{ + static bool DEFAULT = true; + + SrsConfDirective* conf = get_vhost(vhost); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("nack"); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("enabled"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return SRS_CONF_PERFER_TRUE(conf->arg0()); +} + +bool SrsConfig::get_rtc_twcc_enabled(string vhost) +{ + static bool DEFAULT = true; + + SrsConfDirective* conf = get_vhost(vhost); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("twcc"); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("enabled"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + + return SRS_CONF_PERFER_TRUE(conf->arg0()); +} + SrsConfDirective* SrsConfig::get_vhost(string vhost, bool try_default_vhost) { srs_assert(root); @@ -5194,20 +5294,20 @@ int SrsConfig::get_time_jitter(string vhost) SrsConfDirective* conf = get_vhost(vhost); if (!conf) { - return _srs_time_jitter_string2int(DEFAULT); + return srs_time_jitter_string2int(DEFAULT); } conf = conf->get("play"); if (!conf) { - return _srs_time_jitter_string2int(DEFAULT); + return srs_time_jitter_string2int(DEFAULT); } conf = conf->get("time_jitter"); if (!conf || conf->arg0().empty()) { - return _srs_time_jitter_string2int(DEFAULT); + return srs_time_jitter_string2int(DEFAULT); } - return _srs_time_jitter_string2int(conf->arg0()); + return srs_time_jitter_string2int(conf->arg0()); } bool SrsConfig::get_mix_correct(string vhost) @@ -7427,15 +7527,15 @@ int SrsConfig::get_dvr_time_jitter(string vhost) SrsConfDirective* conf = get_dvr(vhost); if (!conf) { - return _srs_time_jitter_string2int(DEFAULT); + return srs_time_jitter_string2int(DEFAULT); } conf = conf->get("time_jitter"); if (!conf || conf->arg0().empty()) { - return _srs_time_jitter_string2int(DEFAULT); + return srs_time_jitter_string2int(DEFAULT); } - return _srs_time_jitter_string2int(conf->arg0()); + return srs_time_jitter_string2int(conf->arg0()); } bool SrsConfig::get_http_api_enabled() @@ -7583,6 +7683,83 @@ bool SrsConfig::get_raw_api_allow_update() return SRS_CONF_PERFER_FALSE(conf->arg0()); } +SrsConfDirective* SrsConfig::get_https_api() +{ + SrsConfDirective* conf = root->get("http_api"); + if (!conf) { + return NULL; + } + + return conf->get("https"); +} + +bool SrsConfig::get_https_api_enabled() +{ + static bool DEFAULT = false; + + SrsConfDirective* conf = get_https_api(); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("enabled"); + if (!conf) { + return DEFAULT; + } + + return SRS_CONF_PERFER_FALSE(conf->arg0()); +} + +string SrsConfig::get_https_api_listen() +{ + static string DEFAULT = "1990"; + + SrsConfDirective* conf = get_https_api(); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("listen"); + if (!conf) { + return DEFAULT; + } + + return conf->arg0(); +} + +string SrsConfig::get_https_api_ssl_key() +{ + static string DEFAULT = "./conf/server.key"; + + SrsConfDirective* conf = get_https_api(); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("key"); + if (!conf) { + return DEFAULT; + } + + return conf->arg0(); +} + +string SrsConfig::get_https_api_ssl_cert() +{ + static string DEFAULT = "./conf/server.crt"; + + SrsConfDirective* conf = get_https_api(); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("cert"); + if (!conf) { + return DEFAULT; + } + + return conf->arg0(); +} bool SrsConfig::get_srt_enabled() { @@ -7871,6 +8048,84 @@ bool SrsConfig::get_http_stream_crossdomain() return SRS_CONF_PERFER_TRUE(conf->arg0()); } +SrsConfDirective* SrsConfig::get_https_stream() +{ + SrsConfDirective* conf = root->get("http_server"); + if (!conf) { + return NULL; + } + + return conf->get("https"); +} + +bool SrsConfig::get_https_stream_enabled() +{ + static bool DEFAULT = false; + + SrsConfDirective* conf = get_https_stream(); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("enabled"); + if (!conf) { + return DEFAULT; + } + + return SRS_CONF_PERFER_FALSE(conf->arg0()); +} + +string SrsConfig::get_https_stream_listen() +{ + static string DEFAULT = "8088"; + + SrsConfDirective* conf = get_https_stream(); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("listen"); + if (!conf) { + return DEFAULT; + } + + return conf->arg0(); +} + +string SrsConfig::get_https_stream_ssl_key() +{ + static string DEFAULT = "./conf/server.key"; + + SrsConfDirective* conf = get_https_stream(); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("key"); + if (!conf) { + return DEFAULT; + } + + return conf->arg0(); +} + +string SrsConfig::get_https_stream_ssl_cert() +{ + static string DEFAULT = "./conf/server.crt"; + + SrsConfDirective* conf = get_https_stream(); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("cert"); + if (!conf) { + return DEFAULT; + } + + return conf->arg0(); +} + bool SrsConfig::get_vhost_http_enabled(string vhost) { static bool DEFAULT = false; diff --git a/trunk/src/app/srs_app_config.hpp b/trunk/src/app/srs_app_config.hpp index 04552c3be..485725912 100644 --- a/trunk/src/app/srs_app_config.hpp +++ b/trunk/src/app/srs_app_config.hpp @@ -34,7 +34,7 @@ #include #include -#include +#include class SrsRequest; class SrsFileWriter; @@ -77,7 +77,7 @@ bool srs_vector_actual_equals(const std::vector& a, const std::vector& b) return true; } -namespace _srs_internal +namespace srs_internal { // The buffer of config content. class SrsConfigBuffer @@ -229,7 +229,7 @@ public: // Parse utilities public: // Parse config directive from file buffer. - virtual srs_error_t parse(_srs_internal::SrsConfigBuffer* buffer); + virtual srs_error_t parse(srs_internal::SrsConfigBuffer* buffer); // Marshal the directive to writer. // @param level, the root is level0, all its directives are level1, and so on. virtual srs_error_t persistence(SrsFileWriter* writer, int level); @@ -253,13 +253,13 @@ private: // 1. read a token(directive args and a ret flag), // 2. initialize the directive by args, args[0] is name, args[1-N] is args of directive, // 3. if ret flag indicates there are child-directives, read_conf(directive, block) recursively. - virtual srs_error_t parse_conf(_srs_internal::SrsConfigBuffer* buffer, SrsDirectiveType type); + virtual srs_error_t parse_conf(srs_internal::SrsConfigBuffer* buffer, SrsDirectiveType type); // Read a token from buffer. // A token, is the directive args and a flag indicates whether has child-directives. // @param args, the output directive args, the first is the directive name, left is the args. // @param line_start, the actual start line of directive. // @return, an error code indicates error or has child-directives. - virtual srs_error_t read_token(_srs_internal::SrsConfigBuffer* buffer, std::vector& args, int& line_start); + virtual srs_error_t read_token(srs_internal::SrsConfigBuffer* buffer, std::vector& args, int& line_start); }; // The config service provider. @@ -425,7 +425,7 @@ protected: // Parse config from the buffer. // @param buffer, the config buffer, user must delete it. // @remark, use protected for the utest to override with mock. - virtual srs_error_t parse_buffer(_srs_internal::SrsConfigBuffer* buffer); + virtual srs_error_t parse_buffer(srs_internal::SrsConfigBuffer* buffer); // global env public: // Get the current work directory. @@ -499,6 +499,8 @@ public: virtual std::string get_stream_caster_output(SrsConfDirective* conf); // Get the listen port of stream caster. virtual int get_stream_caster_listen(SrsConfDirective* conf); + // Get the listen port type of stream caster. + virtual bool get_stream_caster_tcp_enable(SrsConfDirective* conf); // Get the min udp port for rtp of stream caster rtsp. virtual int get_stream_caster_rtp_port_min(SrsConfDirective* conf); // Get the max udp port for rtp of stream caster rtsp. @@ -508,6 +510,7 @@ public: virtual srs_utime_t get_stream_caster_gb28181_ack_timeout(SrsConfDirective* conf); virtual srs_utime_t get_stream_caster_gb28181_keepalive_timeout(SrsConfDirective* conf); virtual bool get_stream_caster_gb28181_audio_enable(SrsConfDirective* conf); + virtual bool get_stream_caster_gb28181_jitterbuffer_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); @@ -521,22 +524,20 @@ public: // rtc section public: - virtual int get_rtc_server_enabled(); + virtual bool get_rtc_server_enabled(); virtual bool get_rtc_server_enabled(SrsConfDirective* conf); virtual int get_rtc_server_listen(); virtual std::string get_rtc_server_candidates(); + virtual std::string get_rtc_server_ip_family(); virtual bool get_rtc_server_ecdsa(); - virtual int get_rtc_server_sendmmsg(); virtual bool get_rtc_server_encrypt(); virtual int get_rtc_server_reuseport(); virtual bool get_rtc_server_merge_nalus(); - virtual bool get_rtc_server_gso(); - virtual int get_rtc_server_padding(); virtual bool get_rtc_server_perf_stat(); - virtual int get_rtc_server_queue_length(); + virtual bool get_rtc_server_black_hole(); + virtual std::string get_rtc_server_black_hole_addr(); private: virtual int get_rtc_server_reuseport2(); - virtual bool get_rtc_server_gso2(); public: SrsConfDirective* get_rtc(std::string vhost); @@ -545,6 +546,11 @@ public: bool get_rtc_aac_discard(std::string vhost); srs_utime_t get_rtc_stun_timeout(std::string vhost); bool get_rtc_stun_strict_check(std::string vhost); + std::string get_rtc_dtls_role(std::string vhost); + std::string get_rtc_dtls_version(std::string vhost); + int get_rtc_drop_for_pt(std::string vhost); + bool get_rtc_nack_enabled(std::string vhost); + bool get_rtc_twcc_enabled(std::string vhost); // vhost specified section public: @@ -1010,6 +1016,14 @@ public: virtual bool get_raw_api_allow_query(); // Whether allow rpc update. virtual bool get_raw_api_allow_update(); +// https api section +private: + SrsConfDirective* get_https_api(); +public: + virtual bool get_https_api_enabled(); + virtual std::string get_https_api_listen(); + virtual std::string get_https_api_ssl_key(); + virtual std::string get_https_api_ssl_cert(); // http stream section private: // Whether http stream enabled. @@ -1024,6 +1038,14 @@ public: virtual std::string get_http_stream_dir(); // Whether enable crossdomain for http static and stream server. virtual bool get_http_stream_crossdomain(); +// https api section +private: + SrsConfDirective* get_https_stream(); +public: + virtual bool get_https_stream_enabled(); + virtual std::string get_https_stream_listen(); + virtual std::string get_https_stream_ssl_key(); + virtual std::string get_https_stream_ssl_cert(); public: // Get whether vhost enabled http stream virtual bool get_vhost_http_enabled(std::string vhost); diff --git a/trunk/src/app/srs_app_conn.cpp b/trunk/src/app/srs_app_conn.cpp index 4018ac512..c7a4d3a84 100644 --- a/trunk/src/app/srs_app_conn.cpp +++ b/trunk/src/app/srs_app_conn.cpp @@ -24,83 +24,378 @@ #include #include +#include using namespace std; #include #include #include #include +#include +#include +#include +#include -SrsConnection::SrsConnection(IConnectionManager* cm, srs_netfd_t c, string cip) +ISrsDisposingHandler::ISrsDisposingHandler() { - manager = cm; - stfd = c; - ip = cip; - create_time = srsu2ms(srs_get_system_time()); - - skt = new SrsStSocket(); - clk = new SrsWallClock(); - kbps = new SrsKbps(clk); - kbps->set_io(skt, skt); - - trd = new SrsSTCoroutine("conn", this); } -SrsConnection::~SrsConnection() +ISrsDisposingHandler::~ISrsDisposingHandler() { - dispose(); - - srs_freep(kbps); - srs_freep(clk); - srs_freep(skt); - srs_freep(trd); - - srs_close_stfd(stfd); } -void SrsConnection::remark(int64_t* in, int64_t* out) +SrsResourceManager::SrsResourceManager(const std::string& label, bool verbose) { - kbps->remark(in, out); + verbose_ = verbose; + label_ = label; + cond = srs_cond_new(); + trd = NULL; + p_disposing_ = NULL; + removing_ = false; } -void SrsConnection::dispose() +SrsResourceManager::~SrsResourceManager() { - trd->interrupt(); + if (trd) { + srs_cond_signal(cond); + trd->stop(); + + srs_freep(trd); + srs_cond_destroy(cond); + } + + clear(); } -srs_error_t SrsConnection::start() +srs_error_t SrsResourceManager::start() { srs_error_t err = srs_success; - - if ((err = skt->initialize(stfd)) != srs_success) { - return srs_error_wrap(err, "init socket"); - } - + + cid_ = _srs_context->generate_id(); + trd = new SrsSTCoroutine("manager", this, cid_); + if ((err = trd->start()) != srs_success) { - return srs_error_wrap(err, "coroutine"); + return srs_error_wrap(err, "conn manager"); } - + return err; } -srs_error_t SrsConnection::set_tcp_nodelay(bool v) +bool SrsResourceManager::empty() +{ + return conns_.empty(); +} + +size_t SrsResourceManager::size() +{ + return conns_.size(); +} + +srs_error_t SrsResourceManager::cycle() { srs_error_t err = srs_success; - + + srs_trace("%s: connection manager run, conns=%d", label_.c_str(), (int)conns_.size()); + + while (true) { + if ((err = trd->pull()) != srs_success) { + return srs_error_wrap(err, "conn manager"); + } + + // Clear all zombies, because we may switch context and lost signal + // when we clear zombie connection. + while (!zombies_.empty()) { + clear(); + } + + srs_cond_wait(cond); + } + + return err; +} + +void SrsResourceManager::add(ISrsResource* conn) +{ + if (std::find(conns_.begin(), conns_.end(), conn) == conns_.end()) { + conns_.push_back(conn); + } +} + +void SrsResourceManager::add_with_id(const std::string& id, ISrsResource* conn) +{ + add(conn); + conns_id_[id] = conn; +} + +void SrsResourceManager::add_with_name(const std::string& name, ISrsResource* conn) +{ + add(conn); + conns_name_[name] = conn; +} + +ISrsResource* SrsResourceManager::at(int index) +{ + return (index < (int)conns_.size())? conns_.at(index) : NULL; +} + +ISrsResource* SrsResourceManager::find_by_id(std::string id) +{ + map::iterator it = conns_id_.find(id); + return (it != conns_id_.end())? it->second : NULL; +} + +ISrsResource* SrsResourceManager::find_by_name(std::string name) +{ + map::iterator it = conns_name_.find(name); + return (it != conns_name_.end())? it->second : NULL; +} + +void SrsResourceManager::subscribe(ISrsDisposingHandler* h) +{ + if (std::find(handlers_.begin(), handlers_.end(), h) == handlers_.end()) { + handlers_.push_back(h); + } + + // Restore the handler from unsubscribing handlers. + vector::iterator it; + if ((it = std::find(unsubs_.begin(), unsubs_.end(), h)) != unsubs_.end()) { + unsubs_.erase(it); + } +} + +void SrsResourceManager::unsubscribe(ISrsDisposingHandler* h) +{ + vector::iterator it = find(handlers_.begin(), handlers_.end(), h); + if (it != handlers_.end()) { + handlers_.erase(it); + } + + // Put it to the unsubscribing handlers. + if (std::find(unsubs_.begin(), unsubs_.end(), h) == unsubs_.end()) { + unsubs_.push_back(h); + } +} + +void SrsResourceManager::remove(ISrsResource* c) +{ + SrsContextRestore(_srs_context->get_id()); + + removing_ = true; + do_remove(c); + removing_ = false; +} + +void SrsResourceManager::do_remove(ISrsResource* c) +{ + bool in_zombie = false; + bool in_disposing = false; + check_remove(c, in_zombie, in_disposing); + bool ignored = in_zombie || in_disposing; + + if (verbose_) { + _srs_context->set_id(c->get_id()); + srs_trace("%s: before dispose resource(%s)(%p), conns=%d, zombies=%d, ign=%d, inz=%d, ind=%d", + label_.c_str(), c->desc().c_str(), c, (int)conns_.size(), (int)zombies_.size(), ignored, + in_zombie, in_disposing); + } + if (ignored) { + return; + } + + // Push to zombies, we will free it in another coroutine. + zombies_.push_back(c); + + // We should copy all handlers, because it may change during callback. + vector handlers = handlers_; + + // Notify other handlers to handle the before-dispose event. + for (int i = 0; i < (int)handlers.size(); i++) { + ISrsDisposingHandler* h = handlers.at(i); + + // Ignore if handler is unsubscribing. + if (!unsubs_.empty() && std::find(unsubs_.begin(), unsubs_.end(), h) != unsubs_.end()) { + srs_warn2(TAG_RESOURCE_UNSUB, "%s: ignore before-dispose resource(%s)(%p) for %p, conns=%d", + label_.c_str(), c->desc().c_str(), c, h, (int)conns_.size()); + continue; + } + + h->on_before_dispose(c); + } + + // Notify the coroutine to free it. + srs_cond_signal(cond); +} + +void SrsResourceManager::check_remove(ISrsResource* c, bool& in_zombie, bool& in_disposing) +{ + // Only notify when not removed(in zombies_). + vector::iterator it = std::find(zombies_.begin(), zombies_.end(), c); + if (it != zombies_.end()) { + in_zombie = true; + } + + // Also ignore when we are disposing it. + if (p_disposing_) { + it = std::find(p_disposing_->begin(), p_disposing_->end(), c); + if (it != p_disposing_->end()) { + in_disposing = true; + } + } +} + +void SrsResourceManager::clear() +{ + if (zombies_.empty()) { + return; + } + + SrsContextRestore(cid_); + if (verbose_) { + srs_trace("%s: clear zombies=%d resources, conns=%d, removing=%d, unsubs=%d", + label_.c_str(), (int)zombies_.size(), (int)conns_.size(), removing_, (int)unsubs_.size()); + } + + // Clear all unsubscribing handlers, if not removing any resource. + if (!removing_ && !unsubs_.empty()) { + vector().swap(unsubs_); + } + + do_clear(); +} + +void SrsResourceManager::do_clear() +{ + // To prevent thread switch when delete connection, + // we copy all connections then free one by one. + vector copy; + copy.swap(zombies_); + p_disposing_ = © + + for (int i = 0; i < (int)copy.size(); i++) { + ISrsResource* conn = copy.at(i); + + if (verbose_) { + _srs_context->set_id(conn->get_id()); + srs_trace("%s: disposing #%d resource(%s)(%p), conns=%d, disposing=%d, zombies=%d", label_.c_str(), + i, conn->desc().c_str(), conn, (int)conns_.size(), (int)copy.size(), (int)zombies_.size()); + } + + dispose(conn); + } + + // Reset it for it points to a local object. + // @remark We must set the disposing to NULL to avoid reusing address, + // because the context might switch. + p_disposing_ = NULL; + + // We should free the resources when finished all disposing callbacks, + // which might cause context switch and reuse the freed addresses. + for (int i = 0; i < (int)copy.size(); i++) { + ISrsResource* conn = copy.at(i); + srs_freep(conn); + } +} + +void SrsResourceManager::dispose(ISrsResource* c) +{ + for (map::iterator it = conns_name_.begin(); it != conns_name_.end();) { + if (c != it->second) { + ++it; + } else { + // Use C++98 style: https://stackoverflow.com/a/4636230 + conns_name_.erase(it++); + } + } + + for (map::iterator it = conns_id_.begin(); it != conns_id_.end();) { + if (c != it->second) { + ++it; + } else { + // Use C++98 style: https://stackoverflow.com/a/4636230 + conns_id_.erase(it++); + } + } + + vector::iterator it = std::find(conns_.begin(), conns_.end(), c); + if (it != conns_.end()) { + conns_.erase(it); + } + + // We should copy all handlers, because it may change during callback. + vector handlers = handlers_; + + // Notify other handlers to handle the disposing event. + for (int i = 0; i < (int)handlers.size(); i++) { + ISrsDisposingHandler* h = handlers.at(i); + + // Ignore if handler is unsubscribing. + if (!unsubs_.empty() && std::find(unsubs_.begin(), unsubs_.end(), h) != unsubs_.end()) { + srs_warn2(TAG_RESOURCE_UNSUB, "%s: ignore disposing resource(%s)(%p) for %p, conns=%d", + label_.c_str(), c->desc().c_str(), c, h, (int)conns_.size()); + continue; + } + + h->on_disposing(c); + } +} + +ISrsExpire::ISrsExpire() +{ +} + +ISrsExpire::~ISrsExpire() +{ +} + +ISrsStartableConneciton::ISrsStartableConneciton() +{ +} + +ISrsStartableConneciton::~ISrsStartableConneciton() +{ +} + +SrsTcpConnection::SrsTcpConnection(srs_netfd_t c) +{ + stfd = c; + skt = new SrsStSocket(); +} + +SrsTcpConnection::~SrsTcpConnection() +{ + srs_freep(skt); + srs_close_stfd(stfd); +} + +srs_error_t SrsTcpConnection::initialize() +{ + srs_error_t err = srs_success; + + if ((err = skt->initialize(stfd)) != srs_success) { + return srs_error_wrap(err, "init socket"); + } + + return err; +} + +srs_error_t SrsTcpConnection::set_tcp_nodelay(bool v) +{ + srs_error_t err = srs_success; + int r0 = 0; socklen_t nb_v = sizeof(int); int fd = srs_netfd_fileno(stfd); - + int ov = 0; if ((r0 = getsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &ov, &nb_v)) != 0) { return srs_error_new(ERROR_SOCKET_NO_NODELAY, "getsockopt fd=%d, r0=%d", fd, r0); } - + #ifndef SRS_PERF_TCP_NODELAY srs_warn("ignore TCP_NODELAY, fd=%d, ov=%d", fd, ov); return err; #endif - + int iv = (v? 1:0); if ((r0 = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &iv, nb_v)) != 0) { return srs_error_new(ERROR_SOCKET_NO_NODELAY, "setsockopt fd=%d, r0=%d", fd, r0); @@ -108,30 +403,30 @@ srs_error_t SrsConnection::set_tcp_nodelay(bool v) if ((r0 = getsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &iv, &nb_v)) != 0) { return srs_error_new(ERROR_SOCKET_NO_NODELAY, "getsockopt fd=%d, r0=%d", fd, r0); } - + srs_trace("set fd=%d TCP_NODELAY %d=>%d", fd, ov, iv); - + return err; } -srs_error_t SrsConnection::set_socket_buffer(srs_utime_t buffer_v) +srs_error_t SrsTcpConnection::set_socket_buffer(srs_utime_t buffer_v) { srs_error_t err = srs_success; - + int r0 = 0; int fd = srs_netfd_fileno(stfd); socklen_t nb_v = sizeof(int); - + int ov = 0; if ((r0 = getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &ov, &nb_v)) != 0) { return srs_error_new(ERROR_SOCKET_SNDBUF, "getsockopt fd=%d, r0=%d", fd, r0); } - + #ifndef SRS_PERF_MW_SO_SNDBUF srs_warn("ignore SO_SNDBUF, fd=%d, ov=%d", fd, ov); return err; #endif - + // the bytes: // 4KB=4096, 8KB=8192, 16KB=16384, 32KB=32768, 64KB=65536, // 128KB=131072, 256KB=262144, 512KB=524288 @@ -144,15 +439,15 @@ srs_error_t SrsConnection::set_socket_buffer(srs_utime_t buffer_v) // 2000*5000/8=1250000B(about 1220KB). int kbps = 4000; int iv = srsu2ms(buffer_v) * kbps / 8; - + // socket send buffer, system will double it. iv = iv / 2; - + // override the send buffer by macro. #ifdef SRS_PERF_SO_SNDBUF_SIZE iv = SRS_PERF_SO_SNDBUF_SIZE / 2; #endif - + // set the socket send buffer when required larger buffer if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &iv, nb_v) < 0) { return srs_error_new(ERROR_SOCKET_SNDBUF, "setsockopt fd=%d, r0=%d", fd, r0); @@ -160,58 +455,348 @@ srs_error_t SrsConnection::set_socket_buffer(srs_utime_t buffer_v) if ((r0 = getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &iv, &nb_v)) != 0) { return srs_error_new(ERROR_SOCKET_SNDBUF, "getsockopt fd=%d, r0=%d", fd, r0); } - + srs_trace("set fd=%d, SO_SNDBUF=%d=>%d, buffer=%dms", fd, ov, iv, srsu2ms(buffer_v)); - + return err; } -srs_error_t SrsConnection::cycle() +void SrsTcpConnection::set_recv_timeout(srs_utime_t tm) { - srs_error_t err = do_cycle(); - - // Notify manager to remove it. - manager->remove(this); - - // success. - if (err == srs_success) { - srs_trace("client finished."); - return err; + skt->set_recv_timeout(tm); +} + +srs_utime_t SrsTcpConnection::get_recv_timeout() +{ + return skt->get_recv_timeout(); +} + +srs_error_t SrsTcpConnection::read_fully(void* buf, size_t size, ssize_t* nread) +{ + return skt->read_fully(buf, size, nread); +} + +int64_t SrsTcpConnection::get_recv_bytes() +{ + return skt->get_recv_bytes(); +} + +int64_t SrsTcpConnection::get_send_bytes() +{ + return skt->get_send_bytes(); +} + +srs_error_t SrsTcpConnection::read(void* buf, size_t size, ssize_t* nread) +{ + return skt->read(buf, size, nread); +} + +void SrsTcpConnection::set_send_timeout(srs_utime_t tm) +{ + skt->set_send_timeout(tm); +} + +srs_utime_t SrsTcpConnection::get_send_timeout() +{ + return skt->get_send_timeout(); +} + +srs_error_t SrsTcpConnection::write(void* buf, size_t size, ssize_t* nwrite) +{ + return skt->write(buf, size, nwrite); +} + +srs_error_t SrsTcpConnection::writev(const iovec *iov, int iov_size, ssize_t* nwrite) +{ + return skt->writev(iov, iov_size, nwrite); +} + +SrsSslConnection::SrsSslConnection(ISrsProtocolReadWriter* c) +{ + transport = c; + ssl_ctx = NULL; + ssl = NULL; +} + +SrsSslConnection::~SrsSslConnection() +{ + if (ssl) { + // this function will free bio_in and bio_out + SSL_free(ssl); + ssl = NULL; } - // It maybe success with message. - if (srs_error_code(err) == ERROR_SUCCESS) { - srs_trace("client finished%s.", srs_error_summary(err).c_str()); - srs_freep(err); - return err; + if (ssl_ctx) { + SSL_CTX_free(ssl_ctx); + ssl_ctx = NULL; } - - // client close peer. - // TODO: FIXME: Only reset the error when client closed it. - if (srs_is_client_gracefully_close(err)) { - srs_warn("client disconnect peer. ret=%d", srs_error_code(err)); - } else if (srs_is_server_gracefully_close(err)) { - srs_warn("server disconnect. ret=%d", srs_error_code(err)); - } else { - srs_error("serve error %s", srs_error_desc(err).c_str()); +} + +srs_error_t SrsSslConnection::handshake(string key_file, string crt_file) +{ + srs_error_t err = srs_success; + + // For HTTPS, try to connect over security transport. +#if (OPENSSL_VERSION_NUMBER < 0x10002000L) // v1.0.2 + ssl_ctx = SSL_CTX_new(TLS_method()); +#else + ssl_ctx = SSL_CTX_new(TLSv1_2_method()); +#endif + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL); + srs_assert(SSL_CTX_set_cipher_list(ssl_ctx, "ALL") == 1); + + // TODO: Setup callback, see SSL_set_ex_data and SSL_set_info_callback + if ((ssl = SSL_new(ssl_ctx)) == NULL) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "SSL_new ssl"); } - - srs_freep(err); - return srs_success; + + if ((bio_in = BIO_new(BIO_s_mem())) == NULL) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_new in"); + } + + if ((bio_out = BIO_new(BIO_s_mem())) == NULL) { + BIO_free(bio_in); + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_new out"); + } + + SSL_set_bio(ssl, bio_in, bio_out); + + // SSL setup active, as server role. + SSL_set_accept_state(ssl); + SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE); + + uint8_t* data = NULL; + int r0, r1, size; + + // Setup the key and cert file for server. + if ((r0 = SSL_use_certificate_file(ssl, crt_file.c_str(), SSL_FILETYPE_PEM)) != 1) { + return srs_error_new(ERROR_HTTPS_KEY_CRT, "use cert %s", crt_file.c_str()); + } + + if ((r0 = SSL_use_RSAPrivateKey_file(ssl, key_file.c_str(), SSL_FILETYPE_PEM)) != 1) { + return srs_error_new(ERROR_HTTPS_KEY_CRT, "use key %s", key_file.c_str()); + } + + if ((r0 = SSL_check_private_key(ssl)) != 1) { + return srs_error_new(ERROR_HTTPS_KEY_CRT, "check key %s with cert %s", + key_file.c_str(), crt_file.c_str()); + } + srs_info("ssl: use key %s and cert %s", key_file.c_str(), crt_file.c_str()); + + // Receive ClientHello + while (true) { + char buf[1024]; ssize_t nn = 0; + if ((err = transport->read(buf, sizeof(buf), &nn)) != srs_success) { + return srs_error_wrap(err, "handshake: read"); + } + + if ((r0 = BIO_write(bio_in, buf, nn)) <= 0) { + // TODO: 0 or -1 maybe block, use BIO_should_retry to check. + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_write r0=%d, data=%p, size=%d", r0, buf, nn); + } + + r0 = SSL_do_handshake(ssl); r1 = SSL_get_error(ssl, r0); + if (r0 != -1 || r1 != SSL_ERROR_WANT_READ) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "handshake r0=%d, r1=%d", r0, r1); + } + + if ((size = BIO_get_mem_data(bio_out, &data)) > 0) { + // OK, reset it for the next write. + if ((r0 = BIO_reset(bio_in)) != 1) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_reset r0=%d", r0); + } + break; + } + } + + srs_info("https: ClientHello done"); + + // Send ServerHello, Certificate, Server Key Exchange, Server Hello Done + size = BIO_get_mem_data(bio_out, &data); + if (!data || size <= 0) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "handshake data=%p, size=%d", data, size); + } + if ((err = transport->write(data, size, NULL)) != srs_success) { + return srs_error_wrap(err, "handshake: write data=%p, size=%d", data, size); + } + if ((r0 = BIO_reset(bio_out)) != 1) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_reset r0=%d", r0); + } + + srs_info("https: ServerHello done"); + + // Receive Client Key Exchange, Change Cipher Spec, Encrypted Handshake Message + while (true) { + char buf[1024]; ssize_t nn = 0; + if ((err = transport->read(buf, sizeof(buf), &nn)) != srs_success) { + return srs_error_wrap(err, "handshake: read"); + } + + if ((r0 = BIO_write(bio_in, buf, nn)) <= 0) { + // TODO: 0 or -1 maybe block, use BIO_should_retry to check. + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_write r0=%d, data=%p, size=%d", r0, buf, nn); + } + + r0 = SSL_do_handshake(ssl); r1 = SSL_get_error(ssl, r0); + if (r0 == 1 && r1 == SSL_ERROR_NONE) { + break; + } + + if (r0 != -1 || r1 != SSL_ERROR_WANT_READ) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "handshake r0=%d, r1=%d", r0, r1); + } + + if ((size = BIO_get_mem_data(bio_out, &data)) > 0) { + // OK, reset it for the next write. + if ((r0 = BIO_reset(bio_in)) != 1) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_reset r0=%d", r0); + } + break; + } + } + + srs_info("https: Client done"); + + // Send New Session Ticket, Change Cipher Spec, Encrypted Handshake Message + size = BIO_get_mem_data(bio_out, &data); + if (!data || size <= 0) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "handshake data=%p, size=%d", data, size); + } + if ((err = transport->write(data, size, NULL)) != srs_success) { + return srs_error_wrap(err, "handshake: write data=%p, size=%d", data, size); + } + if ((r0 = BIO_reset(bio_out)) != 1) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_reset r0=%d", r0); + } + + srs_info("https: Server done"); + + return err; } -int SrsConnection::srs_id() +void SrsSslConnection::set_recv_timeout(srs_utime_t tm) { - return trd->cid(); + transport->set_recv_timeout(tm); } -string SrsConnection::remote_ip() { - return ip; -} - -void SrsConnection::expire() +srs_utime_t SrsSslConnection::get_recv_timeout() { - trd->interrupt(); + return transport->get_recv_timeout(); } +srs_error_t SrsSslConnection::read_fully(void* buf, size_t size, ssize_t* nread) +{ + return transport->read_fully(buf, size, nread); +} + +int64_t SrsSslConnection::get_recv_bytes() +{ + return transport->get_recv_bytes(); +} + +int64_t SrsSslConnection::get_send_bytes() +{ + return transport->get_send_bytes(); +} + +srs_error_t SrsSslConnection::read(void* plaintext, size_t nn_plaintext, ssize_t* nread) +{ + srs_error_t err = srs_success; + + while (true) { + int r0 = SSL_read(ssl, plaintext, nn_plaintext); int r1 = SSL_get_error(ssl, r0); + int r2 = BIO_ctrl_pending(bio_in); int r3 = SSL_is_init_finished(ssl); + + // OK, got data. + if (r0 > 0) { + srs_assert(r0 <= (int)nn_plaintext); + if (nread) { + *nread = r0; + } + return err; + } + + // Need to read more data to feed SSL. + if (r0 == -1 && r1 == SSL_ERROR_WANT_READ) { + // TODO: Can we avoid copy? + int nn_cipher = nn_plaintext; + char* cipher = new char[nn_cipher]; + SrsAutoFreeA(char, cipher); + + // Read the cipher from SSL. + ssize_t nn = 0; + if ((err = transport->read(cipher, nn_cipher, &nn)) != srs_success) { + return srs_error_wrap(err, "https: read"); + } + + int r0 = BIO_write(bio_in, cipher, nn); + if (r0 <= 0) { + // TODO: 0 or -1 maybe block, use BIO_should_retry to check. + return srs_error_new(ERROR_HTTPS_READ, "BIO_write r0=%d, cipher=%p, size=%d", r0, cipher, nn); + } + continue; + } + + // Fail for error. + if (r0 <= 0) { + return srs_error_new(ERROR_HTTPS_READ, "SSL_read r0=%d, r1=%d, r2=%d, r3=%d", + r0, r1, r2, r3); + } + } +} + +void SrsSslConnection::set_send_timeout(srs_utime_t tm) +{ + transport->set_send_timeout(tm); +} + +srs_utime_t SrsSslConnection::get_send_timeout() +{ + return transport->get_send_timeout(); +} + +srs_error_t SrsSslConnection::write(void* plaintext, size_t nn_plaintext, ssize_t* nwrite) +{ + srs_error_t err = srs_success; + + for (char* p = (char*)plaintext; p < (char*)plaintext + nn_plaintext;) { + int left = (int)nn_plaintext - (p - (char*)plaintext); + int r0 = SSL_write(ssl, (const void*)p, left); + int r1 = SSL_get_error(ssl, r0); + if (r0 <= 0) { + return srs_error_new(ERROR_HTTPS_WRITE, "https: write data=%p, size=%d, r0=%d, r1=%d", p, left, r0, r1); + } + + // Move p to the next writing position. + p += r0; + if (nwrite) { + *nwrite += (ssize_t)r0; + } + + uint8_t* data = NULL; + int size = BIO_get_mem_data(bio_out, &data); + if ((err = transport->write(data, size, NULL)) != srs_success) { + return srs_error_wrap(err, "https: write data=%p, size=%d", data, size); + } + if ((r0 = BIO_reset(bio_out)) != 1) { + return srs_error_new(ERROR_HTTPS_WRITE, "BIO_reset r0=%d", r0); + } + } + + return err; +} + +srs_error_t SrsSslConnection::writev(const iovec *iov, int iov_size, ssize_t* nwrite) +{ + srs_error_t err = srs_success; + + for (int i = 0; i < iov_size; i++) { + const iovec* p = iov + i; + if ((err = write((void*)p->iov_base, (size_t)p->iov_len, nwrite)) != srs_success) { + return srs_error_wrap(err, "write iov #%d base=%p, size=%d", i, p->iov_base, p->iov_len); + } + } + + return err; +} diff --git a/trunk/src/app/srs_app_conn.hpp b/trunk/src/app/srs_app_conn.hpp index 0ea514a11..c61bbefbe 100644 --- a/trunk/src/app/srs_app_conn.hpp +++ b/trunk/src/app/srs_app_conn.hpp @@ -27,79 +27,171 @@ #include #include +#include +#include + +#include #include -#include #include #include #include class SrsWallClock; -// The basic connection of SRS, +// Hooks for connection manager, to handle the event when disposing connections. +class ISrsDisposingHandler +{ +public: + ISrsDisposingHandler(); + virtual ~ISrsDisposingHandler(); +public: + // When before disposing resource, trigger when manager.remove(c), sync API. + virtual void on_before_dispose(ISrsResource* c) = 0; + // When disposing resource, async API, c is freed after it. + virtual void on_disposing(ISrsResource* c) = 0; +}; + +// The resource manager remove resource and delete it asynchronously. +class SrsResourceManager : virtual public ISrsCoroutineHandler, virtual public ISrsResourceManager +{ +private: + std::string label_; + SrsContextId cid_; + bool verbose_; +private: + SrsCoroutine* trd; + srs_cond_t cond; + // Callback handlers. + std::vector handlers_; + // Unsubscribing handlers, skip it for notifying. + std::vector unsubs_; + // Whether we are removing resources. + bool removing_; + // The zombie connections, we will delete it asynchronously. + std::vector zombies_; + std::vector* p_disposing_; +private: + // The connections without any id. + std::vector conns_; + // The connections with resource id. + std::map conns_id_; + // The connections with resource name. + std::map conns_name_; +public: + SrsResourceManager(const std::string& label, bool verbose = false); + virtual ~SrsResourceManager(); +public: + srs_error_t start(); + bool empty(); + size_t size(); +// Interface ISrsCoroutineHandler +public: + virtual srs_error_t cycle(); +public: + void add(ISrsResource* conn); + void add_with_id(const std::string& id, ISrsResource* conn); + void add_with_name(const std::string& name, ISrsResource* conn); + ISrsResource* at(int index); + ISrsResource* find_by_id(std::string id); + ISrsResource* find_by_name(std::string name); +public: + void subscribe(ISrsDisposingHandler* h); + void unsubscribe(ISrsDisposingHandler* h); +// Interface ISrsResourceManager +public: + virtual void remove(ISrsResource* c); +private: + void do_remove(ISrsResource* c); + void check_remove(ISrsResource* c, bool& in_zombie, bool& in_disposing); + void clear(); + void do_clear(); + void dispose(ISrsResource* c); +}; + +// If a connection is able to be expired, +// user can use HTTP-API to kick-off it. +class ISrsExpire +{ +public: + ISrsExpire(); + virtual ~ISrsExpire(); +public: + // Set connection to expired to kick-off it. + virtual void expire() = 0; +}; + +// Interface for connection that is startable. +class ISrsStartableConneciton : virtual public ISrsConnection + , virtual public ISrsStartable, virtual public ISrsKbpsDelta +{ +public: + ISrsStartableConneciton(); + virtual ~ISrsStartableConneciton(); +}; + +// The basic connection of SRS, for TCP based protocols, // all connections accept from listener must extends from this base class, // server will add the connection to manager, and delete it when remove. -class SrsConnection : virtual public ISrsConnection, virtual public ISrsCoroutineHandler - , virtual public ISrsKbpsDelta, virtual public ISrsReloadHandler +class SrsTcpConnection : virtual public ISrsProtocolReadWriter { -protected: - // Each connection start a green thread, - // when thread stop, the connection will be delete by server. - SrsCoroutine* trd; - // The manager object to manage the connection. - IConnectionManager* manager; +private: // The underlayer st fd handler. srs_netfd_t stfd; - // The ip of client. - std::string ip; // The underlayer socket. SrsStSocket* skt; - // The connection total kbps. - // not only the rtmp or http connection, all type of connection are - // need to statistic the kbps of io. - // The SrsStatistic will use it indirectly to statistic the bytes delta of current connection. - SrsKbps* kbps; - SrsWallClock* clk; - // The create time in milliseconds. - // for current connection to log self create time and calculate the living time. - int64_t create_time; public: - SrsConnection(IConnectionManager* cm, srs_netfd_t c, std::string cip); - virtual ~SrsConnection(); -// Interface ISrsKbpsDelta + SrsTcpConnection(srs_netfd_t c); + virtual ~SrsTcpConnection(); public: - virtual void remark(int64_t* in, int64_t* out); + virtual srs_error_t initialize(); public: - // To dipose the connection. - virtual void dispose(); - // Start the client green thread. - // when server get a client from listener, - // 1. server will create an concrete connection(for instance, RTMP connection), - // 2. then add connection to its connection manager, - // 3. start the client thread by invoke this start() - // when client cycle thread stop, invoke the on_thread_stop(), which will use server - // To remove the client by server->remove(this). - virtual srs_error_t start(); // Set socket option TCP_NODELAY. virtual srs_error_t set_tcp_nodelay(bool v); // Set socket option SO_SNDBUF in srs_utime_t. virtual srs_error_t set_socket_buffer(srs_utime_t buffer_v); -// Interface ISrsOneCycleThreadHandler +// Interface ISrsProtocolReadWriter public: - // The thread cycle function, - // when serve connection completed, terminate the loop which will terminate the thread, - // thread will invoke the on_thread_stop() when it terminated. - virtual srs_error_t cycle(); + virtual void set_recv_timeout(srs_utime_t tm); + virtual srs_utime_t get_recv_timeout(); + virtual srs_error_t read_fully(void* buf, size_t size, ssize_t* nread); + virtual int64_t get_recv_bytes(); + virtual int64_t get_send_bytes(); + virtual srs_error_t read(void* buf, size_t size, ssize_t* nread); + virtual void set_send_timeout(srs_utime_t tm); + virtual srs_utime_t get_send_timeout(); + virtual srs_error_t write(void* buf, size_t size, ssize_t* nwrite); + virtual srs_error_t writev(const iovec *iov, int iov_size, ssize_t* nwrite); +}; + +// The SSL connection over TCP transport, in server mode. +class SrsSslConnection : virtual public ISrsProtocolReadWriter +{ +private: + // The under-layer plaintext transport. + ISrsProtocolReadWriter* transport; +private: + SSL_CTX* ssl_ctx; + SSL* ssl; + BIO* bio_in; + BIO* bio_out; public: - // Get the srs id which identify the client. - virtual int srs_id(); - // Get the remote ip of peer. - virtual std::string remote_ip(); - // Set connection to expired. - virtual void expire(); -protected: - // For concrete connection to do the cycle. - virtual srs_error_t do_cycle() = 0; + SrsSslConnection(ISrsProtocolReadWriter* c); + virtual ~SrsSslConnection(); +public: + virtual srs_error_t handshake(std::string key_file, std::string crt_file); +// Interface ISrsProtocolReadWriter +public: + virtual void set_recv_timeout(srs_utime_t tm); + virtual srs_utime_t get_recv_timeout(); + virtual srs_error_t read_fully(void* buf, size_t size, ssize_t* nread); + virtual int64_t get_recv_bytes(); + virtual int64_t get_send_bytes(); + virtual srs_error_t read(void* buf, size_t size, ssize_t* nread); + virtual void set_send_timeout(srs_utime_t tm); + virtual srs_utime_t get_send_timeout(); + virtual srs_error_t write(void* buf, size_t size, ssize_t* nwrite); + virtual srs_error_t writev(const iovec *iov, int iov_size, ssize_t* nwrite); }; #endif diff --git a/trunk/src/app/srs_app_dash.cpp b/trunk/src/app/srs_app_dash.cpp index 40e9a9c53..bf67014be 100644 --- a/trunk/src/app/srs_app_dash.cpp +++ b/trunk/src/app/srs_app_dash.cpp @@ -307,6 +307,7 @@ SrsDashController::SrsDashController() vfragments = new SrsFragmentWindow(); afragments = new SrsFragmentWindow(); audio_dts = video_dts = 0; + fragment = 0; } SrsDashController::~SrsDashController() diff --git a/trunk/src/app/srs_app_dtls.cpp b/trunk/src/app/srs_app_dtls.cpp deleted file mode 100644 index abc8abf54..000000000 --- a/trunk/src/app/srs_app_dtls.cpp +++ /dev/null @@ -1,258 +0,0 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2013-2020 Winlin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#include - -using namespace std; - -#include - -#include -#include -#include - -#include -#include - -SrsDtls* SrsDtls::_instance = NULL; - -SrsDtls::SrsDtls() -{ - dtls_ctx = NULL; -} - -SrsDtls::~SrsDtls() -{ - SSL_CTX_free(dtls_ctx); -} - -SrsDtls* SrsDtls::instance() -{ - if (!_instance) { - _instance = new SrsDtls(); - } - return _instance; -} - -// The return value of verify_callback controls the strategy of the further verification process. If verify_callback -// returns 0, the verification process is immediately stopped with "verification failed" state. If SSL_VERIFY_PEER is -// set, a verification failure alert is sent to the peer and the TLS/SSL handshake is terminated. If verify_callback -// returns 1, the verification process is continued. If verify_callback always returns 1, the TLS/SSL handshake will -// not be terminated with respect to verification failures and the connection will be established. The calling process -// can however retrieve the error code of the last verification error using SSL_get_verify_result(3) or by maintaining -// its own error storage managed by verify_callback. -// @see https://www.openssl.org/docs/man1.0.2/man3/SSL_CTX_set_verify.html -static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) -{ - // Always OK, we don't check the certificate of client, - // because we allow client self-sign certificate. - return 1; -} - -srs_error_t SrsDtls::init(const SrsRequest& req) -{ - srs_error_t err = srs_success; - - // Initialize once. - if (dtls_ctx) { - return err; - } - -#if OPENSSL_VERSION_NUMBER < 0x10100000L // v1.1.x - // Initialize SSL library by registering algorithms - // The SSL_library_init() and OpenSSL_add_ssl_algorithms() functions were deprecated in OpenSSL 1.1.0 by OPENSSL_init_ssl(). - // @see https://www.openssl.org/docs/man1.1.0/man3/OpenSSL_add_ssl_algorithms.html - // @see https://web.archive.org/web/20150806185102/http://sctp.fh-muenster.de:80/dtls/dtls_udp_echo.c - OpenSSL_add_ssl_algorithms(); -#endif - -#if OPENSSL_VERSION_NUMBER < 0x10002000L // v1.0.2 - dtls_ctx = SSL_CTX_new(DTLSv1_method()); -#else - //dtls_ctx = SSL_CTX_new(DTLS_method()); - dtls_ctx = SSL_CTX_new(DTLSv1_method()); - //dtls_ctx = SSL_CTX_new(DTLSv1_2_method()); -#endif - - // Initialize SRTP first. - srs_assert(srtp_init() == 0); - - // Whether use ECDSA certificate. - bool is_ecdsa = _srs_config->get_rtc_server_ecdsa(); - - // Create keys by RSA or ECDSA. - EVP_PKEY* dtls_pkey = EVP_PKEY_new(); - srs_assert(dtls_pkey); - if (!is_ecdsa) { // By RSA - RSA* rsa = RSA_new(); - srs_assert(rsa); - - // Initialize the big-number for private key. - BIGNUM* exponent = BN_new(); - srs_assert(exponent); - BN_set_word(exponent, RSA_F4); - - // Generates a key pair and stores it in the RSA structure provided in rsa. - // @see https://www.openssl.org/docs/man1.0.2/man3/RSA_generate_key_ex.html - int key_bits = 1024; - RSA_generate_key_ex(rsa, key_bits, exponent, NULL); - - // @see https://www.openssl.org/docs/man1.1.0/man3/EVP_PKEY_type.html - srs_assert(EVP_PKEY_set1_RSA(dtls_pkey, rsa) == 1); - - RSA_free(rsa); - BN_free(exponent); - } - if (is_ecdsa) { // By ECDSA, https://stackoverflow.com/a/6006898 - EC_KEY* eckey = EC_KEY_new(); - srs_assert(eckey); - -#if OPENSSL_VERSION_NUMBER >= 0x10002000L // v1.0.2 - // For ECDSA, we could set the curves list. - // @see https://www.openssl.org/docs/man1.0.2/man3/SSL_CTX_set1_curves_list.html - SSL_CTX_set1_curves_list(dtls_ctx, "P-521:P-384:P-256"); -#endif - // Should use the curves in ClientHello.supported_groups - // For example: - // Supported Group: x25519 (0x001d) - // Supported Group: secp256r1 (0x0017) - // Supported Group: secp384r1 (0x0018) - // @remark The curve NID_secp256k1 is not secp256r1, k1 != r1. - // TODO: FIXME: Parse ClientHello and choose the curve. - // Note that secp256r1 in openssl is called NID_X9_62_prime256v1, not NID_secp256k1 - // @see https://stackoverflow.com/questions/41950056/openssl1-1-0-b-is-not-support-secp256r1openssl-ecparam-list-curves - EC_GROUP* ecgroup = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1); - //EC_GROUP* ecgroup = EC_GROUP_new_by_curve_name(NID_secp384r1); - srs_assert(ecgroup); -#if OPENSSL_VERSION_NUMBER < 0x10100000L // v1.1.x - // For openssl 1.0, we must set the group parameters, so that cert is ok. - // @see https://github.com/monero-project/monero/blob/master/contrib/epee/src/net_ssl.cpp#L225 - EC_GROUP_set_asn1_flag(ecgroup, OPENSSL_EC_NAMED_CURVE); -#endif - - srs_assert(EC_KEY_set_group(eckey, ecgroup) == 1); - srs_assert(EC_KEY_generate_key(eckey) == 1); - - // For openssl <1.1, we must set the ECDH manually. - // @see https://stackoverrun.com/cn/q/10791887 -#if OPENSSL_VERSION_NUMBER < 0x10100000L // v1.1.x - #if OPENSSL_VERSION_NUMBER < 0x10002000L // v1.0.2 - SSL_CTX_set_tmp_ecdh(dtls_ctx, eckey); - #else - SSL_CTX_set_ecdh_auto(dtls_ctx, 1); - #endif -#endif - // @see https://www.openssl.org/docs/man1.1.0/man3/EVP_PKEY_type.html - srs_assert(EVP_PKEY_set1_EC_KEY(dtls_pkey, eckey) == 1); - - EC_GROUP_free(ecgroup); - EC_KEY_free(eckey); - } - - // Create certificate, from previous generated pkey. - // TODO: Support ECDSA certificate. - X509* dtls_cert = X509_new(); - srs_assert(dtls_cert); - if (true) { - X509_NAME* subject = X509_NAME_new(); - srs_assert(subject); - - int serial = rand(); - ASN1_INTEGER_set(X509_get_serialNumber(dtls_cert), serial); - - const std::string& aor = "ossrs.net"; - X509_NAME_add_entry_by_txt(subject, "CN", MBSTRING_ASC, (unsigned char *) aor.data(), aor.size(), -1, 0); - - X509_set_issuer_name(dtls_cert, subject); - X509_set_subject_name(dtls_cert, subject); - - int expire_day = 365; - const long cert_duration = 60*60*24*expire_day; - - X509_gmtime_adj(X509_get_notBefore(dtls_cert), 0); - X509_gmtime_adj(X509_get_notAfter(dtls_cert), cert_duration); - - X509_set_version(dtls_cert, 2); - srs_assert(X509_set_pubkey(dtls_cert, dtls_pkey) == 1); - srs_assert(X509_sign(dtls_cert, dtls_pkey, EVP_sha1()) != 0); - - X509_NAME_free(subject); - } - - // Setup DTLS context. - if (true) { - // We use "ALL", while you can use "DEFAULT" means "ALL:!EXPORT:!LOW:!aNULL:!eNULL:!SSLv2" - // @see https://www.openssl.org/docs/man1.0.2/man1/ciphers.html - srs_assert(SSL_CTX_set_cipher_list(dtls_ctx, "ALL") == 1); - - // Setup the certificate. - srs_assert(SSL_CTX_use_certificate(dtls_ctx, dtls_cert) == 1); - srs_assert(SSL_CTX_use_PrivateKey(dtls_ctx, dtls_pkey) == 1); - - // Server will send Certificate Request. - // @see https://www.openssl.org/docs/man1.0.2/man3/SSL_CTX_set_verify.html - // TODO: FIXME: Config it, default to off to make the packet smaller. - SSL_CTX_set_verify(dtls_ctx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, verify_callback); - // The depth count is "level 0:peer certificate", "level 1: CA certificate", - // "level 2: higher level CA certificate", and so on. - // @see https://www.openssl.org/docs/man1.0.2/man3/SSL_CTX_set_verify.html - SSL_CTX_set_verify_depth(dtls_ctx, 4); - - // Whether we should read as many input bytes as possible (for non-blocking reads) or not. - // @see https://www.openssl.org/docs/man1.0.2/man3/SSL_CTX_set_read_ahead.html - SSL_CTX_set_read_ahead(dtls_ctx, 1); - - // TODO: Maybe we can use SRTP-GCM in future. - // @see https://bugs.chromium.org/p/chromium/issues/detail?id=713701 - // @see https://groups.google.com/forum/#!topic/discuss-webrtc/PvCbWSetVAQ - // @remark Only support SRTP_AES128_CM_SHA1_80, please read ssl/d1_srtp.c - srs_assert(SSL_CTX_set_tlsext_use_srtp(dtls_ctx, "SRTP_AES128_CM_SHA1_80") == 0); - } - - // Show DTLS fingerprint - if (true) { - char fp[100] = {0}; - char *p = fp; - unsigned char md[EVP_MAX_MD_SIZE]; - unsigned int n = 0; - - // TODO: FIXME: Unused variable. - /*int r = */X509_digest(dtls_cert, EVP_sha256(), md, &n); - - for (unsigned int i = 0; i < n; i++, ++p) { - sprintf(p, "%02X", md[i]); - p += 2; - - if(i < (n-1)) { - *p = ':'; - } else { - *p = '\0'; - } - } - - fingerprint.assign(fp, strlen(fp)); - srs_trace("fingerprint=%s", fingerprint.c_str()); - } - - return err; -} diff --git a/trunk/src/app/srs_app_dtls.hpp b/trunk/src/app/srs_app_dtls.hpp deleted file mode 100644 index 65d38c167..000000000 --- a/trunk/src/app/srs_app_dtls.hpp +++ /dev/null @@ -1,54 +0,0 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2013-2020 Winlin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#ifndef SRS_APP_DTLS_HPP -#define SRS_APP_DTLS_HPP - -#include - -#include - -class SrsRequest; - -#include - -class SrsDtls -{ -private: - static SrsDtls* _instance; -private: - std::string fingerprint; - SSL_CTX* dtls_ctx; -private: - SrsDtls(); - virtual ~SrsDtls(); -public: - srs_error_t init(const SrsRequest& req); -public: - static SrsDtls* instance(); - SSL_CTX* get_dtls_ctx() { return dtls_ctx; } -public: - std::string get_fingerprint() const { return fingerprint; } -}; - -#endif diff --git a/trunk/src/app/srs_app_dvr.cpp b/trunk/src/app/srs_app_dvr.cpp index 453ba1dbc..2dc7b57dc 100644 --- a/trunk/src/app/srs_app_dvr.cpp +++ b/trunk/src/app/srs_app_dvr.cpp @@ -48,6 +48,7 @@ SrsDvrSegmenter::SrsDvrSegmenter() req = NULL; jitter = NULL; plan = NULL; + wait_keyframe = true; fragment = new SrsFragment(); fs = new SrsFileWriter(); @@ -533,7 +534,7 @@ srs_error_t SrsDvrMp4Segmenter::close_encoder() return err; } -SrsDvrAsyncCallOnDvr::SrsDvrAsyncCallOnDvr(int c, SrsRequest* r, string p) +SrsDvrAsyncCallOnDvr::SrsDvrAsyncCallOnDvr(SrsContextId c, SrsRequest* r, string p) { cid = c; req = r->copy(); @@ -585,6 +586,7 @@ string SrsDvrAsyncCallOnDvr::to_string() SrsDvrPlan::SrsDvrPlan() { req = NULL; + hub = NULL; dvr_enabled = false; segment = NULL; @@ -673,7 +675,7 @@ srs_error_t SrsDvrPlan::on_reap_segment() { srs_error_t err = srs_success; - int cid = _srs_context->get_id(); + SrsContextId cid = _srs_context->get_id(); SrsFragment* fragment = segment->current(); string fullpath = fragment->fullpath(); diff --git a/trunk/src/app/srs_app_dvr.hpp b/trunk/src/app/srs_app_dvr.hpp index 716d45e0e..03b04e746 100644 --- a/trunk/src/app/srs_app_dvr.hpp +++ b/trunk/src/app/srs_app_dvr.hpp @@ -159,11 +159,11 @@ protected: class SrsDvrAsyncCallOnDvr : public ISrsAsyncCallTask { private: - int cid; + SrsContextId cid; std::string path; SrsRequest* req; public: - SrsDvrAsyncCallOnDvr(int c, SrsRequest* r, std::string p); + SrsDvrAsyncCallOnDvr(SrsContextId c, SrsRequest* r, std::string p); virtual ~SrsDvrAsyncCallOnDvr(); public: virtual srs_error_t call(); diff --git a/trunk/src/app/srs_app_edge.cpp b/trunk/src/app/srs_app_edge.cpp index d59393daf..13885d1ac 100644 --- a/trunk/src/app/srs_app_edge.cpp +++ b/trunk/src/app/srs_app_edge.cpp @@ -65,6 +65,7 @@ SrsEdgeRtmpUpstream::SrsEdgeRtmpUpstream(string r) { redirect = r; sdk = NULL; + selected_port = 0; } SrsEdgeRtmpUpstream::~SrsEdgeRtmpUpstream() @@ -124,10 +125,15 @@ srs_error_t SrsEdgeRtmpUpstream::connect(SrsRequest* r, SrsLbRoundRobin* lb) if ((err = sdk->connect()) != srs_success) { return srs_error_wrap(err, "edge pull %s failed, cto=%dms, sto=%dms.", url.c_str(), srsu2msi(cto), srsu2msi(sto)); } - - if ((err = sdk->play(_srs_config->get_chunk_size(req->vhost))) != srs_success) { + + // For RTMP client, we pass the vhost in tcUrl when connecting, + // so we publish without vhost in stream. + string stream; + if ((err = sdk->play(_srs_config->get_chunk_size(req->vhost), false, &stream)) != srs_success) { return srs_error_wrap(err, "edge pull %s stream failed", url.c_str()); } + + srs_trace("edge-pull publish url %s, stream=%s%s as %s", url.c_str(), req->stream.c_str(), req->param.c_str(), stream.c_str()); return err; } @@ -440,6 +446,7 @@ SrsEdgeForwarder::SrsEdgeForwarder() edge = NULL; req = NULL; send_error_code = ERROR_SUCCESS; + source = NULL; sdk = NULL; lb = new SrsLbRoundRobin(); @@ -504,8 +511,11 @@ srs_error_t SrsEdgeForwarder::start() if ((err = sdk->connect()) != srs_success) { return srs_error_wrap(err, "sdk connect %s failed, cto=%dms, sto=%dms.", url.c_str(), srsu2msi(cto), srsu2msi(sto)); } - - if ((err = sdk->publish(_srs_config->get_chunk_size(req->vhost))) != srs_success) { + + // For RTMP client, we pass the vhost in tcUrl when connecting, + // so we publish without vhost in stream. + string stream; + if ((err = sdk->publish(_srs_config->get_chunk_size(req->vhost), false, &stream)) != srs_success) { return srs_error_wrap(err, "sdk publish"); } @@ -515,7 +525,8 @@ srs_error_t SrsEdgeForwarder::start() if ((err = trd->start()) != srs_success) { return srs_error_wrap(err, "coroutine"); } - srs_trace("edge-fwr publish url %s", url.c_str()); + + srs_trace("edge-fwr publish url %s, stream=%s%s as %s", url.c_str(), req->stream.c_str(), req->param.c_str(), stream.c_str()); return err; } @@ -689,11 +700,13 @@ void SrsPlayEdge::on_all_client_stop() // when all client disconnected, // and edge is ingesting origin stream, abort it. if (state == SrsEdgeStatePlay || state == SrsEdgeStateIngestConnected) { - ingester->stop(); - SrsEdgeState pstate = state; + state = SrsEdgeStateIngestStopping; + + ingester->stop(); + state = SrsEdgeStateInit; - srs_trace("edge change from %d to state %d (init).", pstate, state); + srs_trace("edge change from %d to %d then %d (init).", pstate, SrsEdgeStateIngestStopping, state); return; } diff --git a/trunk/src/app/srs_app_edge.hpp b/trunk/src/app/srs_app_edge.hpp index 4c7249614..4a673c926 100644 --- a/trunk/src/app/srs_app_edge.hpp +++ b/trunk/src/app/srs_app_edge.hpp @@ -27,7 +27,6 @@ #include #include -#include #include @@ -56,9 +55,12 @@ enum SrsEdgeState SrsEdgeStatePlay = 100, // play stream from origin, ingest stream SrsEdgeStateIngestConnected = 101, - + // For publish edge SrsEdgeStatePublish = 200, + + // We are stopping edge ingesting. + SrsEdgeStateIngestStopping = 300, }; // The state of edge from user, manual machine diff --git a/trunk/src/app/srs_app_encoder.hpp b/trunk/src/app/srs_app_encoder.hpp index 1969547b8..2beb64612 100644 --- a/trunk/src/app/srs_app_encoder.hpp +++ b/trunk/src/app/srs_app_encoder.hpp @@ -29,7 +29,7 @@ #include #include -#include +#include class SrsConfDirective; class SrsRequest; diff --git a/trunk/src/app/srs_app_ffmpeg.cpp b/trunk/src/app/srs_app_ffmpeg.cpp index 1cde4f5f9..3acb1708d 100644 --- a/trunk/src/app/srs_app_ffmpeg.cpp +++ b/trunk/src/app/srs_app_ffmpeg.cpp @@ -45,7 +45,7 @@ using namespace std; #include #include -#ifdef SRS_AUTO_FFMPEG_STUB +#ifdef SRS_FFMPEG_STUB #define SRS_RTMP_ENCODER_COPY "copy" #define SRS_RTMP_ENCODER_NO_VIDEO "vn" diff --git a/trunk/src/app/srs_app_ffmpeg.hpp b/trunk/src/app/srs_app_ffmpeg.hpp index 452b7a564..9e068acbb 100644 --- a/trunk/src/app/srs_app_ffmpeg.hpp +++ b/trunk/src/app/srs_app_ffmpeg.hpp @@ -26,7 +26,7 @@ #include -#ifdef SRS_AUTO_FFMPEG_STUB +#ifdef SRS_FFMPEG_STUB #include #include diff --git a/trunk/src/app/srs_app_forward.cpp b/trunk/src/app/srs_app_forward.cpp index 882d33a8c..957ea7ccb 100755 --- a/trunk/src/app/srs_app_forward.cpp +++ b/trunk/src/app/srs_app_forward.cpp @@ -222,8 +222,11 @@ srs_error_t SrsForwarder::do_cycle() if ((err = sdk->connect()) != srs_success) { return srs_error_wrap(err, "sdk connect url=%s, cto=%dms, sto=%dms.", url.c_str(), srsu2msi(cto), srsu2msi(sto)); } - - if ((err = sdk->publish(_srs_config->get_chunk_size(req->vhost))) != srs_success) { + + // For RTMP client, we pass the vhost in tcUrl when connecting, + // so we publish without vhost in stream. + string stream; + if ((err = sdk->publish(_srs_config->get_chunk_size(req->vhost), false, &stream)) != srs_success) { return srs_error_wrap(err, "sdk publish"); } @@ -234,6 +237,8 @@ srs_error_t SrsForwarder::do_cycle() if ((err = forward()) != srs_success) { return srs_error_wrap(err, "forward"); } + + srs_trace("forward publish url %s, stream=%s%s as %s", url.c_str(), req->stream.c_str(), req->param.c_str(), stream.c_str()); return err; } diff --git a/trunk/src/app/srs_app_forward.hpp b/trunk/src/app/srs_app_forward.hpp index 9b50f89d7..a4adcfc15 100644 --- a/trunk/src/app/srs_app_forward.hpp +++ b/trunk/src/app/srs_app_forward.hpp @@ -29,7 +29,6 @@ #include #include -#include class ISrsProtocolReadWriter; class SrsSharedPtrMessage; diff --git a/trunk/src/app/srs_app_gb28181.cpp b/trunk/src/app/srs_app_gb28181.cpp index acea9f166..89a9c620d 100644 --- a/trunk/src/app/srs_app_gb28181.cpp +++ b/trunk/src/app/srs_app_gb28181.cpp @@ -24,6 +24,8 @@ #include #include #include +#include +#include using namespace std; @@ -57,6 +59,7 @@ using namespace std; SrsPsRtpPacket::SrsPsRtpPacket() { + isFirstPacket = false; } SrsPsRtpPacket::~SrsPsRtpPacket() @@ -191,6 +194,15 @@ void SrsGb28181PsRtpProcessor::clear_pre_packet() } srs_error_t SrsGb28181PsRtpProcessor::on_udp_packet(const sockaddr* from, const int fromlen, char* buf, int nb_buf) +{ + if (config->jitterbuffer_enable){ + return on_rtp_packet_jitter(from, fromlen, buf, nb_buf); + }else{ + return on_rtp_packet(from, fromlen, buf, nb_buf); + } +} + +srs_error_t SrsGb28181PsRtpProcessor::on_rtp_packet(const sockaddr* from, const int fromlen, char* buf, int nb_buf) { srs_error_t err = srs_success; bool completed = false; @@ -203,7 +215,9 @@ srs_error_t SrsGb28181PsRtpProcessor::on_udp_packet(const sockaddr* from, const (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"); + // return srs_error_new(ERROR_SYSTEM_IP_INVALID, "bad address"); + srs_warn("gb28181 ps rtp: bad address"); + return srs_success; } int peer_port = atoi(port_string); @@ -213,11 +227,13 @@ srs_error_t SrsGb28181PsRtpProcessor::on_udp_packet(const sockaddr* from, const SrsPsRtpPacket pkt; if ((err = pkt.decode(&stream)) != srs_success) { - return srs_error_wrap(err, "ps rtp decode error"); + // return srs_error_wrap(err, "ps rtp decode error"); + srs_warn("gb28181 ps rtp: decode error"); + srs_freep(err); + return srs_success; } //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(); @@ -241,7 +257,7 @@ srs_error_t SrsGb28181PsRtpProcessor::on_udp_packet(const sockaddr* from, const //TODO: check sequence number out of order //it may be out of order, or multiple streaming ssrc are the same - if (pre_sequence_number + 1 != pkt.sequence_number && + if (((pre_sequence_number + 1) % 65536) != pkt.sequence_number && pre_sequence_number != pkt.sequence_number){ srs_warn("gb28181: ps sequence_number out of order, ssrc=%#x, pre=%u, cur=%u, peer(%s, %s)", pkt.ssrc, pre_sequence_number, pkt.sequence_number, address_string, port_string); @@ -283,7 +299,6 @@ srs_error_t SrsGb28181PsRtpProcessor::on_udp_packet(const sockaddr* from, const if (!completed){ return err; } - //process completed frame data //clear processed one ps frame //on completed frame data rtp packet in muxer enqueue @@ -291,36 +306,7 @@ srs_error_t SrsGb28181PsRtpProcessor::on_udp_packet(const sockaddr* from, const 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); - } - - //auto crate channel - if (!muxer && config->auto_create_channel){ - //auto create channel generated id - std::stringstream ss, ss1; - ss << "chid" << pkt.ssrc; - std::string tmp_id = ss.str(); - - SrsGb28181StreamChannel channel; - channel.set_channel_id(tmp_id); - channel.set_port_mode(RTP_PORT_MODE_FIXED); - channel.set_ssrc(pkt.ssrc); - - srs_error_t err2 = srs_success; - if ((err2 = _srs_gb28181->create_stream_channel(&channel)) != srs_success){ - srs_warn("gb28181: RtpProcessor create stream channel error %s", srs_error_desc(err2).c_str()); - srs_error_reset(err2); - }; - - muxer = _srs_gb28181->fetch_rtmpmuxer(tmp_id); - } + muxer = fetch_rtmpmuxer(channel_id,pkt.ssrc); if (muxer){ //TODO: fixme: the same device uses the same SSRC to send with different local ports @@ -346,6 +332,464 @@ srs_error_t SrsGb28181PsRtpProcessor::on_udp_packet(const sockaddr* from, const return err; } +SrsGb28181RtmpMuxer* SrsGb28181PsRtpProcessor::fetch_rtmpmuxer(std::string channel_id, uint32_t ssrc) +{ + if(true){ + 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(ssrc); + } + + //auto crate channel + if (!muxer && config->auto_create_channel){ + //auto create channel generated id + std::stringstream ss, ss1; + ss << "chid" << ssrc; + std::string tmp_id = ss.str(); + + SrsGb28181StreamChannel channel; + channel.set_channel_id(tmp_id); + // channel.set_port_mode(RTP_PORT_MODE_FIXED); + if (!config->sip_invite_port_fixed) { + channel.set_port_mode(RTP_PORT_MODE_RANDOM); + }else + { + channel.set_port_mode(RTP_PORT_MODE_FIXED); + } + channel.set_ssrc(ssrc); + + srs_error_t err2 = srs_success; + if ((err2 = _srs_gb28181->create_stream_channel(&channel)) != srs_success){ + srs_warn("gb28181: RtpProcessor create stream channel error %s", srs_error_desc(err2).c_str()); + srs_error_reset(err2); + }; + + muxer = _srs_gb28181->fetch_rtmpmuxer(tmp_id); + } + + return muxer; + }//end if FoundFrame +} + +srs_error_t SrsGb28181PsRtpProcessor::rtmpmuxer_enqueue_data(SrsGb28181RtmpMuxer *muxer, uint32_t ssrc, + int peer_port, std::string address_string, SrsPsRtpPacket *pkt) +{ + srs_error_t err = srs_success; + + if (!muxer) + return err; + + 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_GB28181_CASTER " gb28181: client_id %s, ssrc=%#x, first peer_port=%d cur peer_port=%d", + muxer->get_channel_id().c_str(), ssrc, muxer->channel_peer_port(), peer_port); + }else { + //muxer->ps_packet_enqueue(pkt); + muxer->insert_jitterbuffer(pkt); + }//end if (muxer->channel_peer_port() != peer_port) + }//end if (muxer) + + return err; +} + +srs_error_t SrsGb28181PsRtpProcessor::on_rtp_packet_jitter(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"); + srs_warn("gb28181 ps rtp: bad address"); + return srs_success; + } + + int peer_port = atoi(port_string); + + if (true) { + SrsBuffer stream(buf, nb_buf); + SrsPsRtpPacket *pkt = new SrsPsRtpPacket();; + + if ((err = pkt->decode(&stream)) != srs_success) { + srs_freep(pkt); + // return srs_error_wrap(err, "ps rtp decode error"); + srs_warn("gb28181 ps rtp: decode error"); + srs_freep(err); + return srs_success; + } + + std::stringstream ss3; + ss3 << pkt->ssrc << ":" << port_string; + std::string jitter_key = ss3.str(); + + pkt->completed = pkt->marker; + + + if (pprint->can_print()) { + srs_trace("<- " SRS_CONSTS_LOG_GB28181_CASTER " gb28181: client_id %s, peer(%s, %d) ps rtp packet %dB, age=%d, vt=%d/%u, sts=%u/%u/%#x, paylod=%dB", + channel_id.c_str(), address_string, peer_port, nb_buf, pprint->age(), pkt->version, + pkt->payload_type, pkt->sequence_number, pkt->timestamp, pkt->ssrc, + pkt->payload->length() + ); + } + + SrsGb28181RtmpMuxer *muxer = fetch_rtmpmuxer(channel_id, pkt->ssrc); + if (muxer){ + rtmpmuxer_enqueue_data(muxer, pkt->ssrc, peer_port, address_string, pkt); + } + + SrsAutoFree(SrsPsRtpPacket, pkt); + } + + return err; +} + +//SrsGb28181TcpPsRtpProcessor +SrsGb28181TcpPsRtpProcessor::SrsGb28181TcpPsRtpProcessor(SrsGb28181Config* c, std::string id) +{ + config = c; + pprint = SrsPithyPrint::create_caster(); + channel_id = id; +} + +SrsGb28181TcpPsRtpProcessor::~SrsGb28181TcpPsRtpProcessor() +{ + dispose(); + srs_freep(pprint); +} + +void SrsGb28181TcpPsRtpProcessor::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(); + + clear_pre_packet(); + + return; +} + +void SrsGb28181TcpPsRtpProcessor::clear_pre_packet() +{ + map::iterator it; + for (it = pre_packet.begin(); it != pre_packet.end(); ++it) { + srs_freep(it->second); + } + pre_packet.clear(); +} + +srs_error_t SrsGb28181TcpPsRtpProcessor::on_rtp(char* buf, int nb_buf, std::string ip, int port) +{ + srs_error_t err = srs_success; + + if (config->jitterbuffer_enable) { + err = on_rtp_packet_jitter(buf, nb_buf, ip, port); + if (err != srs_success) { + srs_warn("SrsGb28181TcpPsRtpProcessor::on_rtp on_rtp_packet_jitter err"); + } + } + else { + return on_rtp_packet(buf, nb_buf, ip, port); + } + return err; +} + +srs_error_t SrsGb28181TcpPsRtpProcessor::on_rtp_packet(char* buf, int nb_buf, std::string ip, int port) +{ + srs_error_t err = srs_success; + bool completed = false; + + pprint->elapse(); + + char address_string[64] = {0}; + char port_string[16] = {0}; + /*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"); + }*/ + + //itoa(port, port_string, 10); + int peer_port = 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;// 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 + 1) % 65536) != pkt.sequence_number && + pre_sequence_number != pkt.sequence_number) { + srs_warn("gb28181: ps sequence_number out of order, ssrc=%#x, pre=%u, cur=%u, peer(%s, %s)", + pkt.ssrc, pre_sequence_number, pkt.sequence_number, ip.c_str(), 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_GB28181_CASTER " gb28181: client_id %s, peer(%s, %d) ps rtp packet %dB, age=%d, vt=%d/%u, sts=%u/%u/%#x, paylod=%dB", + channel_id.c_str(), ip.c_str(), peer_port, 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); + } + + //auto crate channel + if (!muxer && config->auto_create_channel) { + //auto create channel generated id + std::stringstream ss, ss1; + ss << "chid" << pkt.ssrc; + std::string tmp_id = ss.str(); + + SrsGb28181StreamChannel channel; + channel.set_channel_id(tmp_id); + channel.set_port_mode(RTP_PORT_MODE_FIXED); + channel.set_ssrc(pkt.ssrc); + + srs_error_t err2 = srs_success; + if ((err2 = _srs_gb28181->create_stream_channel(&channel)) != srs_success) { + srs_warn("gb28181: RtpProcessor create stream channel error %s", srs_error_desc(err2).c_str()); + srs_error_reset(err2); + }; + + muxer = _srs_gb28181->fetch_rtmpmuxer(tmp_id); + } + + 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_GB28181_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 consumer to process, and then free + muxer->ps_packet_enqueue(key->second); + } + } + else { + //no consumer process it, discarded + srs_freep(key->second); + } + cache_ps_rtp_packet.erase(pkt_key); + } + } + return err; +} + +SrsGb28181RtmpMuxer* SrsGb28181TcpPsRtpProcessor::create_rtmpmuxer(std::string channel_id, uint32_t ssrc) +{ + if (true) { + 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(ssrc); + } + + //auto crate channel + if (!muxer && config->auto_create_channel) { + //auto create channel generated id + std::stringstream ss, ss1; + ss << "chid" << ssrc; + std::string tmp_id = ss.str(); + + SrsGb28181StreamChannel channel; + channel.set_channel_id(tmp_id); + channel.set_port_mode(RTP_PORT_MODE_FIXED); + channel.set_ssrc(ssrc); + + srs_error_t err2 = srs_success; + if ((err2 = _srs_gb28181->create_stream_channel(&channel)) != srs_success) { + srs_warn("gb28181: RtpProcessor create stream channel error %s", srs_error_desc(err2).c_str()); + srs_error_reset(err2); + }; + + muxer = _srs_gb28181->fetch_rtmpmuxer(tmp_id); + } + + return muxer; + }//end if FoundFrame +} + +srs_error_t SrsGb28181TcpPsRtpProcessor::rtmpmuxer_enqueue_data(SrsGb28181RtmpMuxer *muxer, uint32_t ssrc, + int peer_port, std::string address_string, SrsPsRtpPacket *pkt) +{ + srs_error_t err = srs_success; + + if (!muxer) + return err; + + 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_GB28181_CASTER " gb28181: client_id %s, ssrc=%#x, first peer_port=%d cur peer_port=%d", + muxer->get_channel_id().c_str(), ssrc, muxer->channel_peer_port(), peer_port); + } + else { + //muxer->ps_packet_enqueue(pkt); + muxer->insert_jitterbuffer(pkt); + }//end if (muxer->channel_peer_port() != peer_port) + }//end if (muxer) + + return err; +} + +srs_error_t SrsGb28181TcpPsRtpProcessor::on_rtp_packet_jitter(char* buf, int nb_buf, std::string ip, int port) +{ + srs_error_t err = srs_success; + bool completed = false; + + pprint->elapse(); + + char address_string[64] = {0}; + char port_string[16] = {0}; + /*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"); + }*/ + + //itoa(port, port_string, 10); + int peer_port = port;// atoi(port_string); + + if (true) { + SrsBuffer stream(buf, nb_buf); + SrsPsRtpPacket *pkt = new SrsPsRtpPacket();; + + if ((err = pkt->decode(&stream)) != srs_success) { + srs_freep(pkt); + return srs_error_wrap(err, "ps rtp decode error"); + } + + std::stringstream ss3; + ss3 << pkt->ssrc << ":" << port;// port_string; + std::string jitter_key = ss3.str(); + + pkt->completed = pkt->marker; + + + if (pprint->can_print()) { + srs_trace("<- " SRS_CONSTS_LOG_GB28181_CASTER " SrsGb28181TcpPsRtpProcessor::on_rtp_packet_jitter gb28181: client_id %s, peer(%s, %d) ps rtp packet %dB, age=%d, vt=%d/%u, sts=%u/%u/%#x, paylod=%dB", + channel_id.c_str(), address_string, peer_port, nb_buf, pprint->age(), pkt->version, + pkt->payload_type, pkt->sequence_number, pkt->timestamp, pkt->ssrc, + pkt->payload->length() + ); + } + + SrsGb28181RtmpMuxer *muxer = create_rtmpmuxer(channel_id, pkt->ssrc); + if (muxer) { + rtmpmuxer_enqueue_data(muxer, pkt->ssrc, peer_port, ip, pkt); + } + + SrsAutoFree(SrsPsRtpPacket, pkt); + } + + return err; +} + //ISrsPsStreamHander ps stream raw video/audio hander interface ISrsPsStreamHander::ISrsPsStreamHander() { @@ -363,10 +807,19 @@ SrsPsStreamDemixer::SrsPsStreamDemixer(ISrsPsStreamHander *h, std::string id, bo wait_first_keyframe = k; channel_id = id; first_keyframe_flag = false; + + video_es_id = 0; + video_es_type = 0; + audio_es_id = 0; + audio_es_type = 0; + audio_check_aac_try_count = 0; + + aac = new SrsRawAacStream(); } SrsPsStreamDemixer::~SrsPsStreamDemixer() { + srs_freep(aac); } bool SrsPsStreamDemixer::can_send_ps_av_packet(){ @@ -379,6 +832,58 @@ bool SrsPsStreamDemixer::can_send_ps_av_packet(){ return false; } +std::string SrsPsStreamDemixer::get_ps_map_type_str(uint8_t type) +{ + switch(type){ + case STREAM_TYPE_VIDEO_MPEG1: //0x01 + return "mpeg1"; + case STREAM_TYPE_VIDEO_MPEG2:// 0x02 + return "mpeg2"; + case STREAM_TYPE_AUDIO_MPEG1:// 0x03 + return "mpeg1"; + case STREAM_TYPE_AUDIO_MPEG2:// 0x04 + return "mpeg2"; + case STREAM_TYPE_PRIVATE_SECTION:// 0x05 + return "private_section"; + case STREAM_TYPE_PRIVATE_DATA:// 0x06 + return "private_data"; + case STREAM_TYPE_AUDIO_AAC:// 0x0f + return "aac"; + case STREAM_TYPE_VIDEO_MPEG4:// 0x10 + return "mpeg4"; + case STREAM_TYPE_VIDEO_H264:// 0x1b + return "h264"; + case STREAM_TYPE_VIDEO_HEVC:// 0x24 + return "hevc"; + case STREAM_TYPE_VIDEO_CAVS:// 0x42 + return "cavs"; + case STREAM_TYPE_VIDEO_SAVC:// 0x80 + return "savc"; + + case STREAM_TYPE_AUDIO_AC3:// 0x81 + return "ac3"; + + case STREAM_TYPE_AUDIO_G711:// 0x90 + return "g711"; + case STREAM_TYPE_AUDIO_G711ULAW:// 0x91 + return "g711ulaw"; + case STREAM_TYPE_AUDIO_G722_1:// 0x92 + return "g722_1"; + case STREAM_TYPE_AUDIO_G723_1:// 0x93 + return "g723_1"; + case STREAM_TYPE_AUDIO_G726:// 0x96 + return "g726"; + case STREAM_TYPE_AUDIO_G729_1:// 0x99 + return "g729_1"; + case STREAM_TYPE_AUDIO_SVAC:// 0x9b + return "svac"; + case STREAM_TYPE_AUDIO_PCM:// 0x9c + return "pcm"; + default: + return "unknow"; + } +} + int64_t SrsPsStreamDemixer::parse_ps_timestamp(const uint8_t* p) { unsigned long b; @@ -406,7 +911,6 @@ int64_t SrsPsStreamDemixer::parse_ps_timestamp(const uint8_t* p) 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; @@ -428,7 +932,8 @@ srs_error_t SrsPsStreamDemixer::on_ps_stream(char* ps_data, int ps_size, uint32_ ps_fw.write(ps_data, ps_size, NULL); #endif - while(incomplete_len >= sizeof(SrsPsPacketStartCode)) + while(incomplete_len > 0 + && incomplete_len >= sizeof(SrsPsPacketStartCode)) { if (next_ps_pack && next_ps_pack[0] == (char)0x00 @@ -468,12 +973,61 @@ srs_error_t SrsPsStreamDemixer::on_ps_stream(char* ps_data, int ps_size, uint32_ //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; + + //parse ps map + uint16_t psm_length=0, ps_info_length=0, es_map_length=0; + char *p = (char*)psmap_pack + sizeof(SrsPsMapPacket); + + SrsBuffer buf(p, (int)psmap_pack->length); + + psm_length =(int)psmap_pack->length; + buf.read_1bytes(); + buf.read_1bytes(); + + ps_info_length = buf.read_2bytes(); + + /* skip program_stream_info */ + buf.skip(ps_info_length); + /*es_map_length = */buf.read_2bytes(); + /* Ignore es_map_length, trust psm_length */ + es_map_length = psm_length - ps_info_length - 10; + + // /* at least one es available? */ + while (es_map_length >= 4) { + uint8_t type = buf.read_1bytes(); + uint8_t es_id = buf.read_1bytes(); + uint16_t es_info_length = buf.read_2bytes(); + std::string s_type = get_ps_map_type_str(type); + + /* remember mapping from stream id to stream type */ + if (es_id >= PS_AUDIO_ID && es_id <= PS_AUDIO_ID_END){ + if (audio_es_type != type){ + srs_trace("gb28181: ps map audio es_type=%s(%x), es_id=%0x, es_info_length=%d", + s_type.c_str(), type, es_id, es_info_length); + } + + audio_es_id = es_id; + audio_es_type = type; + }else if (es_id >= PS_VIDEO_ID && es_id <= PS_VIDEO_ID_END){ + + if (video_es_type != type){ + srs_trace("gb28181: ps map video es_type=%s(%x), es_id=%0x, es_info_length=%d", + s_type.c_str(), type, es_id, es_info_length); + } + + video_es_id = es_id; + video_es_type = type; + } + + /* skip program_stream_info */ + buf.skip(es_info_length); + es_map_length -= 4 + es_info_length; + } } else if(next_ps_pack @@ -550,8 +1104,37 @@ srs_error_t SrsPsStreamDemixer::on_ps_stream(char* ps_data, int ps_size, uint32_ 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); + //if ps map is not aac, but stream many be aac adts , try update type, + //TODO: dahua audio ps map type always is 0x90(g711) + uint8_t p1 = (uint8_t)(next_ps_pack[0]); + uint8_t p2 = (uint8_t)(next_ps_pack[1]); + uint8_t p3 = (uint8_t)(next_ps_pack[2]); + uint8_t p4 = (uint8_t)(next_ps_pack[3]); + + if (audio_enable && audio_es_type != STREAM_TYPE_AUDIO_AAC && + (p1 & 0xFF) == 0xFF && (p2 & 0xF0) == 0xF0) { + + //try update aac type + SrsBuffer avs(next_ps_pack, payload_len); + char* frame = NULL; + int frame_size = 0; + SrsRawAacStreamCodec codec; + + srs_error_t err2 = srs_success; + if ((err2 = aac->adts_demux(&avs, &frame, &frame_size, codec)) != srs_success) { + srs_info("gb28181: client_id %s, audio data not aac adts (%#x/%u) %02x %02x %02x %02x\n", + channel_id.c_str(), ssrc, timestamp, p1, p2, p3, p4); + srs_error_reset(err); + }else{ + srs_warn("gb28181: client_id %s, ps map is not aac (%s) type, but stream many be aac adts, try update type", + channel_id.c_str(), get_ps_map_type_str(audio_es_type).c_str()); + audio_es_type = STREAM_TYPE_AUDIO_AAC; + } + } + + 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"; @@ -565,7 +1148,7 @@ srs_error_t SrsPsStreamDemixer::on_ps_stream(char* ps_data, int ps_size, uint32_ 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) { + if ((err = hander->on_rtp_audio(&audio_stream, audio_pts, audio_es_type)) != srs_success) { return srs_error_wrap(err, "process ps audio packet"); } } @@ -594,13 +1177,12 @@ srs_error_t SrsPsStreamDemixer::on_ps_stream(char* ps_data, int ps_size, uint32_ //ts=1000 seq=4 mark=true payload= audio incomplete_len = ps_size - complete_len; complete_len = complete_len + incomplete_len; - } first_keyframe_flag = false; srs_trace("gb28181: client_id %s, unkonw ps data (%#x/%u) %02x %02x %02x %02x\n", channel_id.c_str(), ssrc, timestamp, - next_ps_pack[0], next_ps_pack[1], next_ps_pack[2], next_ps_pack[3]); + next_ps_pack[0]&0xFF, next_ps_pack[1]&0xFF, next_ps_pack[2]&0xFF, next_ps_pack[3]&0xFF); break; } } @@ -608,9 +1190,10 @@ srs_error_t SrsPsStreamDemixer::on_ps_stream(char* ps_data, int ps_size, uint32_ if (complete_len != ps_size){ srs_trace("gb28181: client_id %s decode ps packet error (%#x/%u)! ps_size=%d complete=%d \n", channel_id.c_str(), ssrc, timestamp, ps_size, complete_len); - }else if (hander && video_stream.length() && can_send_ps_av_packet()) { + }else if (hander && video_stream.length() && can_send_ps_av_packet() && video_es_type == STREAM_TYPE_VIDEO_H264) { if ((err = hander->on_rtp_video(&video_stream, video_pts)) != srs_success) { - return srs_error_wrap(err, "process ps video packet"); + video_es_type = 0; + return srs_error_wrap(err, "process ps video packet"); } } @@ -621,9 +1204,10 @@ static std::string get_host_candidate_ips(SrsConfDirective* c) { string candidate = _srs_config->get_stream_caster_gb28181_host(c); if (candidate == "*" || candidate == "0.0.0.0") { - std::vector ips = srs_get_local_ips(); + std::vector& ips = srs_get_local_ips(); int index = _srs_config->get_stats_network(); - return ips.at(index); + SrsIPAddress* ip = ips.at(index); + return ip->ip; } else { return candidate; } @@ -636,6 +1220,7 @@ SrsGb28181Config::SrsGb28181Config(SrsConfDirective* c) host = get_host_candidate_ips(c); output = _srs_config->get_stream_caster_output(c); rtp_mux_port = _srs_config->get_stream_caster_listen(c); + rtp_mux_tcp_enable = _srs_config->get_stream_caster_tcp_enable(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); @@ -643,6 +1228,7 @@ SrsGb28181Config::SrsGb28181Config(SrsConfDirective* c) wait_keyframe = _srs_config->get_stream_caster_gb28181_wait_keyframe(c); audio_enable = _srs_config->get_stream_caster_gb28181_audio_enable(c); auto_create_channel = _srs_config->get_stream_caster_gb28181_auto_create_channel(c); + jitterbuffer_enable = _srs_config->get_stream_caster_gb28181_jitterbuffer_enable(c); //sip config sip_enable = _srs_config->get_stream_caster_gb28181_sip_enable(c); @@ -670,6 +1256,8 @@ SrsGb28181RtmpMuxer::SrsGb28181RtmpMuxer(SrsGb28181Manger* c, std::string id, bo pprint = SrsPithyPrint::create_caster(); trd = new SrsSTCoroutine("gb28181rtmpmuxer", this); + //change stack size to 256K, fix crash when call FFMpeg + ((SrsSTCoroutine*)trd)->set_stack_size(1 << 18); sdk = NULL; vjitter = new SrsRtspJitter(); @@ -691,13 +1279,32 @@ SrsGb28181RtmpMuxer::SrsGb28181RtmpMuxer(SrsGb28181Manger* c, std::string id, bo h264_pps = ""; aac_specific_config = ""; + req = NULL; + server = NULL; + source = NULL; + source_publish = true; + + jitter_buffer = new SrsPsJitterBuffer(id); + jitter_buffer_audio = new SrsPsJitterBuffer(id); + + ps_buflen = 0; + ps_buffer = NULL; + + ps_buflen_auido = 0; + ps_buffer_audio = NULL; } SrsGb28181RtmpMuxer::~SrsGb28181RtmpMuxer() { + close(); - destroy(); + srs_cond_destroy(wait_ps_queue); + + srs_freep(jitter_buffer); + srs_freep(jitter_buffer_audio); + srs_freepa(ps_buffer); + srs_freepa(ps_buffer_audio); srs_freep(channel); srs_freep(ps_demixer); @@ -706,6 +1313,8 @@ SrsGb28181RtmpMuxer::~SrsGb28181RtmpMuxer() srs_freep(vjitter); srs_freep(ajitter); srs_freep(pprint); + + destroy(); } srs_error_t SrsGb28181RtmpMuxer::serve() @@ -724,6 +1333,16 @@ std::string SrsGb28181RtmpMuxer::remote_ip() return ""; } +const SrsContextId& SrsGb28181RtmpMuxer::get_id() +{ + return _srs_context->get_id(); +} + +std::string SrsGb28181RtmpMuxer::desc() +{ + return "GBConn"; +} + std::string SrsGb28181RtmpMuxer::get_channel_id() { return channel_id; @@ -790,12 +1409,56 @@ void SrsGb28181RtmpMuxer::destroy() } } +srs_error_t SrsGb28181RtmpMuxer::initialize(SrsServer *s, SrsRequest* r) +{ + srs_error_t err = srs_success; + + if (!jitter_buffer) { + jitter_buffer = new SrsPsJitterBuffer(channel_id); + } + + jitter_buffer->SetDecodeErrorMode(kSelectiveErrors); + jitter_buffer->SetNackMode(kNack, -1, -1); + jitter_buffer->SetNackSettings(250, 450, 0); + + if (!jitter_buffer_audio) { + jitter_buffer_audio = new SrsPsJitterBuffer(channel_id); + } + + jitter_buffer_audio->SetDecodeErrorMode(kSelectiveErrors); + jitter_buffer_audio->SetNackMode(kNack, -1, -1); + jitter_buffer_audio->SetNackSettings(250, 450, 0); + + if (!source_publish) return err; + + req = r; + server = s; + + if ((err = _srs_sources->fetch_or_create(req, (ISrsSourceHandler*)server, &source)) != srs_success) { + return srs_error_wrap(err, "create source"); + } + + //TODO: ??? + // if (!source->can_publish(false)) { + // return srs_error_new(ERROR_GB28181_SESSION_IS_EXIST, "stream %s busy", req->get_stream_url().c_str()); + // } + + if ((err = source->on_publish()) != srs_success) { + return srs_error_wrap(err, "on publish"); + } + + return err; +} + + srs_error_t SrsGb28181RtmpMuxer::do_cycle() { srs_error_t err = srs_success; recv_rtp_stream_time = srs_get_system_time(); send_rtmp_stream_time = srs_get_system_time(); - + uint32_t cur_timestamp = 0; + int buffer_size = 0; + //consume ps stream, and check status while (true) { @@ -805,19 +1468,45 @@ srs_error_t SrsGb28181RtmpMuxer::do_cycle() 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); - }; + SrsGb28181Config config = gb28181_manger->get_gb28181_config(); + + if (config.jitterbuffer_enable){ + if(jitter_buffer->FoundFrame(cur_timestamp)){ + jitter_buffer->GetPsFrame(&ps_buffer, ps_buflen, buffer_size, cur_timestamp); + + if (buffer_size > 0){ + if ((err = ps_demixer->on_ps_stream(ps_buffer, buffer_size, cur_timestamp, 0)) != srs_success){ + srs_warn("gb28181: demix ps stream error:%s", srs_error_desc(err).c_str()); + srs_freep(err); + }; + } + } + + if(jitter_buffer_audio->FoundFrame(cur_timestamp)){ + jitter_buffer_audio->GetPsFrame(&ps_buffer_audio, ps_buflen_auido, buffer_size, cur_timestamp); + + if (buffer_size > 0){ + if ((err = ps_demixer->on_ps_stream(ps_buffer_audio, buffer_size, cur_timestamp, 0)) != srs_success){ + srs_warn("gb28181: demix ps stream error:%s", srs_error_desc(err).c_str()); + srs_freep(err); + }; + } + } + }else { + //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); } - ps_queue.pop(); - //must be free pkt - srs_freep(pkt); } if (pprint->can_print()) { @@ -841,7 +1530,7 @@ srs_error_t SrsGb28181RtmpMuxer::do_cycle() channel->set_rtp_peer_ip(""); } - 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; @@ -861,12 +1550,8 @@ srs_error_t SrsGb28181RtmpMuxer::do_cycle() channel_id.c_str()); rtmp_close(); } - - if (ps_queue.empty()){ - srs_cond_timedwait(wait_ps_queue, 200 * SRS_UTIME_MILLISECONDS); - }else { - srs_cond_timedwait(wait_ps_queue, 10 * SRS_UTIME_MILLISECONDS); - } + + srs_usleep(30 * SRS_UTIME_MILLISECONDS); } return err; @@ -882,6 +1567,48 @@ void SrsGb28181RtmpMuxer::stop() close(); } + +void SrsGb28181RtmpMuxer::insert_jitterbuffer(SrsPsRtpPacket *pkt) +{ + if (!pkt){ + return; + } + + recv_rtp_stream_time = srs_get_system_time(); + + char *payload = pkt->payload->bytes(); + if (!payload) { + return; + } + + uint8_t p1 = (uint8_t)(payload[0]); + uint8_t p2 = (uint8_t)(payload[1]); + uint8_t p3 = (uint8_t)(payload[2]); + uint8_t p4 = (uint8_t)(payload[3]); + + + //check for rtp ps audio streaming + bool av_same_ts = true; + + if (p1 == 0x00 && p2 == 0x00 && p3 == 0x01 && p4 == 0xC0 && + ps_rtp_video_ts != pkt->timestamp) { + av_same_ts = false; + } + + //if audio and video are the same clock, + //if both audio and video use jitter_buffer, + //otherwise audio uses jitter_buffer_audio, and video uses jitter_buffer + if (av_same_ts){ + pkt->marker = false; + jitter_buffer->InsertPacket(*pkt, pkt->payload->bytes(), pkt->payload->length(), NULL); + ps_rtp_video_ts = pkt->timestamp; + }else { + jitter_buffer_audio->InsertPacket(*pkt, pkt->payload->bytes(), pkt->payload->length(), NULL); + } + + //srs_cond_signal(wait_ps_queue); +} + void SrsGb28181RtmpMuxer::ps_packet_enqueue(SrsPsRtpPacket *pkt) { srs_assert(pkt); @@ -893,7 +1620,7 @@ void SrsGb28181RtmpMuxer::ps_packet_enqueue(SrsPsRtpPacket *pkt) uint32_t size = ps_queue.size(); if (size > 100){ srs_warn("gb28181: rtmpmuxer too much queue data, need to clear!!!"); - while(ps_queue.empty()) { + while(!ps_queue.empty()) { SrsPsRtpPacket* pkt = ps_queue.front(); ps_queue.pop(); srs_freep(pkt); @@ -901,7 +1628,7 @@ void SrsGb28181RtmpMuxer::ps_packet_enqueue(SrsPsRtpPacket *pkt) } ps_queue.push(pkt); - srs_cond_signal(wait_ps_queue); + //srs_cond_signal(wait_ps_queue); } srs_error_t SrsGb28181RtmpMuxer::cycle() @@ -928,14 +1655,16 @@ srs_error_t SrsGb28181RtmpMuxer::on_rtp_video(SrsSimpleStream *stream, int64_t f { 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 (!source_publish){ + // 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) { @@ -947,89 +1676,184 @@ srs_error_t SrsGb28181RtmpMuxer::on_rtp_video(SrsSimpleStream *stream, int64_t f uint32_t pts = (uint32_t)(fpts / 90); srs_info("gb28181rtmpmuxer: on_rtp_video dts=%u", dts); + if (true) { + char *data = stream->bytes(); + int length = stream->length(); - - 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 = 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 = 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"); - } + err = replace_startcode_with_nalulen(data, length, dts, pts); } - + return err; } -srs_error_t SrsGb28181RtmpMuxer::on_rtp_audio(SrsSimpleStream* stream, int64_t fdts) +srs_error_t SrsGb28181RtmpMuxer::write_h264_ipb_frame2(char *frame, int frame_size, uint32_t pts, uint32_t dts) +{ + srs_error_t err = srs_success; + + if (!frame){ + return srs_error_new(ERROR_GB28181_H264_FRAME_FULL, "h264 frame null"); + } + + if (frame_size <= 0){ + return srs_error_new(ERROR_GB28181_H264_FRAMESIZE, "h264 frame size"); + } + + 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) { + return err; + } + + // 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) { + return err; + } + h264_sps = sps; + + if ((err = write_h264_sps_pps(dts, pts)) != srs_success) { + return srs_error_wrap(err, "write sps/pps"); + } + return err; + } + + // 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) { + return err; + } + h264_pps = pps; + + if ((err = write_h264_sps_pps(dts, pts)) != srs_success) { + return srs_error_wrap(err, "write sps/pps"); + } + return err; + } + + 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::replace_startcode_with_nalulen(char *video_data, int &size, uint32_t pts, uint32_t dts) + { + srs_error_t err = srs_success; + + int index = 0; + std::list list_index; + + for(; index < size; index++){ + if (index > (size-4)) + break; + if (video_data[index] == 0x00 && video_data[index+1] == 0x00 && + video_data[index+2] == 0x00 && video_data[index+3] == 0x01){ + list_index.push_back(index); + } + } + + if (list_index.size() == 1){ + int cur_pos = list_index.front(); + list_index.pop_front(); + + //0001xxxxxxxxxx + //xxxx0001xxxxxxx + uint32_t naluLen = size - cur_pos; + char *p = (char*)&naluLen; + + video_data[cur_pos] = p[3]; + video_data[cur_pos+1] = p[2]; + video_data[cur_pos+2] = p[1]; + video_data[cur_pos+3] = p[0]; + + char *frame = video_data + cur_pos + 4; + int frame_size = naluLen; + + err = write_h264_ipb_frame2(frame, frame_size, dts, pts); + + }else if (list_index.size() > 1){ + int pre_pos = list_index.front(); + list_index.pop_front(); + int first_pos = pre_pos; + + while(list_index.size() > 0){ + int cur_pos = list_index.front(); + list_index.pop_front(); + + //pre=========cur====================== + //0001xxxxxxxx0001xxxxxxxx0001xxxxxxxxx + //xxxxxxxxxxxx0001xxxxxxxx0001xxxxxxxxx + uint32_t naluLen = cur_pos - pre_pos - 4; + char *p = (char*)&naluLen; + + video_data[pre_pos] = p[3]; + video_data[pre_pos+1] = p[2]; + video_data[pre_pos+2] = p[1]; + video_data[pre_pos+3] = p[0]; + + char *frame = video_data + pre_pos + 4; + int frame_size = naluLen; + + pre_pos = cur_pos; + err = write_h264_ipb_frame2(frame, frame_size, dts, pts); + } + + //========================pre========== + //0001xxxxxxxx0001xxxxxxxx0001xxxxxxxxx + if (first_pos != pre_pos){ + + uint32_t naluLen = size - pre_pos - 4; + char *p = (char*)&naluLen; + + video_data[pre_pos] = p[3]; + video_data[pre_pos+1] = p[2]; + video_data[pre_pos+2] = p[1]; + video_data[pre_pos+3] = p[0]; + + char *frame = video_data + pre_pos + 4; + int frame_size = naluLen; + + err = write_h264_ipb_frame2(frame, frame_size, dts, pts); + } + }else{ + //xxxxxxxxxxxxxxxxxxx + char *frame = video_data; + int frame_size = size; + err = write_h264_ipb_frame2(frame, frame_size, dts, pts); + } + + return err; +} + +srs_error_t SrsGb28181RtmpMuxer::on_rtp_audio(SrsSimpleStream* stream, int64_t fdts, int type) { 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 (!source_publish){ + // 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"); } @@ -1040,65 +1864,90 @@ srs_error_t SrsGb28181RtmpMuxer::on_rtp_audio(SrsSimpleStream* stream, int64_t f 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 (type == STREAM_TYPE_AUDIO_AAC) { + 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; - } + 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; - }; + 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"); + + 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"); - } + 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"); + } + } + }else if (type != 0) { + SrsRawAacStreamCodec codec; + codec.aac_packet_type = 0; + + if (type == STREAM_TYPE_AUDIO_G711){ + codec.sound_format = SrsAudioCodecIdReservedG711AlawLogarithmicPCM; + codec.sound_rate = SrsAudioSampleRate5512; + codec.sound_type = 0; //MONO = 0, STEREO = 1 + codec.sound_size = 0; //0=8K, 1=16K + }else if(type == STREAM_TYPE_AUDIO_G711ULAW){ + codec.sound_format = SrsAudioCodecIdReservedG711MuLawLogarithmicPCM; + codec.sound_rate = SrsAudioSampleRate5512; + codec.sound_type = 0; + codec.sound_size = 0; + }else { + return srs_error_wrap(err, "write audio raw frame, type=%d not suppered", type); + } + + char* frame = stream->bytes(); + int frame_size = stream->length(); + 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()) @@ -1109,6 +1958,10 @@ srs_error_t SrsGb28181RtmpMuxer::write_h264_sps_pps(uint32_t dts, uint32_t pts) { srs_error_t err = srs_success; + if (h264_sps == "" || h264_pps == ""){ + 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) { @@ -1130,11 +1983,10 @@ srs_error_t SrsGb28181RtmpMuxer::write_h264_sps_pps(uint32_t dts, uint32_t pts) return srs_error_wrap(err, "write packet"); } - return err; } -srs_error_t SrsGb28181RtmpMuxer::write_h264_ipb_frame(char* frame, int frame_size, uint32_t dts, uint32_t pts) +srs_error_t SrsGb28181RtmpMuxer::write_h264_ipb_frame(char* frame, int frame_size, uint32_t dts, uint32_t pts, bool writelen) { srs_error_t err = srs_success; @@ -1150,8 +2002,13 @@ srs_error_t SrsGb28181RtmpMuxer::write_h264_ipb_frame(char* frame, int frame_siz } std::string ibp; - if ((err = avc->mux_ipb_frame(frame, frame_size, ibp)) != srs_success) { - return srs_error_wrap(err, "mux ibp frame"); + + if (writelen){ + if ((err = avc->mux_ipb_frame(frame, frame_size, ibp)) != srs_success) { + return srs_error_wrap(err, "mux ibp frame"); + } + }else{ + ibp = string(frame, frame_size); } int8_t avc_packet_type = SrsVideoAvcFrameTraitNALU; @@ -1182,6 +2039,10 @@ srs_error_t SrsGb28181RtmpMuxer::write_audio_raw_frame(char* frame, int frame_si srs_error_t SrsGb28181RtmpMuxer::rtmp_write_packet(char type, uint32_t timestamp, char* data, int size) { srs_error_t err = srs_success; + + if (source_publish){ + return rtmp_write_packet_by_source(type, timestamp, data, size); + } if ((err = connect()) != srs_success) { return srs_error_wrap(err, "connect"); @@ -1201,6 +2062,41 @@ srs_error_t SrsGb28181RtmpMuxer::rtmp_write_packet(char type, uint32_t timestamp close(); return srs_error_wrap(err, "write message"); } + return err; +} + +srs_error_t SrsGb28181RtmpMuxer::rtmp_write_packet_by_source(char type, uint32_t timestamp, char* data, int size) +{ + srs_error_t err = srs_success; + + send_rtmp_stream_time = srs_get_system_time(); + + //create a source that will process stream without the need for internal rtmpclient + if (type == SrsFrameTypeAudio) { + SrsMessageHeader header; + header.message_type = RTMP_MSG_AudioMessage; + // TODO: FIXME: Maybe the tbn is not 90k. + header.timestamp = timestamp & 0x3fffffff; + + SrsCommonMessage* shared_video = new SrsCommonMessage(); + SrsAutoFree(SrsCommonMessage, shared_video); + + // TODO: FIXME: Check error. + shared_video->create(&header, data, size); + source->on_audio(shared_video); + }else if(type == SrsFrameTypeVideo) { + SrsMessageHeader header; + header.message_type = RTMP_MSG_VideoMessage; + // TODO: FIXME: Maybe the tbn is not 90k. + header.timestamp = timestamp & 0x3fffffff; + + SrsCommonMessage* shared_video = new SrsCommonMessage(); + SrsAutoFree(SrsCommonMessage, shared_video); + + // TODO: FIXME: Check error. + shared_video->create(&header, data, size); + source->on_video(shared_video); + } return err; } @@ -1247,6 +2143,12 @@ void SrsGb28181RtmpMuxer::close() h264_sps = ""; h264_pps = ""; aac_specific_config = ""; + + // BUGFIX: if don't unpublish, it will always be in the /api/v1/streams list + //if (source_publish && !source){ + if (source_publish && source){ + source->on_unpublish(); + } } void SrsGb28181RtmpMuxer::rtmp_close(){ @@ -1319,21 +2221,21 @@ void SrsGb28181StreamChannel::dumps(SrsJsonObject* obj) SrsGb28181Manger* _srs_gb28181 = NULL; //SrsGb28181Manger -SrsGb28181Manger::SrsGb28181Manger(SrsConfDirective* c) +SrsGb28181Manger::SrsGb28181Manger(SrsServer *s, SrsConfDirective* c) { // TODO: FIXME: support reload. + server = s; config = new SrsGb28181Config(c); - manager = new SrsCoroutineManager(); + manager = new SrsResourceManager("GB28181"); } SrsGb28181Manger::~SrsGb28181Manger() { used_ports.clear(); - + destroy(); + srs_freep(manager); srs_freep(config); - - destroy(); } srs_error_t SrsGb28181Manger::initialize() @@ -1402,7 +2304,7 @@ uint32_t SrsGb28181Manger::generate_ssrc(std::string id) return ssrc; } -srs_error_t SrsGb28181Manger::fetch_or_create_rtmpmuxer(std::string id, SrsGb28181RtmpMuxer** gb28181) +srs_error_t SrsGb28181Manger::fetch_or_create_rtmpmuxer(std::string id, SrsRequest *req, SrsGb28181RtmpMuxer** gb28181) { srs_error_t err = srs_success; @@ -1413,6 +2315,10 @@ srs_error_t SrsGb28181Manger::fetch_or_create_rtmpmuxer(std::string id, SrsGb28 } muxer = new SrsGb28181RtmpMuxer(this, id, config->audio_enable, config->wait_keyframe); + if ((err = muxer->initialize(server, req)) != srs_success) { + return srs_error_wrap(err, "gb28181: rtmp muxer initialize %s", id.c_str()); + } + if ((err = muxer->serve()) != srs_success) { return srs_error_wrap(err, "gb28181: rtmp muxer serve %s", id.c_str()); } @@ -1434,6 +2340,30 @@ SrsGb28181RtmpMuxer* SrsGb28181Manger::fetch_rtmpmuxer(std::string id) return muxer; } +void SrsGb28181Manger::update_rtmpmuxer_to_newssrc_by_id(std::string id, uint32_t ssrc) +{ + SrsGb28181RtmpMuxer* muxer = NULL; + + if (rtmpmuxers.find(id) == rtmpmuxers.end()) { + srs_warn("gb28181: at update_rtmpmuxer_to_newssrc_by_id() client_id not found. client_id=%s",id.c_str()); + return; + } + + muxer = rtmpmuxers[id]; + + SrsGb28181StreamChannel mc = muxer->get_channel(); + uint32_t old_ssrc = mc.get_ssrc(); + if (old_ssrc == ssrc) { + return; + } else { + srs_trace("gb28181: update ssrc of muxer %s from %x to %x", id.c_str(), old_ssrc, ssrc); + } + rtmpmuxer_unmap_by_ssrc(old_ssrc); + mc.set_ssrc(ssrc); + muxer->copy_channel(&mc); + rtmpmuxer_map_by_ssrc(muxer, ssrc); +} + SrsGb28181RtmpMuxer* SrsGb28181Manger::fetch_rtmpmuxer_by_ssrc(uint32_t ssrc) { SrsGb28181RtmpMuxer* muxer = NULL; @@ -1462,18 +2392,20 @@ void SrsGb28181Manger::rtmpmuxer_unmap_by_ssrc(uint32_t ssrc) 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); + if (!config->rtp_mux_tcp_enable) { + //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(); } - rtp_pool.clear(); //destory gb28181 muxer - std::map::iterator it2; - for (it2 = rtmpmuxers.begin(); it2 != rtmpmuxers.end(); ++it2) { - SrsGb28181RtmpMuxer* muxer = it2->second; + std::map::iterator it; + for (it = rtmpmuxers.begin(); it != rtmpmuxers.end(); ++it) { + SrsGb28181RtmpMuxer* muxer = it->second; SrsGb28181StreamChannel sess = muxer->get_channel(); rtmpmuxer_unmap_by_ssrc(sess.get_ssrc()); manager->remove(muxer); @@ -1507,21 +2439,25 @@ srs_error_t SrsGb28181Manger::start_ps_rtp_listen(std::string id, int 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"); - } + /* delete by xbpeng 20201222 should not check rtmpmuxers, becasue it always not find*/ + // map::iterator key = rtmpmuxers.find(id); + // if (key == rtmpmuxers.end()){ + // srs_warn("start rtp listen port rtmp muxer is null. id=%s,port=%d", id.c_str(),port); + // 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"); + if (!config->rtp_mux_tcp_enable) { + 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); } - - srs_trace("gb28181: start rtp ps stream over server-port=%d", port); } return err; @@ -1542,12 +2478,13 @@ void SrsGb28181Manger::stop_rtp_listen(std::string id) return; } - map::iterator it2 = rtp_pool.find(port); - if (it2 != rtp_pool.end()){ - srs_freep(it2->second); - rtp_pool.erase(it2); + if (!config->rtp_mux_tcp_enable) { + 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); } @@ -1568,13 +2505,6 @@ srs_error_t SrsGb28181Manger::create_stream_channel(SrsGb28181StreamChannel *cha return err; } - //create on rtmp muxer, gb28181 stream to rtmp - - if ((err = fetch_or_create_rtmpmuxer(id, &muxer)) != srs_success){ - srs_warn("gb28181: create rtmp muxer error, %s", srs_error_desc(err).c_str()); - return err; - } - //Start RTP listening port, receive gb28181 stream, //fixed is mux port, //random is random allocation port @@ -1610,7 +2540,6 @@ srs_error_t SrsGb28181Manger::create_stream_channel(SrsGb28181StreamChannel *cha //of the string value of the id ssrc = generate_ssrc(id); } - rtmpmuxer_map_by_ssrc(muxer, ssrc); //generate RTMP push stream address, //if the app and stream in the API are empty, @@ -1620,6 +2549,8 @@ srs_error_t SrsGb28181Manger::create_stream_channel(SrsGb28181StreamChannel *cha string app = channel->get_app(); string stream = channel->get_stream(); + SrsRequest request; + if (true) { string tcUrl, stream_name; @@ -1669,8 +2600,19 @@ srs_error_t SrsGb28181Manger::create_stream_channel(SrsGb28181StreamChannel *cha channel->set_ip(config->host); std::string play_url = srs_generate_rtmp_url(config->host, rtmp_port, "", "", app, stream_name, ""); channel->set_rtmp_url(play_url); + + request.app = app; + request.stream = stream_name; + //request.vhost = config->host; } + //create on rtmp muxer, gb28181 stream to rtmp + if ((err = fetch_or_create_rtmpmuxer(id, &request, &muxer)) != srs_success){ + srs_warn("gb28181: create rtmp muxer error, %s", srs_error_desc(err).c_str()); + return err; + } + + rtmpmuxer_map_by_ssrc(muxer, ssrc); muxer->set_rtmp_url(url); srs_trace("gb28181: create new stream channel id:%s rtmp url=%s", id.c_str(), url.c_str()); @@ -1679,17 +2621,19 @@ srs_error_t SrsGb28181Manger::create_stream_channel(SrsGb28181StreamChannel *cha return err; } -srs_error_t SrsGb28181Manger::delete_stream_channel(std::string id) +srs_error_t SrsGb28181Manger::delete_stream_channel(std::string id, std::string chid) { srs_error_t err = srs_success; //notify the device to stop streaming //if an internal sip service controlled channel - notify_sip_bye(id, id); + notify_sip_bye(id, chid); - SrsGb28181RtmpMuxer *muxer = fetch_rtmpmuxer(id); + string channel_id = id + "@" + chid; + + SrsGb28181RtmpMuxer *muxer = fetch_rtmpmuxer(channel_id); if (muxer){ - stop_rtp_listen(id); + stop_rtp_listen(channel_id); muxer->stop(); return err; }else { @@ -1741,6 +2685,12 @@ srs_error_t SrsGb28181Manger::notify_sip_invite(std::string id, std::string ip, //channel not exist SrsGb28181StreamChannel channel; channel.set_channel_id(key); + if (!this->config->sip_invite_port_fixed) { + channel.set_port_mode(RTP_PORT_MODE_RANDOM); + }else + { + channel.set_port_mode(RTP_PORT_MODE_FIXED); + } err = create_stream_channel(&channel); if (err != srs_success){ return err; @@ -1804,7 +2754,11 @@ srs_error_t SrsGb28181Manger::notify_sip_unregister(std::string id) return srs_error_new(ERROR_GB28181_SIP_NOT_RUN, "sip not run"); } sip_service->remove_session(id); - return delete_stream_channel(id); + return srs_success; + // useless, because + // sip session has been removed + // id is not channel id like id@chid + //return delete_stream_channel(id); } srs_error_t SrsGb28181Manger::notify_sip_query_catalog(std::string id) @@ -1815,6 +2769,12 @@ srs_error_t SrsGb28181Manger::notify_sip_query_catalog(std::string id) SrsSipRequest req; req.sip_auth_id = id; + SrsGb28181SipSession *sip_session = sip_service->fetch(req.sip_auth_id); + if (sip_session) { + sip_session->item_list.clear(); + sip_session->clear_device_list(); + srs_trace("notify_sip_query_catalog, clear sip session item and device list"); + } return sip_service->send_query_catalog(&req); } @@ -1825,4 +2785,232 @@ srs_error_t SrsGb28181Manger::query_sip_session(std::string id, SrsJsonArray* ar } return sip_service->query_sip_session(id, arr); -} \ No newline at end of file +} + +srs_error_t SrsGb28181Manger::query_device_list(std::string id, SrsJsonArray* arr) +{ + if (!sip_service){ + return srs_error_new(ERROR_GB28181_SIP_NOT_RUN, "sip not run"); + } + + return sip_service->query_device_list(id, arr); +} + +#define SRS_RTSP_BUFFER 262144 +SrsGb28181Conn::SrsGb28181Conn(SrsGb28181Caster* c, srs_netfd_t fd, SrsGb28181TcpPsRtpProcessor *rtp_processor) +{ + caster = c; + stfd = fd; + skt = new SrsStSocket(); + rtsp = new SrsRtspStack(skt); + trd = new SrsSTCoroutine("rtsp", this); + mbuffer = (char*)malloc(SRS_RTSP_BUFFER); + processor = rtp_processor; +} + +SrsGb28181Conn::~SrsGb28181Conn() +{ + free(mbuffer); + srs_close_stfd(stfd); + + srs_freep(trd); + srs_freep(skt); + srs_freep(rtsp); +} + +srs_error_t SrsGb28181Conn::serve() +{ + srs_error_t err = srs_success; + + if ((err = skt->initialize(stfd)) != srs_success) { + return srs_error_wrap(err, "socket initialize"); + } + + if ((err = trd->start()) != srs_success) { + return srs_error_wrap(err, "rtsp connection"); + } + return err; +} + +std::string SrsGb28181Conn::remote_ip() +{ + // TODO: FIXME: Implement it. + return ""; +} + +srs_error_t SrsGb28181Conn::do_cycle() +{ + srs_error_t err = srs_success; + + // retrieve ip of client. + int fd = srs_netfd_fileno(stfd); + std::string ip = srs_get_peer_ip(fd); + int port = srs_get_peer_port(fd); + + if (ip.empty() && !_srs_config->empty_ip_ok()) { + srs_warn("empty ip for fd=%d", srs_netfd_fileno(stfd)); + } + srs_trace("rtsp: serve %s:%d", ip.c_str(), port); + + char* leftData = (char*)malloc(SRS_RTSP_BUFFER);; + uint32_t leftDataLength = 0; + int16_t length = 0; + char* pp = (char*)&length; + char* p = &(mbuffer[0]); + ssize_t nb_read = 0; + int16_t length2; + + // consume all rtp data. + while (true) { + if ((err = trd->pull()) != srs_success) { + free(leftData); + return srs_error_wrap(err, "rtsp cycle"); + } + + //memset(buffer, 0, SRS_RTSP_BUFFER); + nb_read = 0; + if ((err = skt->read(mbuffer + leftDataLength, SRS_RTSP_BUFFER - leftDataLength, &nb_read)) != srs_success) { + free(leftData); + return srs_error_wrap(err, "recv data"); + } + + nb_read = nb_read + leftDataLength; + + length; + pp = (char*)&length; + p = &(mbuffer[0]); + pp[1] = *p++; + pp[0] = *p++; + + if (nb_read < (length + 2)) {//Not enough one packet. + leftDataLength = leftDataLength + nb_read; + continue; + } + + memset(leftData, 0, SRS_RTSP_BUFFER); + + while (length > 0) { + if ((length + 2) == nb_read) {//Only one packet. + nb_read = nb_read - 2; + processor->on_rtp(mbuffer + 2, nb_read, ip, port); + leftDataLength = 0; + break; + } + else { //multi packets. + pp = (char*)&length2; + p = &(mbuffer[length + 2]); + pp[1] = *p++; + pp[0] = *p++; + + processor->on_rtp(mbuffer + 2, length, ip, port); + + leftDataLength = nb_read - (length + 2); + nb_read = leftDataLength; + memcpy(leftData, mbuffer + length + 2, leftDataLength); + + pp = (char*)&length; + p = &(mbuffer[length + 2]); + pp[1] = *p++; + pp[0] = *p++; + + if (leftDataLength < (length + 2)) {//Not enough one packet. + memcpy(mbuffer, leftData, leftDataLength); + break; + } + else { + memcpy(mbuffer, leftData, leftDataLength); + } + } + } + } + + free(leftData); + + return err; +} + +srs_error_t SrsGb28181Conn::cycle() +{ + // serve the rtsp client. + srs_error_t err = do_cycle(); + + caster->remove(this); + + 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; +} + +std::string SrsGb28181Conn::desc() +{ + return "GB28181TcpConn"; +} + +const SrsContextId& SrsGb28181Conn::get_id() +{ + return trd->cid(); +} + +SrsGb28181Caster::SrsGb28181Caster(SrsConfDirective* c) +{ + // TODO: FIXME: support reload. + output = _srs_config->get_stream_caster_output(c); + config = new SrsGb28181Config(c); + rtp_processor = new SrsGb28181TcpPsRtpProcessor(config, ""); + manager = new SrsResourceManager("GB28181TCP", true); +} + +SrsGb28181Caster::~SrsGb28181Caster() +{ + std::vector::iterator it; + for (it = clients.begin(); it != clients.end(); ++it) { + SrsGb28181Conn* conn = *it; + manager->remove(conn); + } + clients.clear(); + + srs_freep(manager); +} + +srs_error_t SrsGb28181Caster::initialize() +{ + srs_error_t err = srs_success; + if ((err = manager->start()) != srs_success) { + return srs_error_wrap(err, "start manager"); + } + return err; +} + +srs_error_t SrsGb28181Caster::on_tcp_client(srs_netfd_t stfd) +{ + srs_error_t err = srs_success; + + SrsGb28181Conn* conn = new SrsGb28181Conn(this, stfd, rtp_processor); + + if ((err = conn->serve()) != srs_success) { + srs_freep(conn); + return srs_error_wrap(err, "serve conn"); + } + + clients.push_back(conn); + + return err; +} + +void SrsGb28181Caster::remove(SrsGb28181Conn* conn) +{ + std::vector::iterator it = find(clients.begin(), clients.end(), conn); + if (it != clients.end()) { + clients.erase(it); + } + srs_info("rtsp: remove connection from caster."); + + manager->remove(conn); +} + diff --git a/trunk/src/app/srs_app_gb28181.hpp b/trunk/src/app/srs_app_gb28181.hpp index 9f551a26a..0a934a834 100644 --- a/trunk/src/app/srs_app_gb28181.hpp +++ b/trunk/src/app/srs_app_gb28181.hpp @@ -33,7 +33,6 @@ #include #include -#include #include #include #include @@ -41,10 +40,43 @@ #include #include #include +#include +#include +#include +#include #define RTP_PORT_MODE_FIXED "fixed" #define RTP_PORT_MODE_RANDOM "random" +#define PS_AUDIO_ID 0xc0 +#define PS_AUDIO_ID_END 0xdf +#define PS_VIDEO_ID 0xe0 +#define PS_VIDEO_ID_END 0xef + +#define STREAM_TYPE_VIDEO_MPEG1 0x01 +#define STREAM_TYPE_VIDEO_MPEG2 0x02 +#define STREAM_TYPE_AUDIO_MPEG1 0x03 +#define STREAM_TYPE_AUDIO_MPEG2 0x04 +#define STREAM_TYPE_PRIVATE_SECTION 0x05 +#define STREAM_TYPE_PRIVATE_DATA 0x06 +#define STREAM_TYPE_AUDIO_AAC 0x0f +#define STREAM_TYPE_VIDEO_MPEG4 0x10 +#define STREAM_TYPE_VIDEO_H264 0x1b +#define STREAM_TYPE_VIDEO_HEVC 0x24 +#define STREAM_TYPE_VIDEO_CAVS 0x42 +#define STREAM_TYPE_VIDEO_SAVC 0x80 + +#define STREAM_TYPE_AUDIO_AC3 0x81 + +#define STREAM_TYPE_AUDIO_G711 0x90 +#define STREAM_TYPE_AUDIO_G711ULAW 0x91 +#define STREAM_TYPE_AUDIO_G722_1 0x92 +#define STREAM_TYPE_AUDIO_G723_1 0x93 +#define STREAM_TYPE_AUDIO_G726 0x96 +#define STREAM_TYPE_AUDIO_G729_1 0x99 +#define STREAM_TYPE_AUDIO_SVAC 0x9b +#define STREAM_TYPE_AUDIO_PCM 0x9c + class SrsConfDirective; class SrsRtpPacket; class SrsRtmpClient; @@ -63,16 +95,26 @@ class SrsSipRequest; class SrsGb28181RtmpMuxer; class SrsGb28181Config; class SrsGb28181PsRtpProcessor; +class SrsGb28181TcpPsRtpProcessor; class SrsGb28181SipService; class SrsGb28181StreamChannel; class SrsGb28181SipSession; +class SrsPsJitterBuffer; +class SrsServer; +class SrsSource; +class SrsRequest; +class SrsResourceManager; +class SrsGb28181Conn; +class SrsGb28181Caster; //ps rtp header packet parse + class SrsPsRtpPacket: public SrsRtpPacket { public: SrsPsRtpPacket(); virtual ~SrsPsRtpPacket(); + bool isFirstPacket; public: virtual srs_error_t decode(SrsBuffer* stream); }; @@ -119,7 +161,7 @@ private: SrsPithyPrint* pprint; SrsGb28181Config* config; std::map cache_ps_rtp_packet; - std::map pre_packet; + std::map pre_packet; std::string channel_id; bool auto_create_channel; public: @@ -129,9 +171,42 @@ private: bool can_send_ps_av_packet(); void dispose(); void clear_pre_packet(); + SrsGb28181RtmpMuxer* fetch_rtmpmuxer(std::string channel_id, uint32_t ssrc); + srs_error_t rtmpmuxer_enqueue_data(SrsGb28181RtmpMuxer *muxer, uint32_t ssrc, + int peer_port, std::string address_string, SrsPsRtpPacket *pkt); // Interface ISrsUdpHandler public: virtual srs_error_t on_udp_packet(const sockaddr* from, const int fromlen, char* buf, int nb_buf); +public: + virtual srs_error_t on_rtp_packet_jitter(const sockaddr* from, const int fromlen, char* buf, int nb_buf); + virtual srs_error_t on_rtp_packet(const sockaddr* from, const int fromlen, char* buf, int nb_buf); +}; + +class SrsGb28181TcpPsRtpProcessor +{ +private: + SrsPithyPrint* pprint; + SrsGb28181Config* config; + std::map cache_ps_rtp_packet; + std::map pre_packet; + std::string channel_id; + bool auto_create_channel; +public: + SrsGb28181TcpPsRtpProcessor(SrsGb28181Config* c, std::string sid); + virtual ~SrsGb28181TcpPsRtpProcessor(); +private: + bool can_send_ps_av_packet(); + void dispose(); + void clear_pre_packet(); + SrsGb28181RtmpMuxer* create_rtmpmuxer(std::string channel_id, uint32_t ssrc); + srs_error_t rtmpmuxer_enqueue_data(SrsGb28181RtmpMuxer *muxer, uint32_t ssrc, + int peer_port, std::string address_string, SrsPsRtpPacket *pkt); + // Interface ISrsTcpHandler +public: + virtual srs_error_t on_rtp(char* buf, int nb_buf, std::string ip, int port); +public: + virtual srs_error_t on_rtp_packet_jitter(char* buf, int nb_buf, std::string ip, int port); + virtual srs_error_t on_rtp_packet(char* buf, int nb_buf, std::string ip, int port); }; //ps stream processing parsing interface @@ -142,7 +217,7 @@ public: 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; + virtual srs_error_t on_rtp_audio(SrsSimpleStream* stream, int64_t dts, int type)=0; }; //analysis of PS stream and @@ -197,6 +272,14 @@ private: bool audio_enable; std::string channel_id; + uint8_t video_es_id; + uint8_t video_es_type; + uint8_t audio_es_id; + uint8_t audio_es_type; + int audio_check_aac_try_count; + + SrsRawAacStream *aac; + ISrsPsStreamHander *hander; public: SrsPsStreamDemixer(ISrsPsStreamHander *h, std::string sid, bool a, bool k); @@ -205,6 +288,7 @@ private: bool can_send_ps_av_packet(); public: int64_t parse_ps_timestamp(const uint8_t* p); + std::string get_ps_map_type_str(uint8_t); virtual srs_error_t on_ps_stream(char* ps_data, int ps_size, uint32_t timestamp, uint32_t ssrc); }; @@ -242,6 +326,24 @@ private: SrsRawAacStream* aac; std::string aac_specific_config; + SrsRequest* req; + SrsSource* source; + SrsServer* server; + + SrsPsJitterBuffer *jitter_buffer; + SrsPsJitterBuffer *jitter_buffer_audio; + + char *ps_buffer; + char *ps_buffer_audio; + + int ps_buflen; + int ps_buflen_auido; + + uint32_t ps_rtp_video_ts; + uint32_t ps_rtp_audio_ts; + + bool source_publish; + public: std::queue ps_queue; @@ -252,6 +354,7 @@ public: public: virtual srs_error_t serve(); virtual void stop(); + srs_error_t initialize(SrsServer* s, SrsRequest* r); virtual std::string get_channel_id(); virtual void ps_packet_enqueue(SrsPsRtpPacket *pkt); @@ -265,6 +368,8 @@ public: virtual SrsGb28181StreamChannel get_channel(); srs_utime_t get_recv_stream_time(); + void insert_jitterbuffer(SrsPsRtpPacket *pkt); + private: virtual srs_error_t do_cycle(); virtual void destroy(); @@ -272,15 +377,23 @@ private: // Interface ISrsOneCycleThreadHandler public: virtual srs_error_t cycle(); +// Interface ISrsConnection. +public: virtual std::string remote_ip(); + virtual const SrsContextId& get_id(); + virtual std::string desc(); 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); + virtual srs_error_t on_rtp_audio(SrsSimpleStream* stream, int64_t dts, int type); private: + + srs_error_t replace_startcode_with_nalulen(char *video_data, int &size, uint32_t pts, uint32_t dts); + srs_error_t write_h264_ipb_frame2(char *frame, int frame_size, uint32_t pts, uint32_t dts); 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_h264_ipb_frame(char* frame, int frame_size, uint32_t dts, uint32_t pts, bool b = true); 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); + virtual srs_error_t rtmp_write_packet_by_source(char type, uint32_t timestamp, char* data, int size); private: // Connect to RTMP server. virtual srs_error_t connect(); @@ -303,7 +416,9 @@ public: int rtp_port_min; int rtp_port_max; int rtp_mux_port; + bool rtp_mux_tcp_enable; bool auto_create_channel; + bool jitterbuffer_enable; //sip config int sip_port; @@ -393,16 +508,18 @@ private: std::map rtp_pool; std::map rtmpmuxers_ssrc; std::map rtmpmuxers; - SrsCoroutineManager* manager; + SrsResourceManager* manager; SrsGb28181SipService* sip_service; + SrsServer* server; public: - SrsGb28181Manger(SrsConfDirective* c); + SrsGb28181Manger(SrsServer* s, SrsConfDirective* c); virtual ~SrsGb28181Manger(); public: - srs_error_t fetch_or_create_rtmpmuxer(std::string id, SrsGb28181RtmpMuxer** gb28181); + srs_error_t fetch_or_create_rtmpmuxer(std::string id, SrsRequest *req, SrsGb28181RtmpMuxer** gb28181); SrsGb28181RtmpMuxer* fetch_rtmpmuxer(std::string id); SrsGb28181RtmpMuxer* fetch_rtmpmuxer_by_ssrc(uint32_t ssrc); + void update_rtmpmuxer_to_newssrc_by_id(std::string id, 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); @@ -410,11 +527,12 @@ public: void set_sip_service(SrsGb28181SipService *s) { sip_service = s; } SrsGb28181SipService* get_sip_service() { return sip_service; } + SrsGb28181Config* get_gb28181_config_ptr() { return config;} public: //stream channel api srs_error_t create_stream_channel(SrsGb28181StreamChannel *channel); - srs_error_t delete_stream_channel(std::string id); + srs_error_t delete_stream_channel(std::string id, std::string chid); srs_error_t query_stream_channel(std::string id, SrsJsonArray* arr); //sip api srs_error_t notify_sip_invite(std::string id, std::string ip, int port, uint32_t ssrc, std::string chid); @@ -424,6 +542,7 @@ public: srs_error_t notify_sip_query_catalog(std::string id); srs_error_t notify_sip_ptz(std::string id, std::string chid, std::string cmd, uint8_t speed, int priority); srs_error_t query_sip_session(std::string id, SrsJsonArray* arr); + srs_error_t query_device_list(std::string id, SrsJsonArray* arr); private: void destroy(); @@ -445,5 +564,54 @@ public: void remove_sip_session(SrsGb28181SipSession* sess); }; +// The gb28181 tcp connection serve the fd. +class SrsGb28181Conn : public ISrsCoroutineHandler, public ISrsConnection +{ +private: + char* mbuffer; + srs_netfd_t stfd; + SrsStSocket* skt; + SrsRtspStack* rtsp; + SrsGb28181Caster* caster; + SrsCoroutine* trd; + SrsGb28181TcpPsRtpProcessor *processor; +public: + SrsGb28181Conn(SrsGb28181Caster* c, srs_netfd_t fd, SrsGb28181TcpPsRtpProcessor *rtp_processor); + virtual ~SrsGb28181Conn(); +public: + virtual srs_error_t serve(); + virtual std::string remote_ip(); +private: + virtual srs_error_t do_cycle(); + // Interface ISrsOneCycleThreadHandler +public: + virtual srs_error_t cycle(); + virtual std::string desc(); + virtual const SrsContextId& get_id(); +}; + +// The caster for gb28181. +class SrsGb28181Caster : public ISrsTcpHandler +{ +private: + std::string output; + SrsGb28181Config *config; + SrsGb28181TcpPsRtpProcessor *rtp_processor; +private: + std::vector clients; + SrsResourceManager* manager; +public: + SrsGb28181Caster(SrsConfDirective* c); + virtual ~SrsGb28181Caster(); +public: + virtual srs_error_t initialize(); + // Interface ISrsTcpHandler +public: + virtual srs_error_t on_tcp_client(srs_netfd_t stfd); + // internal methods. +public: + virtual void remove(SrsGb28181Conn* conn); +}; + #endif diff --git a/trunk/src/app/srs_app_gb28181_jitbuffer.cpp b/trunk/src/app/srs_app_gb28181_jitbuffer.cpp new file mode 100644 index 000000000..27a40b85e --- /dev/null +++ b/trunk/src/app/srs_app_gb28181_jitbuffer.cpp @@ -0,0 +1,1715 @@ +/** + * 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; + +// Use this rtt if no value has been reported. +static const int64_t kDefaultRtt = 200; + +// Request a keyframe if no continuous frame has been received for this +// number of milliseconds and NACKs are disabled. +static const int64_t kMaxDiscontinuousFramesTime = 1000; + +typedef std::pair FrameListPair; + +bool IsKeyFrame(FrameListPair pair) +{ + return pair.second->GetFrameType() == kVideoFrameKey; +} + +bool HasNonEmptyState(FrameListPair pair) +{ + return pair.second->GetState() != kStateEmpty; +} + +void FrameList::InsertFrame(SrsPsFrameBuffer* frame) +{ + insert(rbegin().base(), FrameListPair(frame->GetTimeStamp(), frame)); +} + +SrsPsFrameBuffer* FrameList::PopFrame(uint32_t timestamp) +{ + FrameList::iterator it = find(timestamp); + + if (it == end()) { + return NULL; + } + + SrsPsFrameBuffer* frame = it->second; + erase(it); + return frame; +} + +SrsPsFrameBuffer* FrameList::Front() const +{ + return begin()->second; +} + +SrsPsFrameBuffer* FrameList::FrontNext() const +{ + FrameList::const_iterator it = begin(); + it++; + + if (it != end()) + { + return it->second; + } + + return NULL; +} + + +SrsPsFrameBuffer* FrameList::Back() const +{ + return rbegin()->second; +} + +int FrameList::RecycleFramesUntilKeyFrame(FrameList::iterator* key_frame_it, + UnorderedFrameList* free_frames) +{ + int drop_count = 0; + FrameList::iterator it = begin(); + + while (!empty()) { + // Throw at least one frame. + it->second->Reset(); + free_frames->push_back(it->second); + erase(it++); + ++drop_count; + + if (it != end() && it->second->GetFrameType() == kVideoFrameKey) { + *key_frame_it = it; + return drop_count; + } + } + + *key_frame_it = end(); + return drop_count; +} + +void FrameList::CleanUpOldOrEmptyFrames(PsDecodingState* decoding_state, UnorderedFrameList* free_frames) +{ + while (!empty()) { + SrsPsFrameBuffer* oldest_frame = Front(); + bool remove_frame = false; + + if (oldest_frame->GetState() == kStateEmpty && size() > 1) { + // This frame is empty, try to update the last decoded state and drop it + // if successful. + remove_frame = decoding_state->UpdateEmptyFrame(oldest_frame); + } else { + remove_frame = decoding_state->IsOldFrame(oldest_frame); + } + + if (!remove_frame) { + break; + } + + free_frames->push_back(oldest_frame); + erase(begin()); + } +} + +void FrameList::Reset(UnorderedFrameList* free_frames) +{ + while (!empty()) { + begin()->second->Reset(); + free_frames->push_back(begin()->second); + erase(begin()); + } +} + + +VCMPacket::VCMPacket() + : + payloadType(0), + timestamp(0), + ntp_time_ms_(0), + seqNum(0), + dataPtr(NULL), + sizeBytes(0), + markerBit(false), + frameType(kEmptyFrame), + //codec(kVideoCodecUnknown), + isFirstPacket(false), + //completeNALU(kNaluUnset), + insertStartCode(false), + width(0), + height(0) + //codecSpecificHeader() +{ +} + + +VCMPacket::VCMPacket(const uint8_t* ptr, + size_t size, + uint16_t seq, + uint32_t ts, + bool mBit) : + payloadType(0), + timestamp(ts), + ntp_time_ms_(0), + seqNum(seq), + dataPtr(ptr), + sizeBytes(size), + markerBit(mBit), + + frameType(kVideoFrameDelta), + //codec(kVideoCodecUnknown), + isFirstPacket(false), + //completeNALU(kNaluComplete), + insertStartCode(false), + width(0), + height(0) + //codecSpecificHeader() +{} + +void VCMPacket::Reset() +{ + payloadType = 0; + timestamp = 0; + ntp_time_ms_ = 0; + seqNum = 0; + dataPtr = NULL; + sizeBytes = 0; + markerBit = false; + frameType = kEmptyFrame; + //codec = kVideoCodecUnknown; + isFirstPacket = false; + //completeNALU = kNaluUnset; + insertStartCode = false; + width = 0; + height = 0; + //memset(&codecSpecificHeader, 0, sizeof(RTPVideoHeader)); +} + + +SrsPsFrameBuffer::SrsPsFrameBuffer() +{ + empty_seq_num_low_ = 0; + empty_seq_num_high_ = 0; + first_packet_seq_num_ = 0; + last_packet_seq_num_ = 0; + complete_ = false; + decodable_ = false; + timeStamp_ = 0; + frame_type_ = kEmptyFrame; + decode_error_mode_ = kNoErrors; + _length = 0; + _size = 0; + _buffer = NULL; +} + +SrsPsFrameBuffer::~SrsPsFrameBuffer() +{ + srs_freepa(_buffer); +} + +void SrsPsFrameBuffer::Reset() +{ + //session_nack_ = false; + complete_ = false; + decodable_ = false; + frame_type_ = kVideoFrameDelta; + packets_.clear(); + empty_seq_num_low_ = -1; + empty_seq_num_high_ = -1; + first_packet_seq_num_ = -1; + last_packet_seq_num_ = -1; + _length = 0; +} + +size_t SrsPsFrameBuffer::Length() const +{ + return _length; +} + +PsFrameBufferEnum SrsPsFrameBuffer::InsertPacket(const VCMPacket& packet, const FrameData& frame_data) +{ + if (packets_.size() == kMaxPacketsInSession) { + srs_error("Max number of packets per frame has been reached."); + return kSizeError; + } + + if (packets_.size() == 0){ + timeStamp_ = packet.timestamp; + } + + uint32_t requiredSizeBytes = Length() + packet.sizeBytes; + + if (requiredSizeBytes >= _size) { + const uint8_t* prevBuffer = _buffer; + const uint32_t increments = requiredSizeBytes / + kBufferIncStepSizeBytes + + (requiredSizeBytes % + kBufferIncStepSizeBytes > 0); + const uint32_t newSize = _size + + increments * kBufferIncStepSizeBytes; + + if (newSize > kMaxJBFrameSizeBytes) { + srs_error("Failed to insert packet due to frame being too big."); + return kSizeError; + } + + VerifyAndAllocate(newSize); + UpdateDataPointers(prevBuffer, _buffer); + } + + // Find the position of this packet in the packet list in sequence number + // order and insert it. Loop over the list in reverse order. + ReversePacketIterator rit = packets_.rbegin(); + + for (; rit != packets_.rend(); ++rit) + if (LatestSequenceNumber(packet.seqNum, (*rit).seqNum) == packet.seqNum) { + break; + } + + // Check for duplicate packets. + if (rit != packets_.rend() && + (*rit).seqNum == packet.seqNum && (*rit).sizeBytes > 0) { + return kDuplicatePacket; + } + + if ((packets_.size() == 0)&& + (first_packet_seq_num_ == -1 || + IsNewerSequenceNumber(first_packet_seq_num_, packet.seqNum))) { + first_packet_seq_num_ = packet.seqNum; + } + + //TODO: not check marker, check a complete frame with timestamp + if (packet.markerBit && + (last_packet_seq_num_ == -1 || + IsNewerSequenceNumber(packet.seqNum, last_packet_seq_num_))) { + last_packet_seq_num_ = packet.seqNum; + } + + // The insert operation invalidates the iterator |rit|. + PacketIterator packet_list_it = packets_.insert(rit.base(), packet); + + //size_t returnLength = (*packet_list_it).sizeBytes; + size_t returnLength = InsertBuffer(_buffer, packet_list_it); + + // update length + _length = Length() + static_cast(returnLength); + UpdateCompleteSession(); + + if (decode_error_mode_ == kWithErrors) { + decodable_ = true; + } else if (decode_error_mode_ == kSelectiveErrors) { + UpdateDecodableSession(frame_data); + } + + if (complete()) { + state_ = kStateComplete; + return kCompleteSession; + } else if (decodable()) { + state_ = kStateDecodable; + return kDecodableSession; + } else if (!complete()) { + state_ = kStateIncomplete; + return kIncomplete; + } + + return kIncomplete; +} + +void SrsPsFrameBuffer::VerifyAndAllocate(const uint32_t minimumSize) +{ + if (minimumSize > _size) { + // create buffer of sufficient size + uint8_t* newBuffer = new uint8_t[minimumSize]; + + if (_buffer) { + // copy old data + memcpy(newBuffer, _buffer, _size); + delete [] _buffer; + } + + srs_info("SrsPsFrameBuffer::VerifyAndAllocate oldbuffer=%d newbuffer=%d, minimumSize=%d, size=%d", + _buffer, newBuffer, minimumSize, _size); + + _buffer = newBuffer; + _size = minimumSize; + } +} + +void SrsPsFrameBuffer::UpdateDataPointers(const uint8_t* old_base_ptr, + const uint8_t* new_base_ptr) +{ + for (PacketIterator it = packets_.begin(); it != packets_.end(); ++it) + if ((*it).dataPtr != NULL) { + //assert(old_base_ptr != NULL && new_base_ptr != NULL); + (*it).dataPtr = new_base_ptr + ((*it).dataPtr - old_base_ptr); + } +} + + +size_t SrsPsFrameBuffer::InsertBuffer(uint8_t* frame_buffer, + PacketIterator packet_it) +{ + VCMPacket& packet = *packet_it; + PacketIterator it; + + // Calculate the offset into the frame buffer for this packet. + size_t offset = 0; + + for (it = packets_.begin(); it != packet_it; ++it) { + offset += (*it).sizeBytes; + } + + // Set the data pointer to pointing to the start of this packet in the + // frame buffer. + const uint8_t* packet_buffer = packet.dataPtr; + packet.dataPtr = frame_buffer + offset; + + ShiftSubsequentPackets( + packet_it, + packet.sizeBytes); + + packet.sizeBytes = Insert(packet_buffer, + packet.sizeBytes, + const_cast(packet.dataPtr)); + return packet.sizeBytes; +} + +size_t SrsPsFrameBuffer::Insert(const uint8_t* buffer, + size_t length, + uint8_t* frame_buffer) +{ + memcpy(frame_buffer, buffer, length); + return length; +} + +void SrsPsFrameBuffer::ShiftSubsequentPackets(PacketIterator it, + int steps_to_shift) +{ + ++it; + + if (it == packets_.end()) { + return; + } + + uint8_t* first_packet_ptr = const_cast((*it).dataPtr); + int shift_length = 0; + + // Calculate the total move length and move the data pointers in advance. + for (; it != packets_.end(); ++it) { + shift_length += (*it).sizeBytes; + + if ((*it).dataPtr != NULL) { + (*it).dataPtr += steps_to_shift; + } + } + + memmove(first_packet_ptr + steps_to_shift, first_packet_ptr, shift_length); +} + +void SrsPsFrameBuffer::UpdateCompleteSession() +{ + if (HaveFirstPacket() && HaveLastPacket()) { + // Do we have all the packets in this session? + bool complete_session = true; + PacketIterator it = packets_.begin(); + PacketIterator prev_it = it; + ++it; + + for (; it != packets_.end(); ++it) { + if (!InSequence(it, prev_it)) { + complete_session = false; + break; + } + + prev_it = it; + } + + complete_ = complete_session; + } +} + +bool SrsPsFrameBuffer::HaveFirstPacket() const +{ + return !packets_.empty() && (first_packet_seq_num_ != -1); +} + +bool SrsPsFrameBuffer::HaveLastPacket() const +{ + return !packets_.empty() && (last_packet_seq_num_ != -1); +} + +bool SrsPsFrameBuffer::InSequence(const PacketIterator& packet_it, + const PacketIterator& prev_packet_it) +{ + // If the two iterators are pointing to the same packet they are considered + // to be in sequence. + return (packet_it == prev_packet_it || + (static_cast((*prev_packet_it).seqNum + 1) == + (*packet_it).seqNum)); +} + +void SrsPsFrameBuffer::UpdateDecodableSession(const FrameData& frame_data) +{ + // Irrelevant if session is already complete or decodable + if (complete_ || decodable_) { + return; + } + + // TODO(agalusza): Account for bursty loss. + // TODO(agalusza): Refine these values to better approximate optimal ones. + // Do not decode frames if the RTT is lower than this. + const int64_t kRttThreshold = 100; + // Do not decode frames if the number of packets is between these two + // thresholds. + const float kLowPacketPercentageThreshold = 0.2f; + const float kHighPacketPercentageThreshold = 0.8f; + + if (frame_data.rtt_ms < kRttThreshold + || !HaveFirstPacket() + || (NumPackets() <= kHighPacketPercentageThreshold + * frame_data.rolling_average_packets_per_frame + && NumPackets() > kLowPacketPercentageThreshold + * frame_data.rolling_average_packets_per_frame)) { + return; + } + + decodable_ = true; +} + +bool SrsPsFrameBuffer::complete() const +{ + return complete_; +} + +bool SrsPsFrameBuffer::decodable() const +{ + return decodable_; +} + +int SrsPsFrameBuffer::NumPackets() const +{ + return packets_.size(); +} + +uint32_t SrsPsFrameBuffer::GetTimeStamp() const +{ + return timeStamp_; +} + +FrameType SrsPsFrameBuffer::GetFrameType() const +{ + return frame_type_; +} + +PsFrameBufferStateEnum SrsPsFrameBuffer::GetState() const +{ + return state_; +} + +int32_t SrsPsFrameBuffer::GetHighSeqNum() const +{ + if (packets_.empty()) { + return empty_seq_num_high_; + } + + if (empty_seq_num_high_ == -1) { + return packets_.back().seqNum; + } + + return LatestSequenceNumber(packets_.back().seqNum, empty_seq_num_high_); + +} + +int32_t SrsPsFrameBuffer::GetLowSeqNum() const +{ + if (packets_.empty()) { + return empty_seq_num_low_; + } + + return packets_.front().seqNum; +} + +const uint8_t* SrsPsFrameBuffer::Buffer() const +{ + return _buffer; +} + + +void SrsPsFrameBuffer::InformOfEmptyPacket(uint16_t seq_num) +{ + // Empty packets may be FEC or filler packets. They are sequential and + // follow the data packets, therefore, we should only keep track of the high + // and low sequence numbers and may assume that the packets in between are + // empty packets belonging to the same frame (timestamp). + if (empty_seq_num_high_ == -1) { + empty_seq_num_high_ = seq_num; + } else { + empty_seq_num_high_ = LatestSequenceNumber(seq_num, empty_seq_num_high_); + } + + if (empty_seq_num_low_ == -1 || IsNewerSequenceNumber(empty_seq_num_low_, + seq_num)) { + empty_seq_num_low_ = seq_num; + } +} + + +size_t SrsPsFrameBuffer::DeletePacketData(PacketIterator start, PacketIterator end) +{ + size_t bytes_to_delete = 0; // The number of bytes to delete. + PacketIterator packet_after_end = end; + //++packet_after_end; + + // Get the number of bytes to delete. + // Clear the size of these packets. + for (PacketIterator it = start; it != packet_after_end; ++it) { + bytes_to_delete += (*it).sizeBytes; + (*it).sizeBytes = 0; + (*it).dataPtr = NULL; + } + + if (bytes_to_delete > 0) { + ShiftSubsequentPackets(end, -static_cast(bytes_to_delete)); + } + + return bytes_to_delete; +} + +size_t SrsPsFrameBuffer::MakeDecodable() +{ + size_t return_length = 0; + + if (packets_.empty()) { + return 0; + } + + PacketIterator begin = packets_.begin(); + PacketIterator end = packets_.end(); + return_length += DeletePacketData(begin, end); + + return return_length; +} + +void SrsPsFrameBuffer::PrepareForDecode(bool continuous) +{ + + size_t bytes_removed = MakeDecodable(); + _length -= bytes_removed; + + // Transfer frame information to EncodedFrame and create any codec + // specific information. + //_frameType = ConvertFrameType(_sessionInfo.FrameType()); + //_completeFrame = _sessionInfo.complete(); + //_missingFrame = !continuous; +} + + + + bool SrsPsFrameBuffer::DeletePacket(int &count) + { + return true; + } + + +///////////////////////////////////////////////////////////////////////////// + +PsDecodingState::PsDecodingState() + : sequence_num_(0), + time_stamp_(0), + //picture_id_(kNoPictureId), + //temporal_id_(kNoTemporalIdx), + //tl0_pic_id_(kNoTl0PicIdx), + full_sync_(true), + in_initial_state_(true), + m_firstPacket(false) {} + +PsDecodingState::~PsDecodingState() {} + +void PsDecodingState::Reset() +{ + // TODO(mikhal): Verify - not always would want to reset the sync + sequence_num_ = 0; + time_stamp_ = 0; + //picture_id_ = kNoPictureId; + //temporal_id_ = kNoTemporalIdx; + //tl0_pic_id_ = kNoTl0PicIdx; + full_sync_ = true; + in_initial_state_ = true; +} + +uint32_t PsDecodingState::time_stamp() const +{ + return time_stamp_; +} + +uint16_t PsDecodingState::sequence_num() const +{ + return sequence_num_; +} + +bool PsDecodingState::IsOldFrame(const SrsPsFrameBuffer* frame) const +{ + //assert(frame != NULL); + if (frame == NULL) { + return false; + } + + if (in_initial_state_) { + return false; + } + + return !IsNewerTimestamp(frame->GetTimeStamp(), time_stamp_); +} + +bool PsDecodingState::IsOldPacket(const VCMPacket* packet) +{ + //assert(packet != NULL); + if (packet == NULL) { + return false; + } + + if (in_initial_state_) { + return false; + } + + if (!m_firstPacket) { + m_firstPacket = true; + time_stamp_ = packet->timestamp - 1; + return false; + } + + return !IsNewerTimestamp(packet->timestamp, time_stamp_); +} + +void PsDecodingState::SetState(const SrsPsFrameBuffer* frame) +{ + //assert(frame != NULL && frame->GetHighSeqNum() >= 0); + UpdateSyncState(frame); + sequence_num_ = static_cast(frame->GetHighSeqNum()); + time_stamp_ = frame->GetTimeStamp(); + in_initial_state_ = false; +} + +void PsDecodingState::CopyFrom(const PsDecodingState& state) +{ + sequence_num_ = state.sequence_num_; + time_stamp_ = state.time_stamp_; + full_sync_ = state.full_sync_; + in_initial_state_ = state.in_initial_state_; +} + +bool PsDecodingState::UpdateEmptyFrame(const SrsPsFrameBuffer* frame) +{ + bool empty_packet = frame->GetHighSeqNum() == frame->GetLowSeqNum(); + + if (in_initial_state_ && empty_packet) { + // Drop empty packets as long as we are in the initial state. + return true; + } + + if ((empty_packet && ContinuousSeqNum(frame->GetHighSeqNum())) || + ContinuousFrame(frame)) { + // Continuous empty packets or continuous frames can be dropped if we + // advance the sequence number. + sequence_num_ = frame->GetHighSeqNum(); + time_stamp_ = frame->GetTimeStamp(); + return true; + } + + return false; +} + +void PsDecodingState::UpdateOldPacket(const VCMPacket* packet) +{ + //assert(packet != NULL); + if (packet == NULL) { + return; + } + + if (packet->timestamp == time_stamp_) { + // Late packet belonging to the last decoded frame - make sure we update the + // last decoded sequence number. + sequence_num_ = LatestSequenceNumber(packet->seqNum, sequence_num_); + } +} + +void PsDecodingState::SetSeqNum(uint16_t new_seq_num) +{ + sequence_num_ = new_seq_num; +} + +bool PsDecodingState::in_initial_state() const +{ + return in_initial_state_; +} + +bool PsDecodingState::full_sync() const +{ + return full_sync_; +} + +void PsDecodingState::UpdateSyncState(const SrsPsFrameBuffer* frame) +{ + if (in_initial_state_) { + return; + } +} + +bool PsDecodingState::ContinuousFrame(const SrsPsFrameBuffer* frame) const +{ + // Check continuity based on the following hierarchy: + // - Temporal layers (stop here if out of sync). + // - Picture Id when available. + // - Sequence numbers. + // Return true when in initial state. + // Note that when a method is not applicable it will return false. + //assert(frame != NULL); + if (frame == NULL) { + return false; + } + + // A key frame is always considered continuous as it doesn't refer to any + // frames and therefore won't introduce any errors even if prior frames are + // missing. + if (frame->GetFrameType() == kVideoFrameKey) { + return true; + } + + // When in the initial state we always require a key frame to start decoding. + if (in_initial_state_) { + return false; + } + + return ContinuousSeqNum(static_cast(frame->GetLowSeqNum())); +} + +bool PsDecodingState::ContinuousSeqNum(uint16_t seq_num) const +{ + return seq_num == static_cast(sequence_num_ + 1); +} + +SrsPsJitterBuffer::SrsPsJitterBuffer(std::string key): + running_(false), + max_number_of_frames_(kStartNumberOfFrames), + free_frames_(), + decodable_frames_(), + incomplete_frames_(), + last_decoded_state_(), + first_packet_since_reset_(true), + incoming_frame_rate_(0), + incoming_frame_count_(0), + time_last_incoming_frame_count_(0), + incoming_bit_count_(0), + incoming_bit_rate_(0), + num_consecutive_old_packets_(0), + num_packets_(0), + num_packets_free_(0), + num_duplicated_packets_(0), + num_discarded_packets_(0), + time_first_packet_ms_(0), + //jitter_estimate_(clock), + //inter_frame_delay_(clock_->TimeInMilliseconds()), + rtt_ms_(kDefaultRtt), + nack_mode_(kNoNack), + low_rtt_nack_threshold_ms_(-1), + high_rtt_nack_threshold_ms_(-1), + missing_sequence_numbers_(SequenceNumberLessThan()), + nack_seq_nums_(), + max_nack_list_size_(0), + max_packet_age_to_nack_(0), + max_incomplete_time_ms_(0), + decode_error_mode_(kNoErrors), + average_packets_per_frame_(0.0f), + frame_counter_(0), + key_(key) +{ + for (int i = 0; i < kStartNumberOfFrames; i++) { + free_frames_.push_back(new SrsPsFrameBuffer()); + } + + wait_cond_t = srs_cond_new(); +} + +SrsPsJitterBuffer::~SrsPsJitterBuffer() +{ + for (UnorderedFrameList::iterator it = free_frames_.begin(); + it != free_frames_.end(); ++it) { + delete *it; + } + + for (FrameList::iterator it = incomplete_frames_.begin(); + it != incomplete_frames_.end(); ++it) { + delete it->second; + } + + for (FrameList::iterator it = decodable_frames_.begin(); + it != decodable_frames_.end(); ++it) { + delete it->second; + } + + srs_cond_destroy(wait_cond_t); +} + +void SrsPsJitterBuffer::SetDecodeErrorMode(PsDecodeErrorMode error_mode) +{ + decode_error_mode_ = error_mode; +} + +void SrsPsJitterBuffer::Flush() +{ + //CriticalSectionScoped cs(crit_sect_); + decodable_frames_.Reset(&free_frames_); + incomplete_frames_.Reset(&free_frames_); + last_decoded_state_.Reset(); // TODO(mikhal): sync reset. + //frame_event_->Reset(); + num_consecutive_old_packets_ = 0; + // Also reset the jitter and delay estimates + //jitter_estimate_.Reset(); + //inter_frame_delay_.Reset(clock_->TimeInMilliseconds()); + //waiting_for_completion_.frame_size = 0; + //waiting_for_completion_.timestamp = 0; + //waiting_for_completion_.latest_packet_time = -1; + first_packet_since_reset_ = true; + missing_sequence_numbers_.clear(); +} + + + +PsFrameBufferEnum SrsPsJitterBuffer::InsertPacket(const SrsPsRtpPacket &pkt, char *buf, int size, + bool* retransmitted) +{ + + const VCMPacket packet((const uint8_t*)buf, size, + pkt.sequence_number, pkt.timestamp, pkt.marker); + + ++num_packets_; + + if (num_packets_ == 1) { + time_first_packet_ms_ = srs_update_system_time(); + } + + //Does this packet belong to an old frame? + // if (last_decoded_state_.IsOldPacket(&packet)) { + + // //return kOldPacket; + // } + + //num_consecutive_old_packets_ = 0; + + SrsPsFrameBuffer* frame; + FrameList* frame_list; + + const PsFrameBufferEnum error = GetFrame(packet, &frame, &frame_list); + + if (error != kNoError) { + return error; + } + + + srs_utime_t now_ms = srs_update_system_time(); + + FrameData frame_data; + frame_data.rtt_ms = 0; //rtt_ms_; + frame_data.rolling_average_packets_per_frame = 25;//average_packets_per_frame_; + + PsFrameBufferEnum buffer_state = frame->InsertPacket(packet, frame_data); + + if (buffer_state > 0) { + incoming_bit_count_ += packet.sizeBytes << 3; + + if (first_packet_since_reset_) { + latest_received_sequence_number_ = packet.seqNum; + first_packet_since_reset_ = false; + } else { + // if (IsPacketRetransmitted(packet)) { + // frame->IncrementNackCount(); + // } + + UpdateNackList(packet.seqNum); + + latest_received_sequence_number_ = LatestSequenceNumber( + latest_received_sequence_number_, packet.seqNum); + } + } + + // Is the frame already in the decodable list? + bool continuous = IsContinuous(*frame); + + switch (buffer_state) { + case kGeneralError: + case kTimeStampError: + case kSizeError: { + free_frames_.push_back(frame); + break; + } + + case kCompleteSession: { + //CountFrame(*frame); + // if (previous_state != kStateDecodable && + // previous_state != kStateComplete) { + // /*CountFrame(*frame);*/ //????????????????????�?? by ylr + // if (continuous) { + // // Signal that we have a complete session. + // frame_event_->Set(); + // } + // } + } + + // Note: There is no break here - continuing to kDecodableSession. + case kDecodableSession: { + // *retransmitted = (frame->GetNackCount() > 0); + + if (true || continuous) { + decodable_frames_.InsertFrame(frame); + FindAndInsertContinuousFrames(*frame); + } else { + incomplete_frames_.InsertFrame(frame); + + // If NACKs are enabled, keyframes are triggered by |GetNackList|. + // if (nack_mode_ == kNoNack && NonContinuousOrIncompleteDuration() > + // 90 * kMaxDiscontinuousFramesTime) { + // return kFlushIndicator; + // } + } + + break; + } + + case kIncomplete: { + if (frame->GetState() == kStateEmpty && + last_decoded_state_.UpdateEmptyFrame(frame)) { + free_frames_.push_back(frame); + return kNoError; + } else { + incomplete_frames_.InsertFrame(frame); + + // If NACKs are enabled, keyframes are triggered by |GetNackList|. + // if (nack_mode_ == kNoNack && NonContinuousOrIncompleteDuration() > + // 90 * kMaxDiscontinuousFramesTime) { + // return kFlushIndicator; + // } + } + + break; + } + + case kNoError: + case kOutOfBoundsPacket: + case kDuplicatePacket: { + // Put back the frame where it came from. + if (frame_list != NULL) { + frame_list->InsertFrame(frame); + } else { + free_frames_.push_back(frame); + } + + ++num_duplicated_packets_; + break; + } + + case kFlushIndicator:{ + free_frames_.push_back(frame); + } + return kFlushIndicator; + + default: + assert(false); + } + + return buffer_state; +} + +// Gets frame to use for this timestamp. If no match, get empty frame. +PsFrameBufferEnum SrsPsJitterBuffer::GetFrame(const VCMPacket& packet, + SrsPsFrameBuffer** frame, + FrameList** frame_list) +{ + *frame = incomplete_frames_.PopFrame(packet.timestamp); + + if (*frame != NULL) { + *frame_list = &incomplete_frames_; + return kNoError; + } + + *frame = decodable_frames_.PopFrame(packet.timestamp); + + if (*frame != NULL) { + *frame_list = &decodable_frames_; + return kNoError; + } + + *frame_list = NULL; + // No match, return empty frame. + *frame = GetEmptyFrame(); + + if (*frame == NULL) { + // No free frame! Try to reclaim some... + bool found_key_frame = RecycleFramesUntilKeyFrame(); + *frame = GetEmptyFrame(); + assert(*frame); + + if (!found_key_frame) { + free_frames_.push_back(*frame); + return kFlushIndicator; + } + } + + (*frame)->Reset(); + return kNoError; +} + +SrsPsFrameBuffer* SrsPsJitterBuffer::GetEmptyFrame() +{ + if (free_frames_.empty()) { + if (!TryToIncreaseJitterBufferSize()) { + return NULL; + } + } + + SrsPsFrameBuffer* frame = free_frames_.front(); + free_frames_.pop_front(); + return frame; +} + +bool SrsPsJitterBuffer::TryToIncreaseJitterBufferSize() +{ + if (max_number_of_frames_ >= kMaxNumberOfFrames) { + return false; + } + + free_frames_.push_back(new SrsPsFrameBuffer()); + ++max_number_of_frames_; + return true; +} + +// Recycle oldest frames up to a key frame, used if jitter buffer is completely +// full. +bool SrsPsJitterBuffer::RecycleFramesUntilKeyFrame() +{ + // First release incomplete frames, and only release decodable frames if there + // are no incomplete ones. + FrameList::iterator key_frame_it; + bool key_frame_found = false; + int dropped_frames = 0; + dropped_frames += incomplete_frames_.RecycleFramesUntilKeyFrame( + &key_frame_it, &free_frames_); + key_frame_found = key_frame_it != incomplete_frames_.end(); + + if (dropped_frames == 0) { + dropped_frames += decodable_frames_.RecycleFramesUntilKeyFrame( + &key_frame_it, &free_frames_); + key_frame_found = key_frame_it != decodable_frames_.end(); + } + + if (key_frame_found) { + //LOG(LS_INFO) << "Found key frame while dropping frames."; + // Reset last decoded state to make sure the next frame decoded is a key + // frame, and start NACKing from here. + last_decoded_state_.Reset(); + DropPacketsFromNackList(EstimatedLowSequenceNumber(*key_frame_it->second)); + } else if (decodable_frames_.empty()) { + // All frames dropped. Reset the decoding state and clear missing sequence + // numbers as we're starting fresh. + last_decoded_state_.Reset(); + missing_sequence_numbers_.clear(); + } + + return key_frame_found; +} + +bool SrsPsJitterBuffer::IsContinuousInState(const SrsPsFrameBuffer& frame, + const PsDecodingState& decoding_state) const +{ + if (decode_error_mode_ == kWithErrors) { + return true; + } + + // Is this frame (complete or decodable) and continuous? + // kStateDecodable will never be set when decode_error_mode_ is false + // as SessionInfo determines this state based on the error mode (and frame + // completeness). + return (frame.GetState() == kStateComplete || + frame.GetState() == kStateDecodable) && + decoding_state.ContinuousFrame(&frame); +} + +bool SrsPsJitterBuffer::IsContinuous(const SrsPsFrameBuffer& frame) const +{ + if (IsContinuousInState(frame, last_decoded_state_)) { + return true; + } + + PsDecodingState decoding_state; + decoding_state.CopyFrom(last_decoded_state_); + + for (FrameList::const_iterator it = decodable_frames_.begin(); + it != decodable_frames_.end(); ++it) { + SrsPsFrameBuffer* decodable_frame = it->second; + + if (IsNewerTimestamp(decodable_frame->GetTimeStamp(), frame.GetTimeStamp())) { + break; + } + + decoding_state.SetState(decodable_frame); + + if (IsContinuousInState(frame, decoding_state)) { + return true; + } + } + + return false; +} + +void SrsPsJitterBuffer::FindAndInsertContinuousFrames(const SrsPsFrameBuffer& new_frame) +{ + PsDecodingState decoding_state; + decoding_state.CopyFrom(last_decoded_state_); + decoding_state.SetState(&new_frame); + + // When temporal layers are available, we search for a complete or decodable + // frame until we hit one of the following: + // 1. Continuous base or sync layer. + // 2. The end of the list was reached. + for (FrameList::iterator it = incomplete_frames_.begin(); + it != incomplete_frames_.end();) { + SrsPsFrameBuffer* frame = it->second; + + if (IsNewerTimestamp(new_frame.GetTimeStamp(), frame->GetTimeStamp())) { + ++it; + continue; + } + + if (IsContinuousInState(*frame, decoding_state)) { + decodable_frames_.InsertFrame(frame); + incomplete_frames_.erase(it++); + decoding_state.SetState(frame); + } else { + ++it; + } + } +} + +// Must be called under the critical section |crit_sect_|. +void SrsPsJitterBuffer::CleanUpOldOrEmptyFrames() +{ + decodable_frames_.CleanUpOldOrEmptyFrames(&last_decoded_state_, + &free_frames_); + incomplete_frames_.CleanUpOldOrEmptyFrames(&last_decoded_state_, + &free_frames_); + + if (!last_decoded_state_.in_initial_state()) { + //DropPacketsFromNackList(last_decoded_state_.sequence_num()); + } +} + +// Returns immediately or a |max_wait_time_ms| ms event hang waiting for a +// complete frame, |max_wait_time_ms| decided by caller. +bool SrsPsJitterBuffer::NextCompleteTimestamp(uint32_t max_wait_time_ms, uint32_t* timestamp) +{ + // crit_sect_->Enter(); + + // if (!running_) { + // crit_sect_->Leave(); + // return false; + // } + + CleanUpOldOrEmptyFrames(); + + if (decodable_frames_.empty() || + decodable_frames_.Front()->GetState() != kStateComplete) { + const int64_t end_wait_time_ms = srs_update_system_time() + + max_wait_time_ms * SRS_UTIME_MILLISECONDS; + int64_t wait_time_ms = max_wait_time_ms * SRS_UTIME_MILLISECONDS; + + while (wait_time_ms > 0) { + int ret = srs_cond_timedwait(wait_cond_t, wait_time_ms); + if (ret == 0) { + // Finding oldest frame ready for decoder. + CleanUpOldOrEmptyFrames(); + + if (decodable_frames_.empty() || + decodable_frames_.Front()->GetState() != kStateComplete) { + wait_time_ms = end_wait_time_ms - srs_update_system_time(); + } else { + break; + } + } else { + break; + } + } + + // Inside |crit_sect_|. + } else { + // We already have a frame, reset the event. + //frame_event_->Reset(); + } + + if (decodable_frames_.empty() || + decodable_frames_.Front()->GetState() != kStateComplete) { + //crit_sect_->Leave(); + return false; + } + + *timestamp = decodable_frames_.Front()->GetTimeStamp(); + //crit_sect_->Leave(); + return true; +} + +bool SrsPsJitterBuffer::NextMaybeIncompleteTimestamp(uint32_t* timestamp) +{ + if (decode_error_mode_ == kNoErrors) { + srs_warn("gb28181 SrsJitterBuffer::NextMaybeIncompleteTimestamp decode_error_mode_ %d", decode_error_mode_); + // No point to continue, as we are not decoding with errors. + return false; + } + + CleanUpOldOrEmptyFrames(); + + SrsPsFrameBuffer* oldest_frame; + + if (decodable_frames_.empty()) { + if (incomplete_frames_.size() <= 1) { + return false; + } + + oldest_frame = incomplete_frames_.Front(); + PsFrameBufferStateEnum oldest_frame_state = oldest_frame->GetState(); + + SrsPsFrameBuffer* next_frame; + next_frame = incomplete_frames_.FrontNext(); + + if (oldest_frame_state != kStateComplete && next_frame && + IsNewerSequenceNumber(next_frame->GetLowSeqNum(), oldest_frame->GetHighSeqNum()) && + next_frame->NumPackets() > 0 ) { + oldest_frame_state = kStateComplete; + } + + // Frame will only be removed from buffer if it is complete (or decodable). + if (oldest_frame_state < kStateComplete) { + int oldest_frame_hight_seq = oldest_frame->GetHighSeqNum(); + int next_frame_low_seq = next_frame->GetLowSeqNum(); + + srs_warn("gb28181 SrsPsJitterBuffer::NextMaybeIncompleteTimestamp key(%s) incomplete oldest_frame (%u,%d)->(%u,%d)", + key_.c_str(), oldest_frame->GetTimeStamp(), oldest_frame_hight_seq, + next_frame->GetTimeStamp(), next_frame_low_seq); + return false; + } + } else { + oldest_frame = decodable_frames_.Front(); + + // If we have exactly one frame in the buffer, release it only if it is + // complete. We know decodable_frames_ is not empty due to the previous + // check. + if (decodable_frames_.size() == 1 && incomplete_frames_.empty() + && oldest_frame->GetState() != kStateComplete) { + return false; + } + } + + *timestamp = oldest_frame->GetTimeStamp(); + return true; +} + +SrsPsFrameBuffer* SrsPsJitterBuffer::ExtractAndSetDecode(uint32_t timestamp) +{ + // Extract the frame with the desired timestamp. + SrsPsFrameBuffer* frame = decodable_frames_.PopFrame(timestamp); + bool continuous = true; + + if (!frame) { + frame = incomplete_frames_.PopFrame(timestamp); + + if (frame) { + continuous = last_decoded_state_.ContinuousFrame(frame); + } else { + return NULL; + } + } + + // The state must be changed to decoding before cleaning up zero sized + // frames to avoid empty frames being cleaned up and then given to the + // decoder. Propagates the missing_frame bit. + //frame->PrepareForDecode(continuous); + + // We have a frame - update the last decoded state and nack list. + last_decoded_state_.SetState(frame); + //DropPacketsFromNackList(last_decoded_state_.sequence_num()); + + // if ((*frame).IsSessionComplete()) { + // //UpdateAveragePacketsPerFrame(frame->NumPackets()); + // } + + return frame; +} + +// Release frame when done with decoding. Should never be used to release +// frames from within the jitter buffer. +void SrsPsJitterBuffer::ReleaseFrame(SrsPsFrameBuffer* frame) +{ + //CriticalSectionScoped cs(crit_sect_); + //VCMFrameBuffer* frame_buffer = static_cast(frame); + + if (frame) { + free_frames_.push_back(frame); + } +} + +bool SrsPsJitterBuffer::FoundFrame(uint32_t& time_stamp) +{ + + bool found_frame = NextCompleteTimestamp(0, &time_stamp); + + if (!found_frame) { + found_frame = NextMaybeIncompleteTimestamp(&time_stamp); + } + + return found_frame; +} + +bool SrsPsJitterBuffer::GetPsFrame(char **buffer, int &buf_len, int &size, const uint32_t time_stamp) +{ + SrsPsFrameBuffer* frame = ExtractAndSetDecode(time_stamp); + + if (frame == NULL) { + return false; + } + + size = frame->Length(); + if (size <= 0){ + return false; + } + + if (buffer == NULL){ + return false; + } + + //verify and allocate ps buffer + if (buf_len < size || *buffer == NULL) { + srs_freepa(*buffer); + + int resize = size + 10240; + *buffer = new char[resize]; + + srs_trace("gb28181: SrsPsJitterBuffer key=%s reallocate ps buffer size(%d>%d) resize(%d)", + key_.c_str(), size, buf_len, resize); + + buf_len = resize; + } + + const uint8_t *frame_buffer = frame->Buffer(); + memcpy(*buffer, frame_buffer, size); + + frame->PrepareForDecode(false); + ReleaseFrame(frame); + return true; +} + + +SrsPsFrameBuffer* SrsPsJitterBuffer::NextFrame() const +{ + if (!decodable_frames_.empty()) { + return decodable_frames_.Front(); + } + + if (!incomplete_frames_.empty()) { + return incomplete_frames_.Front(); + } + + return NULL; +} + +bool SrsPsJitterBuffer::UpdateNackList(uint16_t sequence_number) +{ + if (nack_mode_ == kNoNack) { + return true; + } + + // Make sure we don't add packets which are already too old to be decoded. + if (!last_decoded_state_.in_initial_state()) { + latest_received_sequence_number_ = LatestSequenceNumber( + latest_received_sequence_number_, + last_decoded_state_.sequence_num()); + } + + if (IsNewerSequenceNumber(sequence_number, + latest_received_sequence_number_)) { + // Push any missing sequence numbers to the NACK list. + for (uint16_t i = latest_received_sequence_number_ + 1; + IsNewerSequenceNumber(sequence_number, i); ++i) { + missing_sequence_numbers_.insert(missing_sequence_numbers_.end(), i); + } + + /* + if (TooLargeNackList() && !HandleTooLargeNackList()) { + srs_warn("gb28181: SrsPsJitterBuffer key(%s) requesting key frame due to too large NACK list.", key_.c_str()); + return false; + } + + if (MissingTooOldPacket(sequence_number) && + !HandleTooOldPackets(sequence_number)) { + srs_warn("gb28181: SrsPsJitterBuffer key(%s) requesting key frame due to missing too old packets", key_.c_str()); + return false; + } + */ + } else { + missing_sequence_numbers_.erase(sequence_number); + } + + return true; +} + +bool SrsPsJitterBuffer::TooLargeNackList() const +{ + return missing_sequence_numbers_.size() > max_nack_list_size_; +} + +bool SrsPsJitterBuffer::HandleTooLargeNackList() +{ + // Recycle frames until the NACK list is small enough. It is likely cheaper to + // request a key frame than to retransmit this many missing packets. + srs_warn("gb28181: SrsPsJitterBuffer NACK list has grown too large: %d > %d", + missing_sequence_numbers_.size(), max_nack_list_size_); + bool key_frame_found = false; + + while (TooLargeNackList()) { + key_frame_found = RecycleFramesUntilKeyFrame(); + } + + return key_frame_found; +} + +bool SrsPsJitterBuffer::MissingTooOldPacket(uint16_t latest_sequence_number) const +{ + if (missing_sequence_numbers_.empty()) { + return false; + } + + const uint16_t age_of_oldest_missing_packet = latest_sequence_number - + *missing_sequence_numbers_.begin(); + // Recycle frames if the NACK list contains too old sequence numbers as + // the packets may have already been dropped by the sender. + return age_of_oldest_missing_packet > max_packet_age_to_nack_; +} + +bool SrsPsJitterBuffer::HandleTooOldPackets(uint16_t latest_sequence_number) +{ + bool key_frame_found = false; + const uint16_t age_of_oldest_missing_packet = latest_sequence_number - + *missing_sequence_numbers_.begin(); + srs_warn("gb28181: SrsPsJitterBuffer NACK list contains too old sequence numbers: %d > %d", + age_of_oldest_missing_packet, + max_packet_age_to_nack_); + + while (MissingTooOldPacket(latest_sequence_number)) { + key_frame_found = RecycleFramesUntilKeyFrame(); + } + + return key_frame_found; +} + +void SrsPsJitterBuffer::DropPacketsFromNackList(uint16_t last_decoded_sequence_number) +{ + // Erase all sequence numbers from the NACK list which we won't need any + // longer. + missing_sequence_numbers_.erase(missing_sequence_numbers_.begin(), + missing_sequence_numbers_.upper_bound( + last_decoded_sequence_number)); +} + +void SrsPsJitterBuffer::SetNackMode(PsNackMode mode, + int64_t low_rtt_nack_threshold_ms, + int64_t high_rtt_nack_threshold_ms) +{ + nack_mode_ = mode; + + if (mode == kNoNack) { + missing_sequence_numbers_.clear(); + } + + assert(low_rtt_nack_threshold_ms >= -1 && high_rtt_nack_threshold_ms >= -1); + assert(high_rtt_nack_threshold_ms == -1 || + low_rtt_nack_threshold_ms <= high_rtt_nack_threshold_ms); + assert(low_rtt_nack_threshold_ms > -1 || high_rtt_nack_threshold_ms == -1); + low_rtt_nack_threshold_ms_ = low_rtt_nack_threshold_ms; + high_rtt_nack_threshold_ms_ = high_rtt_nack_threshold_ms; + + // Don't set a high start rtt if high_rtt_nack_threshold_ms_ is used, to not + // disable NACK in hybrid mode. + if (rtt_ms_ == kDefaultRtt && high_rtt_nack_threshold_ms_ != -1) { + rtt_ms_ = 0; + } + + // if (!WaitForRetransmissions()) { + // jitter_estimate_.ResetNackCount(); + // } +} + +void SrsPsJitterBuffer::SetNackSettings(size_t max_nack_list_size, + int max_packet_age_to_nack, + int max_incomplete_time_ms) +{ + assert(max_packet_age_to_nack >= 0); + assert(max_incomplete_time_ms_ >= 0); + max_nack_list_size_ = max_nack_list_size; + max_packet_age_to_nack_ = max_packet_age_to_nack; + max_incomplete_time_ms_ = max_incomplete_time_ms; + nack_seq_nums_.resize(max_nack_list_size_); +} + +PsNackMode SrsPsJitterBuffer::nack_mode() const +{ + return nack_mode_; +} + + +int SrsPsJitterBuffer::NonContinuousOrIncompleteDuration() +{ + if (incomplete_frames_.empty()) { + return 0; + } + + uint32_t start_timestamp = incomplete_frames_.Front()->GetTimeStamp(); + + if (!decodable_frames_.empty()) { + start_timestamp = decodable_frames_.Back()->GetTimeStamp(); + } + + return incomplete_frames_.Back()->GetTimeStamp() - start_timestamp; +} + +uint16_t SrsPsJitterBuffer::EstimatedLowSequenceNumber(const SrsPsFrameBuffer& frame) const +{ + assert(frame.GetLowSeqNum() >= 0); + + if (frame.HaveFirstPacket()) { + return frame.GetLowSeqNum(); + } + + // This estimate is not accurate if more than one packet with lower sequence + // number is lost. + return frame.GetLowSeqNum() - 1; +} + +uint16_t* SrsPsJitterBuffer::GetNackList(uint16_t* nack_list_size, + bool* request_key_frame) +{ + //CriticalSectionScoped cs(crit_sect_); + *request_key_frame = false; + + if (nack_mode_ == kNoNack) { + *nack_list_size = 0; + return NULL; + } + + if (last_decoded_state_.in_initial_state()) { + SrsPsFrameBuffer* next_frame = NextFrame(); + const bool first_frame_is_key = next_frame && + //next_frame->FrameType() == kVideoFrameKey && + next_frame->HaveFirstPacket(); + + if (!first_frame_is_key) { + bool have_non_empty_frame = decodable_frames_.end() != find_if( + decodable_frames_.begin(), decodable_frames_.end(), + HasNonEmptyState); + + if (!have_non_empty_frame) { + have_non_empty_frame = incomplete_frames_.end() != find_if( + incomplete_frames_.begin(), incomplete_frames_.end(), + HasNonEmptyState); + } + + bool found_key_frame = RecycleFramesUntilKeyFrame(); + + if (!found_key_frame) { + *request_key_frame = have_non_empty_frame; + *nack_list_size = 0; + return NULL; + } + } + } + + if (TooLargeNackList()) { + *request_key_frame = !HandleTooLargeNackList(); + } + + if (max_incomplete_time_ms_ > 0) { + int non_continuous_incomplete_duration = + NonContinuousOrIncompleteDuration(); + + if (non_continuous_incomplete_duration > 90 * max_incomplete_time_ms_) { + // LOG_F(LS_WARNING) << "Too long non-decodable duration: " + // << non_continuous_incomplete_duration << " > " + // << 90 * max_incomplete_time_ms_; + FrameList::reverse_iterator rit = find_if(incomplete_frames_.rbegin(), + incomplete_frames_.rend(), IsKeyFrame); + + if (rit == incomplete_frames_.rend()) { + // Request a key frame if we don't have one already. + *request_key_frame = true; + *nack_list_size = 0; + return NULL; + } else { + // Skip to the last key frame. If it's incomplete we will start + // NACKing it. + // Note that the estimated low sequence number is correct for VP8 + // streams because only the first packet of a key frame is marked. + last_decoded_state_.Reset(); + DropPacketsFromNackList(EstimatedLowSequenceNumber(*rit->second)); + } + } + } + + unsigned int i = 0; + SequenceNumberSet::iterator it = missing_sequence_numbers_.begin(); + + for (; it != missing_sequence_numbers_.end(); ++it, ++i) { + nack_seq_nums_[i] = *it; + } + + *nack_list_size = i; + return &nack_seq_nums_[0]; +} + +bool SrsPsJitterBuffer::WaitForRetransmissions() +{ + if (nack_mode_ == kNoNack) { + // NACK disabled -> don't wait for retransmissions. + return false; + } + + // Evaluate if the RTT is higher than |high_rtt_nack_threshold_ms_|, and in + // that case we don't wait for retransmissions. + if (high_rtt_nack_threshold_ms_ >= 0 && + rtt_ms_ >= high_rtt_nack_threshold_ms_) { + return false; + } + + return true; +} diff --git a/trunk/src/app/srs_app_gb28181_jitbuffer.hpp b/trunk/src/app/srs_app_gb28181_jitbuffer.hpp new file mode 100644 index 000000000..e95900813 --- /dev/null +++ b/trunk/src/app/srs_app_gb28181_jitbuffer.hpp @@ -0,0 +1,461 @@ +/** + * 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_JITBUFFER_HPP +#define SRS_APP_GB28181_JITBUFFER_HPP + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +class SrsPsRtpPacket; +class SrsPsFrameBuffer; +class PsDecodingState; +class SrsGb28181RtmpMuxer; +class VCMPacket; + +///jittbuffer + +enum FrameType { + kEmptyFrame = 0, + kAudioFrameSpeech = 1, + kAudioFrameCN = 2, + kVideoFrameKey = 3, // independent frame + kVideoFrameDelta = 4, // depends on the previus frame + kVideoFrameGolden = 5, // depends on a old known previus frame + kVideoFrameAltRef = 6 +}; + +// Used to indicate which decode with errors mode should be used. +enum PsDecodeErrorMode { + kNoErrors, // Never decode with errors. Video will freeze + // if nack is disabled. + kSelectiveErrors, // Frames that are determined decodable in + // VCMSessionInfo may be decoded with missing + // packets. As not all incomplete frames will be + // decodable, video will freeze if nack is disabled. + kWithErrors // Release frames as needed. Errors may be + // introduced as some encoded frames may not be + // complete. +}; + +// Used to estimate rolling average of packets per frame. +static const float kFastConvergeMultiplier = 0.4f; +static const float kNormalConvergeMultiplier = 0.2f; + +enum { kMaxNumberOfFrames = 300 }; +enum { kStartNumberOfFrames = 6 }; +enum { kMaxVideoDelayMs = 10000 }; +enum { kPacketsPerFrameMultiplier = 5 }; +enum { kFastConvergeThreshold = 5}; + +enum PsJitterBufferEnum { + kMaxConsecutiveOldFrames = 60, + kMaxConsecutiveOldPackets = 300, + kMaxPacketsInSession = 800, + kBufferIncStepSizeBytes = 30000, // >20 packets. + kMaxJBFrameSizeBytes = 4000000 // sanity don't go above 4Mbyte. +}; + +enum PsFrameBufferEnum { + kOutOfBoundsPacket = -7, + kNotInitialized = -6, + kOldPacket = -5, + kGeneralError = -4, + kFlushIndicator = -3, // Indicator that a flush has occurred. + kTimeStampError = -2, + kSizeError = -1, + kNoError = 0, + kIncomplete = 1, // Frame incomplete. + kCompleteSession = 3, // at least one layer in the frame complete. + kDecodableSession = 4, // Frame incomplete, but ready to be decoded + kDuplicatePacket = 5 // We're receiving a duplicate packet. +}; + +enum PsFrameBufferStateEnum { + kStateEmpty, // frame popped by the RTP receiver + kStateIncomplete, // frame that have one or more packet(s) stored + kStateComplete, // frame that have all packets + kStateDecodable // Hybrid mode - frame can be decoded +}; + +enum PsNackMode { + kNack, + kNoNack +}; + +// Used to pass data from jitter buffer to session info. +// This data is then used in determining whether a frame is decodable. +struct FrameData { + int64_t rtt_ms; + float rolling_average_packets_per_frame; +}; + +inline bool IsNewerSequenceNumber(uint16_t sequence_number, + uint16_t prev_sequence_number) +{ + return sequence_number != prev_sequence_number && + static_cast(sequence_number - prev_sequence_number) < 0x8000; +} + +inline bool IsNewerTimestamp(uint32_t timestamp, uint32_t prev_timestamp) +{ + return timestamp != prev_timestamp && + static_cast(timestamp - prev_timestamp) < 0x80000000; +} + +inline uint16_t LatestSequenceNumber(uint16_t sequence_number1, + uint16_t sequence_number2) +{ + return IsNewerSequenceNumber(sequence_number1, sequence_number2) + ? sequence_number1 + : sequence_number2; +} + +inline uint32_t LatestTimestamp(uint32_t timestamp1, uint32_t timestamp2) +{ + return IsNewerTimestamp(timestamp1, timestamp2) ? timestamp1 : timestamp2; +} + +typedef std::list UnorderedFrameList; + +class TimestampLessThan { +public: + bool operator() (const uint32_t& timestamp1, + const uint32_t& timestamp2) const + { + return IsNewerTimestamp(timestamp2, timestamp1); + } +}; + +class FrameList + : public std::map { +public: + void InsertFrame(SrsPsFrameBuffer* frame); + SrsPsFrameBuffer* PopFrame(uint32_t timestamp); + SrsPsFrameBuffer* Front() const; + SrsPsFrameBuffer* FrontNext() const; + SrsPsFrameBuffer* Back() const; + int RecycleFramesUntilKeyFrame(FrameList::iterator* key_frame_it, + UnorderedFrameList* free_frames); + void CleanUpOldOrEmptyFrames(PsDecodingState* decoding_state, UnorderedFrameList* free_frames); + void Reset(UnorderedFrameList* free_frames); +}; + + +class VCMPacket { +public: + VCMPacket(); + VCMPacket(const uint8_t* ptr, + size_t size, + uint16_t seqNum, + uint32_t timestamp, + bool markerBit); + + void Reset(); + + uint8_t payloadType; + uint32_t timestamp; + // NTP time of the capture time in local timebase in milliseconds. + int64_t ntp_time_ms_; + uint16_t seqNum; + const uint8_t* dataPtr; + size_t sizeBytes; + bool markerBit; + + FrameType frameType; + //cloopenwebrtc::VideoCodecType codec; + + bool isFirstPacket; // Is this first packet in a frame. + //VCMNaluCompleteness completeNALU; // Default is kNaluIncomplete. + bool insertStartCode; // True if a start code should be inserted before this + // packet. + int width; + int height; + //RTPVideoHeader codecSpecificHeader; +}; + +class SrsPsFrameBuffer { +public: + SrsPsFrameBuffer(); + virtual ~SrsPsFrameBuffer(); + +public: + PsFrameBufferEnum InsertPacket(const VCMPacket& packet, const FrameData& frame_data); + void UpdateCompleteSession(); + void UpdateDecodableSession(const FrameData& frame_data); + bool HaveFirstPacket() const; + bool HaveLastPacket() const; + void Reset(); + + uint32_t GetTimeStamp() const; + FrameType GetFrameType() const; + PsFrameBufferStateEnum GetState() const; + + int32_t GetHighSeqNum() const; + int32_t GetLowSeqNum() const; + size_t Length() const; + const uint8_t* Buffer() const; + + int NumPackets() const; + void InformOfEmptyPacket(uint16_t seq_num); + + bool complete() const; + bool decodable() const; + + bool GetPsPlayload(SrsSimpleStream **ps_data, int &count); + bool DeletePacket(int &count); + void PrepareForDecode(bool continuous); + +private: + + typedef std::list PacketList; + typedef PacketList::iterator PacketIterator; + typedef PacketList::const_iterator PacketIteratorConst; + typedef PacketList::reverse_iterator ReversePacketIterator; + + bool InSequence(const PacketIterator& packet_it, + const PacketIterator& prev_packet_it); + + size_t InsertBuffer(uint8_t* frame_buffer, PacketIterator packet_it); + size_t Insert(const uint8_t* buffer, size_t length, uint8_t* frame_buffer); + void ShiftSubsequentPackets(PacketIterator it, int steps_to_shift); + void VerifyAndAllocate(const uint32_t minimumSize); + void UpdateDataPointers(const uint8_t* old_base_ptr, const uint8_t* new_base_ptr); + size_t DeletePacketData(PacketIterator start, PacketIterator end); + size_t MakeDecodable(); + + + PacketList packets_; + int empty_seq_num_low_; + int empty_seq_num_high_; + + int first_packet_seq_num_; + int last_packet_seq_num_; + + bool complete_; + bool decodable_; + + uint32_t timeStamp_; + FrameType frame_type_; + + PsDecodeErrorMode decode_error_mode_; + PsFrameBufferStateEnum state_; + + uint16_t nackCount_; + int64_t latestPacketTimeMs_; + + // The payload. + uint8_t* _buffer; + size_t _size; + size_t _length; +}; + +class PsDecodingState { +public: + PsDecodingState(); + ~PsDecodingState(); + // Check for old frame + bool IsOldFrame(const SrsPsFrameBuffer* frame) const; + // Check for old packet + bool IsOldPacket(const VCMPacket* packet); + // Check for frame continuity based on current decoded state. Use best method + // possible, i.e. temporal info, picture ID or sequence number. + bool ContinuousFrame(const SrsPsFrameBuffer* frame) const; + void SetState(const SrsPsFrameBuffer* frame); + void CopyFrom(const PsDecodingState& state); + bool UpdateEmptyFrame(const SrsPsFrameBuffer* frame); + // Update the sequence number if the timestamp matches current state and the + // sequence number is higher than the current one. This accounts for packets + // arriving late. + void UpdateOldPacket(const VCMPacket* packet); + void SetSeqNum(uint16_t new_seq_num); + void Reset(); + uint32_t time_stamp() const; + uint16_t sequence_num() const; + // Return true if at initial state. + bool in_initial_state() const; + // Return true when sync is on - decode all layers. + bool full_sync() const; + +private: + void UpdateSyncState(const SrsPsFrameBuffer* frame); + // Designated continuity functions + //bool ContinuousPictureId(int picture_id) const; + bool ContinuousSeqNum(uint16_t seq_num) const; + //bool ContinuousLayer(int temporal_id, int tl0_pic_id) const; + //bool UsingPictureId(const SrsPsFrameBuffer* frame) const; + + // Keep state of last decoded frame. + // TODO(mikhal/stefan): create designated classes to handle these types. + uint16_t sequence_num_; + uint32_t time_stamp_; + int picture_id_; + int temporal_id_; + int tl0_pic_id_; + bool full_sync_; // Sync flag when temporal layers are used. + bool in_initial_state_; + + bool m_firstPacket; +}; + +class SrsPsJitterBuffer +{ +public: + SrsPsJitterBuffer(std::string key); + virtual ~SrsPsJitterBuffer(); + +public: + srs_error_t start(); + void Reset(); + PsFrameBufferEnum InsertPacket(const SrsPsRtpPacket &packet, char *buf, int size, bool* retransmitted); + void ReleaseFrame(SrsPsFrameBuffer* frame); + bool FoundFrame(uint32_t& time_stamp); + bool GetPsFrame(char **buffer, int &buf_len, int &size, const uint32_t time_stamp); + void SetDecodeErrorMode(PsDecodeErrorMode error_mode); + void SetNackMode(PsNackMode mode,int64_t low_rtt_nack_threshold_ms, + int64_t high_rtt_nack_threshold_ms); + void SetNackSettings(size_t max_nack_list_size,int max_packet_age_to_nack, + int max_incomplete_time_ms); + uint16_t* GetNackList(uint16_t* nack_list_size, bool* request_key_frame); + void Flush(); + +private: + + PsFrameBufferEnum GetFrame(const VCMPacket& packet, SrsPsFrameBuffer** frame, + FrameList** frame_list); + SrsPsFrameBuffer* GetEmptyFrame(); + bool NextCompleteTimestamp(uint32_t max_wait_time_ms, uint32_t* timestamp); + bool NextMaybeIncompleteTimestamp(uint32_t* timestamp); + SrsPsFrameBuffer* ExtractAndSetDecode(uint32_t timestamp); + SrsPsFrameBuffer* NextFrame() const; + + + bool TryToIncreaseJitterBufferSize(); + bool RecycleFramesUntilKeyFrame(); + bool IsContinuous(const SrsPsFrameBuffer& frame) const; + bool IsContinuousInState(const SrsPsFrameBuffer& frame, + const PsDecodingState& decoding_state) const; + void FindAndInsertContinuousFrames(const SrsPsFrameBuffer& new_frame); + void CleanUpOldOrEmptyFrames(); + + //nack + bool UpdateNackList(uint16_t sequence_number); + bool TooLargeNackList() const; + bool HandleTooLargeNackList(); + bool MissingTooOldPacket(uint16_t latest_sequence_number) const; + bool HandleTooOldPackets(uint16_t latest_sequence_number); + void DropPacketsFromNackList(uint16_t last_decoded_sequence_number); + PsNackMode nack_mode() const; + int NonContinuousOrIncompleteDuration(); + uint16_t EstimatedLowSequenceNumber(const SrsPsFrameBuffer& frame) const; + bool WaitForRetransmissions(); + +private: + class SequenceNumberLessThan { + public: + bool operator() (const uint16_t& sequence_number1, + const uint16_t& sequence_number2) const + { + return IsNewerSequenceNumber(sequence_number2, sequence_number1); + } + }; + typedef std::set SequenceNumberSet; + + std::string key_; + + srs_cond_t wait_cond_t; + // If we are running (have started) or not. + bool running_; + // Number of allocated frames. + int max_number_of_frames_; + UnorderedFrameList free_frames_; + FrameList decodable_frames_; + FrameList incomplete_frames_; + PsDecodingState last_decoded_state_; + bool first_packet_since_reset_; + + // Statistics. + //VCMReceiveStatisticsCallback* stats_callback_ GUARDED_BY(crit_sect_); + // Frame counts for each type (key, delta, ...) + //FrameCounts receive_statistics_; + // Latest calculated frame rates of incoming stream. + unsigned int incoming_frame_rate_; + unsigned int incoming_frame_count_; + int64_t time_last_incoming_frame_count_; + unsigned int incoming_bit_count_; + unsigned int incoming_bit_rate_; + // Number of frames in a row that have been too old. + int num_consecutive_old_frames_; + // Number of packets in a row that have been too old. + int num_consecutive_old_packets_; + // Number of packets received. + int num_packets_; + int num_packets_free_; + // Number of duplicated packets received. + int num_duplicated_packets_; + // Number of packets discarded by the jitter buffer. + int num_discarded_packets_; + // Time when first packet is received. + int64_t time_first_packet_ms_; + + // Jitter estimation. + // Filter for estimating jitter. + //VCMJitterEstimator jitter_estimate_; + // Calculates network delays used for jitter calculations. + //VCMInterFrameDelay inter_frame_delay_; + //VCMJitterSample waiting_for_completion_; + int64_t rtt_ms_; + + // NACK and retransmissions. + PsNackMode nack_mode_; + int64_t low_rtt_nack_threshold_ms_; + int64_t high_rtt_nack_threshold_ms_; + // Holds the internal NACK list (the missing sequence numbers). + SequenceNumberSet missing_sequence_numbers_; + uint16_t latest_received_sequence_number_; + std::vector nack_seq_nums_; + size_t max_nack_list_size_; + int max_packet_age_to_nack_; // Measured in sequence numbers. + int max_incomplete_time_ms_; + + PsDecodeErrorMode decode_error_mode_; + // Estimated rolling average of packets per frame + float average_packets_per_frame_; + // average_packets_per_frame converges fast if we have fewer than this many + // frames. + int frame_counter_; +}; + +#endif + diff --git a/trunk/src/app/srs_app_gb28181_sip.cpp b/trunk/src/app/srs_app_gb28181_sip.cpp index 169cd1fca..8e416fb11 100644 --- a/trunk/src/app/srs_app_gb28181_sip.cpp +++ b/trunk/src/app/srs_app_gb28181_sip.cpp @@ -68,10 +68,11 @@ std::string srs_get_sip_session_status_str(SrsGb28181SipSessionStatusType status SrsGb28181Device::SrsGb28181Device() { device_id = ""; + device_name = ""; invite_status = SrsGb28181SipSessionUnkonw; invite_time = 0; device_status = ""; - + } SrsGb28181Device::~SrsGb28181Device() @@ -101,6 +102,9 @@ SrsGb28181SipSession::SrsGb28181SipSession(SrsGb28181SipService *c, SrsSipReques _fromlen = 0; _sip_cseq = 100; + + item_list_sumnum = 0; + } SrsGb28181SipSession::~SrsGb28181SipSession() @@ -162,6 +166,8 @@ srs_error_t SrsGb28181SipSession::do_cycle() if (_register_status == SrsGb28181SipSessionRegisterOk && _alive_status == SrsGb28181SipSessionAliveOk) { + std::list auto_play_list; + std::map::iterator it; for (it = _device_list.begin(); it != _device_list.end(); it++) { SrsGb28181Device *device = it->second; @@ -189,9 +195,17 @@ srs_error_t SrsGb28181SipSession::do_cycle() //offline or already invite device does not need to send invite if (device->device_status != "ON" || device->invite_status != SrsGb28181SipSessionUnkonw) continue; + + auto_play_list.push_back(chid); + }//end for (it) + //auto send sip invite and create stream chennal + while(auto_play_list.size() > 0){ + std::string chid = auto_play_list.front(); + auto_play_list.pop_front(); + SrsGb28181StreamChannel ch; - + ch.set_channel_id(_session_id + "@" + chid); ch.set_ip(config->host); @@ -207,7 +221,7 @@ srs_error_t SrsGb28181SipSession::do_cycle() if ((err = _srs_gb28181->create_stream_channel(&ch)) == srs_success){ SrsSipRequest req; req.sip_auth_id = _session_id; - + //send invite to device, req push av stream err = servcie->send_invite(&req, ch.get_ip(), ch.get_rtp_port(), ch.get_ssrc(), chid); @@ -217,13 +231,13 @@ srs_error_t SrsGb28181SipSession::do_cycle() if (err != srs_success){ srs_error_reset(err); } - + //the same device can't be sent too fast. the device can't handle it srs_usleep(1*SRS_UTIME_SECONDS); - + srs_trace("gb28181: %s clients device=%s send invite code=%d", _session_id.c_str(), chid.c_str(), code); - }//end for (it) + }//end while (auto_play_list.size()) }//end if (config) if (_register_status == SrsGb28181SipSessionRegisterOk && @@ -259,7 +273,7 @@ srs_error_t SrsGb28181SipSession::do_cycle() srs_get_sip_session_status_str(_alive_status).c_str(), (reg_duration / SRS_UTIME_SECONDS), (alive_duration / SRS_UTIME_SECONDS)); - + std::map::iterator it; for (it = _device_list.begin(); it != _device_list.end(); it++) { SrsGb28181Device *device = it->second; @@ -272,7 +286,7 @@ srs_error_t SrsGb28181SipSession::do_cycle() invite_duration = 0; } - srs_trace("gb28181: sip session=%s device=%s status(%s, %s), duration(%u)", + srs_info("gb28181: sip session=%s device=%s status(%s, %s), duration(%u)", _session_id.c_str(), chid.c_str(), device->device_status.c_str(), srs_get_sip_session_status_str(device->invite_status).c_str(), (invite_duration / SRS_UTIME_SECONDS)); @@ -290,6 +304,16 @@ std::string SrsGb28181SipSession::remote_ip() return _peer_ip; } +const SrsContextId& SrsGb28181SipSession::get_id() +{ + return _srs_context->get_id(); +} + +std::string SrsGb28181SipSession::desc() +{ + return "SipConn"; +} + srs_error_t SrsGb28181SipSession::cycle() { srs_error_t err = do_cycle(); @@ -317,22 +341,42 @@ void SrsGb28181SipSession::update_device_list(std::map if (_device_list.find(id) == _device_list.end()){ SrsGb28181Device *device = new SrsGb28181Device(); device->device_id = id; - device->device_status = status; + if (status.find(",") != std::string::npos) { + device->device_status = status.substr(0,status.find(",")); + device->device_name = status.substr(status.find(",")+1); + } else { + device->device_status = status; + device->device_name = "NONAME"; + } device->invite_status = SrsGb28181SipSessionUnkonw; device->invite_time = 0; _device_list[id] = device; }else { SrsGb28181Device *device = _device_list[id]; - device->device_status = status; + if (status.find(",") != std::string::npos) { + device->device_status = status.substr(0,status.find(",")); + device->device_name = status.substr(status.find(",")+1); + } else { + device->device_status = status; + device->device_name = "NONAME"; + } } - // srs_trace("gb28181: sip session %s, deviceid=%s status=(%s,%s)", - // _session_id.c_str(), id.c_str(), status.c_str(), - // srs_get_sip_session_status_str(device.invite_status).c_str()); } } +void SrsGb28181SipSession::clear_device_list() +{ + //destory all device + std::map::iterator it; + for (it = _device_list.begin(); it != _device_list.end(); ++it) { + srs_freep(it->second); + } + + _device_list.clear(); +} + SrsGb28181Device* SrsGb28181SipSession::get_device_info(std::string chid) { if (_device_list.find(chid) != _device_list.end()){ @@ -354,23 +398,31 @@ void SrsGb28181SipSession::dumps(SrsJsonObject* obj) SrsJsonObject* obj = SrsJsonAny::object(); arr->append(obj); obj->set("device_id", SrsJsonAny::str(device->device_id.c_str())); + obj->set("device_name", SrsJsonAny::str(device->device_name.c_str())); obj->set("device_status", SrsJsonAny::str(device->device_status.c_str())); obj->set("invite_status", SrsJsonAny::str(srs_get_sip_session_status_str(device->invite_status).c_str())); obj->set("invite_time", SrsJsonAny::integer(device->invite_time/SRS_UTIME_SECONDS)); } +} - //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("rtmp_url", SrsJsonAny::str(rtmp_url.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())); - // obj->set("recv_time", SrsJsonAny::integer(recv_time/SRS_UTIME_SECONDS)); - // obj->set("recv_time_str", SrsJsonAny::str(recv_time_str.c_str())); +void SrsGb28181SipSession::dumpItemList(SrsJsonObject* obj) +{ + obj->set("TopDeviceID", SrsJsonAny::str(_session_id.c_str())); + obj->set("SumNum", SrsJsonAny::integer(item_list_sumnum)); + obj->set("RealSumNum", SrsJsonAny::integer(item_list.size())); + + SrsJsonArray* arr = SrsJsonAny::array(); + obj->set("ItemList", arr); + std::map >::iterator it; + for (it = item_list.begin(); it != item_list.end(); ++it) { + std::map device = it->second; + SrsJsonObject* obj2 = SrsJsonAny::object(); + arr->append(obj2); + std::map::iterator it2; + for (it2 = device.begin(); it2 != device.end(); ++it2) { + obj2->set(it2->first, SrsJsonAny::str(it2->second.c_str())); + } + } } //gb28181 sip Service @@ -379,7 +431,9 @@ SrsGb28181SipService::SrsGb28181SipService(SrsConfDirective* c) // TODO: FIXME: support reload. config = new SrsGb28181Config(c); sip = new SrsSipStack(); - + + lock_session = srs_mutex_new(); + if (_srs_gb28181){ _srs_gb28181->set_sip_service(this); } @@ -388,6 +442,8 @@ SrsGb28181SipService::SrsGb28181SipService(SrsConfDirective* c) SrsGb28181SipService::~SrsGb28181SipService() { destroy(); + srs_mutex_destroy(lock_session); + srs_freep(sip); srs_freep(config); } @@ -410,7 +466,9 @@ srs_error_t SrsGb28181SipService::on_udp_packet(const sockaddr* from, const int (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"); + // return srs_error_new(ERROR_SYSTEM_IP_INVALID, "bad address"); + srs_warn("gb28181: bad address"); + return srs_success; } std::string peer_ip = std::string(address_string); int peer_port = atoi(port_string); @@ -418,7 +476,10 @@ srs_error_t SrsGb28181SipService::on_udp_packet(const sockaddr* from, const int std::string recv_msg(buf, nb_buf); srs_error_t err = on_udp_sip(peer_ip, peer_port, recv_msg, (sockaddr*)from, fromlen); if (err != srs_success) { - return srs_error_wrap(err, "process udp"); + // return srs_error_wrap(err, "process udp"); + srs_warn("gb28181: process udp"); + srs_freep(err); + return srs_success; } return err; } @@ -456,11 +517,11 @@ srs_error_t SrsGb28181SipService::on_udp_sip(string peer_ip, int peer_port, return srs_error_new(ERROR_GB28181_SIP_PRASE_FAILED, "register string split"); } - if (serial.at(0) != config->sip_serial){ - srs_warn("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; - } + //if (serial.at(0) != config->sip_serial){ + // srs_warn("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 client id=%s peer(%s, %d)", req->sip_auth_id.c_str(), peer_ip.c_str(), peer_port); srs_trace("gb28181: %s method=%s, uri=%s, version=%s expires=%d", @@ -510,6 +571,24 @@ srs_error_t SrsGb28181SipService::on_udp_sip(string peer_ip, int peer_port, if (req->device_list_map.size() > 0){ sip_session->update_device_list(req->device_list_map); } + if (!strcasecmp(req->content_type.c_str(),"application/manscdp+xml") + && req->xml_body_map.find("Response@CmdType") != req->xml_body_map.end() + && req->xml_body_map["Response@CmdType"] == "Catalog") { + if (req->xml_body_map.find("Response@SumNum") != req->xml_body_map.end()) { + sip_session->item_list_sumnum = atoi(req->xml_body_map["Response@SumNum"].c_str()); + } + std::vector >::iterator it; + for (it = req->item_list.begin(); it != req->item_list.end(); ++it) { + std::map device = *it; + std::map >::iterator it2 = sip_session->item_list.find(device["DeviceID"]); + if (it2 != sip_session->item_list.end()) { + sip_session->item_list.erase(it2); + sip_session->item_list[device["DeviceID"]] = device; + } else { + sip_session->item_list[device["DeviceID"]] = device; + } + } + } } }else if (req->is_invite()) { @@ -538,6 +617,14 @@ srs_error_t SrsGb28181SipService::on_udp_sip(string peer_ip, int peer_port, srs_trace("gb28181: INVITE response %s client status=%s", req->sip_auth_id.c_str(), req->status.c_str()); if (req->status == "200") { + srs_trace("gb28181: device unique id is %s@%s", sip_session->session_id().c_str(), req->sip_auth_id.c_str()); + // if srs is external realm, ssrc is generated by source realm rather than srs + // so update ssrc to the y line in source realm '200 OK' response + // actually, we should do this all the time + if (req->y_ssrc != 0) { + _srs_gb28181->update_rtmpmuxer_to_newssrc_by_id(sip_session->session_id()+"@"+req->sip_auth_id, req->y_ssrc); + req->y_ssrc = 0; + } send_ack(req, from, fromlen); SrsGb28181Device *device = sip_session->get_device_info(req->sip_auth_id); if (device){ @@ -690,7 +777,7 @@ srs_error_t SrsGb28181SipService::send_invite(SrsSipRequest *req, string ip, i req->from_realm = config->sip_realm; std::stringstream ss; - sip->req_invite(ss, req, ip, port, ssrc); + sip->req_invite(ss, req, ip, port, ssrc, config->rtp_mux_tcp_enable); sockaddr addr = sip_session->sockaddr_from(); @@ -784,8 +871,6 @@ srs_error_t SrsGb28181SipService::send_sip_raw_data(SrsSipRequest *req, std::st srs_error_t SrsGb28181SipService::send_query_catalog(SrsSipRequest *req) { - srs_error_t err = srs_success; - srs_assert(req); SrsGb28181SipSession *sip_session = fetch(req->sip_auth_id); @@ -912,6 +997,31 @@ srs_error_t SrsGb28181SipService::query_sip_session(std::string sid, SrsJsonArr return err; } +srs_error_t SrsGb28181SipService::query_device_list(std::string sid, SrsJsonArray* arr) +{ + srs_error_t err = srs_success; + + if (!sid.empty()){ + SrsGb28181SipSession* sess = fetch(sid); + if (!sess){ + return srs_error_new(ERROR_GB28181_SESSION_IS_NOTEXIST, "sip session not exist"); + } + SrsJsonObject* obj = SrsJsonAny::object(); + arr->append(obj); + sess->dumpItemList(obj); + }else { + std::map::iterator it; + for (it = sessions.begin(); it != sessions.end(); ++it) { + SrsGb28181SipSession* sess = it->second; + SrsJsonObject* obj = SrsJsonAny::object(); + arr->append(obj); + sess->dumpItemList(obj); + } + } + + return err; +} + srs_error_t SrsGb28181SipService::fetch_or_create_sip_session(SrsSipRequest *req, SrsGb28181SipSession** sip_session) { srs_error_t err = srs_success; @@ -935,6 +1045,8 @@ srs_error_t SrsGb28181SipService::fetch_or_create_sip_session(SrsSipRequest *req SrsGb28181SipSession* SrsGb28181SipService::fetch(std::string sid) { + SrsLocker(lock_session); + std::map::iterator it = sessions.find(sid); if (it == sessions.end()){ return NULL; @@ -945,6 +1057,8 @@ SrsGb28181SipSession* SrsGb28181SipService::fetch(std::string sid) void SrsGb28181SipService::remove_session(std::string sid) { + SrsLocker(lock_session); + std::map::iterator it = sessions.find(sid); if (it != sessions.end()){ //srs_freep(it->second); diff --git a/trunk/src/app/srs_app_gb28181_sip.hpp b/trunk/src/app/srs_app_gb28181_sip.hpp index 10d180ee8..6c47dd2ee 100644 --- a/trunk/src/app/srs_app_gb28181_sip.hpp +++ b/trunk/src/app/srs_app_gb28181_sip.hpp @@ -34,7 +34,7 @@ #include #include #include - +#include class SrsConfDirective; class SrsSipRequest; @@ -59,6 +59,7 @@ public: virtual ~SrsGb28181Device(); public: std::string device_id; + std::string device_name; std::string device_status; SrsGb28181SipSessionStatusType invite_status; srs_utime_t invite_time; @@ -132,18 +133,26 @@ public: int sip_cseq(){ return _sip_cseq++;} std::string session_id() { return _session_id;} + std::map > item_list; + int item_list_sumnum; public: void update_device_list(std::map devlist); + void clear_device_list(); SrsGb28181Device *get_device_info(std::string chid); void dumps(SrsJsonObject* obj); + void dumpItemList(SrsJsonObject* obj); public: virtual srs_error_t serve(); // Interface ISrsOneCycleThreadHandler public: - virtual srs_error_t cycle(); + virtual srs_error_t cycle(); +// Interface ISrsConnection. +public: virtual std::string remote_ip(); + virtual const SrsContextId& get_id(); + virtual std::string desc(); private: virtual srs_error_t do_cycle(); }; @@ -157,6 +166,8 @@ private: std::map sessions; std::map sessions_by_callid; + + srs_mutex_t lock_session; public: SrsGb28181SipService(SrsConfDirective* c); virtual ~SrsGb28181SipService(); @@ -195,6 +206,7 @@ public: // srs_error_t send_sip_raw_data(SrsSipRequest *req, std::string data); srs_error_t query_sip_session(std::string sid, SrsJsonArray* arr); + srs_error_t query_device_list(std::string sid, SrsJsonArray* arr); public: srs_error_t fetch_or_create_sip_session(SrsSipRequest *req, SrsGb28181SipSession** sess); diff --git a/trunk/src/app/srs_app_hds.cpp b/trunk/src/app/srs_app_hds.cpp index a260e9470..75f028933 100644 --- a/trunk/src/app/srs_app_hds.cpp +++ b/trunk/src/app/srs_app_hds.cpp @@ -23,7 +23,7 @@ #include -#ifdef SRS_AUTO_HDS +#ifdef SRS_HDS #include #include diff --git a/trunk/src/app/srs_app_hds.hpp b/trunk/src/app/srs_app_hds.hpp index 93ef16a17..58236cea4 100644 --- a/trunk/src/app/srs_app_hds.hpp +++ b/trunk/src/app/srs_app_hds.hpp @@ -26,7 +26,7 @@ #include -#ifdef SRS_AUTO_HDS +#ifdef SRS_HDS #include diff --git a/trunk/src/app/srs_app_heartbeat.cpp b/trunk/src/app/srs_app_heartbeat.cpp index c8c045c6f..f193d71b0 100644 --- a/trunk/src/app/srs_app_heartbeat.cpp +++ b/trunk/src/app/srs_app_heartbeat.cpp @@ -65,10 +65,10 @@ srs_error_t SrsHttpHeartbeat::do_heartbeat() return srs_error_wrap(err, "http uri parse hartbeart url failed. url=%s", url.c_str()); } - std::string ip = ""; + SrsIPAddress* ip = NULL; std::string device_id = _srs_config->get_heartbeat_device_id(); - vector& ips = srs_get_local_ips(); + vector& ips = srs_get_local_ips(); if (!ips.empty()) { ip = ips[_srs_config->get_stats_network() % (int)ips.size()]; } @@ -77,7 +77,7 @@ srs_error_t SrsHttpHeartbeat::do_heartbeat() SrsAutoFree(SrsJsonObject, obj); obj->set("device_id", SrsJsonAny::str(device_id.c_str())); - obj->set("ip", SrsJsonAny::str(ip.c_str())); + obj->set("ip", SrsJsonAny::str(ip->ip.c_str())); if (_srs_config->get_heartbeat_summaries()) { SrsJsonObject* summaries = SrsJsonAny::object(); @@ -87,7 +87,7 @@ srs_error_t SrsHttpHeartbeat::do_heartbeat() } SrsHttpClient http; - if ((err = http.initialize(uri.get_host(), uri.get_port())) != srs_success) { + if ((err = http.initialize(uri.get_schema(), uri.get_host(), uri.get_port())) != srs_success) { return srs_error_wrap(err, "init uri=%s", uri.get_url().c_str()); } diff --git a/trunk/src/app/srs_app_hls.cpp b/trunk/src/app/srs_app_hls.cpp index 7470acf2a..81c7d7a79 100644 --- a/trunk/src/app/srs_app_hls.cpp +++ b/trunk/src/app/srs_app_hls.cpp @@ -55,7 +55,7 @@ using namespace std; // drop the segment when duration of ts too small. // TODO: FIXME: Refine to time unit. -#define SRS_AUTO_HLS_SEGMENT_MIN_DURATION (100 * SRS_UTIME_MILLISECONDS) +#define SRS_HLS_SEGMENT_MIN_DURATION (100 * SRS_UTIME_MILLISECONDS) // fragment plus the deviation percent. #define SRS_HLS_FLOOR_REAP_PERCENT 0.3 @@ -82,7 +82,7 @@ void SrsHlsSegment::config_cipher(unsigned char* key,unsigned char* iv) fw->config_cipher(key, iv); } -SrsDvrAsyncCallOnHls::SrsDvrAsyncCallOnHls(int c, SrsRequest* r, string p, string t, string m, string mu, int s, srs_utime_t d) +SrsDvrAsyncCallOnHls::SrsDvrAsyncCallOnHls(SrsContextId c, SrsRequest* r, string p, string t, string m, string mu, int s, srs_utime_t d) { req = r->copy(); cid = c; @@ -137,7 +137,7 @@ string SrsDvrAsyncCallOnHls::to_string() return "on_hls: " + path; } -SrsDvrAsyncCallOnHlsNotify::SrsDvrAsyncCallOnHlsNotify(int c, SrsRequest* r, string u) +SrsDvrAsyncCallOnHlsNotify::SrsDvrAsyncCallOnHlsNotify(SrsContextId c, SrsRequest* r, string u) { cid = c; req = r->copy(); @@ -200,6 +200,7 @@ SrsHlsMuxer::SrsHlsMuxer() accept_floor_ts = 0; hls_ts_floor = false; max_td = 0; + writer = NULL; _sequence_no = 0; current = NULL; hls_keys = false; @@ -214,6 +215,7 @@ SrsHlsMuxer::SrsHlsMuxer() SrsHlsMuxer::~SrsHlsMuxer() { + srs_freep(segments); srs_freep(current); srs_freep(req); srs_freep(async); @@ -498,7 +500,7 @@ bool SrsHlsMuxer::is_segment_overflow() srs_assert(current); // to prevent very small segment. - if (current->duration() < 2 * SRS_AUTO_HLS_SEGMENT_MIN_DURATION) { + if (current->duration() < 2 * SRS_HLS_SEGMENT_MIN_DURATION) { return false; } @@ -518,7 +520,7 @@ bool SrsHlsMuxer::is_segment_absolutely_overflow() srs_assert(current); // to prevent very small segment. - if (current->duration() < 2 * SRS_AUTO_HLS_SEGMENT_MIN_DURATION) { + if (current->duration() < 2 * SRS_HLS_SEGMENT_MIN_DURATION) { return false; } @@ -619,7 +621,7 @@ srs_error_t SrsHlsMuxer::do_segment_close() // when too small, it maybe not enough data to play. // when too large, it maybe timestamp corrupt. // make the segment more acceptable, when in [min, max_td * 2], it's ok. - bool matchMinDuration = current->duration() >= SRS_AUTO_HLS_SEGMENT_MIN_DURATION; + bool matchMinDuration = current->duration() >= SRS_HLS_SEGMENT_MIN_DURATION; bool matchMaxDuration = current->duration() <= max_td * 2 * 1000; if (matchMinDuration && matchMaxDuration) { // use async to call the http hooks, for it will cause thread switch. @@ -757,11 +759,12 @@ srs_error_t SrsHlsMuxer::_refresh_m3u8(string m3u8_file) // #EXT-X-MEDIA-SEQUENCE:4294967295\n SrsHlsSegment* first = dynamic_cast(segments->first()); + if (first == NULL) { + return srs_error_new(ERROR_HLS_WRITE_FAILED, "segments cast"); + } + ss << "#EXT-X-MEDIA-SEQUENCE:" << first->sequence_no << SRS_CONSTS_LF; - // iterator shared for td generation and segemnts wrote. - std::vector::iterator it; - // #EXT-X-TARGETDURATION:4294967295\n /** * @see hls-m3u8-draft-pantos-http-live-streaming-12.pdf, page 25 diff --git a/trunk/src/app/srs_app_hls.hpp b/trunk/src/app/srs_app_hls.hpp index 1dd72eba5..2d4481708 100644 --- a/trunk/src/app/srs_app_hls.hpp +++ b/trunk/src/app/srs_app_hls.hpp @@ -80,7 +80,7 @@ public: class SrsDvrAsyncCallOnHls : public ISrsAsyncCallTask { private: - int cid; + SrsContextId cid; std::string path; std::string ts_url; std::string m3u8; @@ -90,7 +90,7 @@ private: srs_utime_t duration; public: // TODO: FIXME: Use TBN 1000. - SrsDvrAsyncCallOnHls(int c, SrsRequest* r, std::string p, std::string t, std::string m, std::string mu, int s, srs_utime_t d); + SrsDvrAsyncCallOnHls(SrsContextId c, SrsRequest* r, std::string p, std::string t, std::string m, std::string mu, int s, srs_utime_t d); virtual ~SrsDvrAsyncCallOnHls(); public: virtual srs_error_t call(); @@ -101,11 +101,11 @@ public: class SrsDvrAsyncCallOnHlsNotify : public ISrsAsyncCallTask { private: - int cid; + SrsContextId cid; std::string ts_url; SrsRequest* req; public: - SrsDvrAsyncCallOnHlsNotify(int c, SrsRequest* r, std::string u); + SrsDvrAsyncCallOnHlsNotify(SrsContextId c, SrsRequest* r, std::string u); virtual ~SrsDvrAsyncCallOnHlsNotify(); public: virtual srs_error_t call(); diff --git a/trunk/src/app/srs_app_http_api.cpp b/trunk/src/app/srs_app_http_api.cpp index 071a0b8b5..449146aac 100644 --- a/trunk/src/app/srs_app_http_api.cpp +++ b/trunk/src/app/srs_app_http_api.cpp @@ -26,6 +26,7 @@ #include #include #include +#include using namespace std; #include @@ -46,9 +47,6 @@ using namespace std; #include #include #include -#ifdef SRS_AUTO_RTC -#include -#endif srs_error_t srs_api_response_jsonp(ISrsHttpResponseWriter* w, string callback, string data) { @@ -196,13 +194,25 @@ srs_error_t SrsGoApiRoot::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* SrsAutoFree(SrsJsonObject, obj); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::integer(stat->server_id())); + obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); SrsJsonObject* urls = SrsJsonAny::object(); obj->set("urls", urls); urls->set("api", SrsJsonAny::str("the api root")); - + + if (true) { + SrsJsonObject* rtc = SrsJsonAny::object(); + urls->set("rtc", rtc); + + SrsJsonObject* v1 = SrsJsonAny::object(); + rtc->set("v1", v1); + + v1->set("play", SrsJsonAny::str("Play stream")); + v1->set("publish", SrsJsonAny::str("Publish stream")); + v1->set("nack", SrsJsonAny::str("Simulate the NACK")); + } + return srs_api_response(w, r, obj->dumps()); } @@ -222,7 +232,7 @@ srs_error_t SrsGoApiApi::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* SrsAutoFree(SrsJsonObject, obj); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::integer(stat->server_id())); + obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); SrsJsonObject* urls = SrsJsonAny::object(); obj->set("urls", urls); @@ -248,7 +258,7 @@ srs_error_t SrsGoApiV1::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r SrsAutoFree(SrsJsonObject, obj); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::integer(stat->server_id())); + obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); SrsJsonObject* urls = SrsJsonAny::object(); obj->set("urls", urls); @@ -297,7 +307,7 @@ srs_error_t SrsGoApiVersion::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessa SrsAutoFree(SrsJsonObject, obj); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::integer(stat->server_id())); + obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); SrsJsonObject* data = SrsJsonAny::object(); obj->set("data", data); @@ -326,7 +336,7 @@ srs_error_t SrsGoApiSummaries::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMes SrsAutoFree(SrsJsonObject, obj); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::integer(stat->server_id())); + obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); srs_api_dump_summaries(obj); @@ -349,7 +359,7 @@ srs_error_t SrsGoApiRusages::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessa SrsAutoFree(SrsJsonObject, obj); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::integer(stat->server_id())); + obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); SrsJsonObject* data = SrsJsonAny::object(); obj->set("data", data); @@ -394,7 +404,7 @@ srs_error_t SrsGoApiSelfProcStats::serve_http(ISrsHttpResponseWriter* w, ISrsHtt SrsAutoFree(SrsJsonObject, obj); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::integer(stat->server_id())); + obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); SrsJsonObject* data = SrsJsonAny::object(); obj->set("data", data); @@ -471,7 +481,7 @@ srs_error_t SrsGoApiSystemProcStats::serve_http(ISrsHttpResponseWriter* w, ISrsH SrsAutoFree(SrsJsonObject, obj); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::integer(stat->server_id())); + obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); SrsJsonObject* data = SrsJsonAny::object(); obj->set("data", data); @@ -510,7 +520,7 @@ srs_error_t SrsGoApiMemInfos::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMess SrsAutoFree(SrsJsonObject, obj); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::integer(stat->server_id())); + obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); SrsJsonObject* data = SrsJsonAny::object(); obj->set("data", data); @@ -550,13 +560,13 @@ srs_error_t SrsGoApiAuthors::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessa SrsAutoFree(SrsJsonObject, obj); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::integer(stat->server_id())); + obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); SrsJsonObject* data = SrsJsonAny::object(); obj->set("data", data); data->set("license", SrsJsonAny::str(RTMP_SIG_SRS_LICENSE)); - data->set("contributors", SrsJsonAny::str(SRS_AUTO_CONSTRIBUTORS)); + data->set("contributors", SrsJsonAny::str(SRS_CONSTRIBUTORS)); return srs_api_response(w, r, obj->dumps()); } @@ -577,22 +587,22 @@ srs_error_t SrsGoApiFeatures::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMess SrsAutoFree(SrsJsonObject, obj); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::integer(stat->server_id())); + obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); SrsJsonObject* data = SrsJsonAny::object(); obj->set("data", data); - data->set("options", SrsJsonAny::str(SRS_AUTO_USER_CONFIGURE)); - data->set("options2", SrsJsonAny::str(SRS_AUTO_CONFIGURE)); - data->set("build", SrsJsonAny::str(SRS_AUTO_BUILD_DATE)); - data->set("build2", SrsJsonAny::str(SRS_AUTO_BUILD_TS)); + data->set("options", SrsJsonAny::str(SRS_USER_CONFIGURE)); + data->set("options2", SrsJsonAny::str(SRS_CONFIGURE)); + data->set("build", SrsJsonAny::str(SRS_BUILD_DATE)); + data->set("build2", SrsJsonAny::str(SRS_BUILD_TS)); SrsJsonObject* features = SrsJsonAny::object(); data->set("features", features); features->set("ssl", SrsJsonAny::boolean(true)); features->set("hls", SrsJsonAny::boolean(true)); -#ifdef SRS_AUTO_HDS +#ifdef SRS_HDS features->set("hds", SrsJsonAny::boolean(true)); #else features->set("hds", SrsJsonAny::boolean(false)); @@ -645,7 +655,7 @@ srs_error_t SrsGoApiRequests::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMess SrsAutoFree(SrsJsonObject, obj); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::integer(stat->server_id())); + obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); SrsJsonObject* data = SrsJsonAny::object(); obj->set("data", data); @@ -689,10 +699,10 @@ srs_error_t SrsGoApiVhosts::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessag // path: {pattern}{vhost_id} // e.g. /api/v1/vhosts/100 pattern= /api/v1/vhosts/, vhost_id=100 - int vid = r->parse_rest_id(entry->pattern); + string vid = r->parse_rest_id(entry->pattern); SrsStatisticVhost* vhost = NULL; - if (vid > 0 && (vhost = stat->find_vhost(vid)) == NULL) { + if (!vid.empty() && (vhost = stat->find_vhost_by_id(vid)) == NULL) { return srs_api_response_code(w, r, ERROR_RTMP_VHOST_NOT_FOUND); } @@ -700,7 +710,7 @@ srs_error_t SrsGoApiVhosts::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessag SrsAutoFree(SrsJsonObject, obj); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::integer(stat->server_id())); + obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); if (r->is_http_get()) { if (!vhost) { @@ -745,10 +755,10 @@ srs_error_t SrsGoApiStreams::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessa // path: {pattern}{stream_id} // e.g. /api/v1/streams/100 pattern= /api/v1/streams/, stream_id=100 - int sid = r->parse_rest_id(entry->pattern); + string sid = r->parse_rest_id(entry->pattern); SrsStatisticStream* stream = NULL; - if (sid >= 0 && (stream = stat->find_stream(sid)) == NULL) { + if (!sid.empty() && (stream = stat->find_stream(sid)) == NULL) { return srs_api_response_code(w, r, ERROR_RTMP_STREAM_NOT_FOUND); } @@ -756,7 +766,7 @@ srs_error_t SrsGoApiStreams::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessa SrsAutoFree(SrsJsonObject, obj); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::integer(stat->server_id())); + obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); if (r->is_http_get()) { if (!stream) { @@ -785,314 +795,6 @@ srs_error_t SrsGoApiStreams::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessa return srs_api_response(w, r, obj->dumps()); } -#ifdef SRS_AUTO_RTC -uint32_t SrsGoApiRtcPlay::ssrc_num = 0; - -SrsGoApiRtcPlay::SrsGoApiRtcPlay(SrsRtcServer* rtc_svr) -{ - rtc_server = rtc_svr; -} - -SrsGoApiRtcPlay::~SrsGoApiRtcPlay() -{ -} - - -// Request: -// POST /rtc/v1/play/ -// { -// "sdp":"offer...", "streamurl":"webrtc://r.ossrs.net/live/livestream", -// "api":'http...", "clientip":"..." -// } -// Response: -// {"sdp":"answer...", "sid":"..."} -// @see https://github.com/rtcdn/rtcdn-draft -srs_error_t SrsGoApiRtcPlay::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r) -{ - srs_error_t err = srs_success; - - SrsJsonObject* res = SrsJsonAny::object(); - SrsAutoFree(SrsJsonObject, res); - - if ((err = do_serve_http(w, r, res)) != srs_success) { - srs_warn("RTC error %s", srs_error_desc(err).c_str()); srs_freep(err); - return srs_api_response_code(w, r, SRS_CONSTS_HTTP_BadRequest); - } - - return srs_api_response(w, r, res->dumps()); -} - -srs_error_t SrsGoApiRtcPlay::do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, SrsJsonObject* res) -{ - srs_error_t err = srs_success; - - // For each RTC session, we use short-term HTTP connection. - SrsHttpHeader* hdr = w->header(); - hdr->set("Connection", "Close"); - - // Parse req, the request json object, from body. - SrsJsonObject* req = NULL; - SrsAutoFree(SrsJsonObject, req); - if (true) { - string req_json; - if ((err = r->body_read_all(req_json)) != srs_success) { - return srs_error_wrap(err, "read body"); - } - - SrsJsonAny* json = SrsJsonAny::loads(req_json); - if (!json || !json->is_object()) { - return srs_error_wrap(err, "not json"); - } - - req = json->to_object(); - } - - // Fetch params from req object. - SrsJsonAny* prop = NULL; - if ((prop = req->ensure_property_string("sdp")) == NULL) { - return srs_error_wrap(err, "not sdp"); - } - string remote_sdp_str = prop->to_str(); - - if ((prop = req->ensure_property_string("streamurl")) == NULL) { - return srs_error_wrap(err, "not streamurl"); - } - string streamurl = prop->to_str(); - - string clientip; - if ((prop = req->ensure_property_string("clientip")) != NULL) { - clientip = prop->to_str(); - } - - string api; - if ((prop = req->ensure_property_string("api")) != NULL) { - api = prop->to_str(); - } - - // TODO: FIXME: Parse vhost. - // Parse app and stream from streamurl. - string app; - string stream_name; - if (true) { - string tcUrl; - srs_parse_rtmp_url(streamurl, tcUrl, stream_name); - - int port; - string schema, host, vhost, param; - srs_discovery_tc_url(tcUrl, schema, host, vhost, app, stream_name, port, param); - } - - // For client to specifies the EIP of server. - string eip = r->query_get("eip"); - // For client to specifies whether encrypt by SRTP. - string encrypt = r->query_get("encrypt"); - - srs_trace("RTC play %s, api=%s, clientip=%s, app=%s, stream=%s, offer=%dB, eip=%s, encrypt=%s", - streamurl.c_str(), api.c_str(), clientip.c_str(), app.c_str(), stream_name.c_str(), remote_sdp_str.length(), - eip.c_str(), encrypt.c_str()); - - // TODO: FIXME: It seems remote_sdp doesn't represents the full SDP information. - SrsSdp remote_sdp; - if ((err = remote_sdp.parse(remote_sdp_str)) != srs_success) { - return srs_error_wrap(err, "parse sdp failed: %s", remote_sdp_str.c_str()); - } - - if ((err = check_remote_sdp(remote_sdp)) != srs_success) { - return srs_error_wrap(err, "remote sdp check failed"); - } - - SrsSdp local_sdp; - if ((err = exchange_sdp(app, stream_name, remote_sdp, local_sdp)) != srs_success) { - return srs_error_wrap(err, "remote sdp have error or unsupport attributes"); - } - - SrsRequest request; - request.app = app; - request.stream = stream_name; - - // TODO: FIXME: Parse vhost. - // discovery vhost, resolve the vhost from config - SrsConfDirective* parsed_vhost = _srs_config->get_vhost(""); - if (parsed_vhost) { - request.vhost = parsed_vhost->arg0(); - } - - // TODO: FIXME: Maybe need a better name? - // TODO: FIXME: When server enabled, but vhost disabled, should report error. - SrsRtcSession* rtc_session = rtc_server->create_rtc_session(request, remote_sdp, local_sdp, eip); - if (encrypt.empty()) { - rtc_session->set_encrypt(_srs_config->get_rtc_server_encrypt()); - } else { - rtc_session->set_encrypt(encrypt != "false"); - } - - ostringstream os; - if ((err = local_sdp.encode(os)) != srs_success) { - return srs_error_wrap(err, "encode sdp"); - } - - string local_sdp_str = os.str(); - - srs_verbose("local_sdp=%s", local_sdp_str.c_str()); - - res->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - res->set("server", SrsJsonAny::integer(SrsStatistic::instance()->server_id())); - - // TODO: add candidates in response json? - - res->set("sdp", SrsJsonAny::str(local_sdp_str.c_str())); - res->set("sessionid", SrsJsonAny::str(rtc_session->id().c_str())); - - srs_trace("RTC sid=%s, offer=%dB, answer=%dB", rtc_session->id().c_str(), remote_sdp_str.length(), local_sdp_str.length()); - - return err; -} - -srs_error_t SrsGoApiRtcPlay::check_remote_sdp(const SrsSdp& remote_sdp) -{ - srs_error_t err = srs_success; - - if (remote_sdp.group_policy_ != "BUNDLE") { - return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "now only support BUNDLE, group policy=%s", remote_sdp.group_policy_.c_str()); - } - - if (remote_sdp.media_descs_.empty()) { - return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no media descriptions"); - } - - for (std::vector::const_iterator iter = remote_sdp.media_descs_.begin(); iter != remote_sdp.media_descs_.end(); ++iter) { - if (iter->type_ != "audio" && iter->type_ != "video") { - return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "unsupport media type=%s", iter->type_.c_str()); - } - - if (! iter->rtcp_mux_) { - return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "now only suppor rtcp-mux"); - } - - for (std::vector::const_iterator iter_media = iter->payload_types_.begin(); iter_media != iter->payload_types_.end(); ++iter_media) { - if (iter->sendonly_) { - return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "play API only support sendrecv/recvonly"); - } - } - } - - return err; -} - -srs_error_t SrsGoApiRtcPlay::exchange_sdp(const std::string& app, const std::string& stream, const SrsSdp& remote_sdp, SrsSdp& local_sdp) -{ - srs_error_t err = srs_success; - local_sdp.version_ = "0"; - - local_sdp.username_ = RTMP_SIG_SRS_SERVER; - local_sdp.session_id_ = srs_int2str((int64_t)this); - local_sdp.session_version_ = "2"; - local_sdp.nettype_ = "IN"; - local_sdp.addrtype_ = "IP4"; - local_sdp.unicast_address_ = "0.0.0.0"; - - local_sdp.session_name_ = "live_play_session"; - - local_sdp.msid_semantic_ = "WMS"; - local_sdp.msids_.push_back(app + "/" + stream); - - local_sdp.group_policy_ = "BUNDLE"; - - for (size_t i = 0; i < remote_sdp.media_descs_.size(); ++i) { - const SrsMediaDesc& remote_media_desc = remote_sdp.media_descs_[i]; - - if (remote_media_desc.is_audio()) { - local_sdp.media_descs_.push_back(SrsMediaDesc("audio")); - } else if (remote_media_desc.is_video()) { - local_sdp.media_descs_.push_back(SrsMediaDesc("video")); - } - - SrsMediaDesc& local_media_desc = local_sdp.media_descs_.back(); - - if (remote_media_desc.is_audio()) { - // TODO: check opus format specific param - std::vector payloads = remote_media_desc.find_media_with_encoding_name("opus"); - for (std::vector::iterator iter = payloads.begin(); iter != payloads.end(); ++iter) { - // Only choose one match opus codec. - local_media_desc.payload_types_.push_back(*iter); - break; - } - - if (local_media_desc.payload_types_.empty()) { - return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no found valid opus payload type"); - } - - } else if (remote_media_desc.is_video()) { - std::deque backup_payloads; - std::vector payloads = remote_media_desc.find_media_with_encoding_name("H264"); - for (std::vector::iterator iter = payloads.begin(); iter != payloads.end(); ++iter) { - if (iter->format_specific_param_.empty()) { - backup_payloads.push_front(*iter); - continue; - } - H264SpecificParam h264_param; - if ((err = parse_h264_fmtp(iter->format_specific_param_, h264_param)) != srs_success) { - srs_error_reset(err); continue; - } - - // Try to pick the "best match" H.264 payload type. - if (h264_param.packetization_mode == "1" && h264_param.level_asymmerty_allow == "1") { - // Only choose first match H.264 payload type. - local_media_desc.payload_types_.push_back(*iter); - break; - } - - backup_payloads.push_back(*iter); - } - - // Try my best to pick at least one media payload type. - if (local_media_desc.payload_types_.empty() && ! backup_payloads.empty()) { - srs_warn("choose backup H.264 payload type=%d", backup_payloads.front().payload_type_); - local_media_desc.payload_types_.push_back(backup_payloads.front()); - } - - if (local_media_desc.payload_types_.empty()) { - return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no found valid H.264 payload type"); - } - } - - local_media_desc.mid_ = remote_media_desc.mid_; - local_sdp.groups_.push_back(local_media_desc.mid_); - - local_media_desc.port_ = 9; - local_media_desc.protos_ = "UDP/TLS/RTP/SAVPF"; - - if (remote_media_desc.session_info_.setup_ == "active") { - local_media_desc.session_info_.setup_ = "passive"; - } else if (remote_media_desc.session_info_.setup_ == "passive") { - local_media_desc.session_info_.setup_ = "active"; - } else if (remote_media_desc.session_info_.setup_ == "actpass") { - local_media_desc.session_info_.setup_ = "passive"; - } - - if (remote_media_desc.sendonly_) { - local_media_desc.recvonly_ = true; - } else if (remote_media_desc.recvonly_) { - local_media_desc.sendonly_ = true; - } else if (remote_media_desc.sendrecv_) { - local_media_desc.sendrecv_ = true; - } - - local_media_desc.rtcp_mux_ = true; - local_media_desc.rtcp_rsize_ = true; - - SrsSSRCInfo ssrc_info; - ssrc_info.ssrc_ = ++ssrc_num; - // TODO:use formated cname - ssrc_info.cname_ = "test_sdp_cname"; - local_media_desc.ssrc_infos_.push_back(ssrc_info); - } - - return err; -} - -#endif - SrsGoApiClients::SrsGoApiClients() { } @@ -1109,10 +811,10 @@ srs_error_t SrsGoApiClients::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessa // path: {pattern}{client_id} // e.g. /api/v1/clients/100 pattern= /api/v1/clients/, client_id=100 - int cid = r->parse_rest_id(entry->pattern); + string client_id = r->parse_rest_id(entry->pattern); SrsStatisticClient* client = NULL; - if (cid >= 0 && (client = stat->find_client(cid)) == NULL) { + if (!client_id.empty() && (client = stat->find_client(client_id)) == NULL) { return srs_api_response_code(w, r, ERROR_RTMP_CLIENT_NOT_FOUND); } @@ -1120,7 +822,7 @@ srs_error_t SrsGoApiClients::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessa SrsAutoFree(SrsJsonObject, obj); obj->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); - obj->set("server", SrsJsonAny::integer(stat->server_id())); + obj->set("server", SrsJsonAny::str(stat->server_id().c_str())); if (r->is_http_get()) { if (!client) { @@ -1150,9 +852,9 @@ srs_error_t SrsGoApiClients::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessa if (!client) { return srs_api_response_code(w, r, ERROR_RTMP_CLIENT_NOT_FOUND); } - + client->conn->expire(); - srs_warn("kickoff client id=%d ok", cid); + srs_warn("kickoff client id=%s ok", client_id.c_str()); } else { return srs_go_http_error(w, SRS_CONSTS_HTTP_MethodNotAllowed); } @@ -1633,7 +1335,7 @@ srs_error_t SrsGoApiPerf::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* p->set("target", SrsJsonAny::str(target.c_str())); p->set("reset", SrsJsonAny::str(reset.c_str())); - p->set("help", SrsJsonAny::str("?target=avframes|rtc|rtp|gso|writev_iovs|sendmmsg|bytes|dropped")); + p->set("help", SrsJsonAny::str("?target=avframes|rtc|rtp|writev_iovs|bytes")); p->set("help2", SrsJsonAny::str("?reset=all")); } @@ -1669,24 +1371,6 @@ srs_error_t SrsGoApiPerf::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* } } - if (target.empty() || target == "gso") { - SrsJsonObject* p = SrsJsonAny::object(); - data->set("gso", p); - if ((err = stat->dumps_perf_gso(p)) != srs_success) { - int code = srs_error_code(err); srs_error_reset(err); - return srs_api_response_code(w, r, code); - } - } - - if (target.empty() || target == "sendmmsg") { - SrsJsonObject* p = SrsJsonAny::object(); - data->set("sendmmsg", p); - if ((err = stat->dumps_perf_sendmmsg(p)) != srs_success) { - int code = srs_error_code(err); srs_error_reset(err); - return srs_api_response_code(w, r, code); - } - } - if (target.empty() || target == "writev_iovs") { SrsJsonObject* p = SrsJsonAny::object(); data->set("writev_iovs", p); @@ -1705,15 +1389,6 @@ srs_error_t SrsGoApiPerf::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* } } - if (target.empty() || target == "dropped") { - SrsJsonObject* p = SrsJsonAny::object(); - data->set("dropped", p); - if ((err = stat->dumps_perf_dropped(p)) != srs_success) { - int code = srs_error_code(err); srs_error_reset(err); - return srs_api_response_code(w, r, code); - } - } - return srs_api_response(w, r, obj->dumps()); } @@ -1730,7 +1405,7 @@ srs_error_t SrsGoApiError::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage return srs_api_response_code(w, r, 100); } -#ifdef SRS_AUTO_GB28181 +#ifdef SRS_GB28181 SrsGoApiGb28181::SrsGoApiGb28181() { } @@ -1801,11 +1476,12 @@ srs_error_t SrsGoApiGb28181::do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMe return srs_api_response(w, r, obj->dumps()); } else if(action == "delete_channel"){ - if (id.empty()){ - return srs_error_new(ERROR_GB28181_VALUE_EMPTY, "no id"); + string chid = r->query_get("chid"); + if (id.empty() || chid.empty()){ + return srs_error_new(ERROR_GB28181_VALUE_EMPTY, "no id or chid"); } - if ((err = _srs_gb28181->delete_stream_channel(id)) != srs_success) { + if ((err = _srs_gb28181->delete_stream_channel(id, chid)) != srs_success) { return srs_error_wrap(err, "delete stream channel"); } @@ -1898,6 +1574,16 @@ srs_error_t SrsGoApiGb28181::do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMe } return srs_api_response_code(w, r, 0); + } else if(action == "sip_query_devicelist"){ + SrsJsonArray* arr = SrsJsonAny::array(); + data->set("PlatformID", SrsJsonAny::str(_srs_gb28181->get_gb28181_config_ptr()->sip_serial.c_str())); + data->set("DeviceList", arr); + + if ((err = _srs_gb28181->query_device_list("", arr)) != srs_success) { + return srs_error_wrap(err, "query device list"); + } + + return srs_api_response(w, r, obj->dumps()); } else if(action == "sip_query_session"){ SrsJsonArray* arr = SrsJsonAny::array(); data->set("sessions", arr); @@ -1913,7 +1599,7 @@ srs_error_t SrsGoApiGb28181::do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMe } #endif -#ifdef SRS_AUTO_GPERF +#ifdef SRS_GPERF #include SrsGoApiTcmalloc::SrsGoApiTcmalloc() @@ -1999,126 +1685,149 @@ srs_error_t SrsGoApiTcmalloc::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMess } #endif -SrsHttpApi::SrsHttpApi(IConnectionManager* cm, srs_netfd_t fd, SrsHttpServeMux* m, string cip) - : SrsConnection(cm, fd, cip) +SrsHttpApi::SrsHttpApi(bool https, ISrsResourceManager* cm, srs_netfd_t fd, SrsHttpServeMux* m, string cip, int port) { - mux = m; - cors = new SrsHttpCorsMux(); - parser = new SrsHttpParser(); - + // Create a identify for this client. + _srs_context->set_id(_srs_context->generate_id()); + + manager = cm; + skt = new SrsTcpConnection(fd); + + if (https) { + ssl = new SrsSslConnection(skt); + conn = new SrsHttpConn(this, ssl, m, cip, port); + } else { + ssl = NULL; + conn = new SrsHttpConn(this, skt, m, cip, port); + } + _srs_config->subscribe(this); } SrsHttpApi::~SrsHttpApi() { - srs_freep(parser); - srs_freep(cors); - _srs_config->unsubscribe(this); + + srs_freep(conn); + srs_freep(ssl); + srs_freep(skt); +} + +srs_error_t SrsHttpApi::on_start() +{ + srs_error_t err = srs_success; + + if ((err = conn->set_jsonp(true)) != srs_success) { + return srs_error_wrap(err, "set jsonp"); + } + + if (ssl) { + srs_utime_t starttime = srs_update_system_time(); + string crt_file = _srs_config->get_https_api_ssl_cert(); + string key_file = _srs_config->get_https_api_ssl_key(); + if ((err = ssl->handshake(key_file, crt_file)) != srs_success) { + return srs_error_wrap(err, "handshake"); + } + + int cost = srsu2msi(srs_update_system_time() - starttime); + srs_trace("https: api server done, use key %s and cert %s, cost=%dms", + key_file.c_str(), crt_file.c_str(), cost); + } + + return err; +} + +srs_error_t SrsHttpApi::on_http_message(ISrsHttpMessage* r, SrsHttpResponseWriter* w) +{ + srs_error_t err = srs_success; + + // After parsed the message, set the schema to https. + if (ssl) { + SrsHttpMessage* hm = dynamic_cast(r); + hm->set_https(true); + } + + // TODO: For each API session, we use short-term HTTP connection. + //SrsHttpHeader* hdr = w->header(); + //hdr->set("Connection", "Close"); + + return err; +} + +srs_error_t SrsHttpApi::on_message_done(ISrsHttpMessage* r, SrsHttpResponseWriter* w) +{ + srs_error_t err = srs_success; + + // read all rest bytes in request body. + char buf[SRS_HTTP_READ_CACHE_BYTES]; + ISrsHttpResponseReader* br = r->body_reader(); + while (!br->eof()) { + if ((err = br->read(buf, SRS_HTTP_READ_CACHE_BYTES, NULL)) != srs_success) { + return srs_error_wrap(err, "read response"); + } + } + + return err; +} + +srs_error_t SrsHttpApi::on_conn_done(srs_error_t r0) +{ + // Because we use manager to manage this object, + // not the http connection object, so we must remove it here. + manager->remove(this); + + // For HTTP-API timeout, we think it's done successfully, + // because there may be no request or response for HTTP-API. + if (srs_error_code(r0) == ERROR_SOCKET_TIMEOUT) { + srs_freep(r0); + return srs_success; + } + + return r0; +} + +std::string SrsHttpApi::desc() +{ + if (ssl) { + return "HttpsConn"; + } + return "HttpConn"; } void SrsHttpApi::remark(int64_t* in, int64_t* out) { - // TODO: FIXME: implements it -} - -srs_error_t SrsHttpApi::do_cycle() -{ - srs_error_t err = srs_success; - - srs_trace("API server client, ip=%s", ip.c_str()); - - // initialize parser - if ((err = parser->initialize(HTTP_REQUEST, true)) != srs_success) { - return srs_error_wrap(err, "init parser"); - } - - // set the recv timeout, for some clients never disconnect the connection. - // @see https://github.com/ossrs/srs/issues/398 - skt->set_recv_timeout(SRS_HTTP_RECV_TIMEOUT); - - // initialize the cors, which will proxy to mux. - bool crossdomain_enabled = _srs_config->get_http_api_crossdomain(); - if ((err = cors->initialize(mux, crossdomain_enabled)) != srs_success) { - return srs_error_wrap(err, "init cors"); - } - - // process http messages. - while ((err = trd->pull()) == srs_success) { - ISrsHttpMessage* req = NULL; - - // get a http message - if ((err = parser->parse_message(skt, &req)) != srs_success) { - // For HTTP timeout, we think it's ok. - if (srs_error_code(err) == ERROR_SOCKET_TIMEOUT) { - srs_freep(err); - return srs_error_wrap(srs_success, "http api timeout"); - } - return srs_error_wrap(err, "parse message"); - } - - // if SUCCESS, always NOT-NULL. - // always free it in this scope. - srs_assert(req); - SrsAutoFree(ISrsHttpMessage, req); - - // Attach owner connection to message. - SrsHttpMessage* hreq = (SrsHttpMessage*)req; - hreq->set_connection(this); - - // ok, handle http request. - SrsHttpResponseWriter writer(skt); - if ((err = process_request(&writer, req)) != srs_success) { - return srs_error_wrap(err, "process request"); - } - - // read all rest bytes in request body. - char buf[SRS_HTTP_READ_CACHE_BYTES]; - ISrsHttpResponseReader* br = req->body_reader(); - while (!br->eof()) { - if ((err = br->read(buf, SRS_HTTP_READ_CACHE_BYTES, NULL)) != srs_success) { - return srs_error_wrap(err, "read response"); - } - } - - // donot keep alive, disconnect it. - // @see https://github.com/ossrs/srs/issues/399 - if (!req->is_keep_alive()) { - break; - } - } - - return err; -} - -srs_error_t SrsHttpApi::process_request(ISrsHttpResponseWriter* w, ISrsHttpMessage* r) -{ - srs_error_t err = srs_success; - - SrsHttpMessage* hm = dynamic_cast(r); - srs_assert(hm); - - srs_trace("HTTP API %s %s, content-length=%" PRId64 ", chunked=%d/%d", - r->method_str().c_str(), r->url().c_str(), r->content_length(), - hm->is_chunked(), hm->is_infinite_chunked()); - - // use cors server mux to serve http request, which will proxy to mux. - if ((err = cors->serve_http(w, r)) != srs_success) { - return srs_error_wrap(err, "mux serve"); - } - - return err; + conn->remark(in, out); } srs_error_t SrsHttpApi::on_reload_http_api_crossdomain() { - srs_error_t err = srs_success; - - bool crossdomain_enabled = _srs_config->get_http_api_crossdomain(); - if ((err = cors->initialize(mux, crossdomain_enabled)) != srs_success) { - return srs_error_wrap(err, "reload"); - } - - return err; + bool v = _srs_config->get_http_api_crossdomain(); + return conn->set_crossdomain_enabled(v); +} + +srs_error_t SrsHttpApi::start() +{ + srs_error_t err = srs_success; + + bool v = _srs_config->get_http_api_crossdomain(); + if ((err = conn->set_crossdomain_enabled(v)) != srs_success) { + return srs_error_wrap(err, "set cors=%d", v); + } + + if ((err = skt->initialize()) != srs_success) { + return srs_error_wrap(err, "init socket"); + } + + return conn->start(); +} + +string SrsHttpApi::remote_ip() +{ + return conn->remote_ip(); +} + +const SrsContextId& SrsHttpApi::get_id() +{ + return conn->get_id(); } diff --git a/trunk/src/app/srs_app_http_api.hpp b/trunk/src/app/srs_app_http_api.hpp index b94dfa46c..dbdeafbed 100644 --- a/trunk/src/app/srs_app_http_api.hpp +++ b/trunk/src/app/srs_app_http_api.hpp @@ -26,7 +26,6 @@ #include -class SrsStSocket; class ISrsHttpMessage; class SrsHttpParser; class SrsHttpHandler; @@ -34,11 +33,21 @@ class SrsServer; class SrsRtcServer; class SrsJsonObject; class SrsSdp; +class SrsRequest; +class ISrsHttpResponseWriter; +class SrsHttpConn; + +#include #include #include #include #include +#include + +extern srs_error_t srs_api_response(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, std::string json); +extern srs_error_t srs_api_response_code(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, int code); +extern srs_error_t srs_api_response_code(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, srs_error_t code); // For http root. class SrsGoApiRoot : public ISrsHttpHandler @@ -167,25 +176,6 @@ public: virtual srs_error_t serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r); }; -#ifdef SRS_AUTO_RTC -class SrsGoApiRtcPlay : public ISrsHttpHandler -{ -public: - static uint32_t ssrc_num; -private: - SrsRtcServer* rtc_server; -public: - SrsGoApiRtcPlay(SrsRtcServer* rtc_svr); - virtual ~SrsGoApiRtcPlay(); -public: - virtual srs_error_t serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r); -private: - virtual srs_error_t do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, SrsJsonObject* res); - srs_error_t exchange_sdp(const std::string& app, const std::string& stream, const SrsSdp& remote_sdp, SrsSdp& local_sdp); - srs_error_t check_remote_sdp(const SrsSdp& remote_sdp); -}; -#endif - class SrsGoApiClients : public ISrsHttpHandler { public: @@ -241,7 +231,7 @@ public: virtual srs_error_t serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r); }; -#ifdef SRS_AUTO_GB28181 +#ifdef SRS_GB28181 class SrsGoApiGb28181 : public ISrsHttpHandler { public: @@ -254,7 +244,7 @@ private: }; #endif -#ifdef SRS_AUTO_GPERF +#ifdef SRS_GPERF class SrsGoApiTcmalloc : public ISrsHttpHandler { public: @@ -265,25 +255,49 @@ public: }; #endif -class SrsHttpApi : virtual public SrsConnection, virtual public ISrsReloadHandler +// Handle the HTTP API request. +class SrsHttpApi : virtual public ISrsStartableConneciton, virtual public ISrsHttpConnOwner + , virtual public ISrsReloadHandler { private: - SrsHttpParser* parser; - SrsHttpCorsMux* cors; - SrsHttpServeMux* mux; + // The manager object to manage the connection. + ISrsResourceManager* manager; + SrsTcpConnection* skt; + SrsSslConnection* ssl; + SrsHttpConn* conn; public: - SrsHttpApi(IConnectionManager* cm, srs_netfd_t fd, SrsHttpServeMux* m, std::string cip); + SrsHttpApi(bool https, ISrsResourceManager* cm, srs_netfd_t fd, SrsHttpServeMux* m, std::string cip, int port); virtual ~SrsHttpApi(); +// Interface ISrsHttpConnOwner. +public: + virtual srs_error_t on_start(); + virtual srs_error_t on_http_message(ISrsHttpMessage* r, SrsHttpResponseWriter* w); + virtual srs_error_t on_message_done(ISrsHttpMessage* r, SrsHttpResponseWriter* w); + virtual srs_error_t on_conn_done(srs_error_t r0); +// Interface ISrsResource. +public: + virtual std::string desc(); // Interface ISrsKbpsDelta public: virtual void remark(int64_t* in, int64_t* out); -protected: - virtual srs_error_t do_cycle(); -private: - virtual srs_error_t process_request(ISrsHttpResponseWriter* w, ISrsHttpMessage* r); // Interface ISrsReloadHandler public: virtual srs_error_t on_reload_http_api_crossdomain(); +// Extract APIs from SrsTcpConnection. +// Interface ISrsStartable +public: + // Start the client green thread. + // when server get a client from listener, + // 1. server will create an concrete connection(for instance, RTMP connection), + // 2. then add connection to its connection manager, + // 3. start the client thread by invoke this start() + // when client cycle thread stop, invoke the on_thread_stop(), which will use server + // To remove the client by server->remove(this). + virtual srs_error_t start(); +// Interface ISrsConnection. +public: + virtual std::string remote_ip(); + virtual const SrsContextId& get_id(); }; #endif diff --git a/trunk/src/app/srs_app_http_conn.cpp b/trunk/src/app/srs_app_http_conn.cpp index 8f9429cbc..05d8cada3 100644 --- a/trunk/src/app/srs_app_http_conn.cpp +++ b/trunk/src/app/srs_app_http_conn.cpp @@ -59,87 +59,122 @@ using namespace std; #include #include -SrsHttpConn::SrsHttpConn(IConnectionManager* cm, srs_netfd_t fd, ISrsHttpServeMux* m, string cip) : SrsConnection(cm, fd, cip) +ISrsHttpConnOwner::ISrsHttpConnOwner() +{ +} + +ISrsHttpConnOwner::~ISrsHttpConnOwner() +{ +} + +SrsHttpConn::SrsHttpConn(ISrsHttpConnOwner* handler, ISrsProtocolReadWriter* fd, ISrsHttpServeMux* m, string cip, int cport) { parser = new SrsHttpParser(); cors = new SrsHttpCorsMux(); http_mux = m; + handler_ = handler; + + skt = fd; + ip = cip; + port = cport; + create_time = srsu2ms(srs_get_system_time()); + clk = new SrsWallClock(); + kbps = new SrsKbps(clk); + kbps->set_io(skt, skt); + trd = new SrsSTCoroutine("http", this, _srs_context->get_id()); } SrsHttpConn::~SrsHttpConn() { + trd->interrupt(); + srs_freep(trd); + srs_freep(parser); srs_freep(cors); + + srs_freep(kbps); + srs_freep(clk); +} + +std::string SrsHttpConn::desc() +{ + return "HttpConn"; } void SrsHttpConn::remark(int64_t* in, int64_t* out) { - // TODO: FIXME: implements it + kbps->remark(in, out); +} + +srs_error_t SrsHttpConn::start() +{ + srs_error_t err = srs_success; + + if ((err = trd->start()) != srs_success) { + return srs_error_wrap(err, "coroutine"); + } + + return err; +} + +srs_error_t SrsHttpConn::cycle() +{ + srs_error_t err = do_cycle(); + + // Notify handler to handle it. + // @remark The error may be transformed by handler. + err = handler_->on_conn_done(err); + + // success. + if (err == srs_success) { + srs_trace("client finished."); + return err; + } + + // It maybe success with message. + if (srs_error_code(err) == ERROR_SUCCESS) { + srs_trace("client finished%s.", srs_error_summary(err).c_str()); + srs_freep(err); + return err; + } + + // client close peer. + // TODO: FIXME: Only reset the error when client closed it. + if (srs_is_client_gracefully_close(err)) { + srs_warn("client disconnect peer. ret=%d", srs_error_code(err)); + } else if (srs_is_server_gracefully_close(err)) { + srs_warn("server disconnect. ret=%d", srs_error_code(err)); + } else { + srs_error("serve error %s", srs_error_desc(err).c_str()); + } + + srs_freep(err); + return srs_success; } srs_error_t SrsHttpConn::do_cycle() { srs_error_t err = srs_success; - // initialize parser - if ((err = parser->initialize(HTTP_REQUEST, false)) != srs_success) { - return srs_error_wrap(err, "init parser for %s", ip.c_str()); - } - // set the recv timeout, for some clients never disconnect the connection. // @see https://github.com/ossrs/srs/issues/398 skt->set_recv_timeout(SRS_HTTP_RECV_TIMEOUT); - + + // initialize parser + if ((err = parser->initialize(HTTP_REQUEST)) != srs_success) { + return srs_error_wrap(err, "init parser for %s", ip.c_str()); + } + + // Notify the handler that we are starting to process the connection. + if ((err = handler_->on_start()) != srs_success) { + return srs_error_wrap(err, "start"); + } + SrsRequest* last_req = NULL; SrsAutoFree(SrsRequest, last_req); - - // initialize the cors, which will proxy to mux. - bool crossdomain_enabled = _srs_config->get_http_stream_crossdomain(); - if ((err = cors->initialize(http_mux, crossdomain_enabled)) != srs_success) { - return srs_error_wrap(err, "init cors"); - } - - // process http messages. - for (int req_id = 0; (err = trd->pull()) == srs_success; req_id++) { - // Try to receive a message from http. - srs_trace("HTTP client ip=%s, request=%d, to=%dms", ip.c_str(), req_id, srsu2ms(SRS_HTTP_RECV_TIMEOUT)); - // get a http message - ISrsHttpMessage* req = NULL; - if ((err = parser->parse_message(skt, &req)) != srs_success) { - break; - } - - // if SUCCESS, always NOT-NULL. - // always free it in this scope. - srs_assert(req); - SrsAutoFree(ISrsHttpMessage, req); - - // Attach owner connection to message. - SrsHttpMessage* hreq = (SrsHttpMessage*)req; - hreq->set_connection(this); - - // copy request to last request object. - srs_freep(last_req); - last_req = hreq->to_request(hreq->host()); - - // may should discard the body. - if ((err = on_got_http_message(req)) != srs_success) { - break; - } - - // ok, handle http request. - SrsHttpResponseWriter writer(skt); - if ((err = process_request(&writer, req)) != srs_success) { - break; - } - - // donot keep alive, disconnect it. - // @see https://github.com/ossrs/srs/issues/399 - if (!req->is_keep_alive()) { - break; - } - } + // process all http messages. + err = process_requests(&last_req); srs_error_t r0 = srs_success; if ((r0 = on_disconnect(last_req)) != srs_success) { @@ -150,11 +185,65 @@ srs_error_t SrsHttpConn::do_cycle() return err; } -srs_error_t SrsHttpConn::process_request(ISrsHttpResponseWriter* w, ISrsHttpMessage* r) +srs_error_t SrsHttpConn::process_requests(SrsRequest** preq) +{ + srs_error_t err = srs_success; + + for (int req_id = 0; ; req_id++) { + if ((err = trd->pull()) != srs_success) { + return srs_error_wrap(err, "pull"); + } + + // get a http message + ISrsHttpMessage* req = NULL; + if ((err = parser->parse_message(skt, &req)) != srs_success) { + return srs_error_wrap(err, "parse message"); + } + + // if SUCCESS, always NOT-NULL. + // always free it in this scope. + srs_assert(req); + SrsAutoFree(ISrsHttpMessage, req); + + // Attach owner connection to message. + SrsHttpMessage* hreq = (SrsHttpMessage*)req; + hreq->set_connection(this); + + // copy request to last request object. + srs_freep(*preq); + *preq = hreq->to_request(hreq->host()); + + // may should discard the body. + SrsHttpResponseWriter writer(skt); + if ((err = handler_->on_http_message(req, &writer)) != srs_success) { + return srs_error_wrap(err, "on http message"); + } + + // ok, handle http request. + if ((err = process_request(&writer, req, req_id)) != srs_success) { + return srs_error_wrap(err, "process request=%d", req_id); + } + + // After the request is processed. + if ((err = handler_->on_message_done(req, &writer)) != srs_success) { + return srs_error_wrap(err, "on message done"); + } + + // donot keep alive, disconnect it. + // @see https://github.com/ossrs/srs/issues/399 + if (!req->is_keep_alive()) { + break; + } + } + + return err; +} + +srs_error_t SrsHttpConn::process_request(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, int rid) { srs_error_t err = srs_success; - srs_trace("HTTP %s %s, content-length=%" PRId64 "", + srs_trace("HTTP #%d %s:%d %s %s, content-length=%" PRId64 "", rid, ip.c_str(), port, r->method_str().c_str(), r->url().c_str(), r->content_length()); // use cors server mux to serve http request, which will proxy to http_remux. @@ -171,49 +260,99 @@ srs_error_t SrsHttpConn::on_disconnect(SrsRequest* req) return srs_success; } -srs_error_t SrsHttpConn::on_reload_http_stream_crossdomain() +ISrsHttpConnOwner* SrsHttpConn::handler() +{ + return handler_; +} + +srs_error_t SrsHttpConn::pull() +{ + return trd->pull(); +} + +srs_error_t SrsHttpConn::set_crossdomain_enabled(bool v) { srs_error_t err = srs_success; - + // initialize the cors, which will proxy to mux. - bool crossdomain_enabled = _srs_config->get_http_stream_crossdomain(); - if ((err = cors->initialize(http_mux, crossdomain_enabled)) != srs_success) { - return srs_error_wrap(err, "init mux"); + if ((err = cors->initialize(http_mux, v)) != srs_success) { + return srs_error_wrap(err, "init cors"); } - + return err; } -SrsResponseOnlyHttpConn::SrsResponseOnlyHttpConn(IConnectionManager* cm, srs_netfd_t fd, ISrsHttpServeMux* m, string cip) -: SrsHttpConn(cm, fd, m, cip) +srs_error_t SrsHttpConn::set_jsonp(bool v) { + parser->set_jsonp(v); + return srs_success; +} + +string SrsHttpConn::remote_ip() +{ + return ip; +} + +const SrsContextId& SrsHttpConn::get_id() +{ + return trd->cid(); +} + +void SrsHttpConn::expire() +{ + trd->interrupt(); +} + +SrsResponseOnlyHttpConn::SrsResponseOnlyHttpConn(bool https, ISrsResourceManager* cm, srs_netfd_t fd, ISrsHttpServeMux* m, string cip, int port) +{ + // Create a identify for this client. + _srs_context->set_id(_srs_context->generate_id()); + + manager = cm; + skt = new SrsTcpConnection(fd); + + if (https) { + ssl = new SrsSslConnection(skt); + conn = new SrsHttpConn(this, ssl, m, cip, port); + } else { + ssl = NULL; + conn = new SrsHttpConn(this, skt, m, cip, port); + } + + _srs_config->subscribe(this); } SrsResponseOnlyHttpConn::~SrsResponseOnlyHttpConn() { + _srs_config->unsubscribe(this); + + srs_freep(conn); + srs_freep(ssl); + srs_freep(skt); } 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"); + ISrsProtocolReadWriter* io = skt; + if (ssl) { + io = ssl; } // Check user interrupt by interval. - skt.set_recv_timeout(3 * SRS_UTIME_SECONDS); + io->set_recv_timeout(3 * SRS_UTIME_SECONDS); + // We start a socket to read the stfd, which is writing by conn. + // It's ok, because conn never read it after processing the HTTP request. // drop all request body. char body[4096]; while (true) { - if ((err = trd->pull()) != srs_success) { + if ((err = conn->pull()) != srs_success) { return srs_error_wrap(err, "timeout"); } - if ((err = skt.read(body, 4096, NULL)) != srs_success) { + if ((err = io->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); @@ -227,18 +366,51 @@ srs_error_t SrsResponseOnlyHttpConn::pop_message(ISrsHttpMessage** preq) return err; } -srs_error_t SrsResponseOnlyHttpConn::on_got_http_message(ISrsHttpMessage* msg) +srs_error_t SrsResponseOnlyHttpConn::on_reload_http_stream_crossdomain() +{ + bool v = _srs_config->get_http_stream_crossdomain(); + return conn->set_crossdomain_enabled(v); +} + +srs_error_t SrsResponseOnlyHttpConn::on_start() { srs_error_t err = srs_success; + + if (ssl) { + srs_utime_t starttime = srs_update_system_time(); + string crt_file = _srs_config->get_https_stream_ssl_cert(); + string key_file = _srs_config->get_https_stream_ssl_key(); + if ((err = ssl->handshake(key_file, crt_file)) != srs_success) { + return srs_error_wrap(err, "handshake"); + } + + int cost = srsu2msi(srs_update_system_time() - starttime); + srs_trace("https: stream server done, use key %s and cert %s, cost=%dms", + key_file.c_str(), crt_file.c_str(), cost); + } + + return err; +} + +srs_error_t SrsResponseOnlyHttpConn::on_http_message(ISrsHttpMessage* r, SrsHttpResponseWriter* w) +{ + srs_error_t err = srs_success; + + // After parsed the message, set the schema to https. + if (ssl) { + SrsHttpMessage* hm = dynamic_cast(r); + hm->set_https(true); + } - ISrsHttpResponseReader* br = msg->body_reader(); + ISrsHttpResponseReader* br = r->body_reader(); // when not specified the content length, ignore. - if (msg->content_length() == -1) { + if (r->content_length() == -1) { return err; } - // drop all request body. + // Drop all request body. + // TODO: Should we set timeout for max reading? char body[4096]; while (!br->eof()) { if ((err = br->read(body, 4096, NULL)) != srs_success) { @@ -249,9 +421,67 @@ srs_error_t SrsResponseOnlyHttpConn::on_got_http_message(ISrsHttpMessage* msg) return err; } -void SrsResponseOnlyHttpConn::expire() +srs_error_t SrsResponseOnlyHttpConn::on_message_done(ISrsHttpMessage* r, SrsHttpResponseWriter* w) { - SrsHttpConn::expire(); + return srs_success; +} + +srs_error_t SrsResponseOnlyHttpConn::on_conn_done(srs_error_t r0) +{ + // Because we use manager to manage this object, + // not the http connection object, so we must remove it here. + manager->remove(this); + + return r0; +} + +srs_error_t SrsResponseOnlyHttpConn::set_tcp_nodelay(bool v) +{ + return skt->set_tcp_nodelay(v); +} + +srs_error_t SrsResponseOnlyHttpConn::set_socket_buffer(srs_utime_t buffer_v) +{ + return skt->set_socket_buffer(buffer_v); +} + +std::string SrsResponseOnlyHttpConn::desc() +{ + if (ssl) { + return "HttpsStream"; + } + return "HttpStream"; +} + +std::string SrsResponseOnlyHttpConn::remote_ip() +{ + return conn->remote_ip(); +} + +const SrsContextId& SrsResponseOnlyHttpConn::get_id() +{ + return conn->get_id(); +} + +srs_error_t SrsResponseOnlyHttpConn::start() +{ + srs_error_t err = srs_success; + + bool v = _srs_config->get_http_stream_crossdomain(); + if ((err = conn->set_crossdomain_enabled(v)) != srs_success) { + return srs_error_wrap(err, "set cors=%d", v); + } + + if ((err = skt->initialize()) != srs_success) { + return srs_error_wrap(err, "init socket"); + } + + return conn->start(); +} + +void SrsResponseOnlyHttpConn::remark(int64_t* in, int64_t* out) +{ + conn->remark(in, out); } SrsHttpServer::SrsHttpServer(SrsServer* svr) @@ -273,7 +503,7 @@ srs_error_t SrsHttpServer::initialize() // for SRS go-sharp to detect the status of HTTP server of SRS HTTP FLV Cluster. if ((err = http_static->mux.handle("/api/v1/versions", new SrsGoApiVersion())) != srs_success) { - return srs_error_wrap(err, "handle versin"); + return srs_error_wrap(err, "handle versions"); } if ((err = http_stream->initialize()) != srs_success) { diff --git a/trunk/src/app/srs_app_http_conn.hpp b/trunk/src/app/srs_app_http_conn.hpp index 7529cfc5b..8b97e2dec 100644 --- a/trunk/src/app/srs_app_http_conn.hpp +++ b/trunk/src/app/srs_app_http_conn.hpp @@ -33,7 +33,7 @@ #include #include #include -#include +#include #include #include @@ -50,47 +50,110 @@ class SrsSharedPtrMessage; class SrsRequest; class SrsFastStream; class SrsHttpUri; -class SrsConnection; class SrsHttpMessage; class SrsHttpStreamServer; class SrsHttpStaticServer; +// The owner of HTTP connection. +class ISrsHttpConnOwner +{ +public: + ISrsHttpConnOwner(); + virtual ~ISrsHttpConnOwner(); +public: + // When start the coroutine to process connection. + virtual srs_error_t on_start() = 0; + // Handle the HTTP message r, which may be parsed partially. + // For the static service or api, discard any body. + // For the stream caster, for instance, http flv streaming, may discard the flv header or not. + virtual srs_error_t on_http_message(ISrsHttpMessage* r, SrsHttpResponseWriter* w) = 0; + // When message is processed, we may need to do more things. + virtual srs_error_t on_message_done(ISrsHttpMessage* r, SrsHttpResponseWriter* w) = 0; + // When connection is destroy, should use manager to dispose it. + // The r0 is the original error, we will use the returned new error. + virtual srs_error_t on_conn_done(srs_error_t r0) = 0; +}; + // The http connection which request the static or stream content. -class SrsHttpConn : public SrsConnection +class SrsHttpConn : virtual public ISrsStartableConneciton, virtual public ISrsCoroutineHandler + , virtual public ISrsExpire { protected: SrsHttpParser* parser; ISrsHttpServeMux* http_mux; SrsHttpCorsMux* cors; + ISrsHttpConnOwner* handler_; +protected: + ISrsProtocolReadWriter* skt; + // Each connection start a green thread, + // when thread stop, the connection will be delete by server. + SrsCoroutine* trd; + // The ip and port of client. + std::string ip; + int port; +private: + // The connection total kbps. + // not only the rtmp or http connection, all type of connection are + // need to statistic the kbps of io. + // The SrsStatistic will use it indirectly to statistic the bytes delta of current connection. + SrsKbps* kbps; + SrsWallClock* clk; + // The create time in milliseconds. + // for current connection to log self create time and calculate the living time. + int64_t create_time; public: - SrsHttpConn(IConnectionManager* cm, srs_netfd_t fd, ISrsHttpServeMux* m, std::string cip); + SrsHttpConn(ISrsHttpConnOwner* handler, ISrsProtocolReadWriter* fd, ISrsHttpServeMux* m, std::string cip, int port); virtual ~SrsHttpConn(); +// Interface ISrsResource. +public: + virtual std::string desc(); // Interface ISrsKbpsDelta public: virtual void remark(int64_t* in, int64_t* out); -protected: - virtual srs_error_t do_cycle(); -protected: - // When got http message, - // for the static service or api, discard any body. - // for the stream caster, for instance, http flv streaming, may discard the flv header or not. - virtual srs_error_t on_got_http_message(ISrsHttpMessage* msg) = 0; +// Interface ISrsStartable +public: + virtual srs_error_t start(); +// Interface ISrsOneCycleThreadHandler +public: + virtual srs_error_t cycle(); private: - virtual srs_error_t process_request(ISrsHttpResponseWriter* w, ISrsHttpMessage* r); + virtual srs_error_t do_cycle(); + virtual srs_error_t process_requests(SrsRequest** preq); + virtual srs_error_t process_request(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, int rid); // When the connection disconnect, call this method. // e.g. log msg of connection and report to other system. // @param request: request which is converted by the last http message. virtual srs_error_t on_disconnect(SrsRequest* req); -// Interface ISrsReloadHandler public: - virtual srs_error_t on_reload_http_stream_crossdomain(); + // Get the HTTP message handler. + virtual ISrsHttpConnOwner* handler(); + // Whether the connection coroutine is error or terminated. + virtual srs_error_t pull(); + // Whether enable the CORS(cross-domain). + virtual srs_error_t set_crossdomain_enabled(bool v); + // Whether enable the JSONP. + virtual srs_error_t set_jsonp(bool v); +// Interface ISrsConnection. +public: + virtual std::string remote_ip(); + virtual const SrsContextId& get_id(); +// Interface ISrsExpire. +public: + virtual void expire(); }; // Drop body of request, only process the response. -class SrsResponseOnlyHttpConn : public SrsHttpConn +class SrsResponseOnlyHttpConn : virtual public ISrsStartableConneciton, virtual public ISrsHttpConnOwner + , virtual public ISrsReloadHandler { +private: + // The manager object to manage the connection. + ISrsResourceManager* manager; + SrsTcpConnection* skt; + SrsSslConnection* ssl; + SrsHttpConn* conn; public: - SrsResponseOnlyHttpConn(IConnectionManager* cm, srs_netfd_t fd, ISrsHttpServeMux* m, std::string cip); + SrsResponseOnlyHttpConn(bool https, ISrsResourceManager* cm, srs_netfd_t fd, ISrsHttpServeMux* m, std::string cip, int port); virtual ~SrsResponseOnlyHttpConn(); public: // Directly read a HTTP request message. @@ -99,11 +162,34 @@ public: // @see https://github.com/ossrs/srs/issues/636#issuecomment-298208427 // @remark Should only used in HTTP-FLV streaming connection. virtual srs_error_t pop_message(ISrsHttpMessage** preq); +// Interface ISrsReloadHandler public: - virtual srs_error_t on_got_http_message(ISrsHttpMessage* msg); + virtual srs_error_t on_reload_http_stream_crossdomain(); +// Interface ISrsHttpConnOwner. public: - // Set connection to expired. - virtual void expire(); + virtual srs_error_t on_start(); + virtual srs_error_t on_http_message(ISrsHttpMessage* r, SrsHttpResponseWriter* w); + virtual srs_error_t on_message_done(ISrsHttpMessage* r, SrsHttpResponseWriter* w); + virtual srs_error_t on_conn_done(srs_error_t r0); +// Extract APIs from SrsTcpConnection. +public: + // Set socket option TCP_NODELAY. + virtual srs_error_t set_tcp_nodelay(bool v); + // Set socket option SO_SNDBUF in srs_utime_t. + virtual srs_error_t set_socket_buffer(srs_utime_t buffer_v); +// Interface ISrsResource. +public: + virtual std::string desc(); +// Interface ISrsConnection. +public: + virtual std::string remote_ip(); + virtual const SrsContextId& get_id(); +// Interface ISrsStartable +public: + virtual srs_error_t start(); +// Interface ISrsKbpsDelta +public: + virtual void remark(int64_t* in, int64_t* out); }; // The http server, use http stream or static server to serve requests. diff --git a/trunk/src/app/srs_app_http_hooks.cpp b/trunk/src/app/srs_app_http_hooks.cpp index 9fc2292c0..d899570e2 100644 --- a/trunk/src/app/srs_app_http_hooks.cpp +++ b/trunk/src/app/srs_app_http_hooks.cpp @@ -60,13 +60,13 @@ srs_error_t SrsHttpHooks::on_connect(string url, SrsRequest* req) { srs_error_t err = srs_success; - int client_id = _srs_context->get_id(); + SrsContextId cid = _srs_context->get_id(); SrsJsonObject* obj = SrsJsonAny::object(); SrsAutoFree(SrsJsonObject, obj); obj->set("action", SrsJsonAny::str("on_connect")); - obj->set("client_id", SrsJsonAny::integer(client_id)); + obj->set("client_id", SrsJsonAny::str(cid.c_str())); obj->set("ip", SrsJsonAny::str(req->ip.c_str())); obj->set("vhost", SrsJsonAny::str(req->vhost.c_str())); obj->set("app", SrsJsonAny::str(req->app.c_str())); @@ -79,12 +79,12 @@ srs_error_t SrsHttpHooks::on_connect(string url, SrsRequest* req) SrsHttpClient http; if ((err = do_post(&http, url, data, status_code, res)) != srs_success) { - return srs_error_wrap(err, "http: on_connect failed, client_id=%d, url=%s, request=%s, response=%s, code=%d", - client_id, url.c_str(), data.c_str(), res.c_str(), status_code); + return srs_error_wrap(err, "http: on_connect failed, client_id=%s, url=%s, request=%s, response=%s, code=%d", + cid.c_str(), url.c_str(), data.c_str(), res.c_str(), status_code); } - srs_trace("http: on_connect ok, client_id=%d, url=%s, request=%s, response=%s", - client_id, url.c_str(), data.c_str(), res.c_str()); + srs_trace("http: on_connect ok, client_id=%s, url=%s, request=%s, response=%s", + cid.c_str(), url.c_str(), data.c_str(), res.c_str()); return err; } @@ -93,13 +93,13 @@ void SrsHttpHooks::on_close(string url, SrsRequest* req, int64_t send_bytes, int { srs_error_t err = srs_success; - int client_id = _srs_context->get_id(); + SrsContextId cid = _srs_context->get_id(); SrsJsonObject* obj = SrsJsonAny::object(); SrsAutoFree(SrsJsonObject, obj); obj->set("action", SrsJsonAny::str("on_close")); - obj->set("client_id", SrsJsonAny::integer(client_id)); + obj->set("client_id", SrsJsonAny::str(cid.c_str())); obj->set("ip", SrsJsonAny::str(req->ip.c_str())); obj->set("vhost", SrsJsonAny::str(req->vhost.c_str())); obj->set("app", SrsJsonAny::str(req->app.c_str())); @@ -114,13 +114,13 @@ void SrsHttpHooks::on_close(string url, SrsRequest* req, int64_t send_bytes, int if ((err = do_post(&http, url, data, status_code, res)) != srs_success) { int ret = srs_error_code(err); srs_freep(err); - srs_warn("http: ignore on_close failed, client_id=%d, url=%s, request=%s, response=%s, code=%d, ret=%d", - client_id, url.c_str(), data.c_str(), res.c_str(), status_code, ret); + srs_warn("http: ignore on_close failed, client_id=%s, url=%s, request=%s, response=%s, code=%d, ret=%d", + cid.c_str(), url.c_str(), data.c_str(), res.c_str(), status_code, ret); return; } - srs_trace("http: on_close ok, client_id=%d, url=%s, request=%s, response=%s", - client_id, url.c_str(), data.c_str(), res.c_str()); + srs_trace("http: on_close ok, client_id=%s, url=%s, request=%s, response=%s", + cid.c_str(), url.c_str(), data.c_str(), res.c_str()); return; } @@ -129,13 +129,13 @@ srs_error_t SrsHttpHooks::on_publish(string url, SrsRequest* req) { srs_error_t err = srs_success; - int client_id = _srs_context->get_id(); + SrsContextId cid = _srs_context->get_id(); SrsJsonObject* obj = SrsJsonAny::object(); SrsAutoFree(SrsJsonObject, obj); obj->set("action", SrsJsonAny::str("on_publish")); - obj->set("client_id", SrsJsonAny::integer(client_id)); + obj->set("client_id", SrsJsonAny::str(cid.c_str())); obj->set("ip", SrsJsonAny::str(req->ip.c_str())); obj->set("vhost", SrsJsonAny::str(req->vhost.c_str())); obj->set("app", SrsJsonAny::str(req->app.c_str())); @@ -149,12 +149,12 @@ srs_error_t SrsHttpHooks::on_publish(string url, SrsRequest* req) SrsHttpClient http; if ((err = do_post(&http, url, data, status_code, res)) != srs_success) { - return srs_error_wrap(err, "http: on_publish failed, client_id=%d, url=%s, request=%s, response=%s, code=%d", - client_id, url.c_str(), data.c_str(), res.c_str(), status_code); + return srs_error_wrap(err, "http: on_publish failed, client_id=%s, url=%s, request=%s, response=%s, code=%d", + cid.c_str(), url.c_str(), data.c_str(), res.c_str(), status_code); } - srs_trace("http: on_publish ok, client_id=%d, url=%s, request=%s, response=%s", - client_id, url.c_str(), data.c_str(), res.c_str()); + srs_trace("http: on_publish ok, client_id=%s, url=%s, request=%s, response=%s", + cid.c_str(), url.c_str(), data.c_str(), res.c_str()); return err; } @@ -163,13 +163,13 @@ void SrsHttpHooks::on_unpublish(string url, SrsRequest* req) { srs_error_t err = srs_success; - int client_id = _srs_context->get_id(); + SrsContextId cid = _srs_context->get_id(); SrsJsonObject* obj = SrsJsonAny::object(); SrsAutoFree(SrsJsonObject, obj); obj->set("action", SrsJsonAny::str("on_unpublish")); - obj->set("client_id", SrsJsonAny::integer(client_id)); + obj->set("client_id", SrsJsonAny::str(cid.c_str())); obj->set("ip", SrsJsonAny::str(req->ip.c_str())); obj->set("vhost", SrsJsonAny::str(req->vhost.c_str())); obj->set("app", SrsJsonAny::str(req->app.c_str())); @@ -184,13 +184,13 @@ void SrsHttpHooks::on_unpublish(string url, SrsRequest* req) if ((err = do_post(&http, url, data, status_code, res)) != srs_success) { int ret = srs_error_code(err); srs_freep(err); - srs_warn("http: ignore on_unpublish failed, client_id=%d, url=%s, request=%s, response=%s, status=%d, ret=%d", - client_id, url.c_str(), data.c_str(), res.c_str(), status_code, ret); + srs_warn("http: ignore on_unpublish failed, client_id=%s, url=%s, request=%s, response=%s, status=%d, ret=%d", + cid.c_str(), url.c_str(), data.c_str(), res.c_str(), status_code, ret); return; } - srs_trace("http: on_unpublish ok, client_id=%d, url=%s, request=%s, response=%s", - client_id, url.c_str(), data.c_str(), res.c_str()); + srs_trace("http: on_unpublish ok, client_id=%s, url=%s, request=%s, response=%s", + cid.c_str(), url.c_str(), data.c_str(), res.c_str()); return; } @@ -199,13 +199,13 @@ srs_error_t SrsHttpHooks::on_play(string url, SrsRequest* req) { srs_error_t err = srs_success; - int client_id = _srs_context->get_id(); + SrsContextId cid = _srs_context->get_id(); SrsJsonObject* obj = SrsJsonAny::object(); SrsAutoFree(SrsJsonObject, obj); obj->set("action", SrsJsonAny::str("on_play")); - obj->set("client_id", SrsJsonAny::integer(client_id)); + obj->set("client_id", SrsJsonAny::str(cid.c_str())); obj->set("ip", SrsJsonAny::str(req->ip.c_str())); obj->set("vhost", SrsJsonAny::str(req->vhost.c_str())); obj->set("app", SrsJsonAny::str(req->app.c_str())); @@ -219,12 +219,12 @@ srs_error_t SrsHttpHooks::on_play(string url, SrsRequest* req) SrsHttpClient http; if ((err = do_post(&http, url, data, status_code, res)) != srs_success) { - return srs_error_wrap(err, "http: on_play failed, client_id=%d, url=%s, request=%s, response=%s, status=%d", - client_id, url.c_str(), data.c_str(), res.c_str(), status_code); + return srs_error_wrap(err, "http: on_play failed, client_id=%s, url=%s, request=%s, response=%s, status=%d", + cid.c_str(), url.c_str(), data.c_str(), res.c_str(), status_code); } - srs_trace("http: on_play ok, client_id=%d, url=%s, request=%s, response=%s", - client_id, url.c_str(), data.c_str(), res.c_str()); + srs_trace("http: on_play ok, client_id=%s, url=%s, request=%s, response=%s", + cid.c_str(), url.c_str(), data.c_str(), res.c_str()); return err; } @@ -233,13 +233,13 @@ void SrsHttpHooks::on_stop(string url, SrsRequest* req) { srs_error_t err = srs_success; - int client_id = _srs_context->get_id(); + SrsContextId cid = _srs_context->get_id(); SrsJsonObject* obj = SrsJsonAny::object(); SrsAutoFree(SrsJsonObject, obj); obj->set("action", SrsJsonAny::str("on_stop")); - obj->set("client_id", SrsJsonAny::integer(client_id)); + obj->set("client_id", SrsJsonAny::str(cid.c_str())); obj->set("ip", SrsJsonAny::str(req->ip.c_str())); obj->set("vhost", SrsJsonAny::str(req->vhost.c_str())); obj->set("app", SrsJsonAny::str(req->app.c_str())); @@ -254,29 +254,29 @@ void SrsHttpHooks::on_stop(string url, SrsRequest* req) if ((err = do_post(&http, url, data, status_code, res)) != srs_success) { int ret = srs_error_code(err); srs_freep(err); - srs_warn("http: ignore on_stop failed, client_id=%d, url=%s, request=%s, response=%s, code=%d, ret=%d", - client_id, url.c_str(), data.c_str(), res.c_str(), status_code, ret); + srs_warn("http: ignore on_stop failed, client_id=%s, url=%s, request=%s, response=%s, code=%d, ret=%d", + cid.c_str(), url.c_str(), data.c_str(), res.c_str(), status_code, ret); return; } - srs_trace("http: on_stop ok, client_id=%d, url=%s, request=%s, response=%s", - client_id, url.c_str(), data.c_str(), res.c_str()); + srs_trace("http: on_stop ok, client_id=%s, url=%s, request=%s, response=%s", + cid.c_str(), url.c_str(), data.c_str(), res.c_str()); return; } -srs_error_t SrsHttpHooks::on_dvr(int cid, string url, SrsRequest* req, string file) +srs_error_t SrsHttpHooks::on_dvr(SrsContextId c, string url, SrsRequest* req, string file) { srs_error_t err = srs_success; - int client_id = cid; + SrsContextId cid = c; std::string cwd = _srs_config->cwd(); SrsJsonObject* obj = SrsJsonAny::object(); SrsAutoFree(SrsJsonObject, obj); obj->set("action", SrsJsonAny::str("on_dvr")); - obj->set("client_id", SrsJsonAny::integer(client_id)); + obj->set("client_id", SrsJsonAny::str(cid.c_str())); obj->set("ip", SrsJsonAny::str(req->ip.c_str())); obj->set("vhost", SrsJsonAny::str(req->vhost.c_str())); obj->set("app", SrsJsonAny::str(req->app.c_str())); @@ -291,21 +291,21 @@ srs_error_t SrsHttpHooks::on_dvr(int cid, string url, SrsRequest* req, string fi SrsHttpClient http; if ((err = do_post(&http, url, data, status_code, res)) != srs_success) { - return srs_error_wrap(err, "http post on_dvr uri failed, client_id=%d, url=%s, request=%s, response=%s, code=%d", - client_id, url.c_str(), data.c_str(), res.c_str(), status_code); + return srs_error_wrap(err, "http post on_dvr uri failed, client_id=%s, url=%s, request=%s, response=%s, code=%d", + cid.c_str(), url.c_str(), data.c_str(), res.c_str(), status_code); } - srs_trace("http hook on_dvr success. client_id=%d, url=%s, request=%s, response=%s", - client_id, url.c_str(), data.c_str(), res.c_str()); + srs_trace("http hook on_dvr success. client_id=%s, url=%s, request=%s, response=%s", + cid.c_str(), url.c_str(), data.c_str(), res.c_str()); return err; } -srs_error_t SrsHttpHooks::on_hls(int cid, string url, SrsRequest* req, string file, string ts_url, string m3u8, string m3u8_url, int sn, srs_utime_t duration) +srs_error_t SrsHttpHooks::on_hls(SrsContextId c, string url, SrsRequest* req, string file, string ts_url, string m3u8, string m3u8_url, int sn, srs_utime_t duration) { srs_error_t err = srs_success; - int client_id = cid; + SrsContextId cid = c; std::string cwd = _srs_config->cwd(); // the ts_url is under the same dir of m3u8_url. @@ -318,7 +318,7 @@ srs_error_t SrsHttpHooks::on_hls(int cid, string url, SrsRequest* req, string fi SrsAutoFree(SrsJsonObject, obj); obj->set("action", SrsJsonAny::str("on_hls")); - obj->set("client_id", SrsJsonAny::integer(client_id)); + obj->set("client_id", SrsJsonAny::str(cid.c_str())); obj->set("ip", SrsJsonAny::str(req->ip.c_str())); obj->set("vhost", SrsJsonAny::str(req->vhost.c_str())); obj->set("app", SrsJsonAny::str(req->app.c_str())); @@ -341,17 +341,17 @@ srs_error_t SrsHttpHooks::on_hls(int cid, string url, SrsRequest* req, string fi return srs_error_wrap(err, "http: post %s with %s, status=%d, res=%s", url.c_str(), data.c_str(), status_code, res.c_str()); } - srs_trace("http: on_hls ok, client_id=%d, url=%s, request=%s, response=%s", - client_id, url.c_str(), data.c_str(), res.c_str()); + srs_trace("http: on_hls ok, client_id=%s, url=%s, request=%s, response=%s", + cid.c_str(), url.c_str(), data.c_str(), res.c_str()); return err; } -srs_error_t SrsHttpHooks::on_hls_notify(int cid, std::string url, SrsRequest* req, std::string ts_url, int nb_notify) +srs_error_t SrsHttpHooks::on_hls_notify(SrsContextId c, std::string url, SrsRequest* req, std::string ts_url, int nb_notify) { srs_error_t err = srs_success; - int client_id = cid; + SrsContextId cid = c; std::string cwd = _srs_config->cwd(); if (srs_string_is_http(ts_url)) { @@ -371,7 +371,7 @@ srs_error_t SrsHttpHooks::on_hls_notify(int cid, std::string url, SrsRequest* re } SrsHttpClient http; - if ((err = http.initialize(uri.get_host(), uri.get_port(), SRS_HLS_NOTIFY_TIMEOUT)) != srs_success) { + if ((err = http.initialize(uri.get_schema(), uri.get_host(), uri.get_port(), SRS_HLS_NOTIFY_TIMEOUT)) != srs_success) { return srs_error_wrap(err, "http: init client for %s", url.c_str()); } @@ -406,8 +406,8 @@ srs_error_t SrsHttpHooks::on_hls_notify(int cid, std::string url, SrsRequest* re } int spenttime = (int)(srsu2ms(srs_update_system_time()) - starttime); - srs_trace("http hook on_hls_notify success. client_id=%d, url=%s, code=%d, spent=%dms, read=%dB, err=%s", - client_id, url.c_str(), msg->status_code(), spenttime, nb_read, srs_error_desc(err).c_str()); + srs_trace("http hook on_hls_notify success. client_id=%s, url=%s, code=%d, spent=%dms, read=%dB, err=%s", + cid.c_str(), url.c_str(), msg->status_code(), spenttime, nb_read, srs_error_desc(err).c_str()); // ignore any error for on_hls_notify. srs_error_reset(err); @@ -478,7 +478,7 @@ srs_error_t SrsHttpHooks::do_post(SrsHttpClient* hc, std::string url, std::strin return srs_error_wrap(err, "http: post failed. url=%s", url.c_str()); } - if ((err = hc->initialize(uri.get_host(), uri.get_port())) != srs_success) { + if ((err = hc->initialize(uri.get_schema(), uri.get_host(), uri.get_port())) != srs_success) { return srs_error_wrap(err, "http: init client"); } @@ -532,7 +532,7 @@ srs_error_t SrsHttpHooks::do_post(SrsHttpClient* hc, std::string url, std::strin } if ((res_code->to_integer()) != ERROR_SUCCESS) { - return srs_error_new(ERROR_RESPONSE_CODE, "http: response object code %d %s", res_code->to_integer(), res.c_str()); + return srs_error_new(ERROR_RESPONSE_CODE, "http: response object code %" PRId64 " %s", res_code->to_integer(), res.c_str()); } return err; diff --git a/trunk/src/app/srs_app_http_hooks.hpp b/trunk/src/app/srs_app_http_hooks.hpp index 000780c44..f9588f647 100644 --- a/trunk/src/app/srs_app_http_hooks.hpp +++ b/trunk/src/app/srs_app_http_hooks.hpp @@ -74,7 +74,7 @@ public: // ignore if empty. // @param file the file path, can be relative or absolute path. // @param cid the source connection cid, for the on_dvr is async call. - static srs_error_t on_dvr(int cid, std::string url, SrsRequest* req, std::string file); + static srs_error_t on_dvr(SrsContextId cid, std::string url, SrsRequest* req, std::string file); // When hls reap segment, callback. // @param url the api server url, to process the event. // ignore if empty. @@ -85,7 +85,7 @@ public: // @param sn the seq_no, the sequence number of ts in hls/m3u8. // @param duration the segment duration in srs_utime_t. // @param cid the source connection cid, for the on_dvr is async call. - static srs_error_t on_hls(int cid, std::string url, SrsRequest* req, std::string file, std::string ts_url, + static srs_error_t on_hls(SrsContextId cid, std::string url, SrsRequest* req, std::string file, std::string ts_url, std::string m3u8, std::string m3u8_url, int sn, srs_utime_t duration); // When hls reap segment, callback. // @param url the api server url, to process the event. @@ -93,7 +93,7 @@ public: // @param ts_url the ts uri, used to replace the variable [ts_url] in url. // @param nb_notify the max bytes to read from notify server. // @param cid the source connection cid, for the on_dvr is async call. - static srs_error_t on_hls_notify(int cid, std::string url, SrsRequest* req, std::string ts_url, int nb_notify); + static srs_error_t on_hls_notify(SrsContextId cid, std::string url, SrsRequest* req, std::string ts_url, int nb_notify); // Discover co-workers for origin cluster. static srs_error_t discover_co_workers(std::string url, std::string& host, int& port); private: diff --git a/trunk/src/app/srs_app_http_static.cpp b/trunk/src/app/srs_app_http_static.cpp index 04724bdab..a20fea72f 100644 --- a/trunk/src/app/srs_app_http_static.cpp +++ b/trunk/src/app/srs_app_http_static.cpp @@ -134,7 +134,7 @@ srs_error_t SrsVodStream::serve_flv_stream(ISrsHttpResponseWriter* w, ISrsHttpMe // send data if ((err = copy(w, fs, r, (int)left)) != srs_success) { - return srs_error_wrap(err, "read flv=%s size=%d", fullpath.c_str(), left); + return srs_error_wrap(err, "read flv=%s size=%d", fullpath.c_str(), (int)left); } return err; @@ -184,7 +184,7 @@ srs_error_t SrsVodStream::serve_mp4_stream(ISrsHttpResponseWriter* w, ISrsHttpMe // send data if ((err = copy(w, fs, r, (int)left)) != srs_success) { - return srs_error_wrap(err, "read mp4=%s size=%d", fullpath.c_str(), left); + return srs_error_wrap(err, "read mp4=%s size=%d", fullpath.c_str(), (int)left); } return err; diff --git a/trunk/src/app/srs_app_http_stream.cpp b/trunk/src/app/srs_app_http_stream.cpp index 95a67c21a..943fdf3d3 100755 --- a/trunk/src/app/srs_app_http_stream.cpp +++ b/trunk/src/app/srs_app_http_stream.cpp @@ -128,10 +128,13 @@ srs_error_t SrsBufferCache::cycle() // the stream cache will create consumer to cache stream, // which will trigger to fetch stream from origin for edge. SrsConsumer* consumer = NULL; - if ((err = source->create_consumer(NULL, consumer, false, false, true)) != srs_success) { + SrsAutoFree(SrsConsumer, consumer); + if ((err = source->create_consumer(consumer)) != srs_success) { return srs_error_wrap(err, "create consumer"); } - SrsAutoFree(SrsConsumer, consumer); + if ((err = source->consumer_dumps(consumer, false, false, true)) != srs_success) { + return srs_error_wrap(err, "dumps consumer"); + } SrsPithyPrint* pprint = SrsPithyPrint::create_http_stream_cache(); SrsAutoFree(SrsPithyPrint, pprint); @@ -583,12 +586,14 @@ srs_error_t SrsLiveStream::do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMess // create consumer of souce, ignore gop cache, use the audio gop cache. SrsConsumer* consumer = NULL; - if ((err = source->create_consumer(NULL, consumer, true, true, !enc->has_cache())) != srs_success) { + SrsAutoFree(SrsConsumer, consumer); + if ((err = source->create_consumer(consumer)) != srs_success) { return srs_error_wrap(err, "create consumer"); } - SrsAutoFree(SrsConsumer, consumer); - srs_verbose("http: consumer created success."); - + if ((err = source->consumer_dumps(consumer, true, true, !enc->has_cache())) != srs_success) { + return srs_error_wrap(err, "dumps consumer"); + } + SrsPithyPrint* pprint = SrsPithyPrint::create_http_stream(); SrsAutoFree(SrsPithyPrint, pprint); @@ -597,7 +602,7 @@ srs_error_t SrsLiveStream::do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMess // 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()); + SrsHttpConn* hc = dynamic_cast(hr->connection()); // update the statistic when source disconveried. SrsStatistic* stat = SrsStatistic::instance(); @@ -617,23 +622,29 @@ srs_error_t SrsLiveStream::do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMess return srs_error_wrap(err, "encoder dump cache"); } } - + + // Try to use fast flv encoder, remember that it maybe NULL. SrsFlvStreamEncoder* ffe = dynamic_cast(enc); + + // Note that the handler of hc now is rohc. + SrsResponseOnlyHttpConn* rohc = dynamic_cast(hc->handler()); + srs_assert(rohc); // Set the socket options for transport. bool tcp_nodelay = _srs_config->get_tcp_nodelay(req->vhost); if (tcp_nodelay) { - if ((err = hc->set_tcp_nodelay(tcp_nodelay)) != srs_success) { + if ((err = rohc->set_tcp_nodelay(tcp_nodelay)) != srs_success) { return srs_error_wrap(err, "set tcp nodelay"); } } srs_utime_t mw_sleep = _srs_config->get_mw_sleep(req->vhost); - if ((err = hc->set_socket_buffer(mw_sleep)) != srs_success) { + if ((err = rohc->set_socket_buffer(mw_sleep)) != srs_success) { return srs_error_wrap(err, "set mw_sleep %" PRId64, mw_sleep); } - SrsHttpRecvThread* trd = new SrsHttpRecvThread(hc); + // Start a thread to receive all messages from client, then drop them. + SrsHttpRecvThread* trd = new SrsHttpRecvThread(rohc); SrsAutoFree(SrsHttpRecvThread, trd); if ((err = trd->start()) != srs_success) { @@ -1136,9 +1147,9 @@ srs_error_t SrsHttpStreamServer::hijack(ISrsHttpMessage* request, ISrsHttpHandle // trigger edge to fetch from origin. bool vhost_is_edge = _srs_config->get_vhost_is_edge(r->vhost); - srs_trace("flv: source url=%s, is_edge=%d, source_id=[%d][%d]", - r->get_stream_url().c_str(), vhost_is_edge, ::getpid(), s->source_id()); - + srs_trace("flv: source url=%s, is_edge=%d, source_id=%s/%s", + r->get_stream_url().c_str(), vhost_is_edge, s->source_id().c_str(), s->pre_source_id().c_str()); + return err; } diff --git a/trunk/src/app/srs_app_hybrid.cpp b/trunk/src/app/srs_app_hybrid.cpp index 9094de4f2..a43e08800 100644 --- a/trunk/src/app/srs_app_hybrid.cpp +++ b/trunk/src/app/srs_app_hybrid.cpp @@ -151,6 +151,7 @@ srs_error_t SrsHybridServer::run() { srs_error_t err = srs_success; + // TODO: FIXME: Identify master server directly. // Run master server in this main thread. SrsServerAdapter* master_server = NULL; diff --git a/trunk/src/app/srs_app_ingest.cpp b/trunk/src/app/srs_app_ingest.cpp index 44987f16c..a8c7130e2 100644 --- a/trunk/src/app/srs_app_ingest.cpp +++ b/trunk/src/app/srs_app_ingest.cpp @@ -38,6 +38,7 @@ using namespace std; SrsIngesterFFMPEG::SrsIngesterFFMPEG() { ffmpeg = NULL; + starttime = 0; } SrsIngesterFFMPEG::~SrsIngesterFFMPEG() @@ -194,7 +195,7 @@ void SrsIngester::fast_kill() // when error, ingester sleep for a while and retry. // ingest never sleep a long time, for we must start the stream ASAP. -#define SRS_AUTO_INGESTER_CIMS (3 * SRS_UTIME_SECONDS) +#define SRS_INGESTER_CIMS (3 * SRS_UTIME_SECONDS) srs_error_t SrsIngester::cycle() { @@ -212,7 +213,7 @@ srs_error_t SrsIngester::cycle() srs_freep(err); } - srs_usleep(SRS_AUTO_INGESTER_CIMS); + srs_usleep(SRS_INGESTER_CIMS); } return err; diff --git a/trunk/src/app/srs_app_ingest.hpp b/trunk/src/app/srs_app_ingest.hpp index 4118d77fb..d392a24e3 100644 --- a/trunk/src/app/srs_app_ingest.hpp +++ b/trunk/src/app/srs_app_ingest.hpp @@ -28,7 +28,7 @@ #include -#include +#include #include class SrsFFMPEG; diff --git a/trunk/src/app/srs_app_listener.cpp b/trunk/src/app/srs_app_listener.cpp index 3294c79f5..f2525057b 100755 --- a/trunk/src/app/srs_app_listener.cpp +++ b/trunk/src/app/srs_app_listener.cpp @@ -116,6 +116,51 @@ srs_netfd_t SrsUdpListener::stfd() return lfd; } +void SrsUdpListener::set_socket_buffer() +{ + int default_sndbuf = 0; + // TODO: FIXME: Config it. + int expect_sndbuf = 1024*1024*10; // 10M + int actual_sndbuf = expect_sndbuf; + int r0_sndbuf = 0; + if (true) { + socklen_t opt_len = sizeof(default_sndbuf); + // TODO: FIXME: check err + getsockopt(fd(), SOL_SOCKET, SO_SNDBUF, (void*)&default_sndbuf, &opt_len); + + if ((r0_sndbuf = setsockopt(fd(), SOL_SOCKET, SO_SNDBUF, (void*)&actual_sndbuf, sizeof(actual_sndbuf))) < 0) { + srs_warn("set SO_SNDBUF failed, expect=%d, r0=%d", expect_sndbuf, r0_sndbuf); + } + + opt_len = sizeof(actual_sndbuf); + // TODO: FIXME: check err + getsockopt(fd(), SOL_SOCKET, SO_SNDBUF, (void*)&actual_sndbuf, &opt_len); + } + + int default_rcvbuf = 0; + // TODO: FIXME: Config it. + int expect_rcvbuf = 1024*1024*10; // 10M + int actual_rcvbuf = expect_rcvbuf; + int r0_rcvbuf = 0; + if (true) { + socklen_t opt_len = sizeof(default_rcvbuf); + // TODO: FIXME: check err + getsockopt(fd(), SOL_SOCKET, SO_RCVBUF, (void*)&default_rcvbuf, &opt_len); + + if ((r0_rcvbuf = setsockopt(fd(), SOL_SOCKET, SO_RCVBUF, (void*)&actual_rcvbuf, sizeof(actual_rcvbuf))) < 0) { + srs_warn("set SO_RCVBUF failed, expect=%d, r0=%d", expect_rcvbuf, r0_rcvbuf); + } + + opt_len = sizeof(actual_rcvbuf); + // TODO: FIXME: check err + getsockopt(fd(), SOL_SOCKET, SO_RCVBUF, (void*)&actual_rcvbuf, &opt_len); + } + + srs_trace("UDP #%d LISTEN at %s:%d, SO_SNDBUF(default=%d, expect=%d, actual=%d, r0=%d), SO_RCVBUF(default=%d, expect=%d, actual=%d, r0=%d)", + srs_netfd_fileno(lfd), ip.c_str(), port, default_sndbuf, expect_sndbuf, actual_sndbuf, r0_sndbuf, default_rcvbuf, expect_rcvbuf, actual_rcvbuf, r0_rcvbuf); +} + + srs_error_t SrsUdpListener::listen() { srs_error_t err = srs_success; @@ -124,10 +169,12 @@ srs_error_t SrsUdpListener::listen() return srs_error_wrap(err, "listen %s:%d", ip.c_str(), port); } + set_socket_buffer(); + handler->set_stfd(lfd); srs_freep(trd); - trd = new SrsSTCoroutine("udp", this); + trd = new SrsSTCoroutine("udp", this, _srs_context->get_id()); if ((err = trd->start()) != srs_success) { return srs_error_wrap(err, "start thread"); } @@ -138,7 +185,7 @@ srs_error_t SrsUdpListener::listen() srs_error_t SrsUdpListener::cycle() { srs_error_t err = srs_success; - + while (true) { if ((err = trd->pull()) != srs_success) { return srs_error_wrap(err, "udp listener"); @@ -158,7 +205,7 @@ srs_error_t SrsUdpListener::cycle() && buf[19] == 0x63 && buf[20] == 0x6b) { continue; } - + if ((err = handler->on_udp_packet((const sockaddr*)&from, nb_from, buf, nread)) != srs_success) { return srs_error_wrap(err, "handle packet %d bytes", nread); } @@ -236,24 +283,16 @@ srs_error_t SrsTcpListener::cycle() return err; } -ISrsUdpSender::ISrsUdpSender() -{ -} - -ISrsUdpSender::~ISrsUdpSender() -{ -} - -SrsUdpMuxSocket::SrsUdpMuxSocket(ISrsUdpSender* h, srs_netfd_t fd) +SrsUdpMuxSocket::SrsUdpMuxSocket(srs_netfd_t fd) { nb_buf = SRS_UDP_MAX_PACKET_SIZE; buf = new char[nb_buf]; nread = 0; - handler = h; lfd = fd; fromlen = 0; + peer_port = 0; } SrsUdpMuxSocket::~SrsUdpMuxSocket() @@ -261,23 +300,6 @@ SrsUdpMuxSocket::~SrsUdpMuxSocket() srs_freepa(buf); } -SrsUdpMuxSocket* SrsUdpMuxSocket::copy_sendonly() -{ - SrsUdpMuxSocket* sendonly = new SrsUdpMuxSocket(handler, lfd); - - // Don't copy buffer - srs_freepa(sendonly->buf); - sendonly->nb_buf = 0; - sendonly->nread = 0; - sendonly->lfd = lfd; - sendonly->from = from; - sendonly->fromlen = fromlen; - sendonly->peer_ip = peer_ip; - sendonly->peer_port = peer_port; - - return sendonly; -} - int SrsUdpMuxSocket::recvfrom(srs_utime_t timeout) { fromlen = sizeof(from); @@ -341,7 +363,27 @@ socklen_t SrsUdpMuxSocket::peer_addrlen() return (socklen_t)fromlen; } -std::string SrsUdpMuxSocket::get_peer_id() +char* SrsUdpMuxSocket::data() +{ + return buf; +} + +int SrsUdpMuxSocket::size() +{ + return nread; +} + +std::string SrsUdpMuxSocket::get_peer_ip() const +{ + return peer_ip; +} + +int SrsUdpMuxSocket::get_peer_port() const +{ + return peer_port; +} + +std::string SrsUdpMuxSocket::peer_id() { char id_buf[1024]; int len = snprintf(id_buf, sizeof(id_buf), "%s:%d", peer_ip.c_str(), peer_port); @@ -349,10 +391,26 @@ std::string SrsUdpMuxSocket::get_peer_id() return string(id_buf, len); } -SrsUdpMuxListener::SrsUdpMuxListener(ISrsUdpMuxHandler* h, ISrsUdpSender* s, std::string i, int p) +SrsUdpMuxSocket* SrsUdpMuxSocket::copy_sendonly() +{ + SrsUdpMuxSocket* sendonly = new SrsUdpMuxSocket(lfd); + + // Don't copy buffer + srs_freepa(sendonly->buf); + sendonly->nb_buf = 0; + sendonly->nread = 0; + sendonly->lfd = lfd; + sendonly->from = from; + sendonly->fromlen = fromlen; + sendonly->peer_ip = peer_ip; + sendonly->peer_port = peer_port; + + return sendonly; +} + +SrsUdpMuxListener::SrsUdpMuxListener(ISrsUdpMuxHandler* h, std::string i, int p) { handler = h; - sender = s; ip = i; port = p; @@ -362,6 +420,7 @@ SrsUdpMuxListener::SrsUdpMuxListener(ISrsUdpMuxHandler* h, ISrsUdpSender* s, std buf = new char[nb_buf]; trd = new SrsDummyCoroutine(); + cid = _srs_context->generate_id(); } SrsUdpMuxListener::~SrsUdpMuxListener() @@ -388,11 +447,9 @@ srs_error_t SrsUdpMuxListener::listen() if ((err = srs_udp_listen(ip, port, &lfd)) != srs_success) { return srs_error_wrap(err, "listen %s:%d", ip.c_str(), port); } - - set_socket_buffer(); srs_freep(trd); - trd = new SrsSTCoroutine("udp", this); + trd = new SrsSTCoroutine("udp", this, cid); if ((err = trd->start()) != srs_success) { return srs_error_wrap(err, "start thread"); } @@ -452,6 +509,11 @@ srs_error_t SrsUdpMuxListener::cycle() uint64_t nn_msgs_last = 0; uint64_t nn_loop = 0; srs_utime_t time_last = srs_get_system_time(); + + SrsErrorPithyPrint* pp_pkt_handler_err = new SrsErrorPithyPrint(); + SrsAutoFree(SrsErrorPithyPrint, pp_pkt_handler_err); + + set_socket_buffer(); while (true) { if ((err = trd->pull()) != srs_success) { @@ -460,12 +522,16 @@ srs_error_t SrsUdpMuxListener::cycle() nn_loop++; - SrsUdpMuxSocket skt(sender, lfd); + // TODO: FIXME: Refactor the memory cache for receiver. + // Because we have to decrypt the cipher of received packet payload, + // and the size is not determined, so we think there is at least one copy, + // and we can reuse the plaintext h264/opus with players when got plaintext. + SrsUdpMuxSocket skt(lfd); int nread = skt.recvfrom(SRS_UTIME_NO_TIMEOUT); if (nread <= 0) { if (nread < 0) { - srs_warn("udp recv error"); + srs_warn("udp recv error nn=%d", nread); } // remux udp never return continue; @@ -473,11 +539,21 @@ srs_error_t SrsUdpMuxListener::cycle() nn_msgs++; nn_msgs_stage++; - - if ((err = handler->on_udp_packet(&skt)) != srs_success) { - // remux udp never return - srs_warn("udp packet handler error:%s", srs_error_desc(err).c_str()); - srs_error_reset(err); + + // Restore context when packets processed. + if (true) { + SrsContextRestore(cid); + err = handler->on_udp_packet(&skt); + } + // Use pithy print to show more smart information. + if (err != srs_success) { + uint32_t nn = 0; + if (pp_pkt_handler_err->can_print(err, &nn)) { + // Append more information. + err = srs_error_wrap(err, "size=%u, data=[%s]", skt.size(), srs_string_dumps_hex(skt.data(), skt.size(), 8).c_str()); + srs_warn("handle udp pkt, count=%u/%u, err: %s", pp_pkt_handler_err->nn_count, nn, srs_error_desc(err).c_str()); + } + srs_freep(err); } pprint->elapse(); diff --git a/trunk/src/app/srs_app_listener.hpp b/trunk/src/app/srs_app_listener.hpp index cc20cc756..758c09e73 100644 --- a/trunk/src/app/srs_app_listener.hpp +++ b/trunk/src/app/srs_app_listener.hpp @@ -32,7 +32,6 @@ #include #include -#include struct sockaddr; @@ -102,6 +101,8 @@ public: public: virtual int fd(); virtual srs_netfd_t stfd(); +private: + void set_socket_buffer(); public: virtual srs_error_t listen(); // Interface ISrsReusableThreadHandler. @@ -131,27 +132,10 @@ public: virtual srs_error_t cycle(); }; -class ISrsUdpSender -{ -public: - ISrsUdpSender(); - virtual ~ISrsUdpSender(); -public: - // Fetch a mmsghdr from sender's cache. - virtual srs_error_t fetch(mmsghdr** pphdr) = 0; - // Notify the sender to send out the msg. - virtual srs_error_t sendmmsg(mmsghdr* hdr) = 0; - // Whether sender exceed the max queue, that is, overflow. - virtual bool overflow() = 0; - // Set the queue extra ratio, for example, when mw_msgs > 0, we need larger queue. - // For r, 100 means x1, 200 means x2. - virtual void set_extra_ratio(int r) = 0; -}; - +// TODO: FIXME: Rename it. Refine it for performance issue. class SrsUdpMuxSocket { private: - ISrsUdpSender* handler; char* buf; int nb_buf; int nread; @@ -161,45 +145,37 @@ private: std::string peer_ip; int peer_port; public: - SrsUdpMuxSocket(ISrsUdpSender* h, srs_netfd_t fd); + SrsUdpMuxSocket(srs_netfd_t fd); virtual ~SrsUdpMuxSocket(); - +public: int recvfrom(srs_utime_t timeout); srs_error_t sendto(void* data, int size, srs_utime_t timeout); - srs_netfd_t stfd(); sockaddr_in* peer_addr(); socklen_t peer_addrlen(); - - char* data() { return buf; } - int size() { return nread; } - std::string get_peer_ip() const { return peer_ip; } - int get_peer_port() const { return peer_port; } - std::string get_peer_id(); -public: + char* data(); + int size(); + std::string get_peer_ip() const; + int get_peer_port() const; + std::string peer_id(); SrsUdpMuxSocket* copy_sendonly(); - ISrsUdpSender* sender() { return handler; }; -private: - // Don't allow copy, user copy_sendonly instead - SrsUdpMuxSocket(const SrsUdpMuxSocket& rhs); - SrsUdpMuxSocket& operator=(const SrsUdpMuxSocket& rhs); }; class SrsUdpMuxListener : public ISrsCoroutineHandler { -protected: +private: srs_netfd_t lfd; - ISrsUdpSender* sender; SrsCoroutine* trd; -protected: + SrsContextId cid; +private: char* buf; int nb_buf; -protected: +private: ISrsUdpMuxHandler* handler; std::string ip; int port; public: - SrsUdpMuxListener(ISrsUdpMuxHandler* h, ISrsUdpSender* s, std::string i, int p); + SrsUdpMuxListener(ISrsUdpMuxHandler* h, std::string i, int p); virtual ~SrsUdpMuxListener(); public: virtual int fd(); diff --git a/trunk/src/app/srs_app_log.cpp b/trunk/src/app/srs_app_log.cpp index 4d73ebf8e..9ef9f5571 100644 --- a/trunk/src/app/srs_app_log.cpp +++ b/trunk/src/app/srs_app_log.cpp @@ -37,14 +37,14 @@ #include // the max size of a line of log. -#define LOG_MAX_SIZE 4096 +#define LOG_MAX_SIZE 8192 // the tail append to each log. #define LOG_TAIL '\n' // reserved for the end of log data, it must be strlen(LOG_TAIL) #define LOG_TAIL_SIZE 1 -SrsFastLog::SrsFastLog() +SrsFileLog::SrsFileLog() { level = SrsLogLevelTrace; log_data = new char[LOG_MAX_SIZE]; @@ -54,7 +54,7 @@ SrsFastLog::SrsFastLog() utc = false; } -SrsFastLog::~SrsFastLog() +SrsFileLog::~SrsFileLog() { srs_freepa(log_data); @@ -68,7 +68,7 @@ SrsFastLog::~SrsFastLog() } } -srs_error_t SrsFastLog::initialize() +srs_error_t SrsFileLog::initialize() { if (_srs_config) { _srs_config->subscribe(this); @@ -81,7 +81,7 @@ srs_error_t SrsFastLog::initialize() return srs_success; } -void SrsFastLog::reopen() +void SrsFileLog::reopen() { if (fd > 0) { ::close(fd); @@ -94,7 +94,7 @@ void SrsFastLog::reopen() open_log_file(); } -void SrsFastLog::verbose(const char* tag, int context_id, const char* fmt, ...) +void SrsFileLog::verbose(const char* tag, SrsContextId context_id, const char* fmt, ...) { if (level > SrsLogLevelVerbose) { return; @@ -114,7 +114,7 @@ void SrsFastLog::verbose(const char* tag, int context_id, const char* fmt, ...) write_log(fd, log_data, size, SrsLogLevelVerbose); } -void SrsFastLog::info(const char* tag, int context_id, const char* fmt, ...) +void SrsFileLog::info(const char* tag, SrsContextId context_id, const char* fmt, ...) { if (level > SrsLogLevelInfo) { return; @@ -134,7 +134,7 @@ void SrsFastLog::info(const char* tag, int context_id, const char* fmt, ...) write_log(fd, log_data, size, SrsLogLevelInfo); } -void SrsFastLog::trace(const char* tag, int context_id, const char* fmt, ...) +void SrsFileLog::trace(const char* tag, SrsContextId context_id, const char* fmt, ...) { if (level > SrsLogLevelTrace) { return; @@ -154,7 +154,7 @@ void SrsFastLog::trace(const char* tag, int context_id, const char* fmt, ...) write_log(fd, log_data, size, SrsLogLevelTrace); } -void SrsFastLog::warn(const char* tag, int context_id, const char* fmt, ...) +void SrsFileLog::warn(const char* tag, SrsContextId context_id, const char* fmt, ...) { if (level > SrsLogLevelWarn) { return; @@ -174,7 +174,7 @@ void SrsFastLog::warn(const char* tag, int context_id, const char* fmt, ...) write_log(fd, log_data, size, SrsLogLevelWarn); } -void SrsFastLog::error(const char* tag, int context_id, const char* fmt, ...) +void SrsFileLog::error(const char* tag, SrsContextId context_id, const char* fmt, ...) { if (level > SrsLogLevelError) { return; @@ -200,14 +200,14 @@ void SrsFastLog::error(const char* tag, int context_id, const char* fmt, ...) write_log(fd, log_data, size, SrsLogLevelError); } -srs_error_t SrsFastLog::on_reload_utc_time() +srs_error_t SrsFileLog::on_reload_utc_time() { utc = _srs_config->get_utc_time(); return srs_success; } -srs_error_t SrsFastLog::on_reload_log_tank() +srs_error_t SrsFileLog::on_reload_log_tank() { srs_error_t err = srs_success; @@ -234,7 +234,7 @@ srs_error_t SrsFastLog::on_reload_log_tank() return err; } -srs_error_t SrsFastLog::on_reload_log_level() +srs_error_t SrsFileLog::on_reload_log_level() { srs_error_t err = srs_success; @@ -247,7 +247,7 @@ srs_error_t SrsFastLog::on_reload_log_level() return err; } -srs_error_t SrsFastLog::on_reload_log_file() +srs_error_t SrsFileLog::on_reload_log_file() { srs_error_t err = srs_success; @@ -267,7 +267,7 @@ srs_error_t SrsFastLog::on_reload_log_file() return err; } -void SrsFastLog::write_log(int& fd, char *str_log, int size, int level) +void SrsFileLog::write_log(int& fd, char *str_log, int size, int level) { // ensure the tail and EOF of string // LOG_TAIL_SIZE for the TAIL char. @@ -307,7 +307,7 @@ void SrsFastLog::write_log(int& fd, char *str_log, int size, int level) } } -void SrsFastLog::open_log_file() +void SrsFileLog::open_log_file() { if (!_srs_config) { return; diff --git a/trunk/src/app/srs_app_log.hpp b/trunk/src/app/srs_app_log.hpp index f54ceb29f..e6b82c4eb 100644 --- a/trunk/src/app/srs_app_log.hpp +++ b/trunk/src/app/srs_app_log.hpp @@ -32,10 +32,18 @@ #include #include +// For log TAGs. +#define TAG_MAIN "MAIN" +#define TAG_MAYBE "MAYBE" +#define TAG_DTLS_ALERT "DTLS_ALERT" +#define TAG_DTLS_HANG "DTLS_HANG" +#define TAG_RESOURCE_UNSUB "RESOURCE_UNSUB" +#define TAG_LARGE_TIMER "LARGE_TIMER" + // Use memory/disk cache and donot flush when write log. // it's ok to use it without config, which will log to console, and default trace level. // when you want to use different level, override this classs, set the protected _level. -class SrsFastLog : public ISrsLog, public ISrsReloadHandler +class SrsFileLog : public ISrsLog, public ISrsReloadHandler { private: // Defined in SrsLogLevel. @@ -49,17 +57,17 @@ private: // Whether use utc time. bool utc; public: - SrsFastLog(); - virtual ~SrsFastLog(); + SrsFileLog(); + virtual ~SrsFileLog(); // Interface ISrsLog public: virtual srs_error_t initialize(); virtual void reopen(); - virtual void verbose(const char* tag, int context_id, const char* fmt, ...); - virtual void info(const char* tag, int context_id, const char* fmt, ...); - virtual void trace(const char* tag, int context_id, const char* fmt, ...); - virtual void warn(const char* tag, int context_id, const char* fmt, ...); - virtual void error(const char* tag, int context_id, const char* fmt, ...); + virtual void verbose(const char* tag, SrsContextId context_id, const char* fmt, ...); + virtual void info(const char* tag, SrsContextId context_id, const char* fmt, ...); + virtual void trace(const char* tag, SrsContextId context_id, const char* fmt, ...); + virtual void warn(const char* tag, SrsContextId context_id, const char* fmt, ...); + virtual void error(const char* tag, SrsContextId context_id, const char* fmt, ...); // Interface ISrsReloadHandler. public: virtual srs_error_t on_reload_utc_time(); diff --git a/trunk/src/app/srs_app_ng_exec.hpp b/trunk/src/app/srs_app_ng_exec.hpp index 901aa9535..c00fcdece 100644 --- a/trunk/src/app/srs_app_ng_exec.hpp +++ b/trunk/src/app/srs_app_ng_exec.hpp @@ -29,7 +29,7 @@ #include #include -#include +#include class SrsRequest; class SrsPithyPrint; diff --git a/trunk/src/app/srs_app_pithy_print.cpp b/trunk/src/app/srs_app_pithy_print.cpp index 58f8bca4a..f408d08c5 100644 --- a/trunk/src/app/srs_app_pithy_print.cpp +++ b/trunk/src/app/srs_app_pithy_print.cpp @@ -24,18 +24,20 @@ #include #include -#include +using namespace std; #include #include #include #include -SrsStageInfo::SrsStageInfo(int _stage_id) +SrsStageInfo::SrsStageInfo(int _stage_id, double ratio) { stage_id = _stage_id; nb_clients = 0; age = 0; + nn_count = 0; + interval_ratio = ratio; update_print_time(); @@ -59,7 +61,7 @@ void SrsStageInfo::elapse(srs_utime_t diff) bool SrsStageInfo::can_print() { - srs_utime_t can_print_age = nb_clients * interval; + srs_utime_t can_print_age = nb_clients * (srs_utime_t)(interval_ratio * interval); bool can_print = age >= can_print_age; if (can_print) { @@ -75,7 +77,96 @@ srs_error_t SrsStageInfo::on_reload_pithy_print() return srs_success; } -static std::map _srs_stages; +SrsStageManager::SrsStageManager() +{ +} + +SrsStageManager::~SrsStageManager() +{ + map::iterator it; + for (it = stages.begin(); it != stages.end(); ++it) { + SrsStageInfo* stage = it->second; + srs_freep(stage); + } +} + +SrsStageInfo* SrsStageManager::fetch_or_create(int stage_id, bool* pnew) +{ + std::map::iterator it = stages.find(stage_id); + + // Create one if not exists. + if (it == stages.end()) { + SrsStageInfo* stage = new SrsStageInfo(stage_id); + stages[stage_id] = stage; + + if (pnew) { + *pnew = true; + } + + return stage; + } + + // Exists, fetch it. + SrsStageInfo* stage = it->second; + + if (pnew) { + *pnew = false; + } + + return stage; +} + +SrsErrorPithyPrint::SrsErrorPithyPrint(double ratio) +{ + nn_count = 0; + ratio_ = ratio; +} + +SrsErrorPithyPrint::~SrsErrorPithyPrint() +{ +} + +bool SrsErrorPithyPrint::can_print(srs_error_t err, uint32_t* pnn) +{ + int error_code = srs_error_code(err); + return can_print(error_code, pnn); +} + +bool SrsErrorPithyPrint::can_print(int error_code, uint32_t* pnn) +{ + bool new_stage = false; + SrsStageInfo* stage = stages.fetch_or_create(error_code, &new_stage); + + // Increase the count. + stage->nn_count++; + nn_count++; + + if (pnn) { + *pnn = stage->nn_count; + } + + // Always and only one client. + if (new_stage) { + stage->nb_clients = 1; + stage->interval_ratio = ratio_; + } + + srs_utime_t tick = ticks[error_code]; + if (!tick) { + ticks[error_code] = tick = srs_get_system_time(); + } + + srs_utime_t diff = srs_get_system_time() - tick; + diff = srs_max(0, diff); + + stage->elapse(diff); + ticks[error_code] = srs_get_system_time(); + + return new_stage || stage->can_print(); +} + +// The global stage manager for pithy print, multiple stages. +static SrsStageManager* _srs_stages = new SrsStageManager(); SrsPithyPrint::SrsPithyPrint(int _stage_id) { @@ -194,16 +285,7 @@ SrsPithyPrint::~SrsPithyPrint() int SrsPithyPrint::enter_stage() { - SrsStageInfo* stage = NULL; - - std::map::iterator it = _srs_stages.find(stage_id); - if (it == _srs_stages.end()) { - stage = new SrsStageInfo(stage_id); - _srs_stages[stage_id] = stage; - } else { - stage = it->second; - } - + SrsStageInfo* stage = _srs_stages->fetch_or_create(stage_id); srs_assert(stage != NULL); client_id = stage->nb_clients++; @@ -215,7 +297,7 @@ int SrsPithyPrint::enter_stage() void SrsPithyPrint::leave_stage() { - SrsStageInfo* stage = _srs_stages[stage_id]; + SrsStageInfo* stage = _srs_stages->fetch_or_create(stage_id); srs_assert(stage != NULL); stage->nb_clients--; @@ -226,7 +308,7 @@ void SrsPithyPrint::leave_stage() void SrsPithyPrint::elapse() { - SrsStageInfo* stage = _srs_stages[stage_id]; + SrsStageInfo* stage = _srs_stages->fetch_or_create(stage_id); srs_assert(stage != NULL); srs_utime_t diff = srs_get_system_time() - previous_tick; @@ -239,7 +321,7 @@ void SrsPithyPrint::elapse() bool SrsPithyPrint::can_print() { - SrsStageInfo* stage = _srs_stages[stage_id]; + SrsStageInfo* stage = _srs_stages->fetch_or_create(stage_id); srs_assert(stage != NULL); return stage->can_print(); diff --git a/trunk/src/app/srs_app_pithy_print.hpp b/trunk/src/app/srs_app_pithy_print.hpp index ab289e0e7..0fa3b6da2 100644 --- a/trunk/src/app/srs_app_pithy_print.hpp +++ b/trunk/src/app/srs_app_pithy_print.hpp @@ -26,6 +26,8 @@ #include +#include + #include // The stage info to calc the age. @@ -35,10 +37,14 @@ public: int stage_id; srs_utime_t interval; int nb_clients; + // The number of call of can_print(). + uint32_t nn_count; + // The ratio for interval, 1.0 means no change. + double interval_ratio; public: srs_utime_t age; public: - SrsStageInfo(int _stage_id); + SrsStageInfo(int _stage_id, double ratio = 1.0); virtual ~SrsStageInfo(); virtual void update_print_time(); public: @@ -48,6 +54,41 @@ public: virtual srs_error_t on_reload_pithy_print(); }; +// The manager for stages, it's used for a single client stage. +// Of course, we can add the multiple user support, which is SrsPithyPrint. +class SrsStageManager +{ +private: + std::map stages; +public: + SrsStageManager(); + virtual ~SrsStageManager(); +public: + // Fetch a stage, create one if not exists. + SrsStageInfo* fetch_or_create(int stage_id, bool* pnew = NULL); +}; + +// The error pithy print is a single client stage manager, each stage only has one client. +// For example, we use it for error pithy print for each UDP packet processing. +class SrsErrorPithyPrint +{ +public: + // The number of call of can_print(). + uint32_t nn_count; +private: + double ratio_; + SrsStageManager stages; + std::map ticks; +public: + SrsErrorPithyPrint(double ratio = 1.0); + virtual ~SrsErrorPithyPrint(); +public: + // Whether specified stage is ready for print. + bool can_print(srs_error_t err, uint32_t* pnn = NULL); + // We also support int error code. + bool can_print(int err, uint32_t* pnn = NULL); +}; + // The stage is used for a collection of object to do print, // the print time in a stage is constant and not changed, // that is, we always got one message to print every specified time. diff --git a/trunk/src/app/srs_app_process.cpp b/trunk/src/app/srs_app_process.cpp index 0f12f493d..0ef31197a 100644 --- a/trunk/src/app/srs_app_process.cpp +++ b/trunk/src/app/srs_app_process.cpp @@ -158,12 +158,14 @@ srs_error_t srs_redirect_output(string from_file, int to_fd) if ((fd = ::open(from_file.c_str(), flags, mode)) < 0) { return srs_error_new(ERROR_FORK_OPEN_LOG, "open process %d %s failed", to_fd, from_file.c_str()); } - - if (dup2(fd, to_fd) < 0) { - return srs_error_new(ERROR_FORK_DUP2_LOG, "dup2 process %d failed", to_fd); - } - + + int r0 = dup2(fd, to_fd); ::close(fd); + + if (r0 < 0) { + return srs_error_new(ERROR_FORK_DUP2_LOG, "dup2 fd=%d, to=%d, file=%s failed, r0=%d", + fd, to_fd, from_file.c_str(), r0); + } return err; } @@ -180,7 +182,7 @@ srs_error_t SrsProcess::start() srs_info("fork process: %s", cli.c_str()); // for log - int cid = _srs_context->get_id(); + SrsContextId cid = _srs_context->get_id(); int ppid = getpid(); // TODO: fork or vfork? @@ -221,8 +223,8 @@ srs_error_t SrsProcess::start() // log basic info to stderr. if (true) { fprintf(stdout, "\n"); - fprintf(stdout, "process ppid=%d, cid=%d, pid=%d, in=%d, out=%d, err=%d\n", - ppid, cid, getpid(), STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO); + fprintf(stdout, "process ppid=%d, cid=%s, pid=%d, in=%d, out=%d, err=%d\n", + ppid, cid.c_str(), getpid(), STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO); fprintf(stdout, "process binary=%s, cli: %s\n", bin.c_str(), cli.c_str()); fprintf(stdout, "process actual cli: %s\n", actual_cli.c_str()); } diff --git a/trunk/src/app/srs_app_recv_thread.cpp b/trunk/src/app/srs_app_recv_thread.cpp index 214c42aa1..bc063da26 100644 --- a/trunk/src/app/srs_app_recv_thread.cpp +++ b/trunk/src/app/srs_app_recv_thread.cpp @@ -57,7 +57,7 @@ ISrsMessagePumper::~ISrsMessagePumper() { } -SrsRecvThread::SrsRecvThread(ISrsMessagePumper* p, SrsRtmpServer* r, srs_utime_t tm, int parent_cid) +SrsRecvThread::SrsRecvThread(ISrsMessagePumper* p, SrsRtmpServer* r, srs_utime_t tm, SrsContextId parent_cid) { rtmp = r; pumper = p; @@ -71,7 +71,7 @@ SrsRecvThread::~SrsRecvThread() srs_freep(trd); } -int SrsRecvThread::cid() +SrsContextId SrsRecvThread::cid() { return trd->cid(); } @@ -82,6 +82,9 @@ srs_error_t SrsRecvThread::start() srs_freep(trd); trd = new SrsSTCoroutine("recv", this, _parent_cid); + + //change stack size to 256K, fix crash when call some 3rd-part api. + ((SrsSTCoroutine*)trd)->set_stack_size(1 << 18); if ((err = trd->start()) != srs_success) { return srs_error_wrap(err, "recv thread"); @@ -161,7 +164,7 @@ srs_error_t SrsRecvThread::do_cycle() return err; } -SrsQueueRecvThread::SrsQueueRecvThread(SrsConsumer* consumer, SrsRtmpServer* rtmp_sdk, srs_utime_t tm, int parent_cid) +SrsQueueRecvThread::SrsQueueRecvThread(SrsConsumer* consumer, SrsRtmpServer* rtmp_sdk, srs_utime_t tm, SrsContextId parent_cid) : trd(this, rtmp_sdk, tm, parent_cid) { _consumer = consumer; @@ -278,7 +281,7 @@ void SrsQueueRecvThread::on_stop() } SrsPublishRecvThread::SrsPublishRecvThread(SrsRtmpServer* rtmp_sdk, SrsRequest* _req, - int mr_sock_fd, srs_utime_t tm, SrsRtmpConn* conn, SrsSource* source, int parent_cid) + int mr_sock_fd, srs_utime_t tm, SrsRtmpConn* conn, SrsSource* source, SrsContextId parent_cid) : trd(this, rtmp_sdk, tm, parent_cid) { rtmp = rtmp_sdk; @@ -290,8 +293,7 @@ SrsPublishRecvThread::SrsPublishRecvThread(SrsRtmpServer* rtmp_sdk, SrsRequest* _nb_msgs = 0; video_frames = 0; error = srs_cond_new(); - ncid = cid = 0; - + req = _req; mr_fd = mr_sock_fd; @@ -341,12 +343,12 @@ srs_error_t SrsPublishRecvThread::error_code() return srs_error_copy(recv_error); } -void SrsPublishRecvThread::set_cid(int v) +void SrsPublishRecvThread::set_cid(SrsContextId v) { ncid = v; } -int SrsPublishRecvThread::get_cid() +SrsContextId SrsPublishRecvThread::get_cid() { return ncid; } @@ -374,7 +376,7 @@ srs_error_t SrsPublishRecvThread::consume(SrsCommonMessage* msg) srs_error_t err = srs_success; // when cid changed, change it. - if (ncid != cid) { + if (ncid.compare(cid)) { _srs_context->set_id(ncid); cid = ncid; } diff --git a/trunk/src/app/srs_app_recv_thread.hpp b/trunk/src/app/srs_app_recv_thread.hpp index 2a27a7319..c61cf33ed 100644 --- a/trunk/src/app/srs_app_recv_thread.hpp +++ b/trunk/src/app/srs_app_recv_thread.hpp @@ -27,8 +27,9 @@ #include #include +#include -#include +#include #include #include #include @@ -80,16 +81,16 @@ protected: SrsCoroutine* trd; ISrsMessagePumper* pumper; SrsRtmpServer* rtmp; - int _parent_cid; + SrsContextId _parent_cid; // The recv timeout in srs_utime_t. srs_utime_t timeout; public: // Constructor. // @param tm The receive timeout in srs_utime_t. - SrsRecvThread(ISrsMessagePumper* p, SrsRtmpServer* r, srs_utime_t tm, int parent_cid); + SrsRecvThread(ISrsMessagePumper* p, SrsRtmpServer* r, srs_utime_t tm, SrsContextId parent_cid); virtual ~SrsRecvThread(); public: - virtual int cid(); + virtual SrsContextId cid(); public: virtual srs_error_t start(); virtual void stop(); @@ -116,7 +117,7 @@ private: SrsConsumer* _consumer; public: // TODO: FIXME: Refine timeout in time unit. - SrsQueueRecvThread(SrsConsumer* consumer, SrsRtmpServer* rtmp_sdk, srs_utime_t tm, int parent_cid); + SrsQueueRecvThread(SrsConsumer* consumer, SrsRtmpServer* rtmp_sdk, srs_utime_t tm, SrsContextId parent_cid); virtual ~SrsQueueRecvThread(); public: virtual srs_error_t start(); @@ -167,11 +168,11 @@ private: // @see https://github.com/ossrs/srs/issues/244 srs_cond_t error; // The merged context id. - int cid; - int ncid; + SrsContextId cid; + SrsContextId ncid; public: SrsPublishRecvThread(SrsRtmpServer* rtmp_sdk, SrsRequest* _req, - int mr_sock_fd, srs_utime_t tm, SrsRtmpConn* conn, SrsSource* source, int parent_cid); + int mr_sock_fd, srs_utime_t tm, SrsRtmpConn* conn, SrsSource* source, SrsContextId parent_cid); virtual ~SrsPublishRecvThread(); public: // Wait for error for some timeout. @@ -179,8 +180,8 @@ public: virtual int64_t nb_msgs(); virtual uint64_t nb_video_frames(); virtual srs_error_t error_code(); - virtual void set_cid(int v); - virtual int get_cid(); + virtual void set_cid(SrsContextId v); + virtual SrsContextId get_cid(); public: virtual srs_error_t start(); virtual void stop(); diff --git a/trunk/src/app/srs_app_reload.cpp b/trunk/src/app/srs_app_reload.cpp index 3546f44de..5203617fb 100644 --- a/trunk/src/app/srs_app_reload.cpp +++ b/trunk/src/app/srs_app_reload.cpp @@ -85,6 +85,16 @@ srs_error_t ISrsReloadHandler::on_reload_http_api_disabled() return srs_success; } +srs_error_t ISrsReloadHandler::on_reload_https_api_enabled() +{ + return srs_success; +} + +srs_error_t ISrsReloadHandler::on_reload_https_api_disabled() +{ + return srs_success; +} + srs_error_t ISrsReloadHandler::on_reload_http_api_crossdomain() { return srs_success; diff --git a/trunk/src/app/srs_app_reload.hpp b/trunk/src/app/srs_app_reload.hpp index a7af5e790..bf277f9dd 100644 --- a/trunk/src/app/srs_app_reload.hpp +++ b/trunk/src/app/srs_app_reload.hpp @@ -49,6 +49,8 @@ public: virtual srs_error_t on_reload_pithy_print(); virtual srs_error_t on_reload_http_api_enabled(); virtual srs_error_t on_reload_http_api_disabled(); + virtual srs_error_t on_reload_https_api_enabled(); + virtual srs_error_t on_reload_https_api_disabled(); virtual srs_error_t on_reload_http_api_crossdomain(); virtual srs_error_t on_reload_http_api_raw_api(); virtual srs_error_t on_reload_http_stream_enabled(); diff --git a/trunk/src/app/srs_app_rtc.cpp b/trunk/src/app/srs_app_rtc.cpp deleted file mode 100644 index 95cfea597..000000000 --- a/trunk/src/app/srs_app_rtc.cpp +++ /dev/null @@ -1,370 +0,0 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2013-2020 John - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -using namespace std; - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// TODO: Add this function into SrsRtpMux class. -srs_error_t aac_raw_append_adts_header(SrsSharedPtrMessage* shared_audio, SrsFormat* format, char** pbuf, int* pnn_buf) -{ - srs_error_t err = srs_success; - - if (format->is_aac_sequence_header()) { - return err; - } - - if (format->audio->nb_samples != 1) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "adts"); - } - - int nb_buf = format->audio->samples[0].size + 7; - char* buf = new char[nb_buf]; - SrsBuffer stream(buf, nb_buf); - - // TODO: Add comment. - stream.write_1bytes(0xFF); - stream.write_1bytes(0xF9); - stream.write_1bytes(((format->acodec->aac_object - 1) << 6) | ((format->acodec->aac_sample_rate & 0x0F) << 2) | ((format->acodec->aac_channels & 0x04) >> 2)); - stream.write_1bytes(((format->acodec->aac_channels & 0x03) << 6) | ((nb_buf >> 11) & 0x03)); - stream.write_1bytes((nb_buf >> 3) & 0xFF); - stream.write_1bytes(((nb_buf & 0x07) << 5) | 0x1F); - stream.write_1bytes(0xFC); - - stream.write_bytes(format->audio->samples[0].bytes, format->audio->samples[0].size); - - *pbuf = buf; - *pnn_buf = nb_buf; - - return err; -} - -SrsRtpH264Muxer::SrsRtpH264Muxer() -{ - discard_bframe = false; -} - -SrsRtpH264Muxer::~SrsRtpH264Muxer() -{ -} - -srs_error_t SrsRtpH264Muxer::filter(SrsSharedPtrMessage* shared_frame, SrsFormat* format) -{ - srs_error_t err = srs_success; - - // If IDR, we will insert SPS/PPS before IDR frame. - if (format->video && format->video->has_idr) { - shared_frame->set_has_idr(true); - } - - // Update samples to shared frame. - for (int i = 0; i < format->video->nb_samples; ++i) { - SrsSample* sample = &format->video->samples[i]; - - // Because RTC does not support B-frame, so we will drop them. - // TODO: Drop B-frame in better way, which not cause picture corruption. - if (discard_bframe) { - if ((err = sample->parse_bframe()) != srs_success) { - return srs_error_wrap(err, "parse bframe"); - } - if (sample->bframe) { - continue; - } - } - } - - if (format->video->nb_samples <= 0) { - return err; - } - - shared_frame->set_samples(format->video->samples, format->video->nb_samples); - - return err; -} - -SrsRtpOpusMuxer::SrsRtpOpusMuxer() -{ - codec = NULL; -} - -SrsRtpOpusMuxer::~SrsRtpOpusMuxer() -{ - srs_freep(codec); -} - -srs_error_t SrsRtpOpusMuxer::initialize() -{ - srs_error_t err = srs_success; - - codec = new SrsAudioRecode(kChannel, kSamplerate); - if (!codec) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "SrsAacOpus init failed"); - } - - if ((err = codec->initialize()) != srs_success) { - return srs_error_wrap(err, "init codec"); - } - - return err; -} - -// An AAC packet may be transcoded to many OPUS packets. -const int kMaxOpusPackets = 8; -// The max size for each OPUS packet. -const int kMaxOpusPacketSize = 4096; - -srs_error_t SrsRtpOpusMuxer::transcode(SrsSharedPtrMessage* shared_audio, char* adts_audio, int nn_adts_audio) -{ - srs_error_t err = srs_success; - - // Opus packet cache. - static char* opus_payloads[kMaxOpusPackets]; - - static bool initialized = false; - if (!initialized) { - initialized = true; - - static char opus_packets_cache[kMaxOpusPackets][kMaxOpusPacketSize]; - opus_payloads[0] = &opus_packets_cache[0][0]; - for (int i = 1; i < kMaxOpusPackets; i++) { - opus_payloads[i] = opus_packets_cache[i]; - } - } - - // Transcode an aac packet to many opus packets. - SrsSample aac; - aac.bytes = adts_audio; - aac.size = nn_adts_audio; - - int nn_opus_packets = 0; - int opus_sizes[kMaxOpusPackets]; - if ((err = codec->recode(&aac, opus_payloads, opus_sizes, nn_opus_packets)) != srs_success) { - return srs_error_wrap(err, "recode error"); - } - - // Save OPUS packets in shared message. - if (nn_opus_packets <= 0) { - return err; - } - - int nn_max_extra_payload = 0; - SrsSample samples[nn_opus_packets]; - for (int i = 0; i < nn_opus_packets; i++) { - SrsSample* p = samples + i; - p->size = opus_sizes[i]; - p->bytes = new char[p->size]; - memcpy(p->bytes, opus_payloads[i], p->size); - - nn_max_extra_payload = srs_max(nn_max_extra_payload, p->size); - } - - shared_audio->set_extra_payloads(samples, nn_opus_packets); - shared_audio->set_max_extra_payload(nn_max_extra_payload); - - return err; -} - -SrsRtc::SrsRtc() -{ - req = NULL; - hub = NULL; - - enabled = false; - disposable = false; - last_update_time = 0; - - discard_aac = false; -} - -SrsRtc::~SrsRtc() -{ - srs_freep(rtp_h264_muxer); -} - -void SrsRtc::dispose() -{ - if (enabled) { - on_unpublish(); - } -} - -// TODO: FIXME: Dead code? -srs_error_t SrsRtc::cycle() -{ - srs_error_t err = srs_success; - - return err; -} - -srs_error_t SrsRtc::initialize(SrsOriginHub* h, SrsRequest* r) -{ - srs_error_t err = srs_success; - - hub = h; - req = r; - - rtp_h264_muxer = new SrsRtpH264Muxer(); - rtp_h264_muxer->discard_bframe = _srs_config->get_rtc_bframe_discard(req->vhost); - // TODO: FIXME: Support reload and log it. - discard_aac = _srs_config->get_rtc_aac_discard(req->vhost); - - rtp_opus_muxer = new SrsRtpOpusMuxer(); - if (!rtp_opus_muxer) { - return srs_error_wrap(err, "rtp_opus_muxer nullptr"); - } - - return rtp_opus_muxer->initialize(); -} - -srs_error_t SrsRtc::on_publish() -{ - srs_error_t err = srs_success; - - // update the hls time, for hls_dispose. - last_update_time = srs_get_system_time(); - - // support multiple publish. - if (enabled) { - return err; - } - - if (!_srs_config->get_rtc_enabled(req->vhost)) { - return err; - } - - // if enabled, open the muxer. - enabled = true; - - // ok, the hls can be dispose, or need to be dispose. - disposable = true; - - return err; -} - -void SrsRtc::on_unpublish() -{ - // support multiple unpublish. - if (!enabled) { - return; - } - - enabled = false; -} - -srs_error_t SrsRtc::on_audio(SrsSharedPtrMessage* shared_audio, SrsFormat* format) -{ - srs_error_t err = srs_success; - - if (!enabled) { - return err; - } - - // Ignore if no format->acodec, it means the codec is not parsed, or unknown codec. - // @issue https://github.com/ossrs/srs/issues/1506#issuecomment-562079474 - if (!format->acodec) { - return err; - } - - // update the hls time, for hls_dispose. - last_update_time = srs_get_system_time(); - - // ts support audio codec: aac/mp3 - SrsAudioCodecId acodec = format->acodec->id; - if (acodec != SrsAudioCodecIdAAC && acodec != SrsAudioCodecIdMP3) { - return err; - } - - // When drop aac audio packet, never transcode. - if (discard_aac && acodec == SrsAudioCodecIdAAC) { - return err; - } - - // ignore sequence header - srs_assert(format->audio); - - char* adts_audio = NULL; - int nn_adts_audio = 0; - // TODO: FIXME: Reserve 7 bytes header when create shared message. - if ((err = aac_raw_append_adts_header(shared_audio, format, &adts_audio, &nn_adts_audio)) != srs_success) { - return srs_error_wrap(err, "aac append header"); - } - - if (adts_audio) { - err = rtp_opus_muxer->transcode(shared_audio, adts_audio, nn_adts_audio); - srs_freep(adts_audio); - } - - return err; -} - -srs_error_t SrsRtc::on_video(SrsSharedPtrMessage* shared_video, SrsFormat* format) -{ - srs_error_t err = srs_success; - - // TODO: FIXME: Maybe it should config on vhost level. - if (!enabled) { - return err; - } - - // Ignore if no format->vcodec, it means the codec is not parsed, or unknown codec. - // @issue https://github.com/ossrs/srs/issues/1506#issuecomment-562079474 - if (!format->vcodec) { - return err; - } - - // update the hls time, for hls_dispose. - last_update_time = srs_get_system_time(); - - // ignore info frame, - // @see https://github.com/ossrs/srs/issues/288#issuecomment-69863909 - srs_assert(format->video); - return rtp_h264_muxer->filter(shared_video, format); -} diff --git a/trunk/src/app/srs_app_rtc.hpp b/trunk/src/app/srs_app_rtc.hpp deleted file mode 100644 index d232ca31f..000000000 --- a/trunk/src/app/srs_app_rtc.hpp +++ /dev/null @@ -1,106 +0,0 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2013-2020 John - * - * 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_RTC_HPP -#define SRS_APP_RTC_HPP - -#include - -#include -#include -#include - -class SrsFormat; -class SrsSample; -class SrsSharedPtrMessage; -class SrsRtpSharedPacket; -class SrsRequest; -class SrsOriginHub; -class SrsAudioRecode; -class SrsBuffer; - -// The RTP packet max size, should never exceed this size. -const int kRtpPacketSize = 1500; - -// Payload type will rewrite in srs_app_rtc_conn.cpp when send to client. -const uint8_t kOpusPayloadType = 111; -const uint8_t kH264PayloadType = 102; - -const int kChannel = 2; -const int kSamplerate = 48000; - -// SSRC will rewrite in srs_app_rtc_conn.cpp when send to client. -const uint32_t kAudioSSRC = 1; -const uint32_t kVideoSSRC = 2; - -// TODO: Define interface class like ISrsRtpMuxer -class SrsRtpH264Muxer -{ -public: - bool discard_bframe; -public: - SrsRtpH264Muxer(); - virtual ~SrsRtpH264Muxer(); -public: - srs_error_t filter(SrsSharedPtrMessage* shared_video, SrsFormat* format); -}; - -// TODO: FIXME: It's not a muxer, but a transcoder. -class SrsRtpOpusMuxer -{ -private: - SrsAudioRecode* codec; -public: - SrsRtpOpusMuxer(); - virtual ~SrsRtpOpusMuxer(); - virtual srs_error_t initialize(); -public: - srs_error_t transcode(SrsSharedPtrMessage* shared_audio, char* adts_audio, int nn_adts_audio); -}; - -class SrsRtc -{ -private: - SrsRequest* req; - bool enabled; - bool disposable; - bool discard_aac; - srs_utime_t last_update_time; - SrsRtpH264Muxer* rtp_h264_muxer; - SrsRtpOpusMuxer* rtp_opus_muxer; - SrsOriginHub* hub; -public: - SrsRtc(); - virtual ~SrsRtc(); -public: - virtual void dispose(); - virtual srs_error_t cycle(); -public: - virtual srs_error_t initialize(SrsOriginHub* h, SrsRequest* r); - virtual srs_error_t on_publish(); - virtual void on_unpublish(); - virtual srs_error_t on_audio(SrsSharedPtrMessage* shared_audio, SrsFormat* format); - virtual srs_error_t on_video(SrsSharedPtrMessage* shared_video, SrsFormat* format); -}; - -#endif diff --git a/trunk/src/app/srs_app_rtc_api.cpp b/trunk/src/app/srs_app_rtc_api.cpp new file mode 100644 index 000000000..e1fa754ac --- /dev/null +++ b/trunk/src/app/srs_app_rtc_api.cpp @@ -0,0 +1,825 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2013-2020 Winlin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +using namespace std; + +uint32_t SrsGoApiRtcPlay::ssrc_num = 0; + +SrsGoApiRtcPlay::SrsGoApiRtcPlay(SrsRtcServer* server) +{ + server_ = server; +} + +SrsGoApiRtcPlay::~SrsGoApiRtcPlay() +{ +} + + +// Request: +// POST /rtc/v1/play/ +// { +// "sdp":"offer...", "streamurl":"webrtc://r.ossrs.net/live/livestream", +// "api":'http...", "clientip":"..." +// } +// Response: +// {"sdp":"answer...", "sid":"..."} +// @see https://github.com/rtcdn/rtcdn-draft +srs_error_t SrsGoApiRtcPlay::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r) +{ + srs_error_t err = srs_success; + + SrsJsonObject* res = SrsJsonAny::object(); + SrsAutoFree(SrsJsonObject, res); + + if ((err = do_serve_http(w, r, res)) != srs_success) { + srs_warn("RTC error %s", srs_error_desc(err).c_str()); srs_freep(err); + return srs_api_response_code(w, r, SRS_CONSTS_HTTP_BadRequest); + } + + return srs_api_response(w, r, res->dumps()); +} + +srs_error_t SrsGoApiRtcPlay::do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, SrsJsonObject* res) +{ + srs_error_t err = srs_success; + + // For each RTC session, we use short-term HTTP connection. + SrsHttpHeader* hdr = w->header(); + hdr->set("Connection", "Close"); + + // Parse req, the request json object, from body. + SrsJsonObject* req = NULL; + SrsAutoFree(SrsJsonObject, req); + if (true) { + string req_json; + if ((err = r->body_read_all(req_json)) != srs_success) { + return srs_error_wrap(err, "read body"); + } + + SrsJsonAny* json = SrsJsonAny::loads(req_json); + if (!json || !json->is_object()) { + return srs_error_new(ERROR_RTC_API_BODY, "invalid body %s", req_json.c_str()); + } + + req = json->to_object(); + } + + // Fetch params from req object. + SrsJsonAny* prop = NULL; + if ((prop = req->ensure_property_string("sdp")) == NULL) { + return srs_error_wrap(err, "not sdp"); + } + string remote_sdp_str = prop->to_str(); + + if ((prop = req->ensure_property_string("streamurl")) == NULL) { + return srs_error_wrap(err, "not streamurl"); + } + string streamurl = prop->to_str(); + + string clientip; + if ((prop = req->ensure_property_string("clientip")) != NULL) { + clientip = prop->to_str(); + } + + string api; + if ((prop = req->ensure_property_string("api")) != NULL) { + api = prop->to_str(); + } + + // TODO: FIXME: Parse vhost. + // Parse app and stream from streamurl. + string app; + string stream_name; + if (true) { + string tcUrl; + srs_parse_rtmp_url(streamurl, tcUrl, stream_name); + + int port; + string schema, host, vhost, param; + srs_discovery_tc_url(tcUrl, schema, host, vhost, app, stream_name, port, param); + } + + // For client to specifies the EIP of server. + string eip = r->query_get("eip"); + // For client to specifies whether encrypt by SRTP. + string srtp = r->query_get("encrypt"); + string dtls = r->query_get("dtls"); + + srs_trace("RTC play %s, api=%s, clientip=%s, app=%s, stream=%s, offer=%dB, eip=%s, srtp=%s, dtls=%s", + streamurl.c_str(), api.c_str(), clientip.c_str(), app.c_str(), stream_name.c_str(), remote_sdp_str.length(), eip.c_str(), + srtp.c_str(), dtls.c_str()); + + // TODO: FIXME: It seems remote_sdp doesn't represents the full SDP information. + SrsSdp remote_sdp; + if ((err = remote_sdp.parse(remote_sdp_str)) != srs_success) { + return srs_error_wrap(err, "parse sdp failed: %s", remote_sdp_str.c_str()); + } + + if ((err = check_remote_sdp(remote_sdp)) != srs_success) { + return srs_error_wrap(err, "remote sdp check failed"); + } + + SrsRequest request; + request.app = app; + request.stream = stream_name; + + // TODO: FIXME: Parse vhost. + // discovery vhost, resolve the vhost from config + SrsConfDirective* parsed_vhost = _srs_config->get_vhost(""); + if (parsed_vhost) { + request.vhost = parsed_vhost->arg0(); + } + + SrsSdp local_sdp; + + // Config for SDP and session. + local_sdp.session_config_.dtls_role = _srs_config->get_rtc_dtls_role(request.vhost); + local_sdp.session_config_.dtls_version = _srs_config->get_rtc_dtls_version(request.vhost); + + // Whether enabled. + bool server_enabled = _srs_config->get_rtc_server_enabled(); + bool rtc_enabled = _srs_config->get_rtc_enabled(request.vhost); + if (server_enabled && !rtc_enabled) { + srs_warn("RTC disabled in vhost %s", request.vhost.c_str()); + } + if (!server_enabled || !rtc_enabled) { + return srs_error_new(ERROR_RTC_DISABLED, "Disabled server=%d, rtc=%d, vhost=%s", + server_enabled, rtc_enabled, request.vhost.c_str()); + } + + bool srtp_enabled = true; + if (srtp.empty()) { + srtp_enabled = _srs_config->get_rtc_server_encrypt(); + } else { + srtp_enabled = (srtp != "false"); + } + + bool dtls_enabled = (dtls != "false"); + + // TODO: FIXME: When server enabled, but vhost disabled, should report error. + SrsRtcConnection* session = NULL; + if ((err = server_->create_session(&request, remote_sdp, local_sdp, eip, false, dtls_enabled, srtp_enabled, &session)) != srs_success) { + return srs_error_wrap(err, "create session, dtls=%u, srtp=%u, eip=%s", dtls_enabled, srtp_enabled, eip.c_str()); + } + + ostringstream os; + if ((err = local_sdp.encode(os)) != srs_success) { + return srs_error_wrap(err, "encode sdp"); + } + + string local_sdp_str = os.str(); + // Filter the \r\n to \\r\\n for JSON. + local_sdp_str = srs_string_replace(local_sdp_str.c_str(), "\r\n", "\\r\\n"); + + res->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); + res->set("server", SrsJsonAny::str(SrsStatistic::instance()->server_id().c_str())); + + // TODO: add candidates in response json? + + res->set("sdp", SrsJsonAny::str(local_sdp_str.c_str())); + res->set("sessionid", SrsJsonAny::str(session->username().c_str())); + + srs_trace("RTC username=%s, dtls=%u, srtp=%u, offer=%dB, answer=%dB", session->username().c_str(), + dtls_enabled, srtp_enabled, remote_sdp_str.length(), local_sdp_str.length()); + srs_trace("RTC remote offer: %s", srs_string_replace(remote_sdp_str.c_str(), "\r\n", "\\r\\n").c_str()); + srs_trace("RTC local answer: %s", local_sdp_str.c_str()); + + return err; +} + +srs_error_t SrsGoApiRtcPlay::check_remote_sdp(const SrsSdp& remote_sdp) +{ + srs_error_t err = srs_success; + + if (remote_sdp.group_policy_ != "BUNDLE") { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "now only support BUNDLE, group policy=%s", remote_sdp.group_policy_.c_str()); + } + + if (remote_sdp.media_descs_.empty()) { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no media descriptions"); + } + + for (std::vector::const_iterator iter = remote_sdp.media_descs_.begin(); iter != remote_sdp.media_descs_.end(); ++iter) { + if (iter->type_ != "audio" && iter->type_ != "video") { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "unsupport media type=%s", iter->type_.c_str()); + } + + if (! iter->rtcp_mux_) { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "now only suppor rtcp-mux"); + } + + for (std::vector::const_iterator iter_media = iter->payload_types_.begin(); iter_media != iter->payload_types_.end(); ++iter_media) { + if (iter->sendonly_) { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "play API only support sendrecv/recvonly"); + } + } + } + + return err; +} + +srs_error_t SrsGoApiRtcPlay::exchange_sdp(SrsRequest* req, const SrsSdp& remote_sdp, SrsSdp& local_sdp) +{ + srs_error_t err = srs_success; + local_sdp.version_ = "0"; + + local_sdp.username_ = RTMP_SIG_SRS_SERVER; + local_sdp.session_id_ = srs_int2str((int64_t)this); + local_sdp.session_version_ = "2"; + local_sdp.nettype_ = "IN"; + local_sdp.addrtype_ = "IP4"; + local_sdp.unicast_address_ = "0.0.0.0"; + + local_sdp.session_name_ = "SRSPlaySession"; + + local_sdp.msid_semantic_ = "WMS"; + local_sdp.msids_.push_back(req->app + "/" + req->stream); + + local_sdp.group_policy_ = "BUNDLE"; + + bool nack_enabled = _srs_config->get_rtc_nack_enabled(req->vhost); + + for (size_t i = 0; i < remote_sdp.media_descs_.size(); ++i) { + const SrsMediaDesc& remote_media_desc = remote_sdp.media_descs_[i]; + + if (remote_media_desc.is_audio()) { + local_sdp.media_descs_.push_back(SrsMediaDesc("audio")); + } else if (remote_media_desc.is_video()) { + local_sdp.media_descs_.push_back(SrsMediaDesc("video")); + } + + SrsMediaDesc& local_media_desc = local_sdp.media_descs_.back(); + + if (remote_media_desc.is_audio()) { + // TODO: check opus format specific param + std::vector payloads = remote_media_desc.find_media_with_encoding_name("opus"); + for (std::vector::iterator iter = payloads.begin(); iter != payloads.end(); ++iter) { + local_media_desc.payload_types_.push_back(*iter); + SrsMediaPayloadType& payload_type = local_media_desc.payload_types_.back(); + + // TODO: FIXME: Only support some transport algorithms. + vector rtcp_fb; + payload_type.rtcp_fb_.swap(rtcp_fb); + for (int j = 0; j < (int)rtcp_fb.size(); j++) { + if (nack_enabled) { + if (rtcp_fb.at(j) == "nack" || rtcp_fb.at(j) == "nack pli") { + payload_type.rtcp_fb_.push_back(rtcp_fb.at(j)); + } + } + } + + // Only choose one match opus codec. + break; + } + + if (local_media_desc.payload_types_.empty()) { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no found valid opus payload type"); + } + } else if (remote_media_desc.is_video()) { + std::deque backup_payloads; + std::vector payloads = remote_media_desc.find_media_with_encoding_name("H264"); + for (std::vector::iterator iter = payloads.begin(); iter != payloads.end(); ++iter) { + if (iter->format_specific_param_.empty()) { + backup_payloads.push_front(*iter); + continue; + } + H264SpecificParam h264_param; + if ((err = srs_parse_h264_fmtp(iter->format_specific_param_, h264_param)) != srs_success) { + srs_error_reset(err); continue; + } + + // Try to pick the "best match" H.264 payload type. + if (h264_param.packetization_mode == "1" && h264_param.level_asymmerty_allow == "1") { + local_media_desc.payload_types_.push_back(*iter); + SrsMediaPayloadType& payload_type = local_media_desc.payload_types_.back(); + + // TODO: FIXME: Only support some transport algorithms. + vector rtcp_fb; + payload_type.rtcp_fb_.swap(rtcp_fb); + for (int j = 0; j < (int)rtcp_fb.size(); j++) { + if (nack_enabled) { + if (rtcp_fb.at(j) == "nack" || rtcp_fb.at(j) == "nack pli") { + payload_type.rtcp_fb_.push_back(rtcp_fb.at(j)); + } + } + } + + // Only choose first match H.264 payload type. + break; + } + + backup_payloads.push_back(*iter); + } + + // Try my best to pick at least one media payload type. + if (local_media_desc.payload_types_.empty() && ! backup_payloads.empty()) { + srs_warn("choose backup H.264 payload type=%d", backup_payloads.front().payload_type_); + local_media_desc.payload_types_.push_back(backup_payloads.front()); + } + + if (local_media_desc.payload_types_.empty()) { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no found valid H.264 payload type"); + } + } + + local_media_desc.mid_ = remote_media_desc.mid_; + local_sdp.groups_.push_back(local_media_desc.mid_); + + local_media_desc.port_ = 9; + local_media_desc.protos_ = "UDP/TLS/RTP/SAVPF"; + + if (remote_media_desc.session_info_.setup_ == "active") { + local_media_desc.session_info_.setup_ = "passive"; + } else if (remote_media_desc.session_info_.setup_ == "passive") { + local_media_desc.session_info_.setup_ = "active"; + } else if (remote_media_desc.session_info_.setup_ == "actpass") { + local_media_desc.session_info_.setup_ = local_sdp.session_config_.dtls_role; + } else { + // @see: https://tools.ietf.org/html/rfc4145#section-4.1 + // The default value of the setup attribute in an offer/answer exchange + // is 'active' in the offer and 'passive' in the answer. + local_media_desc.session_info_.setup_ = "passive"; + } + + if (remote_media_desc.sendonly_) { + local_media_desc.recvonly_ = true; + } else if (remote_media_desc.recvonly_) { + local_media_desc.sendonly_ = true; + } else if (remote_media_desc.sendrecv_) { + local_media_desc.sendrecv_ = true; + } + + local_media_desc.rtcp_mux_ = true; + local_media_desc.rtcp_rsize_ = true; + + // TODO: FIXME: Avoid SSRC collision. + if (!ssrc_num) { + ssrc_num = ::getpid() * 10000 + ::getpid() * 100 + ::getpid(); + } + + if (local_media_desc.sendonly_ || local_media_desc.sendrecv_) { + SrsSSRCInfo ssrc_info; + ssrc_info.ssrc_ = ++ssrc_num; + // TODO:use formated cname + ssrc_info.cname_ = "stream"; + local_media_desc.ssrc_infos_.push_back(ssrc_info); + } + } + + return err; +} + +uint32_t SrsGoApiRtcPublish::ssrc_num = 0; + +SrsGoApiRtcPublish::SrsGoApiRtcPublish(SrsRtcServer* server) +{ + server_ = server; +} + +SrsGoApiRtcPublish::~SrsGoApiRtcPublish() +{ +} + + +// Request: +// POST /rtc/v1/publish/ +// { +// "sdp":"offer...", "streamurl":"webrtc://r.ossrs.net/live/livestream", +// "api":'http...", "clientip":"..." +// } +// Response: +// {"sdp":"answer...", "sid":"..."} +// @see https://github.com/rtcdn/rtcdn-draft +srs_error_t SrsGoApiRtcPublish::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r) +{ + srs_error_t err = srs_success; + + SrsJsonObject* res = SrsJsonAny::object(); + SrsAutoFree(SrsJsonObject, res); + + if ((err = do_serve_http(w, r, res)) != srs_success) { + srs_warn("RTC error %s", srs_error_desc(err).c_str()); srs_freep(err); + return srs_api_response_code(w, r, SRS_CONSTS_HTTP_BadRequest); + } + + return srs_api_response(w, r, res->dumps()); +} + +srs_error_t SrsGoApiRtcPublish::do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, SrsJsonObject* res) +{ + srs_error_t err = srs_success; + + // For each RTC session, we use short-term HTTP connection. + SrsHttpHeader* hdr = w->header(); + hdr->set("Connection", "Close"); + + // Parse req, the request json object, from body. + SrsJsonObject* req = NULL; + if (true) { + string req_json; + if ((err = r->body_read_all(req_json)) != srs_success) { + return srs_error_wrap(err, "read body"); + } + + SrsJsonAny* json = SrsJsonAny::loads(req_json); + if (!json || !json->is_object()) { + return srs_error_new(ERROR_RTC_API_BODY, "invalid body %s", req_json.c_str()); + } + + req = json->to_object(); + } + + // Fetch params from req object. + SrsJsonAny* prop = NULL; + if ((prop = req->ensure_property_string("sdp")) == NULL) { + return srs_error_wrap(err, "not sdp"); + } + string remote_sdp_str = prop->to_str(); + + if ((prop = req->ensure_property_string("streamurl")) == NULL) { + return srs_error_wrap(err, "not streamurl"); + } + string streamurl = prop->to_str(); + + string clientip; + if ((prop = req->ensure_property_string("clientip")) != NULL) { + clientip = prop->to_str(); + } + + string api; + if ((prop = req->ensure_property_string("api")) != NULL) { + api = prop->to_str(); + } + + // Parse app and stream from streamurl. + string app; + string stream_name; + if (true) { + string tcUrl; + srs_parse_rtmp_url(streamurl, tcUrl, stream_name); + + int port; + string schema, host, vhost, param; + srs_discovery_tc_url(tcUrl, schema, host, vhost, app, stream_name, port, param); + } + + // For client to specifies the EIP of server. + string eip = r->query_get("eip"); + + srs_trace("RTC publish %s, api=%s, clientip=%s, app=%s, stream=%s, offer=%dB, eip=%s", + streamurl.c_str(), api.c_str(), clientip.c_str(), app.c_str(), stream_name.c_str(), remote_sdp_str.length(), eip.c_str()); + + // TODO: FIXME: It seems remote_sdp doesn't represents the full SDP information. + SrsSdp remote_sdp; + if ((err = remote_sdp.parse(remote_sdp_str)) != srs_success) { + return srs_error_wrap(err, "parse sdp failed: %s", remote_sdp_str.c_str()); + } + + if ((err = check_remote_sdp(remote_sdp)) != srs_success) { + return srs_error_wrap(err, "remote sdp check failed"); + } + + SrsRequest request; + request.app = app; + request.stream = stream_name; + + // TODO: FIXME: Parse vhost. + // discovery vhost, resolve the vhost from config + SrsConfDirective* parsed_vhost = _srs_config->get_vhost(""); + if (parsed_vhost) { + request.vhost = parsed_vhost->arg0(); + } + + SrsSdp local_sdp; + + // TODO: FIXME: move to create_session. + // Config for SDP and session. + local_sdp.session_config_.dtls_role = _srs_config->get_rtc_dtls_role(request.vhost); + local_sdp.session_config_.dtls_version = _srs_config->get_rtc_dtls_version(request.vhost); + + // Whether enabled. + bool server_enabled = _srs_config->get_rtc_server_enabled(); + bool rtc_enabled = _srs_config->get_rtc_enabled(request.vhost); + if (server_enabled && !rtc_enabled) { + srs_warn("RTC disabled in vhost %s", request.vhost.c_str()); + } + if (!server_enabled || !rtc_enabled) { + return srs_error_new(ERROR_RTC_DISABLED, "Disabled server=%d, rtc=%d, vhost=%s", + server_enabled, rtc_enabled, request.vhost.c_str()); + } + + // TODO: FIXME: When server enabled, but vhost disabled, should report error. + SrsRtcConnection* session = NULL; + if ((err = server_->create_session(&request, remote_sdp, local_sdp, eip, true, true, true, &session)) != srs_success) { + return srs_error_wrap(err, "create session"); + } + + ostringstream os; + if ((err = local_sdp.encode(os)) != srs_success) { + return srs_error_wrap(err, "encode sdp"); + } + + string local_sdp_str = os.str(); + // Filter the \r\n to \\r\\n for JSON. + local_sdp_str = srs_string_replace(local_sdp_str.c_str(), "\r\n", "\\r\\n"); + + res->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); + res->set("server", SrsJsonAny::str(SrsStatistic::instance()->server_id().c_str())); + + // TODO: add candidates in response json? + + res->set("sdp", SrsJsonAny::str(local_sdp_str.c_str())); + res->set("sessionid", SrsJsonAny::str(session->username().c_str())); + + srs_trace("RTC username=%s, offer=%dB, answer=%dB", session->username().c_str(), + remote_sdp_str.length(), local_sdp_str.length()); + srs_trace("RTC remote offer: %s", srs_string_replace(remote_sdp_str.c_str(), "\r\n", "\\r\\n").c_str()); + srs_trace("RTC local answer: %s", local_sdp_str.c_str()); + + return err; +} + +srs_error_t SrsGoApiRtcPublish::check_remote_sdp(const SrsSdp& remote_sdp) +{ + srs_error_t err = srs_success; + + if (remote_sdp.group_policy_ != "BUNDLE") { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "now only support BUNDLE, group policy=%s", remote_sdp.group_policy_.c_str()); + } + + if (remote_sdp.media_descs_.empty()) { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no media descriptions"); + } + + for (std::vector::const_iterator iter = remote_sdp.media_descs_.begin(); iter != remote_sdp.media_descs_.end(); ++iter) { + if (iter->type_ != "audio" && iter->type_ != "video") { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "unsupport media type=%s", iter->type_.c_str()); + } + + if (! iter->rtcp_mux_) { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "now only suppor rtcp-mux"); + } + + for (std::vector::const_iterator iter_media = iter->payload_types_.begin(); iter_media != iter->payload_types_.end(); ++iter_media) { + if (iter->recvonly_) { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "publish API only support sendrecv/sendonly"); + } + } + } + + return err; +} + +srs_error_t SrsGoApiRtcPublish::exchange_sdp(SrsRequest* req, const SrsSdp& remote_sdp, SrsSdp& local_sdp) +{ + srs_error_t err = srs_success; + local_sdp.version_ = "0"; + + local_sdp.username_ = RTMP_SIG_SRS_SERVER; + local_sdp.session_id_ = srs_int2str((int64_t)this); + local_sdp.session_version_ = "2"; + local_sdp.nettype_ = "IN"; + local_sdp.addrtype_ = "IP4"; + local_sdp.unicast_address_ = "0.0.0.0"; + + local_sdp.session_name_ = "SRSPublishSession"; + + local_sdp.msid_semantic_ = "WMS"; + local_sdp.msids_.push_back(req->app + "/" + req->stream); + + local_sdp.group_policy_ = "BUNDLE"; + + bool nack_enabled = _srs_config->get_rtc_nack_enabled(req->vhost); + bool twcc_enabled = _srs_config->get_rtc_twcc_enabled(req->vhost); + + for (size_t i = 0; i < remote_sdp.media_descs_.size(); ++i) { + const SrsMediaDesc& remote_media_desc = remote_sdp.media_descs_[i]; + + if (remote_media_desc.is_audio()) { + local_sdp.media_descs_.push_back(SrsMediaDesc("audio")); + } else if (remote_media_desc.is_video()) { + local_sdp.media_descs_.push_back(SrsMediaDesc("video")); + } + + SrsMediaDesc& local_media_desc = local_sdp.media_descs_.back(); + + // Whether feature enabled in remote extmap. + int remote_twcc_id = 0; + if (true) { + map extmaps = remote_media_desc.get_extmaps(); + for(map::iterator it = extmaps.begin(); it != extmaps.end(); ++it) { + if (it->second == kTWCCExt) { + remote_twcc_id = it->first; + break; + } + } + } + if (twcc_enabled && remote_twcc_id) { + local_media_desc.extmaps_[remote_twcc_id] = kTWCCExt; + } + + if (remote_media_desc.is_audio()) { + // TODO: check opus format specific param + std::vector payloads = remote_media_desc.find_media_with_encoding_name("opus"); + for (std::vector::iterator iter = payloads.begin(); iter != payloads.end(); ++iter) { + local_media_desc.payload_types_.push_back(*iter); + SrsMediaPayloadType& payload_type = local_media_desc.payload_types_.back(); + + // TODO: FIXME: Only support some transport algorithms. + vector rtcp_fb; + payload_type.rtcp_fb_.swap(rtcp_fb); + for (int j = 0; j < (int)rtcp_fb.size(); j++) { + if (nack_enabled) { + if (rtcp_fb.at(j) == "nack" || rtcp_fb.at(j) == "nack pli") { + payload_type.rtcp_fb_.push_back(rtcp_fb.at(j)); + } + } + if (twcc_enabled && remote_twcc_id) { + if (rtcp_fb.at(j) == "transport-cc") { + payload_type.rtcp_fb_.push_back(rtcp_fb.at(j)); + } + } + } + + // Only choose one match opus codec. + break; + } + + if (local_media_desc.payload_types_.empty()) { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no valid found opus payload type"); + } + + } else if (remote_media_desc.is_video()) { + std::deque backup_payloads; + std::vector payloads = remote_media_desc.find_media_with_encoding_name("H264"); + for (std::vector::iterator iter = payloads.begin(); iter != payloads.end(); ++iter) { + if (iter->format_specific_param_.empty()) { + backup_payloads.push_front(*iter); + continue; + } + H264SpecificParam h264_param; + if ((err = srs_parse_h264_fmtp(iter->format_specific_param_, h264_param)) != srs_success) { + srs_error_reset(err); continue; + } + + // Try to pick the "best match" H.264 payload type. + if (h264_param.packetization_mode == "1" && h264_param.level_asymmerty_allow == "1") { + local_media_desc.payload_types_.push_back(*iter); + SrsMediaPayloadType& payload_type = local_media_desc.payload_types_.back(); + + // TODO: FIXME: Only support some transport algorithms. + vector rtcp_fb; + payload_type.rtcp_fb_.swap(rtcp_fb); + for (int j = 0; j < (int)rtcp_fb.size(); j++) { + if (nack_enabled) { + if (rtcp_fb.at(j) == "nack" || rtcp_fb.at(j) == "nack pli") { + payload_type.rtcp_fb_.push_back(rtcp_fb.at(j)); + } + } + if (twcc_enabled && remote_twcc_id) { + if (rtcp_fb.at(j) == "transport-cc") { + payload_type.rtcp_fb_.push_back(rtcp_fb.at(j)); + } + } + } + + // Only choose first match H.264 payload type. + break; + } + + backup_payloads.push_back(*iter); + } + + // Try my best to pick at least one media payload type. + if (local_media_desc.payload_types_.empty() && ! backup_payloads.empty()) { + srs_warn("choose backup H.264 payload type=%d", backup_payloads.front().payload_type_); + local_media_desc.payload_types_.push_back(backup_payloads.front()); + } + + if (local_media_desc.payload_types_.empty()) { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no found valid H.264 payload type"); + } + + // TODO: FIXME: Support RRTR? + //local_media_desc.payload_types_.back().rtcp_fb_.push_back("rrtr"); + } + + local_media_desc.mid_ = remote_media_desc.mid_; + local_sdp.groups_.push_back(local_media_desc.mid_); + + local_media_desc.port_ = 9; + local_media_desc.protos_ = "UDP/TLS/RTP/SAVPF"; + + if (remote_media_desc.session_info_.setup_ == "active") { + local_media_desc.session_info_.setup_ = "passive"; + } else if (remote_media_desc.session_info_.setup_ == "passive") { + local_media_desc.session_info_.setup_ = "active"; + } else if (remote_media_desc.session_info_.setup_ == "actpass") { + local_media_desc.session_info_.setup_ = local_sdp.session_config_.dtls_role; + } else { + // @see: https://tools.ietf.org/html/rfc4145#section-4.1 + // The default value of the setup attribute in an offer/answer exchange + // is 'active' in the offer and 'passive' in the answer. + local_media_desc.session_info_.setup_ = "passive"; + } + + local_media_desc.rtcp_mux_ = true; + + // For publisher, we are always sendonly. + local_media_desc.sendonly_ = false; + local_media_desc.recvonly_ = true; + local_media_desc.sendrecv_ = false; + } + + return err; +} + +SrsGoApiRtcNACK::SrsGoApiRtcNACK(SrsRtcServer* server) +{ + server_ = server; +} + +SrsGoApiRtcNACK::~SrsGoApiRtcNACK() +{ +} + +srs_error_t SrsGoApiRtcNACK::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r) +{ + srs_error_t err = srs_success; + + SrsJsonObject* res = SrsJsonAny::object(); + SrsAutoFree(SrsJsonObject, res); + + res->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); + + if ((err = do_serve_http(w, r, res)) != srs_success) { + srs_warn("RTC NACK err %s", srs_error_desc(err).c_str()); + res->set("code", SrsJsonAny::integer(srs_error_code(err))); + srs_freep(err); + } + + return srs_api_response(w, r, res->dumps()); +} + +srs_error_t SrsGoApiRtcNACK::do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, SrsJsonObject* res) +{ + string username = r->query_get("username"); + string dropv = r->query_get("drop"); + + SrsJsonObject* query = SrsJsonAny::object(); + res->set("query", query); + + query->set("username", SrsJsonAny::str(username.c_str())); + query->set("drop", SrsJsonAny::str(dropv.c_str())); + query->set("help", SrsJsonAny::str("?username=string&drop=int")); + + int drop = ::atoi(dropv.c_str()); + if (drop <= 0) { + return srs_error_new(ERROR_RTC_INVALID_PARAMS, "invalid drop=%s/%d", dropv.c_str(), drop); + } + + SrsRtcConnection* session = server_->find_session_by_username(username); + if (!session) { + return srs_error_new(ERROR_RTC_NO_SESSION, "no session username=%s", username.c_str()); + } + + session->simulate_nack_drop(drop); + + srs_trace("RTC: NACK session username=%s, drop=%s/%d", username.c_str(), dropv.c_str(), drop); + + return srs_success; +} + diff --git a/trunk/src/app/srs_app_rtc_api.hpp b/trunk/src/app/srs_app_rtc_api.hpp new file mode 100644 index 000000000..56fab8322 --- /dev/null +++ b/trunk/src/app/srs_app_rtc_api.hpp @@ -0,0 +1,83 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2013-2020 Winlin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef SRS_APP_RTC_API_HPP +#define SRS_APP_RTC_API_HPP + +#include + +#include + +class SrsRtcServer; +class SrsRequest; +class SrsSdp; + +class SrsGoApiRtcPlay : public ISrsHttpHandler +{ +public: + static uint32_t ssrc_num; +private: + SrsRtcServer* server_; +public: + SrsGoApiRtcPlay(SrsRtcServer* server); + virtual ~SrsGoApiRtcPlay(); +public: + virtual srs_error_t serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r); +private: + virtual srs_error_t do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, SrsJsonObject* res); + srs_error_t exchange_sdp(SrsRequest* req, const SrsSdp& remote_sdp, SrsSdp& local_sdp); + srs_error_t check_remote_sdp(const SrsSdp& remote_sdp); +}; + +class SrsGoApiRtcPublish : public ISrsHttpHandler +{ +public: + static uint32_t ssrc_num; +private: + SrsRtcServer* server_; +public: + SrsGoApiRtcPublish(SrsRtcServer* server); + virtual ~SrsGoApiRtcPublish(); +public: + virtual srs_error_t serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r); +private: + virtual srs_error_t do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, SrsJsonObject* res); + srs_error_t exchange_sdp(SrsRequest* req, const SrsSdp& remote_sdp, SrsSdp& local_sdp); + srs_error_t check_remote_sdp(const SrsSdp& remote_sdp); +}; + +class SrsGoApiRtcNACK : public ISrsHttpHandler +{ +private: + SrsRtcServer* server_; +public: + SrsGoApiRtcNACK(SrsRtcServer* server); + virtual ~SrsGoApiRtcNACK(); +public: + virtual srs_error_t serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r); +private: + virtual srs_error_t do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, SrsJsonObject* res); +}; + +#endif + diff --git a/trunk/src/app/srs_app_audio_recode.cpp b/trunk/src/app/srs_app_rtc_codec.cpp similarity index 61% rename from trunk/src/app/srs_app_audio_recode.cpp rename to trunk/src/app/srs_app_rtc_codec.cpp index 6e60c2e96..b3db983ef 100644 --- a/trunk/src/app/srs_app_audio_recode.cpp +++ b/trunk/src/app/srs_app_rtc_codec.cpp @@ -24,16 +24,25 @@ #include #include -#include +#include -static const int kOpusPacketMs = 20; -static const int kOpusMaxbytes = 8000; static const int kFrameBufMax = 40960; static const int kPacketBufMax = 8192; -static const int kPcmBufMax = 4096*4; -SrsAudioDecoder::SrsAudioDecoder(std::string codec) - : codec_name_(codec) +static const char* id2codec_name(SrsAudioCodecId id) +{ + switch (id) { + case SrsAudioCodecIdAAC: + return "aac"; + case SrsAudioCodecIdOpus: + return "libopus"; + default: + return ""; + } +} + +SrsAudioDecoder::SrsAudioDecoder(SrsAudioCodecId codec) + : codec_id_(codec) { frame_ = NULL; packet_ = NULL; @@ -60,13 +69,15 @@ srs_error_t SrsAudioDecoder::initialize() { srs_error_t err = srs_success; - if (codec_name_.compare("aac")) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "Invalid codec name"); + //check codec name,only support "aac","opus" + if (codec_id_ != SrsAudioCodecIdAAC && codec_id_ != SrsAudioCodecIdOpus) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "Invalid codec name %d", codec_id_); } - const AVCodec *codec = avcodec_find_decoder_by_name(codec_name_.c_str()); + const char* codec_name = id2codec_name(codec_id_); + const AVCodec *codec = avcodec_find_decoder_by_name(codec_name); if (!codec) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "Codec not found by name"); + return srs_error_new(ERROR_RTC_RTP_MUXER, "Codec not found by name %d(%s)", codec_id_, codec_name); } codec_ctx_ = avcodec_alloc_context3(codec); @@ -135,82 +146,137 @@ AVCodecContext* SrsAudioDecoder::codec_ctx() return codec_ctx_; } -SrsAudioEncoder::SrsAudioEncoder(int samplerate, int channels, int fec, int complexity) - : inband_fec_(fec), - channels_(channels), +SrsAudioEncoder::SrsAudioEncoder(SrsAudioCodecId codec, int samplerate, int channels) + : channels_(channels), sampling_rate_(samplerate), - complexity_(complexity) + codec_id_(codec), + want_bytes_(0) { - opus_ = NULL; + codec_ctx_ = NULL; } SrsAudioEncoder::~SrsAudioEncoder() { - if (opus_) { - opus_encoder_destroy(opus_); - opus_ = NULL; + if (codec_ctx_) { + avcodec_free_context(&codec_ctx_); } + + if (frame_) { + av_frame_free(&frame_); + } + } srs_error_t SrsAudioEncoder::initialize() { srs_error_t err = srs_success; - int error = 0; - opus_ = opus_encoder_create(sampling_rate_, channels_, OPUS_APPLICATION_VOIP, &error); - if (error != OPUS_OK) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "Error create Opus encoder"); + if (codec_id_ != SrsAudioCodecIdAAC && codec_id_ != SrsAudioCodecIdOpus) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "Invalid codec name %d", codec_id_); } - switch (sampling_rate_) - { - case 48000: - opus_encoder_ctl(opus_, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_FULLBAND)); - break; - - case 24000: - opus_encoder_ctl(opus_, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_SUPERWIDEBAND)); - - case 16000: - opus_encoder_ctl(opus_, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_WIDEBAND)); - break; - - case 12000: - opus_encoder_ctl(opus_, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_MEDIUMBAND)); - break; - - case 8000: - opus_encoder_ctl(opus_, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_NARROWBAND)); - break; - - default: - sampling_rate_ = 16000; - opus_encoder_ctl(opus_, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_WIDEBAND)); - break; + frame_ = av_frame_alloc(); + if (!frame_) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "Could not allocate audio frame"); + } + + const char* codec_name = id2codec_name(codec_id_); + const AVCodec *codec = avcodec_find_encoder_by_name(codec_name); + if (!codec) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "Codec not found by name %d(%s)", codec_id_, codec_name); + } + + codec_ctx_ = avcodec_alloc_context3(codec); + if (!codec_ctx_) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "Could not allocate audio codec context"); + } + + codec_ctx_->sample_rate = sampling_rate_; + codec_ctx_->channels = channels_; + codec_ctx_->channel_layout = av_get_default_channel_layout(channels_); + codec_ctx_->bit_rate = 48000; + if (codec_id_ == SrsAudioCodecIdOpus) { + codec_ctx_->sample_fmt = AV_SAMPLE_FMT_S16; + //TODO: for more level setting + codec_ctx_->compression_level = 1; + } else if (codec_id_ == SrsAudioCodecIdAAC) { + codec_ctx_->sample_fmt = AV_SAMPLE_FMT_FLTP; + } + + // TODO: FIXME: Show detail error. + if (avcodec_open2(codec_ctx_, codec, NULL) < 0) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "Could not open codec"); + } + + want_bytes_ = codec_ctx_->channels * codec_ctx_->frame_size * av_get_bytes_per_sample(codec_ctx_->sample_fmt); + + frame_->format = codec_ctx_->sample_fmt; + frame_->nb_samples = codec_ctx_->frame_size; + frame_->channel_layout = codec_ctx_->channel_layout; + + if (av_frame_get_buffer(frame_, 0) < 0) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "Could not get audio frame buffer"); } - opus_encoder_ctl(opus_, OPUS_SET_INBAND_FEC(inband_fec_)); - opus_encoder_ctl(opus_, OPUS_SET_COMPLEXITY(complexity_)); return err; } +int SrsAudioEncoder::want_bytes() +{ + return want_bytes_; +} + srs_error_t SrsAudioEncoder::encode(SrsSample *frame, char *buf, int &size) { srs_error_t err = srs_success; - int nb_samples = sampling_rate_ * kOpusPacketMs / 1000; - if (frame->size != nb_samples * 2 * channels_) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "invalid frame size %d, should be %d", frame->size, nb_samples * 2 * channels_); + if (want_bytes_ > 0 && frame->size != want_bytes_) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "invalid frame size %d, should be %d", frame->size, want_bytes_); } - opus_int16 *data = (opus_int16 *)frame->bytes; - size = opus_encode(opus_, data, nb_samples, (unsigned char *)buf, kOpusMaxbytes); + // TODO: Directly use frame? + memcpy(frame_->data[0], frame->bytes, frame->size); + + /* send the frame for encoding */ + int r0 = avcodec_send_frame(codec_ctx_, frame_); + if (r0 < 0) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "Error sending the frame to the encoder, %d", r0); + } + + AVPacket pkt; + av_init_packet(&pkt); + pkt.data = NULL; + pkt.size = 0; + + /* read all the available output packets (in general there may be any + * number of them */ + size = 0; + while (r0 >= 0) { + r0 = avcodec_receive_packet(codec_ctx_, &pkt); + if (r0 == AVERROR(EAGAIN) || r0 == AVERROR_EOF) { + break; + } else if (r0 < 0) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "Error during decoding %d", r0); + } + + //TODO: fit encoder out more pkt + memcpy(buf, pkt.data, pkt.size); + size = pkt.size; + av_packet_unref(&pkt); + + // TODO: FIXME: Refine api, got more than one packets. + } return err; } +AVCodecContext* SrsAudioEncoder::codec_ctx() +{ + return codec_ctx_; +} + SrsAudioResample::SrsAudioResample(int src_rate, int src_layout, enum AVSampleFormat src_fmt, - int src_nb, int dst_rate, int dst_layout, enum AVSampleFormat dst_fmt) + int src_nb, int dst_rate, int dst_layout, AVSampleFormat dst_fmt) : src_rate_(src_rate), src_ch_layout_(src_layout), src_sample_fmt_(src_fmt), @@ -329,7 +395,7 @@ srs_error_t SrsAudioResample::resample(SrsSample *pcm, char *buf, int &size) int max = size; size = 0; - if (max > dst_bufsize) { + if (max >= dst_bufsize) { memcpy(buf, dst_data_[0], dst_bufsize); size = dst_bufsize; } @@ -337,55 +403,52 @@ srs_error_t SrsAudioResample::resample(SrsSample *pcm, char *buf, int &size) return err; } -SrsAudioRecode::SrsAudioRecode(int channels, int samplerate) +SrsAudioRecode::SrsAudioRecode(SrsAudioCodecId src_codec, SrsAudioCodecId dst_codec,int channels, int samplerate) : dst_channels_(channels), - dst_samplerate_(samplerate) + dst_samplerate_(samplerate), + src_codec_(src_codec), + dst_codec_(dst_codec) { size_ = 0; - data_ = new char[kPcmBufMax]; + data_ = NULL; + + dec_ = NULL; + enc_ = NULL; + resample_ = NULL; } SrsAudioRecode::~SrsAudioRecode() { - if (dec_) { - delete dec_; - dec_ = NULL; - } - if (enc_) { - delete enc_; - enc_ = NULL; - } - if (resample_) { - delete resample_; - resample_ = NULL; - } - - delete[] data_; + srs_freep(dec_); + srs_freep(enc_); + srs_freep(resample_); + srs_freepa(data_); } srs_error_t SrsAudioRecode::initialize() { srs_error_t err = srs_success; - dec_ = new SrsAudioDecoder("aac"); - if (!dec_) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "SrsAudioDecoder failed"); + dec_ = new SrsAudioDecoder(src_codec_); + if ((err = dec_->initialize()) != srs_success) { + return srs_error_wrap(err, "dec init"); } - dec_->initialize(); - enc_ = new SrsAudioEncoder(dst_samplerate_, dst_channels_, 1, 1); - if (!enc_) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "SrsAudioEncoder failed"); + enc_ = new SrsAudioEncoder(dst_codec_, dst_samplerate_, dst_channels_); + if ((err = enc_->initialize()) != srs_success) { + return srs_error_wrap(err, "enc init"); + } + + enc_want_bytes_ = enc_->want_bytes(); + if (enc_want_bytes_ > 0) { + data_ = new char[enc_want_bytes_]; + srs_assert(data_); } - enc_->initialize(); - - resample_ = NULL; return err; } -// TODO: FIXME: Rename to transcode. -srs_error_t SrsAudioRecode::recode(SrsSample *pkt, char **buf, int *buf_len, int &n) +srs_error_t SrsAudioRecode::transcode(SrsSample *pkt, char **buf, int *buf_len, int &n) { srs_error_t err = srs_success; @@ -396,7 +459,7 @@ srs_error_t SrsAudioRecode::recode(SrsSample *pkt, char **buf, int *buf_len, int int decode_len = kPacketBufMax; static char decode_buffer[kPacketBufMax]; if ((err = dec_->decode(pkt, decode_buffer, decode_len)) != srs_success) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "decode error"); + return srs_error_wrap(err, "decode error"); } if (!resample_) { @@ -404,11 +467,7 @@ srs_error_t SrsAudioRecode::recode(SrsSample *pkt, char **buf, int *buf_len, int AVCodecContext *codec_ctx = dec_->codec_ctx(); resample_ = new SrsAudioResample(codec_ctx->sample_rate, (int)codec_ctx->channel_layout, \ codec_ctx->sample_fmt, codec_ctx->frame_size, dst_samplerate_, channel_layout, \ - AV_SAMPLE_FMT_S16); - - if (!resample_) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "SrsAudioResample failed"); - } + enc_->codec_ctx()->sample_fmt); if ((err = resample_->initialize()) != srs_success) { return srs_error_wrap(err, "init resample"); } @@ -419,50 +478,66 @@ srs_error_t SrsAudioRecode::recode(SrsSample *pkt, char **buf, int *buf_len, int pcm.size = decode_len; int resample_len = kFrameBufMax; static char resample_buffer[kFrameBufMax]; + static char encode_buffer[kPacketBufMax]; if ((err = resample_->resample(&pcm, resample_buffer, resample_len)) != srs_success) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "resample error"); + return srs_error_wrap(err, "resample error"); } n = 0; - int data_left = resample_len; - int total; - total = (dst_samplerate_ * kOpusPacketMs / 1000) * 2 * dst_channels_; - if (size_ + data_left < total) { + // We can encode it in one time. + if (enc_want_bytes_ <= 0) { + int encode_len; + pcm.bytes = (char *)data_; + pcm.size = size_; + if ((err = enc_->encode(&pcm, encode_buffer, encode_len)) != srs_success) { + return srs_error_wrap(err, "encode error"); + } + + memcpy(buf[n], encode_buffer, encode_len); + buf_len[n] = encode_len; + n++; + + return err; + } + + // Need to refill the sample to data, because the frame size is not matched to encoder. + int data_left = resample_len; + if (size_ + data_left < enc_want_bytes_) { memcpy(data_ + size_, resample_buffer, data_left); size_ += data_left; - } else { - int index = 0; - while (1) { - data_left = data_left - (total - size_); - memcpy(data_ + size_, resample_buffer + index, total - size_); - index += total - size_; - size_ += total - size_; - if (!enc_) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "enc_ nullptr"); - } - - int encode_len; - pcm.bytes = (char *)data_; - pcm.size = size_; - static char encode_buffer[kPacketBufMax]; - if ((err = enc_->encode(&pcm, encode_buffer, encode_len)) != srs_success) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "encode error"); - } + return err; + } + int index = 0; + while (1) { + data_left = data_left - (enc_want_bytes_ - size_); + memcpy(data_ + size_, resample_buffer + index, enc_want_bytes_ - size_); + index += enc_want_bytes_ - size_; + size_ += enc_want_bytes_ - size_; + + int encode_len; + pcm.bytes = (char *)data_; + pcm.size = size_; + if ((err = enc_->encode(&pcm, encode_buffer, encode_len)) != srs_success) { + return srs_error_wrap(err, "encode error"); + } + + if (encode_len > 0) { memcpy(buf[n], encode_buffer, encode_len); buf_len[n] = encode_len; n++; + } - size_ = 0; - if(!data_left) - break; + size_ = 0; + if(!data_left) { + break; + } - if(data_left < total) { - memcpy(data_ + size_, resample_buffer + index, data_left); - size_ += data_left; - break; - } + if(data_left < enc_want_bytes_) { + memcpy(data_ + size_, resample_buffer + index, data_left); + size_ += data_left; + break; } } diff --git a/trunk/src/app/srs_app_audio_recode.hpp b/trunk/src/app/srs_app_rtc_codec.hpp similarity index 77% rename from trunk/src/app/srs_app_audio_recode.hpp rename to trunk/src/app/srs_app_rtc_codec.hpp index d37afc381..23df35414 100644 --- a/trunk/src/app/srs_app_audio_recode.hpp +++ b/trunk/src/app/srs_app_rtc_codec.hpp @@ -21,8 +21,8 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef SRS_APP_AUDIO_RECODE_HPP -#define SRS_APP_AUDIO_RECODE_HPP +#ifndef SRS_APP_RTC_CODEC_HPP +#define SRS_APP_RTC_CODEC_HPP #include @@ -40,8 +40,6 @@ extern "C" { #include #include -#include - #ifdef __cplusplus } #endif @@ -54,9 +52,10 @@ private: AVFrame* frame_; AVPacket* packet_; AVCodecContext* codec_ctx_; - std::string codec_name_; + SrsAudioCodecId codec_id_; public: - SrsAudioDecoder(std::string codec); + //Only support "aac","opus" + SrsAudioDecoder(SrsAudioCodecId codec); virtual ~SrsAudioDecoder(); srs_error_t initialize(); virtual srs_error_t decode(SrsSample *pkt, char *buf, int &size); @@ -66,16 +65,22 @@ public: class SrsAudioEncoder { private: - int inband_fec_; int channels_; int sampling_rate_; - int complexity_; - OpusEncoder *opus_; + AVCodecContext* codec_ctx_; + SrsAudioCodecId codec_id_; + int want_bytes_; + AVFrame* frame_; public: - SrsAudioEncoder(int samplerate, int channels, int fec, int complexity); + //Only support "aac","opus" + SrsAudioEncoder(SrsAudioCodecId codec, int samplerate, int channelsy); virtual ~SrsAudioEncoder(); srs_error_t initialize(); + //The encoder wanted bytes to call encode, if > 0, caller must feed the same bytes + //Call after initialize successed + int want_bytes(); virtual srs_error_t encode(SrsSample *frame, char *buf, int &size); + AVCodecContext* codec_ctx(); }; class SrsAudioResample @@ -107,6 +112,7 @@ public: virtual srs_error_t resample(SrsSample *pcm, char *buf, int &size); }; +// TODO: FIXME: Rename to Transcoder. class SrsAudioRecode { private: @@ -117,11 +123,14 @@ private: int dst_samplerate_; int size_; char *data_; + SrsAudioCodecId src_codec_; + SrsAudioCodecId dst_codec_; + int enc_want_bytes_; public: - SrsAudioRecode(int channels, int samplerate); + SrsAudioRecode(SrsAudioCodecId src_codec, SrsAudioCodecId dst_codec,int channels, int samplerate); virtual ~SrsAudioRecode(); srs_error_t initialize(); - virtual srs_error_t recode(SrsSample *pkt, char **buf, int *buf_len, int &n); + virtual srs_error_t transcode(SrsSample *pkt, char **buf, int *buf_len, int &n); }; #endif /* SRS_APP_AUDIO_RECODE_HPP */ diff --git a/trunk/src/app/srs_app_rtc_conn.cpp b/trunk/src/app/srs_app_rtc_conn.cpp index e1a95765a..0131ae978 100644 --- a/trunk/src/app/srs_app_rtc_conn.cpp +++ b/trunk/src/app/srs_app_rtc_conn.cpp @@ -33,27 +33,19 @@ using namespace std; #include #include -#include -// Define macro for UDP GSO. -// @see https://github.com/torvalds/linux/blob/master/tools/testing/selftests/net/udpgso.c -#ifndef UDP_SEGMENT -#define UDP_SEGMENT 103 -#endif - #include #include #include -#include +#include #include #include -#include +#include #include #include -#include #include #include -#include +#include #include #include #include @@ -61,219 +53,97 @@ using namespace std; #include #include #include +#include +#include +#include +#include -// The RTP payload max size, reserved some paddings for SRTP as such: -// kRtpPacketSize = kRtpMaxPayloadSize + paddings -// For example, if kRtpPacketSize is 1500, recommend to set kRtpMaxPayloadSize to 1400, -// which reserves 100 bytes for SRTP or paddings. -const int kRtpMaxPayloadSize = kRtpPacketSize - 200; +#define SRS_TICKID_RTCP 0 +#define SRS_TICKID_TWCC 2 -static bool is_stun(const uint8_t* data, const int size) +ISrsRtcTransport::ISrsRtcTransport() { - return data != NULL && size > 0 && (data[0] == 0 || data[0] == 1); } -static bool is_dtls(const uint8_t* data, size_t len) +ISrsRtcTransport::~ISrsRtcTransport() { - return (len >= 13 && (data[0] > 19 && data[0] < 64)); } -static bool is_rtp_or_rtcp(const uint8_t* data, size_t len) +SrsSecurityTransport::SrsSecurityTransport(SrsRtcConnection* s) { - return (len >= 12 && (data[0] & 0xC0) == 0x80); -} + session_ = s; -static bool is_rtcp(const uint8_t* data, size_t len) -{ - return (len >= 12) && (data[0] & 0x80) && (data[1] >= 200 && data[1] <= 209); -} - -static string gen_random_str(int len) -{ - static string random_table = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - - string ret; - ret.reserve(len); - for (int i = 0; i < len; ++i) { - ret.append(1, random_table[random() % random_table.size()]); - } - - return ret; -} - -const int SRTP_MASTER_KEY_KEY_LEN = 16; -const int SRTP_MASTER_KEY_SALT_LEN = 14; - -static std::vector get_candidate_ips() -{ - std::vector candidate_ips; - - string candidate = _srs_config->get_rtc_server_candidates(); - if (candidate == "*" || candidate == "0.0.0.0") { - std::vector tmp = srs_get_local_ips(); - for (int i = 0; i < (int)tmp.size(); ++i) { - if (tmp[i] != "127.0.0.1") { - candidate_ips.push_back(tmp[i]); - } - } - } else { - candidate_ips.push_back(candidate); - } - - return candidate_ips; -} - -SrsDtlsSession::SrsDtlsSession(SrsRtcSession* s) -{ - rtc_session = s; - - dtls = NULL; - bio_in = NULL; - bio_out = NULL; - - client_key = ""; - server_key = ""; - - srtp_send = NULL; - srtp_recv = NULL; + dtls_ = new SrsDtls((ISrsDtlsCallback*)this); + srtp_ = new SrsSRTP(); handshake_done = false; } -SrsDtlsSession::~SrsDtlsSession() +SrsSecurityTransport::~SrsSecurityTransport() { - if (dtls) { - // this function will free bio_in and bio_out - SSL_free(dtls); - dtls = NULL; - } - - if (srtp_send) { - srtp_dealloc(srtp_send); - } - - if (srtp_recv) { - srtp_dealloc(srtp_recv); - } + srs_freep(dtls_); + srs_freep(srtp_); } -srs_error_t SrsDtlsSession::initialize(const SrsRequest& req) -{ - srs_error_t err = srs_success; - - if ((err = SrsDtls::instance()->init(req)) != srs_success) { - return srs_error_wrap(err, "DTLS init"); - } - - // TODO: FIXME: Support config by vhost to use RSA or ECDSA certificate. - if ((dtls = SSL_new(SrsDtls::instance()->get_dtls_ctx())) == NULL) { - return srs_error_new(ERROR_OpenSslCreateSSL, "SSL_new dtls"); - } - - // Dtls setup passive, as server role. - SSL_set_accept_state(dtls); - - if ((bio_in = BIO_new(BIO_s_mem())) == NULL) { - return srs_error_new(ERROR_OpenSslBIONew, "BIO_new in"); - } - - if ((bio_out = BIO_new(BIO_s_mem())) == NULL) { - BIO_free(bio_in); - return srs_error_new(ERROR_OpenSslBIONew, "BIO_new out"); - } - - SSL_set_bio(dtls, bio_in, bio_out); - - return err; +srs_error_t SrsSecurityTransport::initialize(SrsSessionConfig* cfg) +{ + return dtls_->initialize(cfg->dtls_role, cfg->dtls_version); } -srs_error_t SrsDtlsSession::handshake(SrsUdpMuxSocket* skt) +srs_error_t SrsSecurityTransport::start_active_handshake() +{ + return dtls_->start_active_handshake(); +} + +srs_error_t SrsSecurityTransport::write_dtls_data(void* data, int size) { srs_error_t err = srs_success; - int ret = SSL_do_handshake(dtls); + if (!size) { + return err; + } - unsigned char *out_bio_data; - int out_bio_len = BIO_get_mem_data(bio_out, &out_bio_data); + if ((err = session_->sendonly_skt->sendto(data, size, 0)) != srs_success) { + return srs_error_wrap(err, "send dtls packet"); + } - int ssl_err = SSL_get_error(dtls, ret); - switch(ssl_err) { - case SSL_ERROR_NONE: { - if ((err = on_dtls_handshake_done(skt)) != srs_success) { - return srs_error_wrap(err, "dtls handshake done handle"); - } - break; - } - - case SSL_ERROR_WANT_READ: { - break; - } - - case SSL_ERROR_WANT_WRITE: { - break; - } - - default: { - break; - } - } - - if (out_bio_len) { - if ((err = skt->sendto(out_bio_data, out_bio_len, 0)) != srs_success) { - return srs_error_wrap(err, "send dtls packet"); - } + if (_srs_blackhole->blackhole) { + _srs_blackhole->sendto(data, size); } return err; } -srs_error_t SrsDtlsSession::on_dtls(SrsUdpMuxSocket* skt) +srs_error_t SrsSecurityTransport::on_dtls(char* data, int nb_data) { - srs_error_t err = srs_success; - if (BIO_reset(bio_in) != 1) { - return srs_error_new(ERROR_OpenSslBIOReset, "BIO_reset"); - } - if (BIO_reset(bio_out) != 1) { - return srs_error_new(ERROR_OpenSslBIOReset, "BIO_reset"); - } - - if (BIO_write(bio_in, skt->data(), skt->size()) <= 0) { - // TODO: 0 or -1 maybe block, use BIO_should_retry to check. - return srs_error_new(ERROR_OpenSslBIOWrite, "BIO_write"); - } - - if (! handshake_done) { - err = handshake(skt); - } else { - while (BIO_ctrl_pending(bio_in) > 0) { - char dtls_read_buf[8092]; - int nb = SSL_read(dtls, dtls_read_buf, sizeof(dtls_read_buf)); - - if (nb > 0) { - if ((err =on_dtls_application_data(dtls_read_buf, nb)) != srs_success) { - return srs_error_wrap(err, "dtls application data process"); - } - } - } - } - - return err; + return dtls_->on_dtls(data, nb_data); } -srs_error_t SrsDtlsSession::on_dtls_handshake_done(SrsUdpMuxSocket* skt) +srs_error_t SrsSecurityTransport::on_dtls_alert(std::string type, std::string desc) +{ + return session_->on_dtls_alert(type, desc); +} + +srs_error_t SrsSecurityTransport::on_dtls_handshake_done() { srs_error_t err = srs_success; - srs_trace("dtls handshake done"); + if (handshake_done) { + return err; + } handshake_done = true; + + // TODO: FIXME: Add cost for DTLS. + srs_trace("RTC: DTLS handshake done."); + if ((err = srtp_initialize()) != srs_success) { - return srs_error_wrap(err, "srtp init failed"); + return srs_error_wrap(err, "srtp init"); } - return rtc_session->on_connection_established(skt); + return session_->on_connection_established(); } -srs_error_t SrsDtlsSession::on_dtls_application_data(const char* buf, const int nb_buf) +srs_error_t SrsSecurityTransport::on_dtls_application_data(const char* buf, const int nb_buf) { srs_error_t err = srs_success; @@ -282,435 +152,403 @@ srs_error_t SrsDtlsSession::on_dtls_application_data(const char* buf, const int return err; } -srs_error_t SrsDtlsSession::srtp_initialize() +srs_error_t SrsSecurityTransport::srtp_initialize() { srs_error_t err = srs_success; - unsigned char material[SRTP_MASTER_KEY_LEN * 2] = {0}; // client(SRTP_MASTER_KEY_KEY_LEN + SRTP_MASTER_KEY_SALT_LEN) + server - static const string dtls_srtp_lable = "EXTRACTOR-dtls_srtp"; - if (! SSL_export_keying_material(dtls, material, sizeof(material), dtls_srtp_lable.c_str(), dtls_srtp_lable.size(), NULL, 0, 0)) { - return srs_error_new(ERROR_RTC_SRTP_INIT, "SSL_export_keying_material failed"); - } + std::string send_key; + std::string recv_key; - size_t offset = 0; - - std::string client_master_key(reinterpret_cast(material), SRTP_MASTER_KEY_KEY_LEN); - offset += SRTP_MASTER_KEY_KEY_LEN; - std::string server_master_key(reinterpret_cast(material + offset), SRTP_MASTER_KEY_KEY_LEN); - offset += SRTP_MASTER_KEY_KEY_LEN; - std::string client_master_salt(reinterpret_cast(material + offset), SRTP_MASTER_KEY_SALT_LEN); - offset += SRTP_MASTER_KEY_SALT_LEN; - std::string server_master_salt(reinterpret_cast(material + offset), SRTP_MASTER_KEY_SALT_LEN); - - client_key = client_master_key + client_master_salt; - server_key = server_master_key + server_master_salt; - - if ((err = srtp_send_init()) != srs_success) { - return srs_error_wrap(err, "srtp send init failed"); + if ((err = dtls_->get_srtp_key(recv_key, send_key)) != srs_success) { + return err; } - - if ((err = srtp_recv_init()) != srs_success) { - return srs_error_wrap(err, "srtp recv init failed"); - } - - return err; -} - -srs_error_t SrsDtlsSession::srtp_send_init() -{ - srs_error_t err = srs_success; - - srtp_policy_t policy; - bzero(&policy, sizeof(policy)); - - // TODO: Maybe we can use SRTP-GCM in future. - // @see https://bugs.chromium.org/p/chromium/issues/detail?id=713701 - // @see https://groups.google.com/forum/#!topic/discuss-webrtc/PvCbWSetVAQ - srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtp); - srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtcp); - - policy.ssrc.type = ssrc_any_outbound; - policy.ssrc.value = 0; - // TODO: adjust window_size - policy.window_size = 8192; - policy.allow_repeat_tx = 1; - policy.next = NULL; - - uint8_t *key = new uint8_t[server_key.size()]; - memcpy(key, server_key.data(), server_key.size()); - policy.key = key; - - if (srtp_create(&srtp_send, &policy) != srtp_err_status_ok) { - srs_freepa(key); - return srs_error_new(ERROR_RTC_SRTP_INIT, "srtp_create failed"); - } - - srs_freepa(key); - - return err; -} - -srs_error_t SrsDtlsSession::srtp_recv_init() -{ - srs_error_t err = srs_success; - - srtp_policy_t policy; - bzero(&policy, sizeof(policy)); - - srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtp); - srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtcp); - - policy.ssrc.type = ssrc_any_inbound; - - policy.ssrc.value = 0; - // TODO: adjust window_size - policy.window_size = 8192; - policy.allow_repeat_tx = 1; - policy.next = NULL; - - uint8_t *key = new uint8_t[client_key.size()]; - memcpy(key, client_key.data(), client_key.size()); - policy.key = key; - - if (srtp_create(&srtp_recv, &policy) != srtp_err_status_ok) { - srs_freepa(key); - return srs_error_new(ERROR_RTC_SRTP_INIT, "srtp_create failed"); - } - - srs_freepa(key); - - return err; -} - -srs_error_t SrsDtlsSession::protect_rtp(char* out_buf, const char* in_buf, int& nb_out_buf) -{ - srs_error_t err = srs_success; - - if (srtp_send) { - memcpy(out_buf, in_buf, nb_out_buf); - if (srtp_protect(srtp_send, out_buf, &nb_out_buf) != 0) { - return srs_error_new(ERROR_RTC_SRTP_PROTECT, "rtp protect failed"); - } - - return err; - } - - return srs_error_new(ERROR_RTC_SRTP_PROTECT, "rtp protect failed"); -} - -srs_error_t SrsDtlsSession::protect_rtp2(void* rtp_hdr, int* len_ptr) -{ - srs_error_t err = srs_success; - - if (!srtp_send) { - return srs_error_new(ERROR_RTC_SRTP_PROTECT, "rtp protect"); - } - - if (srtp_protect(srtp_send, rtp_hdr, len_ptr) != 0) { - return srs_error_new(ERROR_RTC_SRTP_PROTECT, "rtp protect"); + if ((err = srtp_->initialize(recv_key, send_key)) != srs_success) { + return srs_error_wrap(err, "srtp init"); } return err; } -srs_error_t SrsDtlsSession::unprotect_rtp(char* out_buf, const char* in_buf, int& nb_out_buf) +srs_error_t SrsSecurityTransport::protect_rtp(const char* plaintext, char* cipher, int& nb_cipher) +{ + return srtp_->protect_rtp(plaintext, cipher, nb_cipher); +} + +srs_error_t SrsSecurityTransport::protect_rtcp(const char* plaintext, char* cipher, int& nb_cipher) +{ + return srtp_->protect_rtcp(plaintext, cipher, nb_cipher); +} + +// TODO: FIXME: Merge with protect_rtp. +srs_error_t SrsSecurityTransport::protect_rtp2(void* rtp_hdr, int* len_ptr) +{ + return srtp_->protect_rtp2(rtp_hdr, len_ptr); +} + +srs_error_t SrsSecurityTransport::unprotect_rtp(const char* cipher, char* plaintext, int& nb_plaintext) +{ + return srtp_->unprotect_rtp(cipher, plaintext, nb_plaintext); +} + +srs_error_t SrsSecurityTransport::unprotect_rtcp(const char* cipher, char* plaintext, int& nb_plaintext) +{ + return srtp_->unprotect_rtcp(cipher, plaintext, nb_plaintext); +} + +SrsSemiSecurityTransport::SrsSemiSecurityTransport(SrsRtcConnection* s) : SrsSecurityTransport(s) +{ +} + +SrsSemiSecurityTransport::~SrsSemiSecurityTransport() +{ +} + +srs_error_t SrsSemiSecurityTransport::protect_rtp(const char* plaintext, char* cipher, int& nb_cipher) +{ + return srs_success; +} + +srs_error_t SrsSemiSecurityTransport::protect_rtcp(const char* plaintext, char* cipher, int& nb_cipher) +{ + return srs_success; +} + +srs_error_t SrsSemiSecurityTransport::protect_rtp2(void* rtp_hdr, int* len_ptr) +{ + return srs_success; +} + +SrsPlaintextTransport::SrsPlaintextTransport(SrsRtcConnection* s) +{ + session_ = s; +} + +SrsPlaintextTransport::~SrsPlaintextTransport() +{ +} + +srs_error_t SrsPlaintextTransport::initialize(SrsSessionConfig* cfg) +{ + return srs_success; +} + +srs_error_t SrsPlaintextTransport::start_active_handshake() +{ + return on_dtls_handshake_done(); +} + +srs_error_t SrsPlaintextTransport::on_dtls(char* data, int nb_data) +{ + return srs_success; +} + +srs_error_t SrsPlaintextTransport::on_dtls_alert(std::string type, std::string desc) +{ + return srs_success; +} + +srs_error_t SrsPlaintextTransport::on_dtls_handshake_done() +{ + srs_trace("RTC: DTLS handshake done."); + return session_->on_connection_established(); +} + +srs_error_t SrsPlaintextTransport::on_dtls_application_data(const char* data, const int len) +{ + return srs_success; +} + +srs_error_t SrsPlaintextTransport::write_dtls_data(void* data, int size) +{ + return srs_success; +} + +srs_error_t SrsPlaintextTransport::protect_rtp(const char* plaintext, char* cipher, int& nb_cipher) +{ + memcpy(cipher, plaintext, nb_cipher); + return srs_success; +} + +srs_error_t SrsPlaintextTransport::protect_rtcp(const char* plaintext, char* cipher, int& nb_cipher) +{ + memcpy(cipher, plaintext, nb_cipher); + return srs_success; +} + +srs_error_t SrsPlaintextTransport::protect_rtp2(void* rtp_hdr, int* len_ptr) +{ + return srs_success; +} + +srs_error_t SrsPlaintextTransport::unprotect_rtp(const char* cipher, char* plaintext, int& nb_plaintext) +{ + memcpy(plaintext, cipher, nb_plaintext); + return srs_success; +} + +srs_error_t SrsPlaintextTransport::unprotect_rtcp(const char* cipher, char* plaintext, int& nb_plaintext) +{ + memcpy(plaintext, cipher, nb_plaintext); + return srs_success; +} + +ISrsRtcPLIWorkerHandler::ISrsRtcPLIWorkerHandler() +{ +} + +ISrsRtcPLIWorkerHandler::~ISrsRtcPLIWorkerHandler() +{ +} + +SrsRtcPLIWorker::SrsRtcPLIWorker(ISrsRtcPLIWorkerHandler* h) +{ + handler_ = h; + wait_ = srs_cond_new(); + trd_ = new SrsSTCoroutine("pli", this, _srs_context->get_id()); +} + +SrsRtcPLIWorker::~SrsRtcPLIWorker() +{ + srs_cond_signal(wait_); + trd_->stop(); + + srs_freep(trd_); + srs_cond_destroy(wait_); +} + +srs_error_t SrsRtcPLIWorker::start() { srs_error_t err = srs_success; - if (srtp_recv) { - memcpy(out_buf, in_buf, nb_out_buf); - if (srtp_unprotect(srtp_recv, out_buf, &nb_out_buf) != 0) { - return srs_error_new(ERROR_RTC_SRTP_UNPROTECT, "rtp unprotect failed"); - } - - return err; + if ((err = trd_->start()) != srs_success) { + return srs_error_wrap(err, "start pli worker"); } - return srs_error_new(ERROR_RTC_SRTP_UNPROTECT, "rtp unprotect failed"); + return err; } -srs_error_t SrsDtlsSession::protect_rtcp(char* out_buf, const char* in_buf, int& nb_out_buf) +void SrsRtcPLIWorker::request_keyframe(uint32_t ssrc, SrsContextId cid) +{ + plis_.insert(make_pair(ssrc, cid)); + srs_cond_signal(wait_); +} + +srs_error_t SrsRtcPLIWorker::cycle() { srs_error_t err = srs_success; - if (srtp_send) { - memcpy(out_buf, in_buf, nb_out_buf); - if (srtp_protect_rtcp(srtp_send, out_buf, &nb_out_buf) != 0) { - return srs_error_new(ERROR_RTC_SRTP_PROTECT, "rtcp protect failed"); + while (true) { + if ((err = trd_->pull()) != srs_success) { + return srs_error_wrap(err, "quit"); } - return err; - } + while (!plis_.empty()) { + std::map plis; + plis.swap(plis_); - return srs_error_new(ERROR_RTC_SRTP_PROTECT, "rtcp protect failed"); -} + for (map::iterator it = plis.begin(); it != plis.end(); ++it) { + uint32_t ssrc = it->first; + SrsContextId cid = it->second; -srs_error_t SrsDtlsSession::unprotect_rtcp(char* out_buf, const char* in_buf, int& nb_out_buf) -{ - srs_error_t err = srs_success; - - if (srtp_recv) { - memcpy(out_buf, in_buf, nb_out_buf); - if (srtp_unprotect_rtcp(srtp_recv, out_buf, &nb_out_buf) != srtp_err_status_ok) { - return srs_error_new(ERROR_RTC_SRTP_UNPROTECT, "rtcp unprotect failed"); + if ((err = handler_->do_request_keyframe(ssrc, cid)) != srs_success) { + srs_warn("PLI error, %s", srs_error_desc(err).c_str()); + srs_error_reset(err); + } + } } - - return err; + srs_cond_wait(wait_); } - return srs_error_new(ERROR_RTC_SRTP_UNPROTECT, "rtcp unprotect failed"); + return err; } -SrsRtcPackets::SrsRtcPackets(int nn_cache_max) +SrsRtcPlayStreamStatistic::SrsRtcPlayStreamStatistic() { -#if defined(SRS_DEBUG) - debug_id = 0; -#endif - - use_gso = false; - should_merge_nalus = false; - nn_rtp_pkts = 0; nn_audios = nn_extras = 0; nn_videos = nn_samples = 0; nn_bytes = nn_rtp_bytes = 0; nn_padding_bytes = nn_paddings = 0; - nn_dropped = 0; - - cursor = 0; - nn_cache = nn_cache_max; - // TODO: FIXME: We should allocate a smaller cache, and increase it when exhausted. - cache = new SrsRtpPacket2[nn_cache]; } -SrsRtcPackets::~SrsRtcPackets() +SrsRtcPlayStreamStatistic::~SrsRtcPlayStreamStatistic() { - srs_freepa(cache); - nn_cache = 0; } -void SrsRtcPackets::reset(bool gso, bool merge_nalus) +SrsRtcPlayStream::SrsRtcPlayStream(SrsRtcConnection* s, const SrsContextId& cid) { - for (int i = 0; i < cursor; i++) { - SrsRtpPacket2* packet = cache + i; - packet->reset(); - } - -#if defined(SRS_DEBUG) - debug_id++; -#endif - - use_gso = gso; - should_merge_nalus = merge_nalus; - - nn_rtp_pkts = 0; - nn_audios = nn_extras = 0; - nn_videos = nn_samples = 0; - nn_bytes = nn_rtp_bytes = 0; - nn_padding_bytes = nn_paddings = 0; - nn_dropped = 0; - - cursor = 0; -} - -SrsRtpPacket2* SrsRtcPackets::fetch() -{ - if (cursor >= nn_cache) { - return NULL; - } - return cache + (cursor++); -} - -SrsRtpPacket2* SrsRtcPackets::back() -{ - srs_assert(cursor > 0); - return cache + cursor - 1; -} - -int SrsRtcPackets::size() -{ - return cursor; -} - -int SrsRtcPackets::capacity() -{ - return nn_cache; -} - -SrsRtpPacket2* SrsRtcPackets::at(int index) -{ - srs_assert(index < cursor); - return cache + index; -} - -SrsRtcSenderThread::SrsRtcSenderThread(SrsRtcSession* s, SrsUdpMuxSocket* u, int parent_cid) - : sendonly_ukt(NULL) -{ - _parent_cid = parent_cid; + cid_ = cid; trd = new SrsDummyCoroutine(); - rtc_session = s; - sendonly_ukt = u->copy_sendonly(); - sender = u->sender(); + req_ = NULL; + source_ = NULL; - gso = false; - merge_nalus = false; - max_padding = 0; + is_started = false; + session_ = s; - audio_timestamp = 0; - audio_sequence = 0; - - video_sequence = 0; - - mw_sleep = 0; mw_msgs = 0; realtime = true; + nack_enabled_ = false; + _srs_config->subscribe(this); + timer_ = new SrsHourGlass(this, 1000 * SRS_UTIME_MILLISECONDS); + nack_epp = new SrsErrorPithyPrint(); + pli_worker_ = new SrsRtcPLIWorker(this); } -SrsRtcSenderThread::~SrsRtcSenderThread() +SrsRtcPlayStream::~SrsRtcPlayStream() { _srs_config->unsubscribe(this); + srs_freep(nack_epp); + srs_freep(pli_worker_); srs_freep(trd); - srs_freep(sendonly_ukt); + srs_freep(timer_); + srs_freep(req_); + + if (true) { + std::map::iterator it; + for (it = audio_tracks_.begin(); it != audio_tracks_.end(); ++it) { + srs_freep(it->second); + } + } + + if (true) { + std::map::iterator it; + for (it = video_tracks_.begin(); it != video_tracks_.end(); ++it) { + srs_freep(it->second); + } + } } -srs_error_t SrsRtcSenderThread::initialize(const uint32_t& vssrc, const uint32_t& assrc, const uint16_t& v_pt, const uint16_t& a_pt) +srs_error_t SrsRtcPlayStream::initialize(SrsRequest* req, std::map sub_relations) { srs_error_t err = srs_success; - video_ssrc = vssrc; - audio_ssrc = assrc; + req_ = req->copy(); - video_payload_type = v_pt; - audio_payload_type = a_pt; + if ((err = _srs_rtc_sources->fetch_or_create(req_, &source_)) != srs_success) { + return srs_error_wrap(err, "rtc fetch source failed"); + } - gso = _srs_config->get_rtc_server_gso(); - merge_nalus = _srs_config->get_rtc_server_merge_nalus(); - max_padding = _srs_config->get_rtc_server_padding(); - srs_trace("RTC sender video(ssrc=%d, pt=%d), audio(ssrc=%d, pt=%d), package(gso=%d, merge_nalus=%d), padding=%d", - video_ssrc, video_payload_type, audio_ssrc, audio_payload_type, gso, merge_nalus, max_padding); + if (true) { + std::map::iterator it = sub_relations.begin(); + while (it != sub_relations.end()) { + if (it->second->type_ == "audio") { + audio_tracks_.insert(make_pair(it->first, new SrsRtcAudioSendTrack(session_, it->second))); + } + + if (it->second->type_ == "video") { + video_tracks_.insert(make_pair(it->first, new SrsRtcVideoSendTrack(session_, it->second))); + } + ++it; + } + } + + // TODO: FIXME: Support reload. + nack_enabled_ = _srs_config->get_rtc_nack_enabled(req->vhost); + srs_trace("RTC player nack=%d", nack_enabled_); + + session_->stat_->nn_subscribers++; return err; } -srs_error_t SrsRtcSenderThread::on_reload_rtc_server() +srs_error_t SrsRtcPlayStream::on_reload_vhost_play(string vhost) { - gso = _srs_config->get_rtc_server_gso(); - merge_nalus = _srs_config->get_rtc_server_merge_nalus(); - max_padding = _srs_config->get_rtc_server_padding(); - - srs_trace("Reload rtc_server gso=%d, merge_nalus=%d, max_padding=%d", gso, merge_nalus, max_padding); - - return srs_success; -} - -srs_error_t SrsRtcSenderThread::on_reload_vhost_play(string vhost) -{ - SrsRequest* req = &rtc_session->request; - - if (req->vhost != vhost) { + if (req_->vhost != vhost) { return srs_success; } - realtime = _srs_config->get_realtime_enabled(req->vhost, true); - mw_msgs = _srs_config->get_mw_msgs(req->vhost, realtime, true); - mw_sleep = _srs_config->get_mw_sleep(req->vhost, true); + realtime = _srs_config->get_realtime_enabled(req_->vhost, true); + mw_msgs = _srs_config->get_mw_msgs(req_->vhost, realtime, true); - srs_trace("Reload play realtime=%d, mw_msgs=%d, mw_sleep=%d", realtime, mw_msgs, mw_sleep); + srs_trace("Reload play realtime=%d, mw_msgs=%d", realtime, mw_msgs); return srs_success; } -srs_error_t SrsRtcSenderThread::on_reload_vhost_realtime(string vhost) +srs_error_t SrsRtcPlayStream::on_reload_vhost_realtime(string vhost) { return on_reload_vhost_play(vhost); } -int SrsRtcSenderThread::cid() +const SrsContextId& SrsRtcPlayStream::context_id() { - return trd->cid(); + return cid_; } -srs_error_t SrsRtcSenderThread::start() +srs_error_t SrsRtcPlayStream::start() { srs_error_t err = srs_success; - + + // If player coroutine allocated, we think the player is started. + // To prevent play multiple times for this play stream. + // @remark Allow start multiple times, for DTLS may retransmit the final packet. + if (is_started) { + return err; + } + srs_freep(trd); - trd = new SrsSTCoroutine("rtc_sender", this, _parent_cid); - + trd = new SrsSTCoroutine("rtc_sender", this, cid_); + if ((err = trd->start()) != srs_success) { return srs_error_wrap(err, "rtc_sender"); } - + + if ((err = timer_->start()) != srs_success) { + return srs_error_wrap(err, "start timer"); + } + + if ((err = pli_worker_->start()) != srs_success) { + return srs_error_wrap(err, "start pli worker"); + } + + if (_srs_rtc_hijacker) { + if ((err = _srs_rtc_hijacker->on_start_play(session_, this, req_)) != srs_success) { + return srs_error_wrap(err, "on start play"); + } + } + + is_started = true; + return err; } -void SrsRtcSenderThread::stop() +void SrsRtcPlayStream::stop() { trd->stop(); } -void SrsRtcSenderThread::stop_loop() -{ - trd->interrupt(); -} - -void SrsRtcSenderThread::update_sendonly_socket(SrsUdpMuxSocket* skt) -{ - srs_trace("session %s address changed, update %s -> %s", - rtc_session->id().c_str(), sendonly_ukt->get_peer_id().c_str(), skt->get_peer_id().c_str()); - - srs_freep(sendonly_ukt); - sendonly_ukt = skt->copy_sendonly(); - sender = skt->sender(); -} - -srs_error_t SrsRtcSenderThread::cycle() +srs_error_t SrsRtcPlayStream::cycle() { srs_error_t err = srs_success; - SrsSource* source = NULL; - SrsRequest* req = &rtc_session->request; + SrsRtcStream* source = source_; - // TODO: FIXME: Should refactor it, directly use http server as handler. - ISrsSourceHandler* handler = _srs_hybrid->srs()->instance(); - if ((err = _srs_sources->fetch_or_create(req, handler, &source)) != srs_success) { - return srs_error_wrap(err, "rtc fetch source failed"); + SrsRtcConsumer* consumer = NULL; + SrsAutoFree(SrsRtcConsumer, consumer); + if ((err = source->create_consumer(consumer)) != srs_success) { + return srs_error_wrap(err, "create consumer, source=%s", req_->get_stream_url().c_str()); } - SrsConsumer* consumer = NULL; - SrsAutoFree(SrsConsumer, consumer); - if ((err = source->create_consumer(NULL, consumer)) != srs_success) { - return srs_error_wrap(err, "rtc create consumer, source url=%s", req->get_stream_url().c_str()); + // TODO: FIXME: Dumps the SPS/PPS from gop cache, without other frames. + if ((err = source->consumer_dumps(consumer)) != srs_success) { + return srs_error_wrap(err, "dumps consumer, url=%s", req_->get_stream_url().c_str()); } - // For RTC, we enable pass-timestamp mode, ignore the timestamp in queue, never depends on the duration, - // because RTC allows the audio and video has its own timebase, that is the audio timestamp and video timestamp - // maybe not monotonically increase. - // In this mode, we use mw_msgs to set the delay. We never shrink the consumer queue, instead, we dumps the - // messages and drop them if the shared sender queue is full. - consumer->enable_pass_timestamp(); + realtime = _srs_config->get_realtime_enabled(req_->vhost, true); + mw_msgs = _srs_config->get_mw_msgs(req_->vhost, realtime, true); - realtime = _srs_config->get_realtime_enabled(req->vhost, true); - mw_sleep = _srs_config->get_mw_sleep(req->vhost, true); - mw_msgs = _srs_config->get_mw_msgs(req->vhost, realtime, true); + // TODO: FIXME: Add cost in ms. + SrsContextId cid = source->source_id(); + srs_trace("RTC: start play url=%s, source_id=%s/%s, realtime=%d, mw_msgs=%d", req_->get_stream_url().c_str(), + cid.c_str(), source->pre_source_id().c_str(), realtime, mw_msgs); - // We merged write more messages, so we need larger queue. - if (mw_msgs > 2) { - sender->set_extra_ratio(150); - } else if (mw_msgs > 0) { - sender->set_extra_ratio(80); - } - - srs_trace("RTC source url=%s, source_id=[%d][%d], encrypt=%d, realtime=%d, mw_sleep=%dms, mw_msgs=%d", req->get_stream_url().c_str(), - ::getpid(), source->source_id(), rtc_session->encrypt, realtime, srsu2msi(mw_sleep), mw_msgs); - - SrsMessageArray msgs(SRS_PERF_MW_MSGS); - SrsRtcPackets pkts(SRS_PERF_RTC_RTP_PACKETS); + SrsErrorPithyPrint* epp = new SrsErrorPithyPrint(); + SrsAutoFree(SrsErrorPithyPrint, epp); SrsPithyPrint* pprint = SrsPithyPrint::create_rtc_play(); SrsAutoFree(SrsPithyPrint, pprint); @@ -718,40 +556,51 @@ srs_error_t SrsRtcSenderThread::cycle() bool stat_enabled = _srs_config->get_rtc_server_perf_stat(); SrsStatistic* stat = SrsStatistic::instance(); + // TODO: FIXME: Use cache for performance? + vector pkts; + uint64_t total_pkts = 0; + + if (_srs_rtc_hijacker) { + if ((err = _srs_rtc_hijacker->on_start_consume(session_, this, req_, consumer)) != srs_success) { + return srs_error_wrap(err, "on start consuming"); + } + } + while (true) { if ((err = trd->pull()) != srs_success) { return srs_error_wrap(err, "rtc sender thread"); } -#ifdef SRS_PERF_QUEUE_COND_WAIT - // Wait for amount of messages or a duration. - consumer->wait(mw_msgs, mw_sleep); -#endif + // Wait for amount of packets. + consumer->wait(mw_msgs); - // Try to read some messages. - int msg_count = 0; - if ((err = consumer->dump_packets(&msgs, msg_count)) != srs_success) { + // TODO: FIXME: Handle error. + consumer->dump_packets(pkts); + + int msg_count = (int)pkts.size(); + if (!msg_count) { continue; } - if (msg_count <= 0) { -#ifndef SRS_PERF_QUEUE_COND_WAIT - srs_usleep(mw_sleep); -#endif - continue; - } + // Update stats for session. + session_->stat_->nn_out_rtp += msg_count; + total_pkts += msg_count; - // Transmux and send out messages. - pkts.reset(gso, merge_nalus); + // Send-out all RTP packets and do cleanup + if (true) { + if ((err = send_packets(source, pkts, info)) != srs_success) { + uint32_t nn = 0; + if (epp->can_print(err, &nn)) { + srs_warn("play send packets=%u, nn=%u/%u, err: %s", pkts.size(), epp->nn_count, nn, srs_error_desc(err).c_str()); + } + srs_freep(err); + } - if ((err = send_messages(source, msgs.msgs, msg_count, pkts)) != srs_success) { - srs_warn("send err %s", srs_error_summary(err).c_str()); srs_error_reset(err); - } - - // Do cleanup messages. - for (int i = 0; i < msg_count; i++) { - SrsSharedPtrMessage* msg = msgs.msgs[i]; - srs_freep(msg); + for (int i = 0; i < msg_count; i++) { + SrsRtpPacket2* pkt = pkts[i]; + srs_freep(pkt); + } + pkts.clear(); } // Stat for performance analysis. @@ -762,904 +611,251 @@ srs_error_t SrsRtcSenderThread::cycle() // Stat the original RAW AV frame, maybe h264+aac. stat->perf_on_msgs(msg_count); // Stat the RTC packets, RAW AV frame, maybe h.264+opus. - int nn_rtc_packets = srs_max(pkts.nn_audios, pkts.nn_extras) + pkts.nn_videos; + int nn_rtc_packets = srs_max(info.nn_audios, info.nn_extras) + info.nn_videos; stat->perf_on_rtc_packets(nn_rtc_packets); // Stat the RAW RTP packets, which maybe group by GSO. - stat->perf_on_rtp_packets(pkts.size()); - // Stat the RTP packets going into kernel. - stat->perf_on_gso_packets(pkts.nn_rtp_pkts); + stat->perf_on_rtp_packets(msg_count); // Stat the bytes and paddings. - stat->perf_on_rtc_bytes(pkts.nn_bytes, pkts.nn_rtp_bytes, pkts.nn_padding_bytes); - // Stat the messages and dropped count. - stat->perf_on_dropped(msg_count, nn_rtc_packets, pkts.nn_dropped); - -#if defined(SRS_DEBUG) - srs_trace("RTC PLAY perf, msgs %d/%d, rtp %d, gso %d, %d audios, %d extras, %d videos, %d samples, %d/%d/%d bytes", - msg_count, nn_rtc_packets, pkts.size(), pkts.nn_rtp_pkts, pkts.nn_audios, pkts.nn_extras, pkts.nn_videos, - pkts.nn_samples, pkts.nn_bytes, pkts.nn_rtp_bytes, pkts.nn_padding_bytes); -#endif + stat->perf_on_rtc_bytes(info.nn_bytes, info.nn_rtp_bytes, info.nn_padding_bytes); pprint->elapse(); if (pprint->can_print()) { // TODO: FIXME: Print stat like frame/s, packet/s, loss_packets. - srs_trace("-> RTC PLAY %d/%d msgs, %d/%d packets, %d audios, %d extras, %d videos, %d samples, %d/%d/%d bytes, %d pad, %d/%d cache", - msg_count, pkts.nn_dropped, pkts.size(), pkts.nn_rtp_pkts, pkts.nn_audios, pkts.nn_extras, pkts.nn_videos, pkts.nn_samples, pkts.nn_bytes, - pkts.nn_rtp_bytes, pkts.nn_padding_bytes, pkts.nn_paddings, pkts.size(), pkts.capacity()); + srs_trace("-> RTC PLAY %d msgs, %d/%d packets, %d audios, %d extras, %d videos, %d samples, %d/%d/%d bytes, %d pad, %d/%d cache", + total_pkts, msg_count, info.nn_rtp_pkts, info.nn_audios, info.nn_extras, info.nn_videos, info.nn_samples, info.nn_bytes, + info.nn_rtp_bytes, info.nn_padding_bytes, info.nn_paddings, msg_count, msg_count); } } } -srs_error_t SrsRtcSenderThread::send_messages( - SrsSource* source, SrsSharedPtrMessage** msgs, int nb_msgs, SrsRtcPackets& packets -) { +srs_error_t SrsRtcPlayStream::send_packets(SrsRtcStream* source, const vector& pkts, SrsRtcPlayStreamStatistic& info) +{ srs_error_t err = srs_success; - // If DTLS is not OK, drop all messages. - if (!rtc_session->dtls_session) { - return err; - } - + vector send_pkts; // Covert kernel messages to RTP packets. - if ((err = messages_to_packets(source, msgs, nb_msgs, packets)) != srs_success) { - return srs_error_wrap(err, "messages to packets"); - } + for (int i = 0; i < (int)pkts.size(); i++) { + SrsRtpPacket2* pkt = pkts[i]; -#ifndef SRS_AUTO_OSX - // If enabled GSO, send out some packets in a msghdr. - if (packets.use_gso) { - if ((err = send_packets_gso(packets)) != srs_success) { - return srs_error_wrap(err, "gso send"); + // TODO: FIXME: Maybe refine for performance issue. + if (!audio_tracks_.count(pkt->header.get_ssrc()) && !video_tracks_.count(pkt->header.get_ssrc())) { + srs_warn("ssrc %u not found", pkt->header.get_ssrc()); + continue; } - return err; - } -#endif - - // By default, we send packets by sendmmsg. - if ((err = send_packets(packets)) != srs_success) { - return srs_error_wrap(err, "raw send"); - } - - return err; -} - -srs_error_t SrsRtcSenderThread::messages_to_packets( - SrsSource* source, SrsSharedPtrMessage** msgs, int nb_msgs, SrsRtcPackets& packets -) { - srs_error_t err = srs_success; - - for (int i = 0; i < nb_msgs; i++) { - SrsSharedPtrMessage* msg = msgs[i]; - - // If overflow, drop all messages. - if (sender->overflow()) { - packets.nn_dropped += nb_msgs - i; - return err; - } - - // Update stats. - packets.nn_bytes += msg->size; - - int nn_extra_payloads = msg->nn_extra_payloads(); - packets.nn_extras += nn_extra_payloads; - - int nn_samples = msg->nn_samples(); - packets.nn_samples += nn_samples; - + // For audio, we transcoded AAC to opus in extra payloads. - if (msg->is_audio()) { - packets.nn_audios++; + if (pkt->is_audio()) { + // TODO: FIXME: Any simple solution? + SrsRtcAudioSendTrack* audio_track = audio_tracks_[pkt->header.get_ssrc()]; - for (int i = 0; i < nn_extra_payloads; i++) { - SrsSample* sample = msg->extra_payloads() + i; - if ((err = packet_opus(sample, packets, msg->nn_max_extra_payloads())) != srs_success) { - return srs_error_wrap(err, "opus package"); - } - } - continue; - } - - // For video, we should process all NALUs in samples. - packets.nn_videos++; - - // Well, for each IDR, we append a SPS/PPS before it, which is packaged in STAP-A. - if (msg->has_idr()) { - if ((err = packet_stap_a(source, msg, packets)) != srs_success) { - return srs_error_wrap(err, "packet stap-a"); - } - } - - // If merge Nalus, we pcakges all NALUs(samples) as one NALU, in a RTP or FUA packet. - if (packets.should_merge_nalus && nn_samples > 1) { - if ((err = packet_nalus(msg, packets)) != srs_success) { - return srs_error_wrap(err, "packet stap-a"); - } - continue; - } - - // By default, we package each NALU(sample) to a RTP or FUA packet. - for (int i = 0; i < nn_samples; i++) { - SrsSample* sample = msg->samples() + i; - - // We always ignore bframe here, if config to discard bframe, - // the bframe flag will not be set. - if (sample->bframe) { - continue; + if ((err = audio_track->on_rtp(pkt, info)) != srs_success) { + return srs_error_wrap(err, "audio track, SSRC=%u, SEQ=%u", pkt->header.get_ssrc(), pkt->header.get_sequence()); } - if (sample->size <= kRtpMaxPayloadSize) { - if ((err = packet_single_nalu(msg, sample, packets)) != srs_success) { - return srs_error_wrap(err, "packet single nalu"); - } - } else { - if ((err = packet_fu_a(msg, sample, kRtpMaxPayloadSize, packets)) != srs_success) { - return srs_error_wrap(err, "packet fu-a"); - } - } - - if (i == nn_samples - 1) { - packets.back()->rtp_header.set_marker(true); - } - } - } - - return err; -} - -srs_error_t SrsRtcSenderThread::send_packets(SrsRtcPackets& packets) -{ - srs_error_t err = srs_success; - - // Cache the encrypt flag. - bool encrypt = rtc_session->encrypt; - - int nn_packets = packets.size(); - for (int i = 0; i < nn_packets; i++) { - SrsRtpPacket2* packet = packets.at(i); - - // Fetch a cached message from queue. - // TODO: FIXME: Maybe encrypt in async, so the state of mhdr maybe not ready. - mmsghdr* mhdr = NULL; - if ((err = sender->fetch(&mhdr)) != srs_success) { - return srs_error_wrap(err, "fetch msghdr"); - } - - // For this message, select the first iovec. - iovec* iov = mhdr->msg_hdr.msg_iov; - mhdr->msg_hdr.msg_iovlen = 1; - - if (!iov->iov_base) { - iov->iov_base = new char[kRtpPacketSize]; - } - iov->iov_len = kRtpPacketSize; - - // Marshal packet to bytes in iovec. - if (true) { - SrsBuffer stream((char*)iov->iov_base, iov->iov_len); - if ((err = packet->encode(&stream)) != srs_success) { - return srs_error_wrap(err, "encode packet"); - } - iov->iov_len = stream.pos(); - } - - // Whether encrypt the RTP bytes. - if (encrypt) { - int nn_encrypt = (int)iov->iov_len; - if ((err = rtc_session->dtls_session->protect_rtp2(iov->iov_base, &nn_encrypt)) != srs_success) { - return srs_error_wrap(err, "srtp protect"); - } - iov->iov_len = (size_t)nn_encrypt; - } - - packets.nn_rtp_bytes += (int)iov->iov_len; - - // Set the address and control information. - sockaddr_in* addr = (sockaddr_in*)sendonly_ukt->peer_addr(); - socklen_t addrlen = (socklen_t)sendonly_ukt->peer_addrlen(); - - mhdr->msg_hdr.msg_name = (sockaddr_in*)addr; - mhdr->msg_hdr.msg_namelen = (socklen_t)addrlen; - mhdr->msg_hdr.msg_controllen = 0; - - // When we send out a packet, we commit a RTP packet. - packets.nn_rtp_pkts++; - - if ((err = sender->sendmmsg(mhdr)) != srs_success) { - return srs_error_wrap(err, "send msghdr"); - } - } - - return err; -} - -// TODO: FIXME: We can gather and pad audios, because they have similar size. -srs_error_t SrsRtcSenderThread::send_packets_gso(SrsRtcPackets& packets) -{ - srs_error_t err = srs_success; - - // Cache the encrypt flag. - bool encrypt = rtc_session->encrypt; - - // Previous handler, if has the same size, we can use GSO. - mmsghdr* gso_mhdr = NULL; int gso_size = 0; int gso_encrypt = 0; int gso_cursor = 0; - // GSO, N packets has same length, the final one may not. - bool using_gso = false; bool gso_final = false; - // The message will marshal in iovec. - iovec* iov = NULL; - - int nn_packets = packets.size(); - for (int i = 0; i < nn_packets; i++) { - SrsRtpPacket2* packet = packets.at(i); - int nn_packet = packet->nb_bytes(); - int padding = 0; - - SrsRtpPacket2* next_packet = NULL; - int nn_next_packet = 0; - if (max_padding > 0) { - if (i < nn_packets - 1) { - next_packet = (i < nn_packets - 1)? packets.at(i + 1):NULL; - nn_next_packet = next_packet? next_packet->nb_bytes() : 0; - } - - // Padding the packet to next or GSO size. - if (next_packet) { - if (!using_gso) { - // Padding to the next packet to merge with it. - if (nn_next_packet > nn_packet) { - padding = nn_next_packet - nn_packet; - } - } else { - // Padding to GSO size for next one to merge with us. - if (nn_next_packet < gso_size) { - padding = gso_size - nn_packet; - } - } - - // Reset padding if exceed max. - if (padding > max_padding) { - padding = 0; - } - - if (padding > 0) { -#if defined(SRS_DEBUG) - srs_trace("#%d, Padding %d bytes %d=>%d, packets %d, max_padding %d", packets.debug_id, - padding, nn_packet, nn_packet + padding, nn_packets, max_padding); -#endif - packet->add_padding(padding); - nn_packet += padding; - packets.nn_paddings++; - packets.nn_padding_bytes += padding; - } - } - } - - // Check whether we can use GSO to send it. - if (using_gso && !gso_final) { - gso_final = (gso_size != nn_packet); - } - - if (next_packet) { - // If not GSO, maybe the first fresh packet, we should see whether the next packet is smaller than this one, - // if smaller, we can still enter GSO. - if (!using_gso) { - using_gso = (nn_packet >= nn_next_packet); - } - - // If GSO, but next is bigger than this one, we must enter the final state. - if (using_gso && !gso_final) { - gso_final = (nn_packet < nn_next_packet); - } - } - - // For GSO, reuse mhdr if possible. - mmsghdr* mhdr = gso_mhdr; - if (!mhdr) { - // Fetch a cached message from queue. - // TODO: FIXME: Maybe encrypt in async, so the state of mhdr maybe not ready. - if ((err = sender->fetch(&mhdr)) != srs_success) { - return srs_error_wrap(err, "fetch msghdr"); - } - - // Now, GSO will use this message and size. - gso_mhdr = mhdr; - gso_size = nn_packet; - } - - // For this message, select a new iovec. - if (!iov) { - iov = mhdr->msg_hdr.msg_iov; + // TODO: FIXME: Padding audio to the max payload in RTP packets. } else { - iov++; - } - gso_cursor++; - mhdr->msg_hdr.msg_iovlen = gso_cursor; + // TODO: FIXME: Any simple solution? + SrsRtcVideoSendTrack* video_track = video_tracks_[pkt->header.get_ssrc()]; - if (gso_cursor > SRS_PERF_RTC_GSO_IOVS && !iov->iov_base) { - iov->iov_base = new char[kRtpPacketSize]; - } - iov->iov_len = kRtpPacketSize; - - // Marshal packet to bytes in iovec. - if (true) { - SrsBuffer stream((char*)iov->iov_base, iov->iov_len); - if ((err = packet->encode(&stream)) != srs_success) { - return srs_error_wrap(err, "encode packet"); - } - iov->iov_len = stream.pos(); - } - - // Whether encrypt the RTP bytes. - if (encrypt) { - int nn_encrypt = (int)iov->iov_len; - if ((err = rtc_session->dtls_session->protect_rtp2(iov->iov_base, &nn_encrypt)) != srs_success) { - return srs_error_wrap(err, "srtp protect"); - } - iov->iov_len = (size_t)nn_encrypt; - } - - packets.nn_rtp_bytes += (int)iov->iov_len; - - // If GSO, they must has same size, except the final one. - if (using_gso && !gso_final && gso_encrypt && gso_encrypt != (int)iov->iov_len) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "GSO size=%d/%d, encrypt=%d/%d", gso_size, nn_packet, gso_encrypt, iov->iov_len); - } - - if (using_gso && !gso_final) { - gso_encrypt = iov->iov_len; - } - - // If exceed the max GSO size, set to final. - if (using_gso && gso_cursor + 1 >= SRS_PERF_RTC_GSO_MAX) { - gso_final = true; - } - - // For last message, or final gso, or determined not using GSO, send it now. - bool do_send = (i == nn_packets - 1 || gso_final || !using_gso); - -#if defined(SRS_DEBUG) - bool is_video = packet->rtp_header.get_payload_type() == video_payload_type; - srs_trace("#%d, Packet %s SSRC=%d, SN=%d, %d/%d bytes", packets.debug_id, is_video? "Video":"Audio", - packet->rtp_header.get_ssrc(), packet->rtp_header.get_sequence(), nn_packet - padding, padding); - if (do_send) { - for (int j = 0; j < (int)mhdr->msg_hdr.msg_iovlen; j++) { - iovec* iov = mhdr->msg_hdr.msg_iov + j; - srs_trace("#%d, %s #%d/%d/%d, %d/%d bytes, size %d/%d", packets.debug_id, (using_gso? "GSO":"RAW"), j, - gso_cursor + 1, mhdr->msg_hdr.msg_iovlen, iov->iov_len, padding, gso_size, gso_encrypt); + if ((err = video_track->on_rtp(pkt, info)) != srs_success) { + return srs_error_wrap(err, "video track, SSRC=%u, SEQ=%u", pkt->header.get_ssrc(), pkt->header.get_sequence()); } } -#endif - if (do_send) { - // Set the address and control information. - sockaddr_in* addr = (sockaddr_in*)sendonly_ukt->peer_addr(); - socklen_t addrlen = (socklen_t)sendonly_ukt->peer_addrlen(); - - mhdr->msg_hdr.msg_name = (sockaddr_in*)addr; - mhdr->msg_hdr.msg_namelen = (socklen_t)addrlen; - mhdr->msg_hdr.msg_controllen = 0; - -#ifndef SRS_AUTO_OSX - if (using_gso) { - mhdr->msg_hdr.msg_controllen = CMSG_SPACE(sizeof(uint16_t)); - if (!mhdr->msg_hdr.msg_control) { - mhdr->msg_hdr.msg_control = new char[mhdr->msg_hdr.msg_controllen]; - } - - cmsghdr* cm = CMSG_FIRSTHDR(&mhdr->msg_hdr); - cm->cmsg_level = SOL_UDP; - cm->cmsg_type = UDP_SEGMENT; - cm->cmsg_len = CMSG_LEN(sizeof(uint16_t)); - *((uint16_t*)CMSG_DATA(cm)) = gso_encrypt; - } -#endif - - // When we send out a packet, we commit a RTP packet. - packets.nn_rtp_pkts++; - - if ((err = sender->sendmmsg(mhdr)) != srs_success) { - return srs_error_wrap(err, "send msghdr"); - } - - // Reset the GSO flag. - gso_mhdr = NULL; gso_size = 0; gso_encrypt = 0; gso_cursor = 0; - using_gso = gso_final = false; iov = NULL; - } + // Detail log, should disable it in release version. + srs_info("RTC: Update PT=%u, SSRC=%#x, Time=%u, %u bytes", pkt->header.get_payload_type(), pkt->header.get_ssrc(), + pkt->header.get_timestamp(), pkt->nb_bytes()); } -#if defined(SRS_DEBUG) - srs_trace("#%d, RTC PLAY summary, rtp %d/%d, videos %d/%d, audios %d/%d, pad %d/%d/%d", packets.debug_id, packets.size(), - packets.nn_rtp_pkts, packets.nn_videos, packets.nn_samples, packets.nn_audios, packets.nn_extras, packets.nn_paddings, - packets.nn_padding_bytes, packets.nn_rtp_bytes); -#endif - return err; } -srs_error_t SrsRtcSenderThread::packet_nalus(SrsSharedPtrMessage* msg, SrsRtcPackets& packets) +void SrsRtcPlayStream::nack_fetch(vector& pkts, uint32_t ssrc, uint16_t seq) { - srs_error_t err = srs_success; + for (map::iterator it = audio_tracks_.begin(); it != audio_tracks_.end(); ++it) { + SrsRtcAudioSendTrack* track = it->second; - SrsRtpRawNALUs* raw = new SrsRtpRawNALUs(); - - for (int i = 0; i < msg->nn_samples(); i++) { - SrsSample* sample = msg->samples() + i; - - // We always ignore bframe here, if config to discard bframe, - // the bframe flag will not be set. - if (sample->bframe) { + // If track is inactive, not process nack request. + if (!track->get_track_status()){ continue; } - raw->push_back(sample->copy()); + if (!track->has_ssrc(ssrc)) { + continue; + } + + // update recv nack statistic + track->on_recv_nack(); + + SrsRtpPacket2* pkt = track->fetch_rtp_packet(seq); + if (pkt != NULL) { + pkts.push_back(pkt); + } + return; } - // Ignore empty. - int nn_bytes = raw->nb_bytes(); - if (nn_bytes <= 0) { - srs_freep(raw); + for (map::iterator it = video_tracks_.begin(); it != video_tracks_.end(); ++it) { + SrsRtcVideoSendTrack* track = it->second; + + // If track is inactive, not process nack request. + if (!track->get_track_status()){ + continue; + } + + if (!track->has_ssrc(ssrc)) { + continue; + } + + // update recv nack statistic + track->on_recv_nack(); + + SrsRtpPacket2* pkt = track->fetch_rtp_packet(seq); + if (pkt != NULL) { + pkts.push_back(pkt); + } + return; + } +} + +void SrsRtcPlayStream::set_all_tracks_status(bool status) +{ + std::ostringstream merged_log; + + // set video track status + if (true) { + std::map::iterator it; + for (it = video_tracks_.begin(); it != video_tracks_.end(); ++it) { + SrsRtcVideoSendTrack* track = it->second; + + bool previous = track->set_track_status(status); + merged_log << "{track: " << track->get_track_id() << ", is_active: " << previous << "=>" << status << "},"; + } + } + + // set audio track status + if (true) { + std::map::iterator it; + for (it = audio_tracks_.begin(); it != audio_tracks_.end(); ++it) { + SrsRtcAudioSendTrack* track = it->second; + + bool previous = track->set_track_status(status); + merged_log << "{track: " << track->get_track_id() << ", is_active: " << previous << "=>" << status << "},"; + } + } + + srs_trace("RTC: Init tracks %s ok", merged_log.str().c_str()); +} + +srs_error_t SrsRtcPlayStream::notify(int type, srs_utime_t interval, srs_utime_t tick) +{ + srs_error_t err = srs_success; + + if (!is_started) { return err; } - if (nn_bytes < kRtpMaxPayloadSize) { - // Package NALUs in a single RTP packet. - SrsRtpPacket2* packet = packets.fetch(); - if (!packet) { - srs_freep(raw); - return srs_error_new(ERROR_RTC_RTP_MUXER, "cache empty"); - } + return err; +} - packet->rtp_header.set_timestamp(msg->timestamp * 90); - packet->rtp_header.set_sequence(video_sequence++); - packet->rtp_header.set_ssrc(video_ssrc); - packet->rtp_header.set_payload_type(video_payload_type); - - packet->payload = raw; +srs_error_t SrsRtcPlayStream::on_rtcp(SrsRtcpCommon* rtcp) +{ + if(SrsRtcpType_rr == rtcp->type()) { + SrsRtcpRR* rr = dynamic_cast(rtcp); + return on_rtcp_rr(rr); + } else if(SrsRtcpType_rtpfb == rtcp->type()) { + //currently rtpfb of nack will be handle by player. TWCC will be handled by SrsRtcConnection + SrsRtcpNack* nack = dynamic_cast(rtcp); + return on_rtcp_nack(nack); + } else if(SrsRtcpType_psfb == rtcp->type()) { + SrsRtcpPsfbCommon* psfb = dynamic_cast(rtcp); + return on_rtcp_ps_feedback(psfb); + } else if(SrsRtcpType_xr == rtcp->type()) { + SrsRtcpXr* xr = dynamic_cast(rtcp); + return on_rtcp_xr(xr); + } else if(SrsRtcpType_bye == rtcp->type()) { + // TODO: FIXME: process rtcp bye. + return srs_success; } else { - // We must free it, should never use RTP packets to free it, - // because more than one RTP packet will refer to it. - SrsAutoFree(SrsRtpRawNALUs, raw); - - // Package NALUs in FU-A RTP packets. - int fu_payload_size = kRtpMaxPayloadSize; - - // The first byte is store in FU-A header. - uint8_t header = raw->skip_first_byte(); - uint8_t nal_type = header & kNalTypeMask; - int nb_left = nn_bytes - 1; - - int num_of_packet = 1 + (nn_bytes - 1) / fu_payload_size; - for (int i = 0; i < num_of_packet; ++i) { - int packet_size = srs_min(nb_left, fu_payload_size); - - SrsRtpPacket2* packet = packets.fetch(); - if (!packet) { - srs_freep(raw); - return srs_error_new(ERROR_RTC_RTP_MUXER, "cache empty"); - } - - packet->rtp_header.set_timestamp(msg->timestamp * 90); - packet->rtp_header.set_sequence(video_sequence++); - packet->rtp_header.set_ssrc(video_ssrc); - packet->rtp_header.set_payload_type(video_payload_type); - - SrsRtpFUAPayload* fua = new SrsRtpFUAPayload(); - packet->payload = fua; - - fua->nri = (SrsAvcNaluType)header; - fua->nalu_type = (SrsAvcNaluType)nal_type; - fua->start = bool(i == 0); - fua->end = bool(i == num_of_packet - 1); - - if ((err = raw->read_samples(fua->nalus, packet_size)) != srs_success) { - return srs_error_wrap(err, "read samples %d bytes, left %d, total %d", packet_size, nb_left, nn_bytes); - } - - nb_left -= packet_size; - } + return srs_error_new(ERROR_RTC_RTCP_CHECK, "unknown rtcp type=%u", rtcp->type()); } +} - if (packets.size() > 0) { - packets.back()->rtp_header.set_marker(true); - } +srs_error_t SrsRtcPlayStream::on_rtcp_rr(SrsRtcpRR* rtcp) +{ + srs_error_t err = srs_success; + + // TODO: FIXME: Implements it. + + session_->stat_->nn_sr++; return err; } -srs_error_t SrsRtcSenderThread::packet_opus(SrsSample* sample, SrsRtcPackets& packets, int nn_max_payload) +srs_error_t SrsRtcPlayStream::on_rtcp_xr(SrsRtcpXr* rtcp) { srs_error_t err = srs_success; - SrsRtpPacket2* packet = packets.fetch(); - if (!packet) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "cache empty"); - } - packet->rtp_header.set_marker(true); - packet->rtp_header.set_timestamp(audio_timestamp); - packet->rtp_header.set_sequence(audio_sequence++); - packet->rtp_header.set_ssrc(audio_ssrc); - packet->rtp_header.set_payload_type(audio_payload_type); + // TODO: FIXME: Implements it. - SrsRtpRawPayload* raw = packet->reuse_raw(); - raw->payload = sample->bytes; - raw->nn_payload = sample->size; - - if (max_padding > 0) { - if (sample->size < nn_max_payload && nn_max_payload - sample->size < max_padding) { - int padding = nn_max_payload - sample->size; - packet->set_padding(padding); - -#if defined(SRS_DEBUG) - srs_trace("#%d, Fast Padding %d bytes %d=>%d, SN=%d, max_payload %d, max_padding %d", packets.debug_id, - padding, sample->size, sample->size + padding, packet->rtp_header.get_sequence(), nn_max_payload, max_padding); -#endif - } - } - - // TODO: FIXME: Why 960? Need Refactoring? - audio_timestamp += 960; + session_->stat_->nn_xr++; return err; } -srs_error_t SrsRtcSenderThread::packet_fu_a(SrsSharedPtrMessage* msg, SrsSample* sample, int fu_payload_size, SrsRtcPackets& packets) +srs_error_t SrsRtcPlayStream::on_rtcp_nack(SrsRtcpNack* rtcp) { srs_error_t err = srs_success; - char* p = sample->bytes + 1; - int nb_left = sample->size - 1; - uint8_t header = sample->bytes[0]; - uint8_t nal_type = header & kNalTypeMask; - - int num_of_packet = 1 + (sample->size - 1) / fu_payload_size; - for (int i = 0; i < num_of_packet; ++i) { - int packet_size = srs_min(nb_left, fu_payload_size); - - SrsRtpPacket2* packet = packets.fetch(); - if (!packet) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "cache empty"); - } - - packet->rtp_header.set_timestamp(msg->timestamp * 90); - packet->rtp_header.set_sequence(video_sequence++); - packet->rtp_header.set_ssrc(video_ssrc); - packet->rtp_header.set_payload_type(video_payload_type); - - SrsRtpFUAPayload2* fua = packet->reuse_fua(); - - fua->nri = (SrsAvcNaluType)header; - fua->nalu_type = (SrsAvcNaluType)nal_type; - fua->start = bool(i == 0); - fua->end = bool(i == num_of_packet - 1); - - fua->payload = p; - fua->size = packet_size; - - p += packet_size; - nb_left -= packet_size; - } - - return err; -} - -// Single NAL Unit Packet @see https://tools.ietf.org/html/rfc6184#section-5.6 -srs_error_t SrsRtcSenderThread::packet_single_nalu(SrsSharedPtrMessage* msg, SrsSample* sample, SrsRtcPackets& packets) -{ - srs_error_t err = srs_success; - - SrsRtpPacket2* packet = packets.fetch(); - if (!packet) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "cache empty"); - } - packet->rtp_header.set_timestamp(msg->timestamp * 90); - packet->rtp_header.set_sequence(video_sequence++); - packet->rtp_header.set_ssrc(video_ssrc); - packet->rtp_header.set_payload_type(video_payload_type); - - SrsRtpRawPayload* raw = packet->reuse_raw(); - raw->payload = sample->bytes; - raw->nn_payload = sample->size; - - return err; -} - -srs_error_t SrsRtcSenderThread::packet_stap_a(SrsSource* source, SrsSharedPtrMessage* msg, SrsRtcPackets& packets) -{ - srs_error_t err = srs_success; - - SrsMetaCache* meta = source->cached_meta(); - if (!meta) { + // If NACK disabled, print a log. + if (!nack_enabled_) { + vector sns = rtcp->get_lost_sns(); + srs_trace("RTC NACK ssrc=%u, seq=%s, ignored", rtcp->get_media_ssrc(), srs_join_vector_string(sns, ",").c_str()); return err; } - SrsFormat* format = meta->vsh_format(); - if (!format || !format->vcodec) { - return err; - } + // TODO: FIXME: Support ARQ. + vector resend_pkts; - const vector& sps = format->vcodec->sequenceParameterSetNALUnit; - const vector& pps = format->vcodec->pictureParameterSetNALUnit; - if (sps.empty() || pps.empty()) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "sps/pps empty"); - } - - SrsRtpPacket2* packet = packets.fetch(); - if (!packet) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "cache empty"); - } - packet->rtp_header.set_marker(false); - packet->rtp_header.set_timestamp(msg->timestamp * 90); - packet->rtp_header.set_sequence(video_sequence++); - packet->rtp_header.set_ssrc(video_ssrc); - packet->rtp_header.set_payload_type(video_payload_type); - - SrsRtpSTAPPayload* stap = new SrsRtpSTAPPayload(); - packet->payload = stap; - - uint8_t header = sps[0]; - stap->nri = (SrsAvcNaluType)header; - - if (true) { - SrsSample* sample = new SrsSample(); - sample->bytes = (char*)&sps[0]; - sample->size = (int)sps.size(); - stap->nalus.push_back(sample); - } - - if (true) { - SrsSample* sample = new SrsSample(); - sample->bytes = (char*)&pps[0]; - sample->size = (int)pps.size(); - stap->nalus.push_back(sample); - } - - return err; -} - -SrsRtcSession::SrsRtcSession(SrsRtcServer* rtc_svr, const SrsRequest& req, const std::string& un, int context_id) -{ - rtc_server = rtc_svr; - session_state = INIT; - dtls_session = new SrsDtlsSession(this); - dtls_session->initialize(req); - strd = NULL; - - username = un; - - last_stun_time = srs_get_system_time(); - - request = req; - source = NULL; - - cid = context_id; - encrypt = true; - - // TODO: FIXME: Support reload. - sessionStunTimeout = _srs_config->get_rtc_stun_timeout(req.vhost); -} - -SrsRtcSession::~SrsRtcSession() -{ - srs_freep(dtls_session); - - if (strd) { - strd->stop(); - } - srs_freep(strd); -} - -void SrsRtcSession::set_local_sdp(const SrsSdp& sdp) -{ - local_sdp = sdp; -} - -void SrsRtcSession::switch_to_context() -{ - _srs_context->set_id(cid); -} - -srs_error_t SrsRtcSession::on_stun(SrsUdpMuxSocket* skt, SrsStunPacket* stun_req) -{ - srs_error_t err = srs_success; - - if (stun_req->is_binding_request()) { - if ((err = on_binding_request(skt, stun_req)) != srs_success) { - return srs_error_wrap(err, "stun binding request failed"); - } - - last_stun_time = srs_get_system_time(); - - if (strd && strd->sendonly_ukt) { - // We are running in the ice-lite(server) mode. If client have multi network interface, - // we only choose one candidate pair which is determined by client. - if (stun_req->get_use_candidate() && strd->sendonly_ukt->get_peer_id() != skt->get_peer_id()) { - strd->update_sendonly_socket(skt); - } - } - } - - return err; -} - -srs_error_t SrsRtcSession::check_source() -{ - srs_error_t err = srs_success; - - if (source == NULL) { - // TODO: FIXME: Should refactor it, directly use http server as handler. - ISrsSourceHandler* handler = _srs_hybrid->srs()->instance(); - if ((err = _srs_sources->fetch_or_create(&request, handler, &source)) != srs_success) { - return srs_error_wrap(err, "create source"); - } - } - - return err; -} - -#ifdef SRS_AUTO_OSX -// These functions are similar to the older byteorder(3) family of functions. -// For example, be32toh() is identical to ntohl(). -// @see https://linux.die.net/man/3/be32toh -#define be32toh ntohl -#endif - -srs_error_t SrsRtcSession::on_binding_request(SrsUdpMuxSocket* skt, SrsStunPacket* stun_req) -{ - srs_error_t err = srs_success; - - bool strict_check = _srs_config->get_rtc_stun_strict_check(request.vhost); - if (strict_check && stun_req->get_ice_controlled()) { - // @see: https://tools.ietf.org/html/draft-ietf-ice-rfc5245bis-00#section-6.1.3.1 - // TODO: Send 487 (Role Conflict) error response. - return srs_error_new(ERROR_RTC_STUN, "Peer must not in ice-controlled role in ice-lite mode."); - } - - SrsStunPacket stun_binding_response; - char buf[kRtpPacketSize]; - SrsBuffer* stream = new SrsBuffer(buf, sizeof(buf)); - SrsAutoFree(SrsBuffer, stream); - - stun_binding_response.set_message_type(BindingResponse); - stun_binding_response.set_local_ufrag(stun_req->get_remote_ufrag()); - stun_binding_response.set_remote_ufrag(stun_req->get_local_ufrag()); - stun_binding_response.set_transcation_id(stun_req->get_transcation_id()); - // FIXME: inet_addr is deprecated, IPV6 support - stun_binding_response.set_mapped_address(be32toh(inet_addr(skt->get_peer_ip().c_str()))); - stun_binding_response.set_mapped_port(skt->get_peer_port()); - - if ((err = stun_binding_response.encode(get_local_sdp()->get_ice_pwd(), stream)) != srs_success) { - return srs_error_wrap(err, "stun binding response encode failed"); - } - - if ((err = skt->sendto(stream->data(), stream->pos(), 0)) != srs_success) { - return srs_error_wrap(err, "stun binding response send failed"); - } - - if (get_session_state() == WAITING_STUN) { - set_session_state(DOING_DTLS_HANDSHAKE); - - peer_id = skt->get_peer_id(); - rtc_server->insert_into_id_sessions(peer_id, this); - } - - return err; -} - -srs_error_t SrsRtcSession::on_rtcp_feedback(char* buf, int nb_buf, SrsUdpMuxSocket* skt) -{ - srs_error_t err = srs_success; - - if (nb_buf < 12) { - return srs_error_new(ERROR_RTC_RTCP_CHECK, "invalid rtp feedback packet, nb_buf=%d", nb_buf); - } - - SrsBuffer* stream = new SrsBuffer(buf, nb_buf); - SrsAutoFree(SrsBuffer, stream); - - // @see: https://tools.ietf.org/html/rfc4585#section-6.1 - /* - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - |V=2|P| FMT | PT | length | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | SSRC of packet sender | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | SSRC of media source | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - : Feedback Control Information (FCI) : - : : - */ - /*uint8_t first = */stream->read_1bytes(); - //uint8_t version = first & 0xC0; - //uint8_t padding = first & 0x20; - //uint8_t fmt = first & 0x1F; - - /*uint8_t payload_type = */stream->read_1bytes(); - /*uint16_t length = */stream->read_2bytes(); - /*uint32_t ssrc_of_sender = */stream->read_4bytes(); - /*uint32_t ssrc_of_media_source = */stream->read_4bytes(); - - /* - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | PID | BLP | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - */ - - uint16_t pid = stream->read_2bytes(); - int blp = stream->read_2bytes(); - - srs_verbose("pid=%u, blp=%d", pid, blp); - - if ((err = check_source()) != srs_success) { - return srs_error_wrap(err, "check"); - } - if (! source) { - return srs_error_new(ERROR_RTC_SOURCE_CHECK, "can not found source"); - } - - vector resend_pkts; - SrsRtpSharedPacket* pkt = source->find_rtp_packet(pid); - if (pkt) { - resend_pkts.push_back(pkt); - } - - uint16_t mask = 0x01; - for (int i = 1; i < 16 && blp; ++i, mask <<= 1) { - if (! (blp & mask)) { - continue; - } - - uint32_t loss_seq = pid + i; - - SrsRtpSharedPacket* pkt = source->find_rtp_packet(loss_seq); - if (! pkt) { - continue; - } - - resend_pkts.push_back(pkt); + vector sns = rtcp->get_lost_sns(); + for(int i = 0; i < (int)sns.size(); ++i) { + uint16_t seq = sns.at(i); + nack_fetch(resend_pkts, rtcp->get_media_ssrc(), seq); } for (int i = 0; i < (int)resend_pkts.size(); ++i) { - if (dtls_session) { - char protected_buf[kRtpPacketSize]; - int nb_protected_buf = resend_pkts[i]->size; + SrsRtpPacket2* pkt = resend_pkts[i]; + info.nn_bytes += pkt->nb_bytes(); - srs_verbose("resend pkt sequence=%u", resend_pkts[i]->rtp_header.get_sequence()); - - dtls_session->protect_rtp(protected_buf, resend_pkts[i]->payload, nb_protected_buf); - skt->sendto(protected_buf, nb_protected_buf, 0); + uint32_t nn = 0; + if (nack_epp->can_print(pkt->header.get_ssrc(), &nn)) { + srs_trace("RTC NACK ARQ seq=%u, ssrc=%u, ts=%u, count=%u/%u, %d bytes", pkt->header.get_sequence(), + pkt->header.get_ssrc(), pkt->header.get_timestamp(), nn, nack_epp->nn_count, pkt->nb_bytes()); } } + // By default, we send packets by sendmmsg. + if ((err = session_->do_send_packets(resend_pkts, info)) != srs_success) { + return srs_error_wrap(err, "raw send"); + } + + session_->stat_->nn_nack++; + return err; } -srs_error_t SrsRtcSession::on_rtcp_ps_feedback(char* buf, int nb_buf, SrsUdpMuxSocket* skt) +srs_error_t SrsRtcPlayStream::on_rtcp_ps_feedback(SrsRtcpPsfbCommon* rtcp) { srs_error_t err = srs_success; - if (nb_buf < 12) { - return srs_error_new(ERROR_RTC_RTCP_CHECK, "invalid rtp feedback packet, nb_buf=%d", nb_buf); - } - - SrsBuffer* stream = new SrsBuffer(buf, nb_buf); - SrsAutoFree(SrsBuffer, stream); - - uint8_t first = stream->read_1bytes(); - //uint8_t version = first & 0xC0; - //uint8_t padding = first & 0x20; - uint8_t fmt = first & 0x1F; - - // TODO: FIXME: Dead code? - /*uint8_t payload_type = */stream->read_1bytes(); - /*uint16_t length = */stream->read_2bytes(); - /*uint32_t ssrc_of_sender = */stream->read_4bytes(); - /*uint32_t ssrc_of_media_source = */stream->read_4bytes(); - + uint8_t fmt = rtcp->get_rc(); switch (fmt) { case kPLI: { - srs_verbose("pli"); + uint32_t ssrc = get_video_publish_ssrc(rtcp->get_media_ssrc()); + if (ssrc) { + pli_worker_->request_keyframe(ssrc, cid_); + } + + session_->stat_->nn_pli++; break; } case kSLI: { @@ -1682,781 +878,2658 @@ srs_error_t SrsRtcSession::on_rtcp_ps_feedback(char* buf, int nb_buf, SrsUdpMuxS return err; } -srs_error_t SrsRtcSession::on_rtcp_receiver_report(char* buf, int nb_buf, SrsUdpMuxSocket* skt) +uint32_t SrsRtcPlayStream::get_video_publish_ssrc(uint32_t play_ssrc) +{ + std::map::iterator it; + for (it = video_tracks_.begin(); it != video_tracks_.end(); ++it) { + if (it->second->has_ssrc(play_ssrc)) { + return it->first; + } + } + + return 0; +} + +srs_error_t SrsRtcPlayStream::do_request_keyframe(uint32_t ssrc, SrsContextId cid) { srs_error_t err = srs_success; - if (nb_buf < 8) { - return srs_error_new(ERROR_RTC_RTCP_CHECK, "invalid rtp receiver report packet, nb_buf=%d", nb_buf); - } + // The source MUST exists, when PLI thread is running. + srs_assert(source_); - SrsBuffer* stream = new SrsBuffer(buf, nb_buf); - SrsAutoFree(SrsBuffer, stream); - - // @see: https://tools.ietf.org/html/rfc3550#section-6.4.2 - /* - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -header |V=2|P| RC | PT=RR=201 | length | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | SSRC of packet sender | - +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ -report | SSRC_1 (SSRC of first source) | -block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - 1 | fraction lost | cumulative number of packets lost | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | extended highest sequence number received | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | interarrival jitter | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | last SR (LSR) | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | delay since last SR (DLSR) | - +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ -report | SSRC_2 (SSRC of second source) | -block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - 2 : ... : - +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ - | profile-specific extensions | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - */ - uint8_t first = stream->read_1bytes(); - //uint8_t version = first & 0xC0; - //uint8_t padding = first & 0x20; - uint8_t rc = first & 0x1F; - - /*uint8_t payload_type = */stream->read_1bytes(); - uint16_t length = stream->read_2bytes(); - /*uint32_t ssrc_of_sender = */stream->read_4bytes(); - - if (((length + 1) * 4) != (rc * 24 + 8)) { - return srs_error_new(ERROR_RTC_RTCP_CHECK, "invalid rtcp receiver packet, length=%u, rc=%u", length, rc); - } - - for (int i = 0; i < rc; ++i) { - uint32_t ssrc = stream->read_4bytes(); - uint8_t fraction_lost = stream->read_1bytes(); - uint32_t cumulative_number_of_packets_lost = stream->read_3bytes(); - uint32_t highest_seq = stream->read_4bytes(); - uint32_t jitter = stream->read_4bytes(); - uint32_t lst = stream->read_4bytes(); - uint32_t dlsr = stream->read_4bytes(); - - (void)ssrc; (void)fraction_lost; (void)cumulative_number_of_packets_lost; (void)highest_seq; (void)jitter; (void)lst; (void)dlsr; - srs_verbose("ssrc=%u, fraction_lost=%u, cumulative_number_of_packets_lost=%u, highest_seq=%u, jitter=%u, lst=%u, dlst=%u", - ssrc, fraction_lost, cumulative_number_of_packets_lost, highest_seq, jitter, lst, dlsr); - } - - return err; -} - -srs_error_t SrsRtcSession::on_connection_established(SrsUdpMuxSocket* skt) -{ - srs_trace("rtc session=%s, to=%dms connection established", id().c_str(), srsu2msi(sessionStunTimeout)); - return start_play(skt); -} - -srs_error_t SrsRtcSession::start_play(SrsUdpMuxSocket* skt) -{ - srs_error_t err = srs_success; - - srs_freep(strd); - strd = new SrsRtcSenderThread(this, skt, _srs_context->get_id()); - - uint32_t video_ssrc = 0; - uint32_t audio_ssrc = 0; - uint16_t video_payload_type = 0; - uint16_t audio_payload_type = 0; - for (size_t i = 0; i < local_sdp.media_descs_.size(); ++i) { - const SrsMediaDesc& media_desc = local_sdp.media_descs_[i]; - if (media_desc.is_audio()) { - audio_ssrc = media_desc.ssrc_infos_[0].ssrc_; - audio_payload_type = media_desc.payload_types_[0].payload_type_; - } else if (media_desc.is_video()) { - video_ssrc = media_desc.ssrc_infos_[0].ssrc_; - video_payload_type = media_desc.payload_types_[0].payload_type_; - } - } - - if ((err =strd->initialize(video_ssrc, audio_ssrc, video_payload_type, audio_payload_type)) != srs_success) { - return srs_error_wrap(err, "SrsRtcSenderThread init"); - } - - if ((err = strd->start()) != srs_success) { - return srs_error_wrap(err, "start SrsRtcSenderThread"); - } - - return err; -} - -bool SrsRtcSession::is_stun_timeout() -{ - return last_stun_time + sessionStunTimeout < srs_get_system_time(); -} - -srs_error_t SrsRtcSession::on_dtls(SrsUdpMuxSocket* skt) -{ - return dtls_session->on_dtls(skt); -} - -srs_error_t SrsRtcSession::on_rtcp(SrsUdpMuxSocket* skt) -{ - srs_error_t err = srs_success; - - if (dtls_session == NULL) { - return srs_error_new(ERROR_RTC_RTCP, "recv unexpect rtp packet before dtls done"); - } - - char unprotected_buf[kRtpPacketSize]; - int nb_unprotected_buf = skt->size(); - if ((err = dtls_session->unprotect_rtcp(unprotected_buf, skt->data(), nb_unprotected_buf)) != srs_success) { - return srs_error_wrap(err, "rtcp unprotect failed"); - } - - char* ph = unprotected_buf; - int nb_left = nb_unprotected_buf; - while (nb_left) { - uint8_t payload_type = ph[1]; - uint16_t length_4bytes = (((uint16_t)ph[2]) << 8) | ph[3]; - - int length = (length_4bytes + 1) * 4; - - if (length > nb_unprotected_buf) { - return srs_error_new(ERROR_RTC_RTCP, "invalid rtcp packet, length=%u", length); - } - - srs_verbose("on rtcp, payload_type=%u", payload_type); - - switch (payload_type) { - case kSR: { - break; - } - case kRR: { - err = on_rtcp_receiver_report(ph, length, skt); - break; - } - case kSDES: { - break; - } - case kBye: { - break; - } - case kApp: { - break; - } - case kRtpFb: { - err = on_rtcp_feedback(ph, length, skt); - break; - } - case kPsFb: { - err = on_rtcp_ps_feedback(ph, length, skt); - break; - } - default:{ - return srs_error_new(ERROR_RTC_RTCP_CHECK, "unknown rtcp type=%u", payload_type); - break; - } - } - - if (err != srs_success) { - return srs_error_wrap(err, "rtcp"); - } - - ph += length; - nb_left -= length; - } - - return err; -} - -SrsUdpMuxSender::SrsUdpMuxSender(SrsRtcServer* s) -{ - lfd = NULL; - server = s; - - waiting_msgs = false; - cond = srs_cond_new(); - trd = new SrsDummyCoroutine(); - - cache_pos = 0; - max_sendmmsg = 0; - queue_length = 0; - extra_ratio = 0; - extra_queue = 0; - gso = false; - nn_senders = 0; - - _srs_config->subscribe(this); -} - -SrsUdpMuxSender::~SrsUdpMuxSender() -{ - _srs_config->unsubscribe(this); - - srs_freep(trd); - srs_cond_destroy(cond); - - free_mhdrs(hotspot); - hotspot.clear(); - - free_mhdrs(cache); - cache.clear(); -} - -srs_error_t SrsUdpMuxSender::initialize(srs_netfd_t fd, int senders) -{ - srs_error_t err = srs_success; - - lfd = fd; - - srs_freep(trd); - trd = new SrsSTCoroutine("udp", this); - if ((err = trd->start()) != srs_success) { - return srs_error_wrap(err, "start coroutine"); - } - - max_sendmmsg = _srs_config->get_rtc_server_sendmmsg(); - gso = _srs_config->get_rtc_server_gso(); - queue_length = srs_max(128, _srs_config->get_rtc_server_queue_length()); - nn_senders = senders; - - // For no GSO, we need larger queue. - if (!gso) { - queue_length *= 2; - } - - srs_trace("RTC sender #%d init ok, max_sendmmsg=%d, gso=%d, queue_max=%dx%d, extra_ratio=%d/%d", srs_netfd_fileno(fd), - max_sendmmsg, gso, queue_length, nn_senders, extra_ratio, extra_queue); - - return err; -} - -void SrsUdpMuxSender::free_mhdrs(std::vector& mhdrs) -{ - int nn_mhdrs = (int)mhdrs.size(); - for (int i = 0; i < nn_mhdrs; i++) { - // @see https://linux.die.net/man/2/sendmmsg - // @see https://linux.die.net/man/2/sendmsg - mmsghdr* hdr = &mhdrs[i]; - - // Free control for GSO. - char* msg_control = (char*)hdr->msg_hdr.msg_control; - srs_freepa(msg_control); - - // Free iovec. - for (int j = SRS_PERF_RTC_GSO_MAX - 1; j >= 0 ; j--) { - iovec* iov = hdr->msg_hdr.msg_iov + j; - char* data = (char*)iov->iov_base; - srs_freepa(data); - srs_freepa(iov); - } - } - mhdrs.clear(); -} - -srs_error_t SrsUdpMuxSender::fetch(mmsghdr** pphdr) -{ - // TODO: FIXME: Maybe need to shrink? - if (cache_pos >= (int)cache.size()) { - // @see https://linux.die.net/man/2/sendmmsg - // @see https://linux.die.net/man/2/sendmsg - mmsghdr mhdr; - - mhdr.msg_len = 0; - mhdr.msg_hdr.msg_flags = 0; - mhdr.msg_hdr.msg_control = NULL; - - mhdr.msg_hdr.msg_iovlen = SRS_PERF_RTC_GSO_MAX; - mhdr.msg_hdr.msg_iov = new iovec[mhdr.msg_hdr.msg_iovlen]; - memset((void*)mhdr.msg_hdr.msg_iov, 0, sizeof(iovec) * mhdr.msg_hdr.msg_iovlen); - - for (int i = 0; i < SRS_PERF_RTC_GSO_IOVS; i++) { - iovec* p = mhdr.msg_hdr.msg_iov + i; - p->iov_base = new char[kRtpPacketSize]; - } - - cache.push_back(mhdr); - } - - *pphdr = &cache[cache_pos++]; - return srs_success; -} - -bool SrsUdpMuxSender::overflow() -{ - return cache_pos > queue_length + extra_queue; -} - -void SrsUdpMuxSender::set_extra_ratio(int r) -{ - // We use the larger extra ratio, because all vhosts shares the senders. - if (extra_ratio > r) { - return; - } - - extra_ratio = r; - extra_queue = queue_length * r / 100; - - srs_trace("RTC sender #%d extra queue, max_sendmmsg=%d, gso=%d, queue_max=%dx%d, extra_ratio=%d/%d, cache=%d/%d/%d", srs_netfd_fileno(lfd), - max_sendmmsg, gso, queue_length, nn_senders, extra_ratio, extra_queue, cache_pos, (int)cache.size(), (int)hotspot.size()); -} - -srs_error_t SrsUdpMuxSender::sendmmsg(mmsghdr* hdr) -{ - if (waiting_msgs) { - waiting_msgs = false; - srs_cond_signal(cond); - } - - return srs_success; -} - -srs_error_t SrsUdpMuxSender::cycle() -{ - srs_error_t err = srs_success; - - uint64_t nn_msgs = 0; uint64_t nn_msgs_last = 0; int nn_msgs_max = 0; - uint64_t nn_bytes = 0; int nn_bytes_max = 0; - uint64_t nn_gso_msgs = 0; uint64_t nn_gso_iovs = 0; int nn_gso_msgs_max = 0; int nn_gso_iovs_max = 0; - int nn_loop = 0; int nn_wait = 0; - srs_utime_t time_last = srs_get_system_time(); - - bool stat_enabled = _srs_config->get_rtc_server_perf_stat(); - SrsStatistic* stat = SrsStatistic::instance(); - - SrsPithyPrint* pprint = SrsPithyPrint::create_rtc_send(srs_netfd_fileno(lfd)); - SrsAutoFree(SrsPithyPrint, pprint); - - while (true) { - if ((err = trd->pull()) != srs_success) { - return err; - } - - nn_loop++; - - int pos = cache_pos; - int gso_iovs = 0; - if (pos <= 0) { - waiting_msgs = true; - nn_wait++; - srs_cond_wait(cond); - continue; - } - - // We are working on hotspot now. - cache.swap(hotspot); - cache_pos = 0; - - int gso_pos = 0; - int nn_writen = 0; - if (pos > 0) { - // Send out all messages. - // @see https://linux.die.net/man/2/sendmmsg - // @see https://linux.die.net/man/2/sendmsg - mmsghdr* p = &hotspot[0]; mmsghdr* end = p + pos; - for (p = &hotspot[0]; p < end; p += max_sendmmsg) { - int vlen = (int)(end - p); - vlen = srs_min(max_sendmmsg, vlen); - - int r0 = srs_sendmmsg(lfd, p, (unsigned int)vlen, 0, SRS_UTIME_NO_TIMEOUT); - if (r0 != vlen) { - srs_warn("sendmmsg %d msgs, %d done", vlen, r0); - } - - if (stat_enabled) { - stat->perf_on_sendmmsg_packets(vlen); - } - } - - // Collect informations for GSO. - if (stat_enabled) { - // Stat the messages, iovs and bytes. - // @see https://linux.die.net/man/2/sendmmsg - // @see https://linux.die.net/man/2/sendmsg - for (int i = 0; i < pos; i++) { - mmsghdr* mhdr = &hotspot[i]; - - nn_writen += (int)mhdr->msg_len; - - int real_iovs = mhdr->msg_hdr.msg_iovlen; - gso_pos++; nn_gso_msgs++; nn_gso_iovs += real_iovs; - gso_iovs += real_iovs; - } - } - } - - if (!stat_enabled) { - continue; - } - - // Increase total messages. - nn_msgs += pos + gso_iovs; - nn_msgs_max = srs_max(pos, nn_msgs_max); - nn_bytes += nn_writen; - nn_bytes_max = srs_max(nn_bytes_max, nn_writen); - nn_gso_msgs_max = srs_max(gso_pos, nn_gso_msgs_max); - nn_gso_iovs_max = srs_max(gso_iovs, nn_gso_iovs_max); - - pprint->elapse(); - if (pprint->can_print()) { - // TODO: FIXME: Extract a PPS calculator. - int pps_average = 0; int pps_last = 0; - if (true) { - if (srs_get_system_time() > srs_get_system_startup_time()) { - pps_average = (int)(nn_msgs * SRS_UTIME_SECONDS / (srs_get_system_time() - srs_get_system_startup_time())); - } - if (srs_get_system_time() > time_last) { - pps_last = (int)((nn_msgs - nn_msgs_last) * SRS_UTIME_SECONDS / (srs_get_system_time() - time_last)); - } - } - - string pps_unit = ""; - if (pps_last > 10000 || pps_average > 10000) { - pps_unit = "(w)"; pps_last /= 10000; pps_average /= 10000; - } else if (pps_last > 1000 || pps_average > 1000) { - pps_unit = "(k)"; pps_last /= 1000; pps_average /= 1000; - } - - int nn_cache = 0; - int nn_hotspot_size = (int)hotspot.size(); - for (int i = 0; i < nn_hotspot_size; i++) { - mmsghdr* hdr = &hotspot[i]; - nn_cache += hdr->msg_hdr.msg_iovlen; - } - - srs_trace("-> RTC SEND #%d, sessions %d, udp %d/%d/%" PRId64 ", gso %d/%d/%" PRId64 ", iovs %d/%d/%" PRId64 ", pps %d/%d%s, cache %d/%d, bytes %d/%" PRId64, - srs_netfd_fileno(lfd), (int)server->nn_sessions(), pos, nn_msgs_max, nn_msgs, gso_pos, nn_gso_msgs_max, nn_gso_msgs, gso_iovs, - nn_gso_iovs_max, nn_gso_iovs, pps_average, pps_last, pps_unit.c_str(), (int)hotspot.size(), nn_cache, nn_bytes_max, nn_bytes); - nn_msgs_last = nn_msgs; time_last = srs_get_system_time(); - nn_loop = nn_wait = nn_msgs_max = 0; - nn_gso_msgs_max = 0; nn_gso_iovs_max = 0; - nn_bytes_max = 0; - } - } - - return err; -} - -srs_error_t SrsUdpMuxSender::on_reload_rtc_server() -{ - if (true) { - int v = _srs_config->get_rtc_server_sendmmsg(); - if (max_sendmmsg != v) { - srs_trace("Reload max_sendmmsg %d=>%d", max_sendmmsg, v); - max_sendmmsg = v; - } - } - - return srs_success; -} - -SrsRtcServer::SrsRtcServer() -{ - timer = new SrsHourGlass(this, 1 * SRS_UTIME_SECONDS); -} - -SrsRtcServer::~SrsRtcServer() -{ - srs_freep(timer); - - if (true) { - vector::iterator it; - for (it = listeners.begin(); it != listeners.end(); ++it) { - SrsUdpMuxListener* listener = *it; - srs_freep(listener); - } - } - - if (true) { - vector::iterator it; - for (it = senders.begin(); it != senders.end(); ++it) { - SrsUdpMuxSender* sender = *it; - srs_freep(sender); - } - } -} - -srs_error_t SrsRtcServer::initialize() -{ - srs_error_t err = srs_success; - - if ((err = timer->tick(1 * SRS_UTIME_SECONDS)) != srs_success) { - return srs_error_wrap(err, "hourglass tick"); - } - - if ((err = timer->start()) != srs_success) { - return srs_error_wrap(err, "start timer"); - } - - srs_trace("RTC server init ok"); - - return err; -} - -srs_error_t SrsRtcServer::listen_udp() -{ - srs_error_t err = srs_success; - - if (!_srs_config->get_rtc_server_enabled()) { + ISrsRtcPublishStream* publisher = source_->publish_stream(); + if (!publisher) { return err; } - int port = _srs_config->get_rtc_server_listen(); - if (port <= 0) { - return srs_error_new(ERROR_RTC_PORT, "invalid port=%d", port); - } - - string ip = srs_any_address_for_listener(); - srs_assert(listeners.empty()); - - int nn_listeners = _srs_config->get_rtc_server_reuseport(); - for (int i = 0; i < nn_listeners; i++) { - SrsUdpMuxSender* sender = new SrsUdpMuxSender(this); - SrsUdpMuxListener* listener = new SrsUdpMuxListener(this, sender, ip, port); - - if ((err = listener->listen()) != srs_success) { - srs_freep(listener); - return srs_error_wrap(err, "listen %s:%d", ip.c_str(), port); - } - - if ((err = sender->initialize(listener->stfd(), nn_listeners)) != srs_success) { - return srs_error_wrap(err, "init sender"); - } - - srs_trace("rtc listen at udp://%s:%d, fd=%d", ip.c_str(), port, listener->fd()); - listeners.push_back(listener); - senders.push_back(sender); - } + publisher->request_keyframe(ssrc); return err; } -srs_error_t SrsRtcServer::on_udp_packet(SrsUdpMuxSocket* skt) +SrsRtcPublishStream::SrsRtcPublishStream(SrsRtcConnection* session, const SrsContextId& cid) { - if (is_stun(reinterpret_cast(skt->data()), skt->size())) { - return on_stun(skt); - } else if (is_dtls(reinterpret_cast(skt->data()), skt->size())) { - return on_dtls(skt); - } else if (is_rtp_or_rtcp(reinterpret_cast(skt->data()), skt->size())) { - return on_rtp_or_rtcp(skt); - } + timer_ = new SrsHourGlass(this, 10 * SRS_UTIME_MILLISECONDS); - return srs_error_new(ERROR_RTC_UDP, "unknown udp packet type"); + cid_ = cid; + is_started = false; + session_ = session; + request_keyframe_ = false; + pli_epp = new SrsErrorPithyPrint(); + + req = NULL; + source = NULL; + nn_simulate_nack_drop = 0; + nack_enabled_ = false; + pt_to_drop_ = 0; + + nn_audio_frames = 0; + twcc_id_ = 0; + twcc_fb_count_ = 0; + + pli_worker_ = new SrsRtcPLIWorker(this); + last_time_send_twcc_ = 0; } -srs_error_t SrsRtcServer::listen_api() +SrsRtcPublishStream::~SrsRtcPublishStream() +{ + if (_srs_rtc_hijacker) { + _srs_rtc_hijacker->on_stop_publish(session_, this, req); + } + + // TODO: FIXME: Should remove and delete source. + if (source) { + source->set_publish_stream(NULL); + source->on_unpublish(); + } + + for (int i = 0; i < (int)video_tracks_.size(); ++i) { + SrsRtcVideoRecvTrack* track = video_tracks_.at(i); + srs_freep(track); + } + video_tracks_.clear(); + + for (int i = 0; i < (int)audio_tracks_.size(); ++i) { + SrsRtcAudioRecvTrack* track = audio_tracks_.at(i); + srs_freep(track); + } + audio_tracks_.clear(); + + srs_freep(timer_); + srs_freep(pli_worker_); + srs_freep(pli_epp); + srs_freep(req); +} + +srs_error_t SrsRtcPublishStream::initialize(SrsRequest* r, SrsRtcStreamDescription* stream_desc) { srs_error_t err = srs_success; - // TODO: FIXME: Fetch api from hybrid manager. - SrsHttpServeMux* http_api_mux = _srs_hybrid->srs()->instance()->api_server(); - if ((err = http_api_mux->handle("/rtc/v1/play/", new SrsGoApiRtcPlay(this))) != srs_success) { - return srs_error_wrap(err, "handle sdp"); + req = r->copy(); + + audio_tracks_.push_back(new SrsRtcAudioRecvTrack(session_, stream_desc->audio_track_desc_)); + for (int i = 0; i < (int)stream_desc->video_track_descs_.size(); ++i) { + SrsRtcTrackDescription* desc = stream_desc->video_track_descs_.at(i); + video_tracks_.push_back(new SrsRtcVideoRecvTrack(session_, desc)); + } + + int twcc_id = -1; + uint32_t media_ssrc = 0; + // because audio_track_desc have not twcc id, for example, h5demo + // fetch twcc_id from video track description, + for (int i = 0; i < (int)stream_desc->video_track_descs_.size(); ++i) { + SrsRtcTrackDescription* desc = stream_desc->video_track_descs_.at(i); + twcc_id = desc->get_rtp_extension_id(kTWCCExt); + media_ssrc = desc->ssrc_; + break; + } + if (twcc_id != -1) { + twcc_id_ = twcc_id; + extension_types_.register_by_uri(twcc_id_, kTWCCExt); + rtcp_twcc_.set_media_ssrc(media_ssrc); + } + + nack_enabled_ = _srs_config->get_rtc_nack_enabled(req->vhost); + pt_to_drop_ = (uint16_t)_srs_config->get_rtc_drop_for_pt(req->vhost); + bool twcc_enabled = _srs_config->get_rtc_twcc_enabled(req->vhost); + + srs_trace("RTC publisher nack=%d, pt-drop=%u, twcc=%u/%d", nack_enabled_, pt_to_drop_, twcc_enabled, twcc_id); + + session_->stat_->nn_publishers++; + + // Setup the publish stream in source to enable PLI as such. + if ((err = _srs_rtc_sources->fetch_or_create(req, &source)) != srs_success) { + return srs_error_wrap(err, "create source"); + } + source->set_publish_stream(this); + + return err; +} + +srs_error_t SrsRtcPublishStream::start() +{ + srs_error_t err = srs_success; + + if (is_started) { + return err; + } + + if ((err = timer_->tick(SRS_TICKID_TWCC, 50 * SRS_UTIME_MILLISECONDS)) != srs_success) { + return srs_error_wrap(err, "twcc tick"); + } + + if ((err = timer_->tick(SRS_TICKID_RTCP, 200 * SRS_UTIME_MILLISECONDS)) != srs_success) { + return srs_error_wrap(err, "rtcp tick"); + } + + if ((err = timer_->start()) != srs_success) { + return srs_error_wrap(err, "start timer"); + } + + if ((err = source->on_publish()) != srs_success) { + return srs_error_wrap(err, "on publish"); + } + + if ((err = pli_worker_->start()) != srs_success) { + return srs_error_wrap(err, "start pli worker"); + } + + if (_srs_rtc_hijacker) { + if ((err = _srs_rtc_hijacker->on_start_publish(session_, this, req)) != srs_success) { + return srs_error_wrap(err, "on start publish"); + } + } + + is_started = true; + + return err; +} + +void SrsRtcPublishStream::set_all_tracks_status(bool status) +{ + std::ostringstream merged_log; + + // set video track status + if (true) { + std::vector::iterator it; + for (it = video_tracks_.begin(); it != video_tracks_.end(); ++it) { + SrsRtcVideoRecvTrack* track = *it; + + bool previous = track->set_track_status(status); + merged_log << "{track: " << track->get_track_id() << ", is_active: " << previous << "=>" << status << "},"; + } + } + + // set audio track status + if (true) { + std::vector::iterator it; + for (it = audio_tracks_.begin(); it != audio_tracks_.end(); ++it) { + SrsRtcAudioRecvTrack* track = *it; + + bool previous = track->set_track_status(status); + merged_log << "{track: " << track->get_track_id() << ", is_active: " << previous << "=>" << status << "},"; + } + } + + srs_trace("RTC: Init tracks %s ok", merged_log.str().c_str()); +} + +const SrsContextId& SrsRtcPublishStream::context_id() +{ + return cid_; +} + +srs_error_t SrsRtcPublishStream::send_rtcp_rr() +{ + srs_error_t err = srs_success; + + for (int i = 0; i < (int)video_tracks_.size(); ++i) { + SrsRtcVideoRecvTrack* track = video_tracks_.at(i); + if ((err = track->send_rtcp_rr()) != srs_success) { + return srs_error_wrap(err, "track=%s", track->get_track_id().c_str()); + } + } + + for (int i = 0; i < (int)audio_tracks_.size(); ++i) { + SrsRtcAudioRecvTrack* track = audio_tracks_.at(i); + if ((err = track->send_rtcp_rr()) != srs_success) { + return srs_error_wrap(err, "track=%s", track->get_track_id().c_str()); + } + } + + session_->stat_->nn_rr++; + + return err; +} + +srs_error_t SrsRtcPublishStream::send_rtcp_xr_rrtr() +{ + srs_error_t err = srs_success; + + for (int i = 0; i < (int)video_tracks_.size(); ++i) { + SrsRtcVideoRecvTrack* track = video_tracks_.at(i); + if ((err = track->send_rtcp_xr_rrtr()) != srs_success) { + return srs_error_wrap(err, "track=%s", track->get_track_id().c_str()); + } + } + + for (int i = 0; i < (int)audio_tracks_.size(); ++i) { + SrsRtcAudioRecvTrack* track = audio_tracks_.at(i); + if ((err = track->send_rtcp_xr_rrtr()) != srs_success) { + return srs_error_wrap(err, "track=%s", track->get_track_id().c_str()); + } + } + + session_->stat_->nn_xr++; + + return err; +} + +srs_error_t SrsRtcPublishStream::on_twcc(uint16_t sn) { + srs_error_t err = srs_success; + + srs_utime_t now = srs_get_system_time(); + err = rtcp_twcc_.recv_packet(sn, now); + + session_->stat_->nn_in_twcc++; + + return err; +} + +srs_error_t SrsRtcPublishStream::on_rtp(char* data, int nb_data) +{ + srs_error_t err = srs_success; + + session_->stat_->nn_in_rtp++; + + // For NACK simulator, drop packet. + if (nn_simulate_nack_drop) { + SrsBuffer b(data, nb_data); SrsRtpHeader h; h.ignore_padding(true); + err = h.decode(&b); srs_freep(err); // Ignore any error for simluate drop. + simulate_drop_packet(&h, nb_data); + return err; + } + + // Decode the header first. + SrsRtpHeader h; + if (pt_to_drop_ && twcc_id_) { + SrsBuffer b(data, nb_data); + h.ignore_padding(true); h.set_extensions(&extension_types_); + if ((err = h.decode(&b)) != srs_success) { + return srs_error_wrap(err, "twcc decode header"); + } + } + + // We must parse the TWCC from RTP header before SRTP unprotect, because: + // 1. Client may send some padding packets with invalid SequenceNumber, which causes the SRTP fail. + // 2. Server may send multiple duplicated NACK to client, and got more than one ARQ packet, which also fail SRTP. + // so, we must parse the header before SRTP unprotect(which may fail and drop packet). + if (twcc_id_) { + uint16_t twcc_sn = 0; + if ((err = h.get_twcc_sequence_number(twcc_sn)) == srs_success) { + if((err = on_twcc(twcc_sn)) != srs_success) { + return srs_error_wrap(err, "on twcc"); + } + } else { + srs_error_reset(err); + } + } + + // If payload type is configed to drop, ignore this packet. + if (pt_to_drop_ && pt_to_drop_ == h.get_payload_type()) { + return err; + } + + // Decrypt the cipher to plaintext RTP data. + int nb_unprotected_buf = nb_data; + char* unprotected_buf = new char[kRtpPacketSize]; + if ((err = session_->transport_->unprotect_rtp(data, unprotected_buf, nb_unprotected_buf)) != srs_success) { + // We try to decode the RTP header for more detail error informations. + SrsBuffer b(data, nb_data); SrsRtpHeader h; h.ignore_padding(true); + srs_error_t r0 = h.decode(&b); srs_freep(r0); // Ignore any error for header decoding. + err = srs_error_wrap(err, "marker=%u, pt=%u, seq=%u, ts=%u, ssrc=%u, pad=%u, payload=%uB", h.get_marker(), h.get_payload_type(), + h.get_sequence(), h.get_timestamp(), h.get_ssrc(), h.get_padding(), nb_data - b.pos()); + + srs_freepa(unprotected_buf); + return err; + } + + if (_srs_blackhole->blackhole) { + _srs_blackhole->sendto(unprotected_buf, nb_unprotected_buf); + } + + // Handle the plaintext RTP packet. + if ((err = do_on_rtp(unprotected_buf, nb_unprotected_buf)) != srs_success) { + int nb_header = h.nb_bytes(); + const char* body = unprotected_buf + nb_header; + int nb_body = nb_unprotected_buf - nb_header; + return srs_error_wrap(err, "cipher=%u, plaintext=%u, body=[%s]", nb_data, nb_unprotected_buf, + srs_string_dumps_hex(body, nb_body, 8).c_str()); } return err; } -SrsRtcSession* SrsRtcServer::create_rtc_session(const SrsRequest& req, const SrsSdp& remote_sdp, SrsSdp& local_sdp, const string& mock_eip) +srs_error_t SrsRtcPublishStream::do_on_rtp(char* plaintext, int nb_plaintext) { - std::string local_pwd = gen_random_str(32); - std::string local_ufrag = ""; - std::string username = ""; - while (true) { - local_ufrag = gen_random_str(8); + srs_error_t err = srs_success; - username = local_ufrag + ":" + remote_sdp.get_ice_ufrag(); - if (! map_username_session.count(username)) - break; + char* buf = plaintext; + int nb_buf = nb_plaintext; + + // Decode the RTP packet from buffer. + SrsRtpPacket2* pkt = new SrsRtpPacket2(); + SrsAutoFree(SrsRtpPacket2, pkt); + + if (true) { + pkt->set_decode_handler(this); + pkt->set_extension_types(&extension_types_); + pkt->shared_msg = new SrsSharedPtrMessage(); + pkt->shared_msg->wrap(buf, nb_buf); + + SrsBuffer b(buf, nb_buf); + if ((err = pkt->decode(&b)) != srs_success) { + return srs_error_wrap(err, "decode rtp packet"); + } } - int cid = _srs_context->get_id(); - SrsRtcSession* session = new SrsRtcSession(this, req, username, cid); - map_username_session.insert(make_pair(username, session)); - - local_sdp.set_ice_ufrag(local_ufrag); - local_sdp.set_ice_pwd(local_pwd); - local_sdp.set_fingerprint_algo("sha-256"); - local_sdp.set_fingerprint(SrsDtls::instance()->get_fingerprint()); - - // We allows to mock the eip of server. - if (!mock_eip.empty()) { - local_sdp.add_candidate(mock_eip, _srs_config->get_rtc_server_listen(), "host"); + // For source to consume packet. + uint32_t ssrc = pkt->header.get_ssrc(); + SrsRtcAudioRecvTrack* audio_track = get_audio_track(ssrc); + SrsRtcVideoRecvTrack* video_track = get_video_track(ssrc); + if (audio_track) { + pkt->frame_type = SrsFrameTypeAudio; + if ((err = audio_track->on_rtp(source, pkt)) != srs_success) { + return srs_error_wrap(err, "on audio"); + } + } else if (video_track) { + pkt->frame_type = SrsFrameTypeVideo; + if ((err = video_track->on_rtp(source, pkt)) != srs_success) { + return srs_error_wrap(err, "on video"); + } } else { - std::vector candidate_ips = get_candidate_ips(); - for (int i = 0; i < (int)candidate_ips.size(); ++i) { - local_sdp.add_candidate(candidate_ips[i], _srs_config->get_rtc_server_listen(), "host"); + return srs_error_new(ERROR_RTC_RTP, "unknown ssrc=%u", ssrc); + } + + // Check then send NACK every each RTP packet, to make it more efficient. + // For example, NACK of video track maybe triggered by audio RTP packets. + if ((err = check_send_nacks()) != srs_success) { + srs_warn("ignore nack err %s", srs_error_desc(err).c_str()); + srs_freep(err); + } + + if (_srs_rtc_hijacker) { + // TODO: FIXME: copy pkt by hijacker itself + if ((err = _srs_rtc_hijacker->on_rtp_packet(session_, this, req, pkt->copy())) != srs_success) { + return srs_error_wrap(err, "on rtp packet"); } } - session->set_remote_sdp(remote_sdp); - session->set_local_sdp(local_sdp); - - session->set_session_state(WAITING_STUN); - - return session; -} - -SrsRtcSession* SrsRtcServer::find_rtc_session_by_peer_id(const string& peer_id) -{ - map::iterator iter = map_id_session.find(peer_id); - if (iter == map_id_session.end()) { - return NULL; - } - - return iter->second; -} - -srs_error_t SrsRtcServer::on_stun(SrsUdpMuxSocket* skt) -{ - srs_error_t err = srs_success; - - SrsStunPacket stun_req; - if ((err = stun_req.decode(skt->data(), skt->size())) != srs_success) { - return srs_error_wrap(err, "decode stun packet failed"); - } - - srs_verbose("recv stun packet from %s, use-candidate=%d, ice-controlled=%d, ice-controlling=%d", - skt->get_peer_id().c_str(), stun_req.get_use_candidate(), stun_req.get_ice_controlled(), stun_req.get_ice_controlling()); - - std::string username = stun_req.get_username(); - SrsRtcSession* rtc_session = find_rtc_session_by_username(username); - if (rtc_session == NULL) { - return srs_error_new(ERROR_RTC_STUN, "can not find rtc_session, stun username=%s", username.c_str()); - } - - // Now, we got the RTC session to handle the packet, switch to its context - // to make all logs write to the "correct" pid+cid. - rtc_session->switch_to_context(); - - return rtc_session->on_stun(skt, &stun_req); -} - -srs_error_t SrsRtcServer::on_dtls(SrsUdpMuxSocket* skt) -{ - SrsRtcSession* rtc_session = find_rtc_session_by_peer_id(skt->get_peer_id()); - - if (rtc_session == NULL) { - return srs_error_new(ERROR_RTC_DTLS, "can not find rtc session by peer_id=%s", skt->get_peer_id().c_str()); - } - - // Now, we got the RTC session to handle the packet, switch to its context - // to make all logs write to the "correct" pid+cid. - rtc_session->switch_to_context(); - - return rtc_session->on_dtls(skt); -} - -srs_error_t SrsRtcServer::on_rtp_or_rtcp(SrsUdpMuxSocket* skt) -{ - srs_error_t err = srs_success; - - SrsRtcSession* rtc_session = find_rtc_session_by_peer_id(skt->get_peer_id()); - - if (rtc_session == NULL) { - return srs_error_new(ERROR_RTC_RTP, "can not find rtc session by peer_id=%s", skt->get_peer_id().c_str()); - } - - // Now, we got the RTC session to handle the packet, switch to its context - // to make all logs write to the "correct" pid+cid. - rtc_session->switch_to_context(); - - if (is_rtcp(reinterpret_cast(skt->data()), skt->size())) { - err = rtc_session->on_rtcp(skt); - } else { - // We disable it because no RTP for player. - // see https://github.com/ossrs/srs/blob/018577e685a07d9de7a47354e7a9c5f77f5f4202/trunk/src/app/srs_app_rtc_conn.cpp#L1081 - // err = rtc_session->on_rtp(skt); - } - return err; } -SrsRtcSession* SrsRtcServer::find_rtc_session_by_username(const std::string& username) +srs_error_t SrsRtcPublishStream::check_send_nacks() { - map::iterator iter = map_username_session.find(username); - if (iter == map_username_session.end()) { - return NULL; + srs_error_t err = srs_success; + + for (int i = 0; i < (int)video_tracks_.size(); ++i) { + SrsRtcVideoRecvTrack* track = video_tracks_.at(i); + if ((err = track->check_send_nacks()) != srs_success) { + return srs_error_wrap(err, "video track=%s", track->get_track_id().c_str()); + } } - return iter->second; + for (int i = 0; i < (int)audio_tracks_.size(); ++i) { + SrsRtcAudioRecvTrack* track = audio_tracks_.at(i); + if ((err = track->check_send_nacks()) != srs_success) { + return srs_error_wrap(err, "audio track=%s", track->get_track_id().c_str()); + } + } + + return err; } -bool SrsRtcServer::insert_into_id_sessions(const string& peer_id, SrsRtcSession* rtc_session) +void SrsRtcPublishStream::on_before_decode_payload(SrsRtpPacket2* pkt, SrsBuffer* buf, ISrsRtpPayloader** ppayload) { - return map_id_session.insert(make_pair(peer_id, rtc_session)).second; -} + // No payload, ignore. + if (buf->empty()) { + return; + } -void SrsRtcServer::check_and_clean_timeout_session() -{ - map::iterator iter = map_username_session.begin(); - while (iter != map_username_session.end()) { - SrsRtcSession* session = iter->second; - if (session == NULL) { - map_username_session.erase(iter++); - continue; + uint32_t ssrc = pkt->header.get_ssrc(); + if (get_audio_track(ssrc)) { + *ppayload = new SrsRtpRawPayload(); + } else if (get_video_track(ssrc)) { + uint8_t v = (uint8_t)pkt->nalu_type; + if (v == kStapA) { + *ppayload = new SrsRtpSTAPPayload(); + } else if (v == kFuA) { + *ppayload = new SrsRtpFUAPayload2(); + } else { + *ppayload = new SrsRtpRawPayload(); } - - if (session->is_stun_timeout()) { - // Now, we got the RTC session to cleanup, switch to its context - // to make all logs write to the "correct" pid+cid. - session->switch_to_context(); - - srs_trace("rtc session=%s, stun timeout", session->id().c_str()); - map_username_session.erase(iter++); - map_id_session.erase(session->get_peer_id()); - delete session; - continue; - } - - ++iter; } } -srs_error_t SrsRtcServer::notify(int type, srs_utime_t interval, srs_utime_t tick) +srs_error_t SrsRtcPublishStream::send_periodic_twcc() +{ + srs_error_t err = srs_success; + + if (last_time_send_twcc_) { + srs_utime_t duration = srs_duration(last_time_send_twcc_, srs_get_system_time()); + if (duration > 80 * SRS_UTIME_MILLISECONDS) { + srs_warn2(TAG_LARGE_TIMER, "send_twcc interval exceeded %dms > 100ms", srsu2msi(duration)); + } + } + last_time_send_twcc_ = srs_get_system_time(); + + if (!rtcp_twcc_.need_feedback()) { + return err; + } + + char pkt[kRtcpPacketSize]; + SrsBuffer *buffer = new SrsBuffer(pkt, sizeof(pkt)); + SrsAutoFree(SrsBuffer, buffer); + + rtcp_twcc_.set_feedback_count(twcc_fb_count_); + twcc_fb_count_++; + + if((err = rtcp_twcc_.encode(buffer)) != srs_success) { + return srs_error_wrap(err, "encode, count=%u", twcc_fb_count_); + } + + int nb_protected_buf = buffer->pos(); + char protected_buf[kRtpPacketSize]; + if ((err = session_->transport_->protect_rtcp(pkt, protected_buf, nb_protected_buf)) != srs_success) { + return srs_error_wrap(err, "protect rtcp, size=%u", nb_protected_buf); + } + + return session_->sendonly_skt->sendto(protected_buf, nb_protected_buf, 0); +} + +srs_error_t SrsRtcPublishStream::on_rtcp(SrsRtcpCommon* rtcp) +{ + if(SrsRtcpType_sr == rtcp->type()) { + SrsRtcpSR* sr = dynamic_cast(rtcp); + return on_rtcp_sr(sr); + } else if(SrsRtcpType_xr == rtcp->type()) { + SrsRtcpXr* xr = dynamic_cast(rtcp); + return on_rtcp_xr(xr); + } else if(SrsRtcpType_sdes == rtcp->type()) { + //ignore RTCP SDES + return srs_success; + } else if(SrsRtcpType_bye == rtcp->type()) { + // TODO: FIXME: process rtcp bye. + return srs_success; + } else { + return srs_error_new(ERROR_RTC_RTCP_CHECK, "unknown rtcp type=%u", rtcp->type()); + } +} + +srs_error_t SrsRtcPublishStream::on_rtcp_sr(SrsRtcpSR* rtcp) +{ + srs_error_t err = srs_success; + SrsNtp srs_ntp = SrsNtp::to_time_ms(rtcp->get_ntp()); + + srs_verbose("sender report, ssrc_of_sender=%u, rtp_time=%u, sender_packet_count=%u, sender_octec_count=%u", + rtcp->get_ssrc(), rtcp->get_rtp_ts(), rtcp->get_rtp_send_packets(), rtcp->get_rtp_send_bytes()); + + update_send_report_time(rtcp->get_ssrc(), srs_ntp); + + return err; +} + +srs_error_t SrsRtcPublishStream::on_rtcp_xr(SrsRtcpXr* rtcp) +{ + srs_error_t err = srs_success; + + /* + @see: http://www.rfc-editor.org/rfc/rfc3611.html#section-2 + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P|reserved | PT=XR=207 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : report blocks : + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + + SrsBuffer stream(rtcp->data(), rtcp->size()); + /*uint8_t first = */stream.read_1bytes(); + uint8_t pt = stream.read_1bytes(); + srs_assert(pt == kXR); + uint16_t length = (stream.read_2bytes() + 1) * 4; + /*uint32_t ssrc = */stream.read_4bytes(); + + if (length > rtcp->size()) { + return srs_error_new(ERROR_RTC_RTCP_CHECK, "invalid XR packet, length=%u, nb_buf=%d", length, rtcp->size()); + } + + while (stream.pos() + 4 < length) { + uint8_t bt = stream.read_1bytes(); + stream.skip(1); + uint16_t block_length = (stream.read_2bytes() + 1) * 4; + + if (stream.pos() + block_length - 4 > rtcp->size()) { + return srs_error_new(ERROR_RTC_RTCP_CHECK, "invalid XR packet block, block_length=%u, nb_buf=%d", block_length, rtcp->size()); + } + + if (bt == 5) { + for (int i = 4; i < block_length; i += 12) { + uint32_t ssrc = stream.read_4bytes(); + uint32_t lrr = stream.read_4bytes(); + uint32_t dlrr = stream.read_4bytes(); + + SrsNtp cur_ntp = SrsNtp::from_time_ms(srs_update_system_time() / 1000); + uint32_t compact_ntp = (cur_ntp.ntp_second_ << 16) | (cur_ntp.ntp_fractions_ >> 16); + + int rtt_ntp = compact_ntp - lrr - dlrr; + int rtt = ((rtt_ntp * 1000) >> 16) + ((rtt_ntp >> 16) * 1000); + srs_verbose("ssrc=%u, compact_ntp=%u, lrr=%u, dlrr=%u, rtt=%d", + ssrc, compact_ntp, lrr, dlrr, rtt); + + update_rtt(ssrc, rtt); + } + } + } + + return err; +} + +void SrsRtcPublishStream::request_keyframe(uint32_t ssrc) +{ + SrsContextId sub_cid = _srs_context->get_id(); + pli_worker_->request_keyframe(ssrc, sub_cid); + + uint32_t nn = 0; + if (pli_epp->can_print(ssrc, &nn)) { + // The player(subscriber) cid, which requires PLI. + srs_trace("RTC: Need PLI ssrc=%u, play=[%s], publish=[%s], count=%u/%u", ssrc, sub_cid.c_str(), + cid_.c_str(), nn, pli_epp->nn_count); + } +} + +srs_error_t SrsRtcPublishStream::do_request_keyframe(uint32_t ssrc, SrsContextId sub_cid) +{ + srs_error_t err = srs_success; + if ((err = session_->send_rtcp_fb_pli(ssrc, sub_cid)) != srs_success) { + srs_warn("PLI err %s", srs_error_desc(err).c_str()); + srs_freep(err); + } + + session_->stat_->nn_pli++; + + return err; +} + +srs_error_t SrsRtcPublishStream::notify(int type, srs_utime_t interval, srs_utime_t tick) +{ + srs_error_t err = srs_success; + + if (!is_started) { + return err; + } + + if (type == SRS_TICKID_RTCP) { + if ((err = send_rtcp_rr()) != srs_success) { + srs_warn("RR err %s", srs_error_desc(err).c_str()); + srs_freep(err); + } + + if ((err = send_rtcp_xr_rrtr()) != srs_success) { + srs_warn("XR err %s", srs_error_desc(err).c_str()); + srs_freep(err); + } + } + + if (type == SRS_TICKID_TWCC) { + // We should not depends on the received packet, + // instead we should send feedback every Nms. + if ((err = send_periodic_twcc()) != srs_success) { + srs_warn("TWCC err %s", srs_error_desc(err).c_str()); + srs_freep(err); + } + } + + return err; +} + +void SrsRtcPublishStream::simulate_nack_drop(int nn) +{ + nn_simulate_nack_drop = nn; +} + +void SrsRtcPublishStream::simulate_drop_packet(SrsRtpHeader* h, int nn_bytes) +{ + srs_warn("RTC NACK simulator #%d drop seq=%u, ssrc=%u/%s, ts=%u, %d bytes", nn_simulate_nack_drop, + h->get_sequence(), h->get_ssrc(), (get_video_track(h->get_ssrc())? "Video":"Audio"), h->get_timestamp(), + nn_bytes); + + nn_simulate_nack_drop--; +} + +SrsRtcVideoRecvTrack* SrsRtcPublishStream::get_video_track(uint32_t ssrc) +{ + for (int i = 0; i < (int)video_tracks_.size(); ++i) { + SrsRtcVideoRecvTrack* track = video_tracks_.at(i); + if (track->has_ssrc(ssrc)) { + return track; + } + } + + return NULL; +} + +SrsRtcAudioRecvTrack* SrsRtcPublishStream::get_audio_track(uint32_t ssrc) +{ + for (int i = 0; i < (int)audio_tracks_.size(); ++i) { + SrsRtcAudioRecvTrack* track = audio_tracks_.at(i); + if (track->has_ssrc(ssrc)) { + return track; + } + } + + return NULL; +} + +void SrsRtcPublishStream::update_rtt(uint32_t ssrc, int rtt) +{ + SrsRtcVideoRecvTrack* video_track = get_video_track(ssrc); + if (video_track) { + return video_track->update_rtt(rtt); + } + + SrsRtcAudioRecvTrack* audio_track = get_audio_track(ssrc); + if (audio_track) { + return audio_track->update_rtt(rtt); + } +} + +void SrsRtcPublishStream::update_send_report_time(uint32_t ssrc, const SrsNtp& ntp) +{ + SrsRtcVideoRecvTrack* video_track = get_video_track(ssrc); + if (video_track) { + return video_track->update_send_report_time(ntp); + } + + SrsRtcAudioRecvTrack* audio_track = get_audio_track(ssrc); + if (audio_track) { + return audio_track->update_send_report_time(ntp); + } +} + +SrsRtcConnectionStatistic::SrsRtcConnectionStatistic() +{ + dead = born = srs_get_system_time(); + nn_publishers = nn_subscribers = 0; + nn_rr = nn_xr = 0; + nn_sr = nn_nack = nn_pli = 0; + nn_in_twcc = nn_in_rtp = nn_in_audios = nn_in_videos = 0; + nn_out_twcc = nn_out_rtp = nn_out_audios = nn_out_videos = 0; +} + +SrsRtcConnectionStatistic::~SrsRtcConnectionStatistic() +{ +} + +string SrsRtcConnectionStatistic::summary() +{ + dead = srs_get_system_time(); + + stringstream ss; + + ss << "alive=" << srsu2msi(dead - born) << "ms"; + + if (nn_publishers) ss << ", npub=" << nn_publishers; + if (nn_subscribers) ss << ", nsub=" << nn_subscribers; + + if (nn_rr) ss << ", nrr=" << nn_rr; + if (nn_xr) ss << ", nxr=" << nn_xr; + + if (nn_sr) ss << ", nsr=" << nn_sr; + if (nn_nack) ss << ", nnack=" << nn_nack; + if (nn_pli) ss << ", npli=" << nn_pli; + + if (nn_in_twcc) ss << ", in_ntwcc=" << nn_in_twcc; + if (nn_in_rtp) ss << ", in_nrtp=" << nn_in_rtp; + if (nn_in_audios) ss << ", in_naudio=" << nn_in_audios; + if (nn_in_videos) ss << ", in_nvideo=" << nn_in_videos; + + if (nn_out_twcc) ss << ", out_ntwcc=" << nn_out_twcc; + if (nn_out_rtp) ss << ", out_nrtp=" << nn_out_rtp; + if (nn_out_audios) ss << ", out_naudio=" << nn_out_audios; + if (nn_out_videos) ss << ", out_nvideo=" << nn_out_videos; + + return ss.str(); +} + +ISrsRtcConnectionHijacker::ISrsRtcConnectionHijacker() +{ +} + +ISrsRtcConnectionHijacker::~ISrsRtcConnectionHijacker() +{ +} + +SrsRtcConnection::SrsRtcConnection(SrsRtcServer* s, const SrsContextId& cid) +{ + req = NULL; + cid_ = cid; + stat_ = new SrsRtcConnectionStatistic(); + timer_ = new SrsHourGlass(this, 1000 * SRS_UTIME_MILLISECONDS); + hijacker_ = NULL; + + sendonly_skt = NULL; + server_ = s; + transport_ = new SrsSecurityTransport(this); + + state_ = INIT; + last_stun_time = 0; + session_timeout = 0; + disposing_ = false; + + twcc_id_ = 0; + nn_simulate_player_nack_drop = 0; + pp_address_change = new SrsErrorPithyPrint(); + pli_epp = new SrsErrorPithyPrint(); + + _srs_rtc_manager->subscribe(this); +} + +SrsRtcConnection::~SrsRtcConnection() +{ + _srs_rtc_manager->unsubscribe(this); + + srs_freep(timer_); + + // Cleanup publishers. + for(map::iterator it = publishers_.begin(); it != publishers_.end(); ++it) { + SrsRtcPublishStream* publisher = it->second; + srs_freep(publisher); + } + publishers_.clear(); + publishers_ssrc_map_.clear(); + + // Cleanup players. + for(map::iterator it = players_.begin(); it != players_.end(); ++it) { + SrsRtcPlayStream* player = it->second; + srs_freep(player); + } + players_.clear(); + players_ssrc_map_.clear(); + + // Note that we should never delete the sendonly_skt, + // it's just point to the object in peer_addresses_. + map::iterator it; + for (it = peer_addresses_.begin(); it != peer_addresses_.end(); ++it) { + SrsUdpMuxSocket* addr = it->second; + srs_freep(addr); + } + + srs_freep(transport_); + srs_freep(req); + srs_freep(stat_); + srs_freep(pp_address_change); + srs_freep(pli_epp); +} + +void SrsRtcConnection::on_before_dispose(ISrsResource* c) +{ + if (disposing_) { + return; + } + + SrsRtcConnection* session = dynamic_cast(c); + if (session == this) { + disposing_ = true; + } + + if (session && session == this) { + _srs_context->set_id(cid_); + srs_trace("RTC: session detach from [%s](%s), disposing=%d", c->get_id().c_str(), + c->desc().c_str(), disposing_); + } +} + +void SrsRtcConnection::on_disposing(ISrsResource* c) +{ + if (disposing_) { + return; + } +} + +SrsSdp* SrsRtcConnection::get_local_sdp() +{ + return &local_sdp; +} + +void SrsRtcConnection::set_local_sdp(const SrsSdp& sdp) +{ + local_sdp = sdp; +} + +SrsSdp* SrsRtcConnection::get_remote_sdp() +{ + return &remote_sdp; +} + +void SrsRtcConnection::set_remote_sdp(const SrsSdp& sdp) +{ + remote_sdp = sdp; +} + +SrsRtcConnectionStateType SrsRtcConnection::state() +{ + return state_; +} + +void SrsRtcConnection::set_state(SrsRtcConnectionStateType state) +{ + state_ = state; +} + +string SrsRtcConnection::username() +{ + return username_; +} + +vector SrsRtcConnection::peer_addresses() +{ + vector addresses; + + map::iterator it; + for (it = peer_addresses_.begin(); it != peer_addresses_.end(); ++it) { + SrsUdpMuxSocket* addr = it->second; + addresses.push_back(addr); + } + + return addresses; +} + +const SrsContextId& SrsRtcConnection::get_id() +{ + return cid_; +} + +std::string SrsRtcConnection::desc() +{ + return "RtcConn"; +} + +void SrsRtcConnection::switch_to_context() +{ + _srs_context->set_id(cid_); +} + +const SrsContextId& SrsRtcConnection::context_id() +{ + return cid_; +} + +srs_error_t SrsRtcConnection::add_publisher(SrsRequest* req, const SrsSdp& remote_sdp, SrsSdp& local_sdp) +{ + srs_error_t err = srs_success; + + SrsRtcStreamDescription* stream_desc = new SrsRtcStreamDescription(); + SrsAutoFree(SrsRtcStreamDescription, stream_desc); + + if ((err = negotiate_publish_capability(req, remote_sdp, stream_desc)) != srs_success) { + return srs_error_wrap(err, "publish negotiate"); + } + + if ((err = generate_publish_local_sdp(req, local_sdp, stream_desc, remote_sdp.is_unified())) != srs_success) { + return srs_error_wrap(err, "generate local sdp"); + } + + SrsRtcStream* source = NULL; + if ((err = _srs_rtc_sources->fetch_or_create(req, &source)) != srs_success) { + return srs_error_wrap(err, "create source"); + } + + // When SDP is done, we set the stream to create state, to prevent multiple publisher. + // @note Here, we check the stream again. + if (!source->can_publish()) { + return srs_error_new(ERROR_RTC_SOURCE_BUSY, "stream %s busy", req->get_stream_url().c_str()); + } + source->set_stream_created(); + + // Apply the SDP to source. + source->set_stream_desc(stream_desc); + + // TODO: FIXME: What happends when error? + if ((err = create_publisher(req, stream_desc)) != srs_success) { + return srs_error_wrap(err, "create publish"); + } + + return err; +} + +// TODO: FIXME: Error when play before publishing. +srs_error_t SrsRtcConnection::add_player(SrsRequest* req, const SrsSdp& remote_sdp, SrsSdp& local_sdp) +{ + srs_error_t err = srs_success; + + if (_srs_rtc_hijacker) { + if ((err = _srs_rtc_hijacker->on_before_play(this, req)) != srs_success) { + return srs_error_wrap(err, "before play"); + } + } + + std::map play_sub_relations; + if ((err = negotiate_play_capability(req, remote_sdp, play_sub_relations)) != srs_success) { + return srs_error_wrap(err, "play negotiate"); + } + + if (!play_sub_relations.size()) { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no play relations"); + } + + SrsRtcStreamDescription* stream_desc = new SrsRtcStreamDescription(); + SrsAutoFree(SrsRtcStreamDescription, stream_desc); + std::map::iterator it = play_sub_relations.begin(); + while (it != play_sub_relations.end()) { + SrsRtcTrackDescription* track_desc = it->second; + + if (track_desc->type_ == "audio" || !stream_desc->audio_track_desc_) { + stream_desc->audio_track_desc_ = track_desc->copy(); + } + + if (track_desc->type_ == "video") { + stream_desc->video_track_descs_.push_back(track_desc->copy()); + } + ++it; + } + + if ((err = generate_play_local_sdp(req, local_sdp, stream_desc, remote_sdp.is_unified())) != srs_success) { + return srs_error_wrap(err, "generate local sdp"); + } + + if ((err = create_player(req, play_sub_relations)) != srs_success) { + return srs_error_wrap(err, "create player"); + } + + return err; +} + +srs_error_t SrsRtcConnection::add_player2(SrsRequest* req, bool unified_plan, SrsSdp& local_sdp) +{ + srs_error_t err = srs_success; + + if (_srs_rtc_hijacker) { + if ((err = _srs_rtc_hijacker->on_before_play(this, req)) != srs_success) { + return srs_error_wrap(err, "before play"); + } + } + + std::map play_sub_relations; + if ((err = fetch_source_capability(req, play_sub_relations)) != srs_success) { + return srs_error_wrap(err, "play negotiate"); + } + + if (!play_sub_relations.size()) { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no play relations"); + } + + SrsRtcStreamDescription* stream_desc = new SrsRtcStreamDescription(); + SrsAutoFree(SrsRtcStreamDescription, stream_desc); + + std::map::iterator it = play_sub_relations.begin(); + while (it != play_sub_relations.end()) { + SrsRtcTrackDescription* track_desc = it->second; + + if (track_desc->type_ == "audio" || !stream_desc->audio_track_desc_) { + stream_desc->audio_track_desc_ = track_desc->copy(); + } + + if (track_desc->type_ == "video") { + stream_desc->video_track_descs_.push_back(track_desc->copy()); + } + ++it; + } + + if ((err = generate_play_local_sdp(req, local_sdp, stream_desc, unified_plan)) != srs_success) { + return srs_error_wrap(err, "generate local sdp"); + } + + if ((err = create_player(req, play_sub_relations)) != srs_success) { + return srs_error_wrap(err, "create player"); + } + + return err; +} + +srs_error_t SrsRtcConnection::initialize(SrsRequest* r, bool dtls, bool srtp, string username) +{ + srs_error_t err = srs_success; + + username_ = username; + req = r->copy(); + + if (!srtp) { + srs_freep(transport_); + if (dtls) { + transport_ = new SrsSemiSecurityTransport(this); + } else { + transport_ = new SrsPlaintextTransport(this); + } + } + + SrsSessionConfig* cfg = &local_sdp.session_config_; + if ((err = transport_->initialize(cfg)) != srs_success) { + return srs_error_wrap(err, "init"); + } + + if ((err = timer_->start()) != srs_success) { + return srs_error_wrap(err, "start timer"); + } + + // TODO: FIXME: Support reload. + session_timeout = _srs_config->get_rtc_stun_timeout(req->vhost); + last_stun_time = srs_get_system_time(); + + srs_trace("RTC init session, user=%s, url=%s, encrypt=%u/%u, DTLS(role=%s, version=%s), timeout=%dms", username.c_str(), + r->get_stream_url().c_str(), dtls, srtp, cfg->dtls_role.c_str(), cfg->dtls_version.c_str(), srsu2msi(session_timeout)); + + return err; +} + +srs_error_t SrsRtcConnection::on_stun(SrsUdpMuxSocket* skt, SrsStunPacket* r) +{ + srs_error_t err = srs_success; + + if (!r->is_binding_request()) { + return err; + } + + // We are running in the ice-lite(server) mode. If client have multi network interface, + // we only choose one candidate pair which is determined by client. + update_sendonly_socket(skt); + + // Write STUN messages to blackhole. + if (_srs_blackhole->blackhole) { + _srs_blackhole->sendto(skt->data(), skt->size()); + } + + if ((err = on_binding_request(r)) != srs_success) { + return srs_error_wrap(err, "stun binding request failed"); + } + + return err; +} + +srs_error_t SrsRtcConnection::on_dtls(char* data, int nb_data) +{ + return transport_->on_dtls(data, nb_data); +} + +srs_error_t SrsRtcConnection::on_rtcp(char* data, int nb_data) +{ + srs_error_t err = srs_success; + + char unprotected_buf[kRtpPacketSize]; + int nb_unprotected_buf = nb_data; + if ((err = transport_->unprotect_rtcp(data, unprotected_buf, nb_unprotected_buf)) != srs_success) { + return srs_error_wrap(err, "rtcp unprotect"); + } + + if (_srs_blackhole->blackhole) { + _srs_blackhole->sendto(unprotected_buf, nb_unprotected_buf); + } + + SrsBuffer* buffer = new SrsBuffer(unprotected_buf, nb_unprotected_buf); + SrsAutoFree(SrsBuffer, buffer); + + SrsRtcpCompound rtcp_compound; + if(srs_success != (err = rtcp_compound.decode(buffer))) { + return srs_error_wrap(err, "decode rtcp plaintext=%u, bytes=[%s], at=%s", nb_unprotected_buf, + srs_string_dumps_hex(unprotected_buf, nb_unprotected_buf, 8).c_str(), + srs_string_dumps_hex(buffer->head(), buffer->left(), 8).c_str()); + } + + SrsRtcpCommon* rtcp = NULL; + while(NULL != (rtcp = rtcp_compound.get_next_rtcp())) { + err = dispatch_rtcp(rtcp); + SrsAutoFree(SrsRtcpCommon, rtcp); + + if(srs_success != err) { + return srs_error_wrap(err, "cipher=%u, plaintext=%u, bytes=[%s], rtcp=(%u,%u,%u,%u)", nb_data, nb_unprotected_buf, + srs_string_dumps_hex(rtcp->data(), rtcp->size(), rtcp->size()).c_str(), + rtcp->get_rc(), rtcp->type(), rtcp->get_ssrc(), rtcp->size()); + } + } + + return err; +} + +srs_error_t SrsRtcConnection::dispatch_rtcp(SrsRtcpCommon* rtcp) +{ + srs_error_t err = srs_success; + + // For TWCC packet. + if (SrsRtcpType_rtpfb == rtcp->type() && 15 == rtcp->get_rc()) { + return on_rtcp_feedback_twcc(rtcp->data(), rtcp->size()); + } + + // For REMB packet. + if (SrsRtcpType_psfb == rtcp->type()) { + SrsRtcpPsfbCommon* psfb = dynamic_cast(rtcp); + if (15 == psfb->get_rc()) { + return on_rtcp_feedback_remb(psfb); + } + } + + // Ignore special packet. + if (SrsRtcpType_rr == rtcp->type()) { + SrsRtcpRR* rr = dynamic_cast(rtcp); + if (rr->get_rb_ssrc() == 0) { //for native client + return err; + } + } + + // The feedback packet for specified SSRC. + // For example, if got SR packet, we required a publisher to handle it. + uint32_t required_publisher_ssrc = 0, required_player_ssrc = 0; + if (SrsRtcpType_sr == rtcp->type()) { + required_publisher_ssrc = rtcp->get_ssrc(); + } else if (SrsRtcpType_rr == rtcp->type()) { + SrsRtcpRR* rr = dynamic_cast(rtcp); + required_player_ssrc = rr->get_rb_ssrc(); + } else if (SrsRtcpType_rtpfb == rtcp->type()) { + if(1 == rtcp->get_rc()) { + SrsRtcpNack* nack = dynamic_cast(rtcp); + required_player_ssrc = nack->get_media_ssrc(); + } + } else if(SrsRtcpType_psfb == rtcp->type()) { + SrsRtcpPsfbCommon* psfb = dynamic_cast(rtcp); + required_player_ssrc = psfb->get_media_ssrc(); + } + + // Find the publisher or player by SSRC, always try to got one. + SrsRtcPlayStream* player = NULL; + SrsRtcPublishStream* publisher = NULL; + if (true) { + uint32_t ssrc = required_publisher_ssrc? required_publisher_ssrc : rtcp->get_ssrc(); + map::iterator it = publishers_ssrc_map_.find(ssrc); + if (it != publishers_ssrc_map_.end()) { + publisher = it->second; + } + } + + if (true) { + uint32_t ssrc = required_player_ssrc? required_player_ssrc : rtcp->get_ssrc(); + map::iterator it = players_ssrc_map_.find(ssrc); + if (it != players_ssrc_map_.end()) { + player = it->second; + } + } + + // Ignore if packet is required by publisher or player. + if (required_publisher_ssrc && !publisher) { + srs_warn("no ssrc %u in publishers. rtcp type:%u", required_publisher_ssrc, rtcp->type()); + return err; + } + if (required_player_ssrc && !player) { + srs_warn("no ssrc %u in players. rtcp type:%u", required_player_ssrc, rtcp->type()); + return err; + } + + // Handle packet by publisher or player. + if (publisher && srs_success != (err = publisher->on_rtcp(rtcp))) { + return srs_error_wrap(err, "handle rtcp"); + } + if (player && srs_success != (err = player->on_rtcp(rtcp))) { + return srs_error_wrap(err, "handle rtcp"); + } + + return err; +} + +srs_error_t SrsRtcConnection::on_rtcp_feedback_twcc(char* data, int nb_data) { - check_and_clean_timeout_session(); return srs_success; } -RtcServerAdapter::RtcServerAdapter() +srs_error_t SrsRtcConnection::on_rtcp_feedback_remb(SrsRtcpPsfbCommon *rtcp) { - rtc = new SrsRtcServer(); + //ignore REMB + return srs_success; } -RtcServerAdapter::~RtcServerAdapter() +void SrsRtcConnection::set_hijacker(ISrsRtcConnectionHijacker* h) { - srs_freep(rtc); + hijacker_ = h; } -srs_error_t RtcServerAdapter::initialize() +srs_error_t SrsRtcConnection::on_rtp(char* data, int nb_data) { srs_error_t err = srs_success; - if ((err = rtc->initialize()) != srs_success) { - return srs_error_wrap(err, "rtc server initialize"); + if (publishers_.size() == 0) { + return srs_error_new(ERROR_RTC_RTCP, "no publisher"); + } + + SrsRtpHeader header; + if (true) { + SrsBuffer* buffer = new SrsBuffer(data, nb_data); + SrsAutoFree(SrsBuffer, buffer); + header.ignore_padding(true); + if(srs_success != (err = header.decode(buffer))) { + return srs_error_wrap(err, "decode rtp header"); + } + } + + map::iterator it = publishers_ssrc_map_.find(header.get_ssrc()); + if(it == publishers_ssrc_map_.end()) { + return srs_error_new(ERROR_RTC_NO_PUBLISHER, "no publisher for ssrc:%u", header.get_ssrc()); + } + + SrsRtcPublishStream* publisher = it->second; + return publisher->on_rtp(data, nb_data); +} + +srs_error_t SrsRtcConnection::on_connection_established() +{ + srs_error_t err = srs_success; + + // If DTLS done packet received many times, such as ARQ, ignore. + if(ESTABLISHED == state_) { + return err; + } + state_ = ESTABLISHED; + + srs_trace("RTC: session pub=%u, sub=%u, to=%dms connection established", publishers_.size(), players_.size(), + srsu2msi(session_timeout)); + + // start all publisher + for(map::iterator it = publishers_.begin(); it != publishers_.end(); ++it) { + string url = it->first; + SrsRtcPublishStream* publisher = it->second; + + srs_trace("RTC: Publisher url=%s established", url.c_str()); + + if ((err = publisher->start()) != srs_success) { + return srs_error_wrap(err, "start publish"); + } + } + + // start all player + for(map::iterator it = players_.begin(); it != players_.end(); ++it) { + string url = it->first; + SrsRtcPlayStream* player = it->second; + + srs_trace("RTC: Subscriber url=%s established", url.c_str()); + + if ((err = player->start()) != srs_success) { + return srs_error_wrap(err, "start play"); + } + } + + if (hijacker_) { + if ((err = hijacker_->on_dtls_done()) != srs_success) { + return srs_error_wrap(err, "hijack on dtls done"); + } } return err; } -srs_error_t RtcServerAdapter::run() +srs_error_t SrsRtcConnection::on_dtls_alert(std::string type, std::string desc) { srs_error_t err = srs_success; - if ((err = rtc->listen_udp()) != srs_success) { - return srs_error_wrap(err, "listen udp"); - } + // CN(Close Notify) is sent when client close the PeerConnection. + if (type == "warning" && desc == "CN") { + SrsContextRestore(_srs_context->get_id()); + switch_to_context(); - if ((err = rtc->listen_api()) != srs_success) { - return srs_error_wrap(err, "listen api"); + srs_trace("RTC: session destroy by DTLS alert, username=%s, summary: %s", + username_.c_str(), stat_->summary().c_str()); + _srs_rtc_manager->remove(this); } return err; } -void RtcServerAdapter::stop() +srs_error_t SrsRtcConnection::start_play(string stream_uri) +{ + srs_error_t err = srs_success; + + map::iterator it = players_.find(stream_uri); + if(it == players_.end()) { + return srs_error_new(ERROR_RTC_NO_PLAYER, "not subscribe %s", stream_uri.c_str()); + } + + SrsRtcPlayStream* player = it->second; + if ((err = player->start()) != srs_success) { + return srs_error_wrap(err, "start"); + } + + return err; +} + +srs_error_t SrsRtcConnection::start_publish(std::string stream_uri) +{ + srs_error_t err = srs_success; + + map::iterator it = publishers_.find(stream_uri); + if(it == publishers_.end()) { + return srs_error_new(ERROR_RTC_NO_PUBLISHER, "no %s publisher", stream_uri.c_str()); + } + + if ((err = it->second->start()) != srs_success) { + return srs_error_wrap(err, "start"); + } + + return err; +} + +bool SrsRtcConnection::is_alive() +{ + return last_stun_time + session_timeout < srs_get_system_time(); +} + +void SrsRtcConnection::alive() +{ + last_stun_time = srs_get_system_time(); +} + +void SrsRtcConnection::update_sendonly_socket(SrsUdpMuxSocket* skt) +{ + // TODO: FIXME: Refine performance. + string prev_peer_id, peer_id = skt->peer_id(); + if (sendonly_skt) { + prev_peer_id = sendonly_skt->peer_id(); + } + + // Ignore if same address. + if (prev_peer_id == peer_id) { + return; + } + + // Find object from cache. + SrsUdpMuxSocket* addr_cache = NULL; + if (true) { + map::iterator it = peer_addresses_.find(peer_id); + if (it != peer_addresses_.end()) { + addr_cache = it->second; + } + } + + // Show address change log. + if (prev_peer_id.empty()) { + srs_trace("RTC: session address init %s", peer_id.c_str()); + } else { + uint32_t nn = 0; + if (pp_address_change->can_print(skt->get_peer_port(), &nn)) { + srs_trace("RTC: session address change %s -> %s, cached=%d, nn_change=%u/%u, nn_address=%u", prev_peer_id.c_str(), + peer_id.c_str(), (addr_cache? 1:0), pp_address_change->nn_count, nn, peer_addresses_.size()); + } + } + + // If no cache, build cache and setup the relations in connection. + if (!addr_cache) { + peer_addresses_[peer_id] = addr_cache = skt->copy_sendonly(); + server_->insert_into_id_sessions(peer_id, this); + } + + // Update the transport. + sendonly_skt = addr_cache; +} + +srs_error_t SrsRtcConnection::notify(int type, srs_utime_t interval, srs_utime_t tick) +{ + srs_error_t err = srs_success; + return err; +} + +srs_error_t SrsRtcConnection::send_rtcp(char *data, int nb_data) +{ + srs_error_t err = srs_success; + + int nb_buf = nb_data; + char protected_buf[kRtpPacketSize]; + if ((err = transport_->protect_rtcp(data, protected_buf, nb_buf)) != srs_success) { + return srs_error_wrap(err, "protect rtcp"); + } + + if ((err = sendonly_skt->sendto(protected_buf, nb_buf, 0)) != srs_success) { + return srs_error_wrap(err, "send"); + } + + return err; +} + +void SrsRtcConnection::check_send_nacks(SrsRtpNackForReceiver* nack, uint32_t ssrc, uint32_t& sent_nacks, uint32_t& timeout_nacks) +{ + SrsRtcpNack rtcpNack(ssrc); + + rtcpNack.set_media_ssrc(ssrc); + nack->get_nack_seqs(rtcpNack, timeout_nacks); + + sent_nacks = rtcpNack.get_lost_sns().size(); + if(!sent_nacks){ + return; + } + + char buf[kRtcpPacketSize]; + SrsBuffer stream(buf, sizeof(buf)); + + // TODO: FIXME: Check error. + rtcpNack.encode(&stream); + + // TODO: FIXME: Check error. + char protected_buf[kRtpPacketSize]; + int nb_protected_buf = stream.pos(); + transport_->protect_rtcp(stream.data(), protected_buf, nb_protected_buf); + + // TODO: FIXME: Check error. + sendonly_skt->sendto(protected_buf, nb_protected_buf, 0); +} + +srs_error_t SrsRtcConnection::send_rtcp_rr(uint32_t ssrc, SrsRtpRingBuffer* rtp_queue, const uint64_t& last_send_systime, const SrsNtp& last_send_ntp) +{ + srs_error_t err = srs_success; + + // @see https://tools.ietf.org/html/rfc3550#section-6.4.2 + char buf[kRtpPacketSize]; + SrsBuffer stream(buf, sizeof(buf)); + stream.write_1bytes(0x81); + stream.write_1bytes(kRR); + stream.write_2bytes(7); + stream.write_4bytes(ssrc); // TODO: FIXME: Should be 1? + + uint8_t fraction_lost = 0; + uint32_t cumulative_number_of_packets_lost = 0 & 0x7FFFFF; + uint32_t extended_highest_sequence = rtp_queue->get_extended_highest_sequence(); + uint32_t interarrival_jitter = 0; + + uint32_t rr_lsr = 0; + uint32_t rr_dlsr = 0; + + if (last_send_systime > 0) { + rr_lsr = (last_send_ntp.ntp_second_ << 16) | (last_send_ntp.ntp_fractions_ >> 16); + uint32_t dlsr = (srs_update_system_time() - last_send_systime) / 1000; + rr_dlsr = ((dlsr / 1000) << 16) | ((dlsr % 1000) * 65536 / 1000); + } + + stream.write_4bytes(ssrc); + stream.write_1bytes(fraction_lost); + stream.write_3bytes(cumulative_number_of_packets_lost); + stream.write_4bytes(extended_highest_sequence); + stream.write_4bytes(interarrival_jitter); + stream.write_4bytes(rr_lsr); + stream.write_4bytes(rr_dlsr); + + srs_info("RR ssrc=%u, fraction_lost=%u, cumulative_number_of_packets_lost=%u, extended_highest_sequence=%u, interarrival_jitter=%u", + ssrc, fraction_lost, cumulative_number_of_packets_lost, extended_highest_sequence, interarrival_jitter); + + char protected_buf[kRtpPacketSize]; + int nb_protected_buf = stream.pos(); + if ((err = transport_->protect_rtcp(stream.data(), protected_buf, nb_protected_buf)) != srs_success) { + return srs_error_wrap(err, "protect rtcp rr"); + } + + return sendonly_skt->sendto(protected_buf, nb_protected_buf, 0); +} + +srs_error_t SrsRtcConnection::send_rtcp_xr_rrtr(uint32_t ssrc) +{ + srs_error_t err = srs_success; + + /* + @see: http://www.rfc-editor.org/rfc/rfc3611.html#section-2 + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P|reserved | PT=XR=207 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : report blocks : + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + @see: http://www.rfc-editor.org/rfc/rfc3611.html#section-4.4 + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | BT=4 | reserved | block length = 2 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | NTP timestamp, most significant word | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | NTP timestamp, least significant word | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + srs_utime_t now = srs_update_system_time(); + SrsNtp cur_ntp = SrsNtp::from_time_ms(now / 1000); + + char buf[kRtpPacketSize]; + SrsBuffer stream(buf, sizeof(buf)); + stream.write_1bytes(0x80); + stream.write_1bytes(kXR); + stream.write_2bytes(4); + stream.write_4bytes(ssrc); + stream.write_1bytes(4); + stream.write_1bytes(0); + stream.write_2bytes(2); + stream.write_4bytes(cur_ntp.ntp_second_); + stream.write_4bytes(cur_ntp.ntp_fractions_); + + char protected_buf[kRtpPacketSize]; + int nb_protected_buf = stream.pos(); + if ((err = transport_->protect_rtcp(stream.data(), protected_buf, nb_protected_buf)) != srs_success) { + return srs_error_wrap(err, "protect rtcp xr"); + } + + return sendonly_skt->sendto(protected_buf, nb_protected_buf, 0); +} + +srs_error_t SrsRtcConnection::send_rtcp_fb_pli(uint32_t ssrc, const SrsContextId& cid_of_subscriber) +{ + srs_error_t err = srs_success; + + char buf[kRtpPacketSize]; + SrsBuffer stream(buf, sizeof(buf)); + stream.write_1bytes(0x81); + stream.write_1bytes(kPsFb); + stream.write_2bytes(2); + stream.write_4bytes(ssrc); + stream.write_4bytes(ssrc); + + uint32_t nn = 0; + if (pli_epp->can_print(ssrc, &nn)) { + srs_trace("RTC: Request PLI ssrc=%u, play=[%s], count=%u/%u, bytes=%uB", ssrc, cid_of_subscriber.c_str(), + nn, pli_epp->nn_count, stream.pos()); + } + + if (_srs_blackhole->blackhole) { + _srs_blackhole->sendto(stream.data(), stream.pos()); + } + + char protected_buf[kRtpPacketSize]; + int nb_protected_buf = stream.pos(); + if ((err = transport_->protect_rtcp(stream.data(), protected_buf, nb_protected_buf)) != srs_success) { + return srs_error_wrap(err, "protect rtcp psfb pli"); + } + + return sendonly_skt->sendto(protected_buf, nb_protected_buf, 0); +} + +void SrsRtcConnection::simulate_nack_drop(int nn) +{ + for(map::iterator it = publishers_.begin(); it != publishers_.end(); ++it) { + SrsRtcPublishStream* publisher = it->second; + publisher->simulate_nack_drop(nn); + } + + nn_simulate_player_nack_drop = nn; +} + +void SrsRtcConnection::simulate_player_drop_packet(SrsRtpHeader* h, int nn_bytes) +{ + srs_warn("RTC NACK simulator #%d player drop seq=%u, ssrc=%u, ts=%u, %d bytes", nn_simulate_player_nack_drop, + h->get_sequence(), h->get_ssrc(), h->get_timestamp(), + nn_bytes); + + nn_simulate_player_nack_drop--; +} + +srs_error_t SrsRtcConnection::do_send_packets(const std::vector& pkts, SrsRtcPlayStreamStatistic& info) +{ + srs_error_t err = srs_success; + + for (int i = 0; i < (int)pkts.size(); i++) { + SrsRtpPacket2* pkt = pkts.at(i); + + // For this message, select the first iovec. + iovec* iov = new iovec(); + SrsAutoFree(iovec, iov); + + char* iov_base = new char[kRtpPacketSize]; + SrsAutoFreeA(char, iov_base); + + iov->iov_base = iov_base; + iov->iov_len = kRtpPacketSize; + + // Marshal packet to bytes in iovec. + if (true) { + SrsBuffer stream((char*)iov->iov_base, iov->iov_len); + if ((err = pkt->encode(&stream)) != srs_success) { + return srs_error_wrap(err, "encode packet"); + } + iov->iov_len = stream.pos(); + } + + // Cipher RTP to SRTP packet. + if (true) { + int nn_encrypt = (int)iov->iov_len; + if ((err = transport_->protect_rtp2(iov->iov_base, &nn_encrypt)) != srs_success) { + return srs_error_wrap(err, "srtp protect"); + } + iov->iov_len = (size_t)nn_encrypt; + } + + info.nn_rtp_bytes += (int)iov->iov_len; + + // When we send out a packet, increase the stat counter. + info.nn_rtp_pkts++; + + // For NACK simulator, drop packet. + if (nn_simulate_player_nack_drop) { + simulate_player_drop_packet(&pkt->header, (int)iov->iov_len); + iov->iov_len = 0; + continue; + } + + // TODO: FIXME: Handle error. + sendonly_skt->sendto(iov->iov_base, iov->iov_len, 0); + + // Detail log, should disable it in release version. + srs_info("RTC: SEND PT=%u, SSRC=%#x, SEQ=%u, Time=%u, %u/%u bytes", pkt->header.get_payload_type(), pkt->header.get_ssrc(), + pkt->header.get_sequence(), pkt->header.get_timestamp(), pkt->nb_bytes(), iov->iov_len); + } + + return err; +} + +void SrsRtcConnection::set_all_tracks_status(std::string stream_uri, bool is_publish, bool status) +{ + // For publishers. + if (is_publish) { + map::iterator it = publishers_.find(stream_uri); + if (publishers_.end() == it) { + return; + } + + SrsRtcPublishStream* publisher = it->second; + publisher->set_all_tracks_status(status); + return; + } + + // For players. + map::iterator it = players_.find(stream_uri); + if (players_.end() == it) { + return; + } + + SrsRtcPlayStream* player = it->second; + player->set_all_tracks_status(status); +} + +#ifdef SRS_OSX +// These functions are similar to the older byteorder(3) family of functions. +// For example, be32toh() is identical to ntohl(). +// @see https://linux.die.net/man/3/be32toh +#define be32toh ntohl +#endif + +srs_error_t SrsRtcConnection::on_binding_request(SrsStunPacket* r) +{ + srs_error_t err = srs_success; + + bool strict_check = _srs_config->get_rtc_stun_strict_check(req->vhost); + if (strict_check && r->get_ice_controlled()) { + // @see: https://tools.ietf.org/html/draft-ietf-ice-rfc5245bis-00#section-6.1.3.1 + // TODO: Send 487 (Role Conflict) error response. + return srs_error_new(ERROR_RTC_STUN, "Peer must not in ice-controlled role in ice-lite mode."); + } + + SrsStunPacket stun_binding_response; + char buf[kRtpPacketSize]; + SrsBuffer* stream = new SrsBuffer(buf, sizeof(buf)); + SrsAutoFree(SrsBuffer, stream); + + stun_binding_response.set_message_type(BindingResponse); + stun_binding_response.set_local_ufrag(r->get_remote_ufrag()); + stun_binding_response.set_remote_ufrag(r->get_local_ufrag()); + stun_binding_response.set_transcation_id(r->get_transcation_id()); + // FIXME: inet_addr is deprecated, IPV6 support + stun_binding_response.set_mapped_address(be32toh(inet_addr(sendonly_skt->get_peer_ip().c_str()))); + stun_binding_response.set_mapped_port(sendonly_skt->get_peer_port()); + + if ((err = stun_binding_response.encode(get_local_sdp()->get_ice_pwd(), stream)) != srs_success) { + return srs_error_wrap(err, "stun binding response encode failed"); + } + + if ((err = sendonly_skt->sendto(stream->data(), stream->pos(), 0)) != srs_success) { + return srs_error_wrap(err, "stun binding response send failed"); + } + + if (state_ == WAITING_STUN) { + state_ = DOING_DTLS_HANDSHAKE; + // TODO: FIXME: Add cost. + srs_trace("RTC: session STUN done, waiting DTLS handshake."); + + if((err = transport_->start_active_handshake()) != srs_success) { + return srs_error_wrap(err, "fail to dtls handshake"); + } + } + + if (_srs_blackhole->blackhole) { + _srs_blackhole->sendto(stream->data(), stream->pos()); + } + + return err; +} + +bool srs_sdp_has_h264_profile(const SrsMediaPayloadType& payload_type, const string& profile) +{ + srs_error_t err = srs_success; + + if (payload_type.format_specific_param_.empty()) { + return false; + } + + H264SpecificParam h264_param; + if ((err = srs_parse_h264_fmtp(payload_type.format_specific_param_, h264_param)) != srs_success) { + srs_error_reset(err); + return false; + } + + if (h264_param.profile_level_id == profile) { + return true; + } + + return false; +} + +// For example, 42001f 42e01f, see https://blog.csdn.net/epubcn/article/details/102802108 +bool srs_sdp_has_h264_profile(const SrsSdp& sdp, const string& profile) +{ + for (size_t i = 0; i < sdp.media_descs_.size(); ++i) { + const SrsMediaDesc& desc = sdp.media_descs_[i]; + if (!desc.is_video()) { + continue; + } + + std::vector payloads = desc.find_media_with_encoding_name("H264"); + if (payloads.empty()) { + continue; + } + + for (std::vector::iterator it = payloads.begin(); it != payloads.end(); ++it) { + const SrsMediaPayloadType& payload_type = *it; + if (srs_sdp_has_h264_profile(payload_type, profile)) { + return true; + } + } + } + + return false; +} + +srs_error_t SrsRtcConnection::negotiate_publish_capability(SrsRequest* req, const SrsSdp& remote_sdp, SrsRtcStreamDescription* stream_desc) +{ + srs_error_t err = srs_success; + + if (!stream_desc) { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "stream description is NULL"); + } + + bool nack_enabled = _srs_config->get_rtc_nack_enabled(req->vhost); + bool twcc_enabled = _srs_config->get_rtc_twcc_enabled(req->vhost); + // TODO: FIME: Should check packetization-mode=1 also. + bool has_42e01f = srs_sdp_has_h264_profile(remote_sdp, "42e01f"); + + for (size_t i = 0; i < remote_sdp.media_descs_.size(); ++i) { + const SrsMediaDesc& remote_media_desc = remote_sdp.media_descs_[i]; + + SrsRtcTrackDescription* track_desc = new SrsRtcTrackDescription(); + SrsAutoFree(SrsRtcTrackDescription, track_desc); + + track_desc->set_direction("recvonly"); + track_desc->set_mid(remote_media_desc.mid_); + // Whether feature enabled in remote extmap. + int remote_twcc_id = 0; + if (true) { + map extmaps = remote_media_desc.get_extmaps(); + for(map::iterator it = extmaps.begin(); it != extmaps.end(); ++it) { + if (it->second == kTWCCExt) { + remote_twcc_id = it->first; + break; + } + } + } + + if (twcc_enabled && remote_twcc_id) { + track_desc->add_rtp_extension_desc(remote_twcc_id, kTWCCExt); + } + + if (remote_media_desc.is_audio()) { + // TODO: check opus format specific param + std::vector payloads = remote_media_desc.find_media_with_encoding_name("opus"); + if (payloads.empty()) { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no valid found opus payload type"); + } + + for (std::vector::iterator iter = payloads.begin(); iter != payloads.end(); ++iter) { + // if the playload is opus, and the encoding_param_ is channel + SrsAudioPayload* audio_payload = new SrsAudioPayload(iter->payload_type_, iter->encoding_name_, iter->clock_rate_, ::atol(iter->encoding_param_.c_str())); + audio_payload->set_opus_param_desc(iter->format_specific_param_); + // TODO: FIXME: Only support some transport algorithms. + for (int j = 0; j < (int)iter->rtcp_fb_.size(); ++j) { + if (nack_enabled) { + if (iter->rtcp_fb_.at(j) == "nack" || iter->rtcp_fb_.at(j) == "nack pli") { + audio_payload->rtcp_fbs_.push_back(iter->rtcp_fb_.at(j)); + } + } + if (twcc_enabled && remote_twcc_id) { + if (iter->rtcp_fb_.at(j) == "transport-cc") { + audio_payload->rtcp_fbs_.push_back(iter->rtcp_fb_.at(j)); + } + } + } + track_desc->type_ = "audio"; + track_desc->set_codec_payload((SrsCodecPayload*)audio_payload); + // Only choose one match opus codec. + break; + } + } else if (remote_media_desc.is_video()) { + std::vector payloads = remote_media_desc.find_media_with_encoding_name("H264"); + if (payloads.empty()) { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no found valid H.264 payload type"); + } + + std::deque backup_payloads; + for (std::vector::iterator iter = payloads.begin(); iter != payloads.end(); ++iter) { + if (iter->format_specific_param_.empty()) { + backup_payloads.push_front(*iter); + continue; + } + H264SpecificParam h264_param; + if ((err = srs_parse_h264_fmtp(iter->format_specific_param_, h264_param)) != srs_success) { + srs_error_reset(err); continue; + } + + // If not exists 42e01f, we pick up any profile such as 42001f. + bool profile_matched = (!has_42e01f || h264_param.profile_level_id == "42e01f"); + + // Try to pick the "best match" H.264 payload type. + if (h264_param.packetization_mode == "1" && h264_param.level_asymmerty_allow == "1" && profile_matched) { + // if the playload is opus, and the encoding_param_ is channel + SrsVideoPayload* video_payload = new SrsVideoPayload(iter->payload_type_, iter->encoding_name_, iter->clock_rate_); + video_payload->set_h264_param_desc(iter->format_specific_param_); + + // TODO: FIXME: Only support some transport algorithms. + for (int j = 0; j < (int)iter->rtcp_fb_.size(); ++j) { + if (nack_enabled) { + if (iter->rtcp_fb_.at(j) == "nack" || iter->rtcp_fb_.at(j) == "nack pli") { + video_payload->rtcp_fbs_.push_back(iter->rtcp_fb_.at(j)); + } + } + if (twcc_enabled && remote_twcc_id) { + if (iter->rtcp_fb_.at(j) == "transport-cc") { + video_payload->rtcp_fbs_.push_back(iter->rtcp_fb_.at(j)); + } + } + } + + track_desc->type_ = "video"; + track_desc->set_codec_payload((SrsCodecPayload*)video_payload); + // Only choose first match H.264 payload type. + break; + } + + backup_payloads.push_back(*iter); + } + + // Try my best to pick at least one media payload type. + if (!track_desc->media_ && ! backup_payloads.empty()) { + SrsMediaPayloadType media_pt= backup_payloads.front(); + // if the playload is opus, and the encoding_param_ is channel + SrsVideoPayload* video_payload = new SrsVideoPayload(media_pt.payload_type_, media_pt.encoding_name_, media_pt.clock_rate_); + + std::vector rtcp_fbs = media_pt.rtcp_fb_; + // TODO: FIXME: Only support some transport algorithms. + for (int j = 0; j < (int)rtcp_fbs.size(); ++j) { + if (nack_enabled) { + if (rtcp_fbs.at(j) == "nack" || rtcp_fbs.at(j) == "nack pli") { + video_payload->rtcp_fbs_.push_back(rtcp_fbs.at(j)); + } + } + + if (twcc_enabled && remote_twcc_id) { + if (rtcp_fbs.at(j) == "transport-cc") { + video_payload->rtcp_fbs_.push_back(rtcp_fbs.at(j)); + } + } + } + + track_desc->set_codec_payload((SrsCodecPayload*)video_payload); + + srs_warn("choose backup H.264 payload type=%d", backup_payloads.front().payload_type_); + } + + // TODO: FIXME: Support RRTR? + //local_media_desc.payload_types_.back().rtcp_fb_.push_back("rrtr"); + } + + // TODO: FIXME: use one parse paylod from sdp. + + track_desc->create_auxiliary_payload(remote_media_desc.find_media_with_encoding_name("red")); + track_desc->create_auxiliary_payload(remote_media_desc.find_media_with_encoding_name("rtx")); + track_desc->create_auxiliary_payload(remote_media_desc.find_media_with_encoding_name("ulpfec")); + + std::string track_id; + for (int i = 0; i < (int)remote_media_desc.ssrc_infos_.size(); ++i) { + SrsSSRCInfo ssrc_info = remote_media_desc.ssrc_infos_.at(i); + // ssrc have same track id, will be description in the same track description. + if(track_id != ssrc_info.msid_tracker_) { + SrsRtcTrackDescription* track_desc_copy = track_desc->copy(); + track_desc_copy->ssrc_ = ssrc_info.ssrc_; + track_desc_copy->id_ = ssrc_info.msid_tracker_; + track_desc_copy->msid_ = ssrc_info.msid_; + + if (remote_media_desc.is_audio() && !stream_desc->audio_track_desc_) { + stream_desc->audio_track_desc_ = track_desc_copy; + } else if (remote_media_desc.is_video()) { + stream_desc->video_track_descs_.push_back(track_desc_copy); + } + } + track_id = ssrc_info.msid_tracker_; + } + + // set track fec_ssrc and rtx_ssrc + for (int i = 0; i < (int)remote_media_desc.ssrc_groups_.size(); ++i) { + SrsSSRCGroup ssrc_group = remote_media_desc.ssrc_groups_.at(i); + SrsRtcTrackDescription* track_desc = stream_desc->find_track_description_by_ssrc(ssrc_group.ssrcs_[0]); + if (!track_desc) { + continue; + } + + if (ssrc_group.semantic_ == "FID") { + track_desc->set_rtx_ssrc(ssrc_group.ssrcs_[1]); + } else if (ssrc_group.semantic_ == "FEC") { + track_desc->set_fec_ssrc(ssrc_group.ssrcs_[1]); + } + } + } + + return err; +} + +srs_error_t SrsRtcConnection::generate_publish_local_sdp(SrsRequest* req, SrsSdp& local_sdp, SrsRtcStreamDescription* stream_desc, bool unified_plan) +{ + srs_error_t err = srs_success; + + if (!stream_desc) { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "stream description is NULL"); + } + + local_sdp.version_ = "0"; + + local_sdp.username_ = RTMP_SIG_SRS_SERVER; + local_sdp.session_id_ = srs_int2str((int64_t)this); + local_sdp.session_version_ = "2"; + local_sdp.nettype_ = "IN"; + local_sdp.addrtype_ = "IP4"; + local_sdp.unicast_address_ = "0.0.0.0"; + + local_sdp.session_name_ = "SRSPublishSession"; + + local_sdp.msid_semantic_ = "WMS"; + std::string stream_id = req->app + "/" + req->stream; + local_sdp.msids_.push_back(stream_id); + + local_sdp.group_policy_ = "BUNDLE"; + + // generate audio media desc + if (stream_desc->audio_track_desc_) { + SrsRtcTrackDescription* audio_track = stream_desc->audio_track_desc_; + + local_sdp.media_descs_.push_back(SrsMediaDesc("audio")); + SrsMediaDesc& local_media_desc = local_sdp.media_descs_.back(); + + local_media_desc.port_ = 9; + local_media_desc.protos_ = "UDP/TLS/RTP/SAVPF"; + local_media_desc.rtcp_mux_ = true; + local_media_desc.rtcp_rsize_ = true; + + local_media_desc.mid_ = audio_track->mid_; + local_sdp.groups_.push_back(local_media_desc.mid_); + + // anwer not need set stream_id and track_id; + // local_media_desc.msid_ = stream_id; + // local_media_desc.msid_tracker_ = audio_track->id_; + local_media_desc.extmaps_ = audio_track->extmaps_; + + if (audio_track->direction_ == "recvonly") { + local_media_desc.recvonly_ = true; + } else if (audio_track->direction_ == "sendonly") { + local_media_desc.sendonly_ = true; + } else if (audio_track->direction_ == "sendrecv") { + local_media_desc.sendrecv_ = true; + } else if (audio_track->direction_ == "inactive_") { + local_media_desc.inactive_ = true; + } + + SrsAudioPayload* payload = (SrsAudioPayload*)audio_track->media_; + local_media_desc.payload_types_.push_back(payload->generate_media_payload_type()); + } + + for (int i = 0; i < (int)stream_desc->video_track_descs_.size(); ++i) { + SrsRtcTrackDescription* video_track = stream_desc->video_track_descs_.at(i); + + local_sdp.media_descs_.push_back(SrsMediaDesc("video")); + SrsMediaDesc& local_media_desc = local_sdp.media_descs_.back(); + + local_media_desc.port_ = 9; + local_media_desc.protos_ = "UDP/TLS/RTP/SAVPF"; + local_media_desc.rtcp_mux_ = true; + local_media_desc.rtcp_rsize_ = true; + + local_media_desc.mid_ = video_track->mid_; + local_sdp.groups_.push_back(local_media_desc.mid_); + + // anwer not need set stream_id and track_id; + //local_media_desc.msid_ = stream_id; + //local_media_desc.msid_tracker_ = video_track->id_; + local_media_desc.extmaps_ = video_track->extmaps_; + + if (video_track->direction_ == "recvonly") { + local_media_desc.recvonly_ = true; + } else if (video_track->direction_ == "sendonly") { + local_media_desc.sendonly_ = true; + } else if (video_track->direction_ == "sendrecv") { + local_media_desc.sendrecv_ = true; + } else if (video_track->direction_ == "inactive_") { + local_media_desc.inactive_ = true; + } + + SrsVideoPayload* payload = (SrsVideoPayload*)video_track->media_; + local_media_desc.payload_types_.push_back(payload->generate_media_payload_type()); + + if (video_track->red_) { + SrsRedPayload* payload = (SrsRedPayload*)video_track->red_; + local_media_desc.payload_types_.push_back(payload->generate_media_payload_type()); + } + + if(!unified_plan) { + // For PlanB, only need media desc info, not ssrc info; + break; + } + } + + return err; +} + +srs_error_t SrsRtcConnection::negotiate_play_capability(SrsRequest* req, const SrsSdp& remote_sdp, std::map& sub_relations) +{ + srs_error_t err = srs_success; + + bool nack_enabled = _srs_config->get_rtc_nack_enabled(req->vhost); + bool twcc_enabled = _srs_config->get_rtc_twcc_enabled(req->vhost); + // TODO: FIME: Should check packetization-mode=1 also. + bool has_42e01f = srs_sdp_has_h264_profile(remote_sdp, "42e01f"); + + SrsRtcStream* source = NULL; + if ((err = _srs_rtc_sources->fetch_or_create(req, &source)) != srs_success) { + return srs_error_wrap(err, "fetch rtc source"); + } + + for (size_t i = 0; i < remote_sdp.media_descs_.size(); ++i) { + const SrsMediaDesc& remote_media_desc = remote_sdp.media_descs_[i]; + + // Whether feature enabled in remote extmap. + int remote_twcc_id = 0; + if (true) { + map extmaps = remote_media_desc.get_extmaps(); + for(map::iterator it = extmaps.begin(); it != extmaps.end(); ++it) { + if (it->second == kTWCCExt) { + remote_twcc_id = it->first; + break; + } + } + } + + std::vector track_descs; + SrsMediaPayloadType remote_payload(0); + if (remote_media_desc.is_audio()) { + // TODO: check opus format specific param + vector payloads = remote_media_desc.find_media_with_encoding_name("opus"); + if (payloads.empty()) { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no valid found opus payload type"); + } + + remote_payload = payloads.at(0); + track_descs = source->get_track_desc("audio", "opus"); + } else if (remote_media_desc.is_video()) { + // TODO: check opus format specific param + vector payloads = remote_media_desc.find_media_with_encoding_name("H264"); + if (payloads.empty()) { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no valid found h264 payload type"); + } + + remote_payload = payloads.at(0); + for (int j = 0; j < (int)payloads.size(); j++) { + SrsMediaPayloadType& payload = payloads.at(j); + + // If exists 42e01f profile, choose it; otherwise, use the first payload. + // TODO: FIME: Should check packetization-mode=1 also. + if (!has_42e01f || srs_sdp_has_h264_profile(payload, "42e01f")) { + remote_payload = payload; + break; + } + } + + track_descs = source->get_track_desc("video", "H264"); + } + + for (int i = 0; i < (int)track_descs.size(); ++i) { + SrsRtcTrackDescription* track = track_descs[i]->copy(); + + // Use remote/source/offer PayloadType. + track->media_->pt_of_publisher_ = track->media_->pt_; + track->media_->pt_ = remote_payload.payload_type_; + + vector red_pts = remote_media_desc.find_media_with_encoding_name("red"); + if (!red_pts.empty() && track->red_) { + SrsMediaPayloadType red_pt = red_pts.at(0); + + track->red_->pt_of_publisher_ = track->red_->pt_; + track->red_->pt_ = red_pt.payload_type_; + } + + track->mid_ = remote_media_desc.mid_; + uint32_t publish_ssrc = track->ssrc_; + + vector rtcp_fb; + track->media_->rtcp_fbs_.swap(rtcp_fb); + for (int j = 0; j < (int)rtcp_fb.size(); j++) { + if (nack_enabled) { + if (rtcp_fb.at(j) == "nack" || rtcp_fb.at(j) == "nack pli") { + track->media_->rtcp_fbs_.push_back(rtcp_fb.at(j)); + } + } + if (twcc_enabled && remote_twcc_id) { + if (rtcp_fb.at(j) == "transport-cc") { + track->media_->rtcp_fbs_.push_back(rtcp_fb.at(j)); + } + track->add_rtp_extension_desc(remote_twcc_id, kTWCCExt); + } + } + + track->ssrc_ = SrsRtcSSRCGenerator::instance()->generate_ssrc(); + + // TODO: FIXME: set audio_payload rtcp_fbs_, + // according by whether downlink is support transport algorithms. + // TODO: FIXME: if we support downlink RTX, MUST assign rtx_ssrc_, rtx_pt, rtx_apt + // not support rtx + if (true) { + srs_freep(track->rtx_); + track->rtx_ssrc_ = 0; + } + + track->set_direction("sendonly"); + sub_relations.insert(make_pair(publish_ssrc, track)); + } + } + + return err; +} + +srs_error_t SrsRtcConnection::negotiate_play_capability(SrsRequest* req, SrsRtcStreamDescription* req_stream_desc, + std::map& sub_relations) +{ + srs_error_t err = srs_success; + + SrsRtcStream* source = NULL; + if ((err = _srs_rtc_sources->fetch_or_create(req, &source)) != srs_success) { + return srs_error_wrap(err, "fetch rtc source"); + } + + std::vector src_track_descs; + //negotiate audio media + if(NULL != req_stream_desc->audio_track_desc_) { + SrsRtcTrackDescription* req_audio_track = req_stream_desc->audio_track_desc_; + + src_track_descs = source->get_track_desc("audio", "opus"); + if (src_track_descs.size() > 0) { + // FIXME: use source sdp or subscribe sdp? native subscribe may have no sdp + SrsRtcTrackDescription *track = src_track_descs[0]->copy(); + + // Use remote/source/offer PayloadType. + track->media_->pt_of_publisher_ = track->media_->pt_; + track->media_->pt_ = req_audio_track->media_->pt_; + + if (req_audio_track->red_ && track->red_) { + track->red_->pt_of_publisher_ = track->red_->pt_; + track->red_->pt_ = req_audio_track->red_->pt_; + } + + track->mid_ = req_audio_track->mid_; + sub_relations.insert(make_pair(track->ssrc_, track)); + track->set_direction("sendonly"); + track->ssrc_ = SrsRtcSSRCGenerator::instance()->generate_ssrc(); + } + } + + //negotiate video media + std::vector req_video_tracks = req_stream_desc->video_track_descs_; + src_track_descs = source->get_track_desc("video", "h264"); + for(int i = 0; i < (int)req_video_tracks.size(); ++i) { + SrsRtcTrackDescription* req_video = req_video_tracks.at(i); + for(int j = 0; j < (int)src_track_descs.size(); ++j) { + SrsRtcTrackDescription* src_video = src_track_descs.at(j); + if(req_video->id_ == src_video->id_) { + // FIXME: use source sdp or subscribe sdp? native subscribe may have no sdp + SrsRtcTrackDescription *track = src_video->copy(); + + // Use remote/source/offer PayloadType. + track->media_->pt_of_publisher_ = track->media_->pt_; + track->media_->pt_ = req_video->media_->pt_; + + if (req_video->red_ && track->red_) { + track->red_->pt_of_publisher_ = track->red_->pt_; + track->red_->pt_ = req_video->red_->pt_; + } + + track->mid_ = req_video->mid_; + sub_relations.insert(make_pair(track->ssrc_, track)); + track->set_direction("sendonly"); + track->ssrc_ = SrsRtcSSRCGenerator::instance()->generate_ssrc(); + } + } + } + + return err; +} + +srs_error_t SrsRtcConnection::fetch_source_capability(SrsRequest* req, std::map& sub_relations) +{ + srs_error_t err = srs_success; + + bool nack_enabled = _srs_config->get_rtc_nack_enabled(req->vhost); + bool twcc_enabled = _srs_config->get_rtc_twcc_enabled(req->vhost); + + SrsRtcStream* source = NULL; + if ((err = _srs_rtc_sources->fetch_or_create(req, &source)) != srs_success) { + return srs_error_wrap(err, "fetch rtc source"); + } + + std::vector track_descs = source->get_track_desc("audio", "opus"); + std::vector video_track_desc = source->get_track_desc("video", "H264"); + + track_descs.insert(track_descs.end(), video_track_desc.begin(), video_track_desc.end()); + for (int i = 0; i < (int)track_descs.size(); ++i) { + SrsRtcTrackDescription* track = track_descs[i]->copy(); + uint32_t publish_ssrc = track->ssrc_; + + int local_twcc_id = track->get_rtp_extension_id(kTWCCExt); + + vector rtcp_fb; + track->media_->rtcp_fbs_.swap(rtcp_fb); + for (int j = 0; j < (int)rtcp_fb.size(); j++) { + if (nack_enabled) { + if (rtcp_fb.at(j) == "nack" || rtcp_fb.at(j) == "nack pli") { + track->media_->rtcp_fbs_.push_back(rtcp_fb.at(j)); + } + } + if (twcc_enabled && local_twcc_id) { + if (rtcp_fb.at(j) == "transport-cc") { + track->media_->rtcp_fbs_.push_back(rtcp_fb.at(j)); + } + track->add_rtp_extension_desc(local_twcc_id, kTWCCExt); + } + } + + track->ssrc_ = SrsRtcSSRCGenerator::instance()->generate_ssrc(); + + // TODO: FIXME: set audio_payload rtcp_fbs_, + // according by whether downlink is support transport algorithms. + // TODO: FIXME: if we support downlink RTX, MUST assign rtx_ssrc_, rtx_pt, rtx_apt + // not support rtx + srs_freep(track->rtx_); + track->rtx_ssrc_ = 0; + + track->set_direction("sendonly"); + sub_relations.insert(make_pair(publish_ssrc, track)); + } + + return err; +} + +void video_track_generate_play_offer(SrsRtcTrackDescription* track, string mid, SrsSdp& local_sdp) +{ + local_sdp.media_descs_.push_back(SrsMediaDesc("video")); + SrsMediaDesc& local_media_desc = local_sdp.media_descs_.back(); + + local_media_desc.port_ = 9; + local_media_desc.protos_ = "UDP/TLS/RTP/SAVPF"; + local_media_desc.rtcp_mux_ = true; + local_media_desc.rtcp_rsize_ = true; + + local_media_desc.extmaps_ = track->extmaps_; + + // If mid not duplicated, use mid_ of track. Otherwise, use transformed mid. + if (true) { + bool mid_duplicated = false; + for (int i = 0; i < (int)local_sdp.groups_.size(); ++i) { + string& existed_mid = local_sdp.groups_.at(i); + if(existed_mid == track->mid_) { + mid_duplicated = true; + break; + } + } + if (mid_duplicated) { + local_media_desc.mid_ = mid; + } else { + local_media_desc.mid_ = track->mid_; + } + local_sdp.groups_.push_back(local_media_desc.mid_); + } + + if (track->direction_ == "recvonly") { + local_media_desc.recvonly_ = true; + } else if (track->direction_ == "sendonly") { + local_media_desc.sendonly_ = true; + } else if (track->direction_ == "sendrecv") { + local_media_desc.sendrecv_ = true; + } else if (track->direction_ == "inactive_") { + local_media_desc.inactive_ = true; + } + + SrsVideoPayload* payload = (SrsVideoPayload*)track->media_; + + local_media_desc.payload_types_.push_back(payload->generate_media_payload_type()); + + if (track->red_) { + SrsRedPayload* red_payload = (SrsRedPayload*)track->red_; + local_media_desc.payload_types_.push_back(red_payload->generate_media_payload_type()); + } +} + +srs_error_t SrsRtcConnection::generate_play_local_sdp(SrsRequest* req, SrsSdp& local_sdp, SrsRtcStreamDescription* stream_desc, bool unified_plan) +{ + srs_error_t err = srs_success; + + if (!stream_desc) { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "stream description is NULL"); + } + + local_sdp.version_ = "0"; + + local_sdp.username_ = RTMP_SIG_SRS_SERVER; + local_sdp.session_id_ = srs_int2str((int64_t)this); + local_sdp.session_version_ = "2"; + local_sdp.nettype_ = "IN"; + local_sdp.addrtype_ = "IP4"; + local_sdp.unicast_address_ = "0.0.0.0"; + + local_sdp.session_name_ = "SRSPlaySession"; + + local_sdp.msid_semantic_ = "WMS"; + std::string stream_id = req->app + "/" + req->stream; + local_sdp.msids_.push_back(stream_id); + + local_sdp.group_policy_ = "BUNDLE"; + + std::string cname = srs_random_str(16); + + // generate audio media desc + if (stream_desc->audio_track_desc_) { + SrsRtcTrackDescription* audio_track = stream_desc->audio_track_desc_; + + local_sdp.media_descs_.push_back(SrsMediaDesc("audio")); + SrsMediaDesc& local_media_desc = local_sdp.media_descs_.back(); + + local_media_desc.port_ = 9; + local_media_desc.protos_ = "UDP/TLS/RTP/SAVPF"; + local_media_desc.rtcp_mux_ = true; + local_media_desc.rtcp_rsize_ = true; + + local_media_desc.extmaps_ = audio_track->extmaps_; + + local_media_desc.mid_ = audio_track->mid_; + local_sdp.groups_.push_back(local_media_desc.mid_); + + if (audio_track->direction_ == "recvonly") { + local_media_desc.recvonly_ = true; + } else if (audio_track->direction_ == "sendonly") { + local_media_desc.sendonly_ = true; + } else if (audio_track->direction_ == "sendrecv") { + local_media_desc.sendrecv_ = true; + } else if (audio_track->direction_ == "inactive_") { + local_media_desc.inactive_ = true; + } + + if (audio_track->red_) { + SrsRedPayload* red_payload = (SrsRedPayload*)audio_track->red_; + local_media_desc.payload_types_.push_back(red_payload->generate_media_payload_type()); + } + + SrsAudioPayload* payload = (SrsAudioPayload*)audio_track->media_; + local_media_desc.payload_types_.push_back(payload->generate_media_payload_type()); + + //TODO: FIXME: add red, rtx, ulpfec..., payload_types_. + //local_media_desc.payload_types_.push_back(payload->generate_media_payload_type()); + + local_media_desc.ssrc_infos_.push_back(SrsSSRCInfo(audio_track->ssrc_, cname, audio_track->msid_, audio_track->id_)); + + if (audio_track->rtx_) { + std::vector group_ssrcs; + group_ssrcs.push_back(audio_track->ssrc_); + group_ssrcs.push_back(audio_track->rtx_ssrc_); + + local_media_desc.ssrc_groups_.push_back(SrsSSRCGroup("FID", group_ssrcs)); + local_media_desc.ssrc_infos_.push_back(SrsSSRCInfo(audio_track->rtx_ssrc_, cname, audio_track->msid_, audio_track->id_)); + } + + if (audio_track->ulpfec_) { + std::vector group_ssrcs; + group_ssrcs.push_back(audio_track->ssrc_); + group_ssrcs.push_back(audio_track->fec_ssrc_); + local_media_desc.ssrc_groups_.push_back(SrsSSRCGroup("FEC", group_ssrcs)); + + local_media_desc.ssrc_infos_.push_back(SrsSSRCInfo(audio_track->fec_ssrc_, cname, audio_track->msid_, audio_track->id_)); + } + } + + for (int i = 0; i < (int)stream_desc->video_track_descs_.size(); ++i) { + SrsRtcTrackDescription* track = stream_desc->video_track_descs_[i]; + + if (!unified_plan) { + // for plan b, we only add one m= for video track. + if (i == 0) { + video_track_generate_play_offer(track, "video-" +srs_int2str(i), local_sdp); + } + } else { + // unified plan SDP, generate a m= for each video track. + video_track_generate_play_offer(track, "video-" +srs_int2str(i), local_sdp); + } + + SrsMediaDesc& local_media_desc = local_sdp.media_descs_.back(); + local_media_desc.ssrc_infos_.push_back(SrsSSRCInfo(track->ssrc_, cname, track->msid_, track->id_)); + + if (track->rtx_ && track->rtx_ssrc_) { + std::vector group_ssrcs; + group_ssrcs.push_back(track->ssrc_); + group_ssrcs.push_back(track->rtx_ssrc_); + + local_media_desc.ssrc_groups_.push_back(SrsSSRCGroup("FID", group_ssrcs)); + local_media_desc.ssrc_infos_.push_back(SrsSSRCInfo(track->rtx_ssrc_, cname, track->msid_, track->id_)); + } + + if (track->ulpfec_ && track->fec_ssrc_) { + std::vector group_ssrcs; + group_ssrcs.push_back(track->ssrc_); + group_ssrcs.push_back(track->fec_ssrc_); + local_media_desc.ssrc_groups_.push_back(SrsSSRCGroup("FEC", group_ssrcs)); + + local_media_desc.ssrc_infos_.push_back(SrsSSRCInfo(track->fec_ssrc_, cname, track->msid_, track->id_)); + } + } + + return err; +} + +srs_error_t SrsRtcConnection::create_player(SrsRequest* req, std::map sub_relations) +{ + srs_error_t err = srs_success; + + // Ignore if exists. + if(players_.end() != players_.find(req->get_stream_url())) { + return err; + } + + SrsRtcPlayStream* player = new SrsRtcPlayStream(this, _srs_context->get_id()); + if ((err = player->initialize(req, sub_relations)) != srs_success) { + srs_freep(player); + return srs_error_wrap(err, "SrsRtcPlayStream init"); + } + players_.insert(make_pair(req->get_stream_url(), player)); + + // make map between ssrc and player for fastly searching + for(map::iterator it = sub_relations.begin(); it != sub_relations.end(); ++it) { + SrsRtcTrackDescription* track_desc = it->second; + map::iterator it_player = players_ssrc_map_.find(track_desc->ssrc_); + if((players_ssrc_map_.end() != it_player) && (player != it_player->second)) { + return srs_error_new(ERROR_RTC_DUPLICATED_SSRC, "duplicate ssrc %d, track id: %s", + track_desc->ssrc_, track_desc->id_.c_str()); + } + players_ssrc_map_[track_desc->ssrc_] = player; + + if(0 != track_desc->fec_ssrc_) { + if(players_ssrc_map_.end() != players_ssrc_map_.find(track_desc->fec_ssrc_)) { + return srs_error_new(ERROR_RTC_DUPLICATED_SSRC, "duplicate fec ssrc %d, track id: %s", + track_desc->fec_ssrc_, track_desc->id_.c_str()); + } + players_ssrc_map_[track_desc->fec_ssrc_] = player; + } + + if(0 != track_desc->rtx_ssrc_) { + if(players_ssrc_map_.end() != players_ssrc_map_.find(track_desc->rtx_ssrc_)) { + return srs_error_new(ERROR_RTC_DUPLICATED_SSRC, "duplicate rtx ssrc %d, track id: %s", + track_desc->rtx_ssrc_, track_desc->id_.c_str()); + } + players_ssrc_map_[track_desc->rtx_ssrc_] = player; + } + } + + // TODO: FIXME: Support reload. + // The TWCC ID is the ext-map ID in local SDP, and we set to enable GCC. + // Whatever the ext-map, we will disable GCC when config disable it. + int twcc_id = 0; + if (true) { + std::map::iterator it = sub_relations.begin(); + while (it != sub_relations.end()) { + if (it->second->type_ == "video") { + SrsRtcTrackDescription* track = it->second; + twcc_id = track->get_rtp_extension_id(kTWCCExt); + } + ++it; + } + } + srs_trace("RTC connection player gcc=%d", twcc_id); + + // If DTLS done, start the player. Because maybe create some players after DTLS done. + // For example, for single PC, we maybe start publisher when create it, because DTLS is done. + if(ESTABLISHED == state_) { + if(srs_success != (err = player->start())) { + return srs_error_wrap(err, "start player"); + } + } + + return err; +} + +srs_error_t SrsRtcConnection::create_publisher(SrsRequest* req, SrsRtcStreamDescription* stream_desc) +{ + srs_error_t err = srs_success; + + srs_assert(stream_desc); + + // Ignore if exists. + if(publishers_.end() != publishers_.find(req->get_stream_url())) { + return err; + } + + SrsRtcPublishStream* publisher = new SrsRtcPublishStream(this, _srs_context->get_id()); + if ((err = publisher->initialize(req, stream_desc)) != srs_success) { + srs_freep(publisher); + return srs_error_wrap(err, "rtc publisher init"); + } + publishers_[req->get_stream_url()] = publisher; + + if(NULL != stream_desc->audio_track_desc_) { + if(publishers_ssrc_map_.end() != publishers_ssrc_map_.find(stream_desc->audio_track_desc_->ssrc_)) { + return srs_error_new(ERROR_RTC_DUPLICATED_SSRC, " duplicate ssrc %d, track id: %s", + stream_desc->audio_track_desc_->ssrc_, stream_desc->audio_track_desc_->id_.c_str()); + } + publishers_ssrc_map_[stream_desc->audio_track_desc_->ssrc_] = publisher; + + if(0 != stream_desc->audio_track_desc_->fec_ssrc_ + && stream_desc->audio_track_desc_->ssrc_ != stream_desc->audio_track_desc_->fec_ssrc_) { + if(publishers_ssrc_map_.end() != publishers_ssrc_map_.find(stream_desc->audio_track_desc_->fec_ssrc_)) { + return srs_error_new(ERROR_RTC_DUPLICATED_SSRC, " duplicate fec ssrc %d, track id: %s", + stream_desc->audio_track_desc_->fec_ssrc_, stream_desc->audio_track_desc_->id_.c_str()); + } + publishers_ssrc_map_[stream_desc->audio_track_desc_->fec_ssrc_] = publisher; + } + + if(0 != stream_desc->audio_track_desc_->rtx_ssrc_ + && stream_desc->audio_track_desc_->ssrc_ != stream_desc->audio_track_desc_->rtx_ssrc_) { + if(publishers_ssrc_map_.end() != publishers_ssrc_map_.find(stream_desc->audio_track_desc_->rtx_ssrc_)) { + return srs_error_new(ERROR_RTC_DUPLICATED_SSRC, " duplicate rtx ssrc %d, track id: %s", + stream_desc->audio_track_desc_->rtx_ssrc_, stream_desc->audio_track_desc_->id_.c_str()); + } + publishers_ssrc_map_[stream_desc->audio_track_desc_->rtx_ssrc_] = publisher; + } + } + + for(int i = 0; i < (int)stream_desc->video_track_descs_.size(); ++i) { + SrsRtcTrackDescription* track_desc = stream_desc->video_track_descs_.at(i); + if(publishers_ssrc_map_.end() != publishers_ssrc_map_.find(track_desc->ssrc_)) { + return srs_error_new(ERROR_RTC_DUPLICATED_SSRC, " duplicate ssrc %d, track id: %s", + track_desc->ssrc_, track_desc->id_.c_str()); + } + publishers_ssrc_map_[track_desc->ssrc_] = publisher; + + if(0 != track_desc->fec_ssrc_ && track_desc->ssrc_ != track_desc->fec_ssrc_) { + if(publishers_ssrc_map_.end() != publishers_ssrc_map_.find(track_desc->fec_ssrc_)) { + return srs_error_new(ERROR_RTC_DUPLICATED_SSRC, " duplicate fec ssrc %d, track id: %s", + track_desc->fec_ssrc_, track_desc->id_.c_str()); + } + publishers_ssrc_map_[track_desc->fec_ssrc_] = publisher; + } + + if(0 != track_desc->rtx_ssrc_ && track_desc->ssrc_ != track_desc->rtx_ssrc_) { + if(publishers_ssrc_map_.end() != publishers_ssrc_map_.find(track_desc->rtx_ssrc_)) { + return srs_error_new(ERROR_RTC_DUPLICATED_SSRC, " duplicate rtx ssrc %d, track id: %s", + track_desc->rtx_ssrc_, track_desc->id_.c_str()); + } + publishers_ssrc_map_[track_desc->rtx_ssrc_] = publisher; + } + } + + if (_srs_rtc_hijacker) { + if ((err = _srs_rtc_hijacker->on_create_publish(this, publisher, req)) != srs_success) { + return srs_error_wrap(err, "on create publish"); + } + } + + // If DTLS done, start the publisher. Because maybe create some publishers after DTLS done. + // For example, for single PC, we maybe start publisher when create it, because DTLS is done. + if(ESTABLISHED == state()) { + if(srs_success != (err = publisher->start())) { + return srs_error_wrap(err, "start publisher"); + } + } + + return err; +} + +ISrsRtcHijacker::ISrsRtcHijacker() { } +ISrsRtcHijacker::~ISrsRtcHijacker() +{ +} + +ISrsRtcHijacker* _srs_rtc_hijacker = NULL; + diff --git a/trunk/src/app/srs_app_rtc_conn.hpp b/trunk/src/app/srs_app_rtc_conn.hpp index c3796c711..e04c9671e 100644 --- a/trunk/src/app/srs_app_rtc_conn.hpp +++ b/trunk/src/app/srs_app_rtc_conn.hpp @@ -31,26 +31,37 @@ #include #include #include -#include +#include #include +#include +#include +#include +#include +#include +#include +#include #include #include #include #include -#include -#include - class SrsUdpMuxSocket; class SrsConsumer; class SrsStunPacket; class SrsRtcServer; -class SrsRtcSession; +class SrsRtcConnection; class SrsSharedPtrMessage; -class SrsSource; +class SrsRtcStream; class SrsRtpPacket2; -class ISrsUdpSender; +class ISrsCodec; +class SrsRtpNackForReceiver; +class SrsRtpIncommingVideoFrame; +class SrsRtpRingBuffer; +class SrsRtcConsumer; +class SrsRtcAudioSendTrack; +class SrsRtcVideoSendTrack; +class SrsErrorPithyPrint; const uint8_t kSR = 200; const uint8_t kRR = 201; @@ -61,74 +72,148 @@ const uint8_t kApp = 204; // @see: https://tools.ietf.org/html/rfc4585#section-6.1 const uint8_t kRtpFb = 205; const uint8_t kPsFb = 206; +const uint8_t kXR = 207; -// @see: https://tools.ietf.org/html/rfc4585#section-6.3 -const uint8_t kPLI = 1; -const uint8_t kSLI = 2; -const uint8_t kRPSI = 3; -const uint8_t kAFB = 15; - -enum SrsRtcSessionStateType +enum SrsRtcConnectionStateType { // TODO: FIXME: Should prefixed by enum name. INIT = -1, - WAITING_STUN = 1, - DOING_DTLS_HANDSHAKE = 2, - ESTABLISHED = 3, - CLOSED = 4, + WAITING_ANSWER = 1, + WAITING_STUN = 2, + DOING_DTLS_HANDSHAKE = 3, + ESTABLISHED = 4, + CLOSED = 5, }; -class SrsDtlsSession +// The transport for RTC connection. +class ISrsRtcTransport : public ISrsDtlsCallback +{ +public: + ISrsRtcTransport(); + virtual ~ISrsRtcTransport(); +public: + virtual srs_error_t initialize(SrsSessionConfig* cfg) = 0; + virtual srs_error_t start_active_handshake() = 0; + virtual srs_error_t on_dtls(char* data, int nb_data) = 0; + virtual srs_error_t on_dtls_alert(std::string type, std::string desc) = 0; +public: + virtual srs_error_t protect_rtp(const char* plaintext, char* cipher, int& nb_cipher) = 0; + virtual srs_error_t protect_rtcp(const char* plaintext, char* cipher, int& nb_cipher) = 0; + virtual srs_error_t protect_rtp2(void* rtp_hdr, int* len_ptr) = 0; + virtual srs_error_t unprotect_rtp(const char* cipher, char* plaintext, int& nb_plaintext) = 0; + virtual srs_error_t unprotect_rtcp(const char* cipher, char* plaintext, int& nb_plaintext) = 0; +}; + +// The security transport, use DTLS/SRTP to protect the data. +class SrsSecurityTransport : public ISrsRtcTransport { private: - SrsRtcSession* rtc_session; - - SSL* dtls; - BIO* bio_in; - BIO* bio_out; - - std::string client_key; - std::string server_key; - - srtp_t srtp_send; - srtp_t srtp_recv; - + SrsRtcConnection* session_; + SrsDtls* dtls_; + SrsSRTP* srtp_; bool handshake_done; - public: - SrsDtlsSession(SrsRtcSession* s); - virtual ~SrsDtlsSession(); + SrsSecurityTransport(SrsRtcConnection* s); + virtual ~SrsSecurityTransport(); - srs_error_t initialize(const SrsRequest& req); - - srs_error_t on_dtls(SrsUdpMuxSocket* skt); - srs_error_t on_dtls_handshake_done(SrsUdpMuxSocket* skt); - srs_error_t on_dtls_application_data(const char* data, const int len); + srs_error_t initialize(SrsSessionConfig* cfg); + // When play role of dtls client, it send handshake. + srs_error_t start_active_handshake(); + srs_error_t on_dtls(char* data, int nb_data); + srs_error_t on_dtls_alert(std::string type, std::string desc); public: - srs_error_t protect_rtp(char* protected_buf, const char* ori_buf, int& nb_protected_buf); + // Encrypt the input plaintext to output cipher with nb_cipher bytes. + // @remark Note that the nb_cipher is the size of input plaintext, and + // it also is the length of output cipher when return. + srs_error_t protect_rtp(const char* plaintext, char* cipher, int& nb_cipher); + srs_error_t protect_rtcp(const char* plaintext, char* cipher, int& nb_cipher); + // Encrypt the input rtp_hdr with *len_ptr bytes. + // @remark the input plaintext and out cipher reuse rtp_hdr. srs_error_t protect_rtp2(void* rtp_hdr, int* len_ptr); - srs_error_t unprotect_rtp(char* unprotected_buf, const char* ori_buf, int& nb_unprotected_buf); - srs_error_t protect_rtcp(char* protected_buf, const char* ori_buf, int& nb_protected_buf); - srs_error_t unprotect_rtcp(char* unprotected_buf, const char* ori_buf, int& nb_unprotected_buf); -private: - srs_error_t handshake(SrsUdpMuxSocket* skt); + // Decrypt the input cipher to output cipher with nb_cipher bytes. + // @remark Note that the nb_plaintext is the size of input cipher, and + // it also is the length of output plaintext when return. + srs_error_t unprotect_rtp(const char* cipher, char* plaintext, int& nb_plaintext); + srs_error_t unprotect_rtcp(const char* cipher, char* plaintext, int& nb_plaintext); +// implement ISrsDtlsCallback +public: + virtual srs_error_t on_dtls_handshake_done(); + virtual srs_error_t on_dtls_application_data(const char* data, const int len); + virtual srs_error_t write_dtls_data(void* data, int size); private: srs_error_t srtp_initialize(); - srs_error_t srtp_send_init(); - srs_error_t srtp_recv_init(); }; -// A group of RTP packets. -class SrsRtcPackets +// Semi security transport, setup DTLS and SRTP, with SRTP decrypt, without SRTP encrypt. +class SrsSemiSecurityTransport : public SrsSecurityTransport { public: - bool use_gso; - bool should_merge_nalus; + SrsSemiSecurityTransport(SrsRtcConnection* s); + virtual ~SrsSemiSecurityTransport(); public: -#if defined(SRS_DEBUG) - // Debug id. - uint32_t debug_id; -#endif + virtual srs_error_t protect_rtp(const char* plaintext, char* cipher, int& nb_cipher); + virtual srs_error_t protect_rtcp(const char* plaintext, char* cipher, int& nb_cipher); + virtual srs_error_t protect_rtp2(void* rtp_hdr, int* len_ptr); +}; + +// Plaintext transport, without DTLS or SRTP. +class SrsPlaintextTransport : public ISrsRtcTransport +{ +private: + SrsRtcConnection* session_; +public: + SrsPlaintextTransport(SrsRtcConnection* s); + virtual ~SrsPlaintextTransport(); +public: + virtual srs_error_t initialize(SrsSessionConfig* cfg); + virtual srs_error_t start_active_handshake(); + virtual srs_error_t on_dtls(char* data, int nb_data); + virtual srs_error_t on_dtls_alert(std::string type, std::string desc); + virtual srs_error_t on_dtls_handshake_done(); + virtual srs_error_t on_dtls_application_data(const char* data, const int len); + virtual srs_error_t write_dtls_data(void* data, int size); +public: + virtual srs_error_t protect_rtp(const char* plaintext, char* cipher, int& nb_cipher); + virtual srs_error_t protect_rtcp(const char* plaintext, char* cipher, int& nb_cipher); + virtual srs_error_t protect_rtp2(void* rtp_hdr, int* len_ptr); + virtual srs_error_t unprotect_rtp(const char* cipher, char* plaintext, int& nb_plaintext); + virtual srs_error_t unprotect_rtcp(const char* cipher, char* plaintext, int& nb_plaintext); +}; + +// The handler for PLI worker coroutine. +class ISrsRtcPLIWorkerHandler +{ +public: + ISrsRtcPLIWorkerHandler(); + virtual ~ISrsRtcPLIWorkerHandler(); +public: + virtual srs_error_t do_request_keyframe(uint32_t ssrc, SrsContextId cid) = 0; +}; + +// A worker coroutine to request the PLI. +class SrsRtcPLIWorker : virtual public ISrsCoroutineHandler +{ +private: + SrsCoroutine* trd_; + srs_cond_t wait_; + ISrsRtcPLIWorkerHandler* handler_; +private: + // Key is SSRC, value is the CID of subscriber which requests PLI. + std::map plis_; +public: + SrsRtcPLIWorker(ISrsRtcPLIWorkerHandler* h); + virtual ~SrsRtcPLIWorker(); +public: + virtual srs_error_t start(); + virtual void request_keyframe(uint32_t ssrc, SrsContextId cid); +// interface ISrsCoroutineHandler +public: + virtual srs_error_t cycle(); +}; + +// A group of RTP packets for outgoing(send to players). +class SrsRtcPlayStreamStatistic +{ public: // The total bytes of AVFrame packets. int nn_bytes; @@ -141,9 +226,11 @@ public: // one msghdr by GSO, it's only one RTP packet, because we only send once. int nn_rtp_pkts; // For video, the samples or NALUs. + // TODO: FIXME: Remove it because we may don't know. int nn_samples; // For audio, the generated extra audio packets. // For example, when transcoding AAC to opus, may many extra payloads for a audio. + // TODO: FIXME: Remove it because we may don't know. int nn_extras; // The original audio messages. int nn_audios; @@ -151,248 +238,348 @@ public: int nn_videos; // The number of padded packet. int nn_paddings; - // The number of dropped messages. - int nn_dropped; -private: - int cursor; - int nn_cache; - SrsRtpPacket2* cache; public: - SrsRtcPackets(int nn_cache_max); - virtual ~SrsRtcPackets(); -public: - void reset(bool gso, bool merge_nalus); - SrsRtpPacket2* fetch(); - SrsRtpPacket2* back(); - int size(); - int capacity(); - SrsRtpPacket2* at(int index); + SrsRtcPlayStreamStatistic(); + virtual ~SrsRtcPlayStreamStatistic(); }; -class SrsRtcSenderThread : virtual public ISrsCoroutineHandler, virtual public ISrsReloadHandler +// A RTC play stream, client pull and play stream from SRS. +class SrsRtcPlayStream : virtual public ISrsCoroutineHandler, virtual public ISrsReloadHandler + , virtual public ISrsHourGlass, virtual public ISrsRtcPLIWorkerHandler { -protected: +private: + SrsContextId cid_; SrsCoroutine* trd; - int _parent_cid; + SrsRtcConnection* session_; + SrsRtcPLIWorker* pli_worker_; private: - SrsRtcSession* rtc_session; - uint32_t video_ssrc; - uint32_t audio_ssrc; - uint16_t video_payload_type; - uint16_t audio_payload_type; + SrsRequest* req_; + SrsRtcStream* source_; + SrsHourGlass* timer_; + // key: publish_ssrc, value: send track to process rtp/rtcp + std::map audio_tracks_; + std::map video_tracks_; + // The pithy print for special stage. + SrsErrorPithyPrint* nack_epp; private: - // TODO: FIXME: How to handle timestamp overflow? - uint32_t audio_timestamp; - uint16_t audio_sequence; -private: - uint16_t video_sequence; -public: - SrsUdpMuxSocket* sendonly_ukt; -private: - ISrsUdpSender* sender; -private: - bool merge_nalus; - bool gso; - int max_padding; -private: - srs_utime_t mw_sleep; + // For merged-write messages. int mw_msgs; bool realtime; + // Whether enabled nack. + bool nack_enabled_; +private: + // Whether palyer started. + bool is_started; + // The statistic for consumer to send packets to player. + SrsRtcPlayStreamStatistic info; public: - SrsRtcSenderThread(SrsRtcSession* s, SrsUdpMuxSocket* u, int parent_cid); - virtual ~SrsRtcSenderThread(); + SrsRtcPlayStream(SrsRtcConnection* s, const SrsContextId& cid); + virtual ~SrsRtcPlayStream(); public: - srs_error_t initialize(const uint32_t& vssrc, const uint32_t& assrc, const uint16_t& v_pt, const uint16_t& a_pt); + srs_error_t initialize(SrsRequest* request, std::map sub_relations); // interface ISrsReloadHandler public: - virtual srs_error_t on_reload_rtc_server(); virtual srs_error_t on_reload_vhost_play(std::string vhost); virtual srs_error_t on_reload_vhost_realtime(std::string vhost); -public: - virtual int cid(); + virtual const SrsContextId& context_id(); public: virtual srs_error_t start(); virtual void stop(); - virtual void stop_loop(); -public: - void update_sendonly_socket(SrsUdpMuxSocket* skt); public: virtual srs_error_t cycle(); private: - srs_error_t send_messages(SrsSource* source, SrsSharedPtrMessage** msgs, int nb_msgs, SrsRtcPackets& packets); - srs_error_t messages_to_packets(SrsSource* source, SrsSharedPtrMessage** msgs, int nb_msgs, SrsRtcPackets& packets); - srs_error_t send_packets(SrsRtcPackets& packets); - srs_error_t send_packets_gso(SrsRtcPackets& packets); -private: - srs_error_t packet_opus(SrsSample* sample, SrsRtcPackets& packets, int nn_max_payload); -private: - srs_error_t packet_fu_a(SrsSharedPtrMessage* msg, SrsSample* sample, int fu_payload_size, SrsRtcPackets& packets); - srs_error_t packet_nalus(SrsSharedPtrMessage* msg, SrsRtcPackets& packets); - srs_error_t packet_single_nalu(SrsSharedPtrMessage* msg, SrsSample* sample, SrsRtcPackets& packets); - srs_error_t packet_stap_a(SrsSource* source, SrsSharedPtrMessage* msg, SrsRtcPackets& packets); -}; - -class SrsRtcSession -{ - friend class SrsRtcSenderThread; -private: - SrsRtcServer* rtc_server; - SrsSdp remote_sdp; - SrsSdp local_sdp; - SrsRtcSessionStateType session_state; - SrsDtlsSession* dtls_session; - SrsRtcSenderThread* strd; - std::string username; - std::string peer_id; - srs_utime_t last_stun_time; -private: - // For each RTC session, we use a specified cid for debugging logs. - int cid; - // For each RTC session, whether requires encrypt. - // Read config value, rtc_server.encrypt, default to on. - // Sepcifies by HTTP API, query encrypt, optional. - // TODO: FIXME: Support reload. - bool encrypt; - // The timeout of session, keep alive by STUN ping pong. - srs_utime_t sessionStunTimeout; + srs_error_t send_packets(SrsRtcStream* source, const std::vector& pkts, SrsRtcPlayStreamStatistic& info); + void nack_fetch(std::vector& pkts, uint32_t ssrc, uint16_t seq); public: - SrsRequest request; - SrsSource* source; -public: - SrsRtcSession(SrsRtcServer* rtc_svr, const SrsRequest& req, const std::string& un, int context_id); - virtual ~SrsRtcSession(); -public: - SrsSdp* get_local_sdp() { return &local_sdp; } - void set_local_sdp(const SrsSdp& sdp); - - SrsSdp* get_remote_sdp() { return &remote_sdp; } - void set_remote_sdp(const SrsSdp& sdp) { remote_sdp = sdp; } - - SrsRtcSessionStateType get_session_state() { return session_state; } - void set_session_state(SrsRtcSessionStateType state) { session_state = state; } - - std::string id() const { return peer_id + "_" + username; } - - std::string get_peer_id() const { return peer_id; } - void set_peer_id(const std::string& id) { peer_id = id; } - - void set_encrypt(bool v) { encrypt = v; } - - void switch_to_context(); -public: - srs_error_t on_stun(SrsUdpMuxSocket* skt, SrsStunPacket* stun_req); - srs_error_t on_dtls(SrsUdpMuxSocket* skt); - srs_error_t on_rtcp(SrsUdpMuxSocket* skt); -public: - srs_error_t send_client_hello(SrsUdpMuxSocket* skt); - srs_error_t on_connection_established(SrsUdpMuxSocket* skt); - srs_error_t start_play(SrsUdpMuxSocket* skt); -public: - bool is_stun_timeout(); -private: - srs_error_t check_source(); -private: - srs_error_t on_binding_request(SrsUdpMuxSocket* skt, SrsStunPacket* stun_req); -private: - srs_error_t on_rtcp_feedback(char* buf, int nb_buf, SrsUdpMuxSocket* skt); - srs_error_t on_rtcp_ps_feedback(char* buf, int nb_buf, SrsUdpMuxSocket* skt); - srs_error_t on_rtcp_receiver_report(char* buf, int nb_buf, SrsUdpMuxSocket* skt); -}; - -class SrsUdpMuxSender : virtual public ISrsUdpSender, virtual public ISrsCoroutineHandler, virtual public ISrsReloadHandler -{ -private: - srs_netfd_t lfd; - SrsRtcServer* server; - SrsCoroutine* trd; -private: - srs_cond_t cond; - bool waiting_msgs; - bool gso; - int nn_senders; -private: - // Hotspot msgs, we are working on it. - // @remark We will wait util all messages are ready. - std::vector hotspot; - // Cache msgs, for other coroutines to fill it. - std::vector cache; - int cache_pos; - // The max number of messages for sendmmsg. If 1, we use sendmsg to send. - int max_sendmmsg; - // The total queue length, for each sender. - int queue_length; - // The extra queue ratio. - int extra_ratio; - int extra_queue; -public: - SrsUdpMuxSender(SrsRtcServer* s); - virtual ~SrsUdpMuxSender(); -public: - virtual srs_error_t initialize(srs_netfd_t fd, int senders); -private: - void free_mhdrs(std::vector& mhdrs); -public: - virtual srs_error_t fetch(mmsghdr** pphdr); - virtual srs_error_t sendmmsg(mmsghdr* hdr); - virtual bool overflow(); - virtual void set_extra_ratio(int r); -public: - virtual srs_error_t cycle(); -// interface ISrsReloadHandler -public: - virtual srs_error_t on_reload_rtc_server(); -}; - -class SrsRtcServer : virtual public ISrsUdpMuxHandler, virtual public ISrsHourGlass -{ -private: - SrsHourGlass* timer; - std::vector listeners; - std::vector senders; -private: - std::map map_username_session; // key: username(local_ufrag + ":" + remote_ufrag) - std::map map_id_session; // key: peerip(ip + ":" + port) -public: - SrsRtcServer(); - virtual ~SrsRtcServer(); -public: - virtual srs_error_t initialize(); -public: - // TODO: FIXME: Support gracefully quit. - // TODO: FIXME: Support reload. - virtual srs_error_t listen_udp(); - virtual srs_error_t on_udp_packet(SrsUdpMuxSocket* skt); -public: - virtual srs_error_t listen_api(); - SrsRtcSession* create_rtc_session(const SrsRequest& req, const SrsSdp& remote_sdp, SrsSdp& local_sdp, const std::string& mock_eip); - bool insert_into_id_sessions(const std::string& peer_id, SrsRtcSession* rtc_session); - void check_and_clean_timeout_session(); - int nn_sessions() { return (int)map_username_session.size(); } -private: - srs_error_t on_stun(SrsUdpMuxSocket* skt); - srs_error_t on_dtls(SrsUdpMuxSocket* skt); - srs_error_t on_rtp_or_rtcp(SrsUdpMuxSocket* skt); -private: - SrsRtcSession* find_rtc_session_by_username(const std::string& ufrag); - SrsRtcSession* find_rtc_session_by_peer_id(const std::string& peer_id); + // Directly set the status of track, generally for init to set the default value. + void set_all_tracks_status(bool status); // interface ISrsHourGlass public: virtual srs_error_t notify(int type, srs_utime_t interval, srs_utime_t tick); +public: + srs_error_t on_rtcp(SrsRtcpCommon* rtcp); +private: + srs_error_t on_rtcp_xr(SrsRtcpXr* rtcp); + srs_error_t on_rtcp_nack(SrsRtcpNack* rtcp); + srs_error_t on_rtcp_ps_feedback(SrsRtcpPsfbCommon* rtcp); + srs_error_t on_rtcp_rr(SrsRtcpRR* rtcp); + uint32_t get_video_publish_ssrc(uint32_t play_ssrc); +// inteface ISrsRtcPLIWorkerHandler +public: + virtual srs_error_t do_request_keyframe(uint32_t ssrc, SrsContextId cid); }; -// The RTC server adapter. -class RtcServerAdapter : public ISrsHybridServer +// A RTC publish stream, client push and publish stream to SRS. +class SrsRtcPublishStream : virtual public ISrsHourGlass, virtual public ISrsRtpPacketDecodeHandler + , virtual public ISrsRtcPublishStream, virtual public ISrsRtcPLIWorkerHandler { private: - SrsRtcServer* rtc; + SrsContextId cid_; + SrsHourGlass* timer_; + uint64_t nn_audio_frames; + SrsRtcPLIWorker* pli_worker_; +private: + SrsRtcConnection* session_; + uint16_t pt_to_drop_; + // Whether enabled nack. + bool nack_enabled_; +private: + bool request_keyframe_; + SrsErrorPithyPrint* pli_epp; +private: + SrsRequest* req; + SrsRtcStream* source; + // Simulators. + int nn_simulate_nack_drop; +private: + // track vector + std::vector audio_tracks_; + std::vector video_tracks_; +private: + int twcc_id_; + uint8_t twcc_fb_count_; + SrsRtcpTWCC rtcp_twcc_; + SrsRtpExtensionTypes extension_types_; + bool is_started; + srs_utime_t last_time_send_twcc_; public: - RtcServerAdapter(); - virtual ~RtcServerAdapter(); + SrsRtcPublishStream(SrsRtcConnection* session, const SrsContextId& cid); + virtual ~SrsRtcPublishStream(); public: - virtual srs_error_t initialize(); - virtual srs_error_t run(); - virtual void stop(); + srs_error_t initialize(SrsRequest* req, SrsRtcStreamDescription* stream_desc); + srs_error_t start(); + // Directly set the status of track, generally for init to set the default value. + void set_all_tracks_status(bool status); + virtual const SrsContextId& context_id(); +private: + srs_error_t send_rtcp_rr(); + srs_error_t send_rtcp_xr_rrtr(); +public: + srs_error_t on_rtp(char* buf, int nb_buf); +private: + srs_error_t do_on_rtp(char* plaintext, int nb_plaintext); + srs_error_t check_send_nacks(); +public: + virtual void on_before_decode_payload(SrsRtpPacket2* pkt, SrsBuffer* buf, ISrsRtpPayloader** ppayload); +private: + srs_error_t send_periodic_twcc(); +public: + srs_error_t on_rtcp(SrsRtcpCommon* rtcp); +private: + srs_error_t on_rtcp_sr(SrsRtcpSR* rtcp); + srs_error_t on_rtcp_xr(SrsRtcpXr* rtcp); +public: + void request_keyframe(uint32_t ssrc); + virtual srs_error_t do_request_keyframe(uint32_t ssrc, SrsContextId cid); +// interface ISrsHourGlass +public: + virtual srs_error_t notify(int type, srs_utime_t interval, srs_utime_t tick); +public: + void simulate_nack_drop(int nn); +private: + void simulate_drop_packet(SrsRtpHeader* h, int nn_bytes); +private: + srs_error_t on_twcc(uint16_t sn); + SrsRtcAudioRecvTrack* get_audio_track(uint32_t ssrc); + SrsRtcVideoRecvTrack* get_video_track(uint32_t ssrc); + void update_rtt(uint32_t ssrc, int rtt); + void update_send_report_time(uint32_t ssrc, const SrsNtp& ntp); }; +// The statistics for RTC connection. +class SrsRtcConnectionStatistic +{ +public: + int nn_publishers; int nn_subscribers; + int nn_rr; int nn_xr; int nn_sr; int nn_nack; int nn_pli; + uint64_t nn_in_twcc; uint64_t nn_in_rtp; uint64_t nn_in_audios; uint64_t nn_in_videos; + uint64_t nn_out_twcc; uint64_t nn_out_rtp; uint64_t nn_out_audios; uint64_t nn_out_videos; +private: + srs_utime_t born; + srs_utime_t dead; +public: + SrsRtcConnectionStatistic(); + virtual ~SrsRtcConnectionStatistic(); +public: + std::string summary(); +}; + +// Callback for RTC connection. +class ISrsRtcConnectionHijacker +{ +public: + ISrsRtcConnectionHijacker(); + virtual ~ISrsRtcConnectionHijacker(); +public: + virtual srs_error_t on_dtls_done() = 0; +}; + +// A RTC Peer Connection, SDP level object. +class SrsRtcConnection : virtual public ISrsHourGlass, virtual public ISrsResource + , virtual public ISrsDisposingHandler +{ + friend class SrsSecurityTransport; + friend class SrsRtcPlayStream; + friend class SrsRtcPublishStream; +public: + bool disposing_; + SrsRtcConnectionStatistic* stat_; + ISrsRtcConnectionHijacker* hijacker_; +private: + SrsRtcServer* server_; + SrsRtcConnectionStateType state_; + ISrsRtcTransport* transport_; + SrsHourGlass* timer_; +private: + // key: stream id + std::map players_; + //key: player track's ssrc + std::map players_ssrc_map_; + // key: stream id + std::map publishers_; + // key: publisher track's ssrc + std::map publishers_ssrc_map_; +private: + // The local:remote username, such as m5x0n128:jvOm where local name is m5x0n128. + std::string username_; + // The peer address, client maybe use more than one address, it's the current selected one. + SrsUdpMuxSocket* sendonly_skt; + // The address list, client may use multiple addresses. + std::map peer_addresses_; +private: + // TODO: FIXME: Rename it. + // The timeout of session, keep alive by STUN ping pong. + srs_utime_t session_timeout; + // TODO: FIXME: Rename it. + srs_utime_t last_stun_time; +private: + // For each RTC session, we use a specified cid for debugging logs. + SrsContextId cid_; + // TODO: FIXME: Rename to req_. + SrsRequest* req; + SrsSdp remote_sdp; + SrsSdp local_sdp; +private: + // twcc handler + int twcc_id_; + // Simulators. + int nn_simulate_player_nack_drop; + // Pithy print for address change, use port as error code. + SrsErrorPithyPrint* pp_address_change; + // Pithy print for PLI request. + SrsErrorPithyPrint* pli_epp; +public: + SrsRtcConnection(SrsRtcServer* s, const SrsContextId& cid); + virtual ~SrsRtcConnection(); +// interface ISrsDisposingHandler +public: + virtual void on_before_dispose(ISrsResource* c); + virtual void on_disposing(ISrsResource* c); +public: + // TODO: FIXME: save only connection info. + SrsSdp* get_local_sdp(); + void set_local_sdp(const SrsSdp& sdp); + SrsSdp* get_remote_sdp(); + void set_remote_sdp(const SrsSdp& sdp); + // Connection level state machine, for ARQ of UDP packets. + SrsRtcConnectionStateType state(); + void set_state(SrsRtcConnectionStateType state); + // Get username pair for this connection, used as ID of session. + std::string username(); + // Get all addresses client used. + std::vector peer_addresses(); +// Interface ISrsResource. +public: + virtual const SrsContextId& get_id(); + virtual std::string desc(); +public: + void switch_to_context(); + const SrsContextId& context_id(); +public: + srs_error_t add_publisher(SrsRequest* request, const SrsSdp& remote_sdp, SrsSdp& local_sdp); + srs_error_t add_player(SrsRequest* request, const SrsSdp& remote_sdp, SrsSdp& local_sdp); + // server send offer sdp to client, local sdp derivate from source stream desc. + srs_error_t add_player2(SrsRequest* request, bool unified_plan, SrsSdp& local_sdp); +public: + // Before initialize, user must set the local SDP, which is used to inititlize DTLS. + srs_error_t initialize(SrsRequest* r, bool dtls, bool srtp, std::string username); + // The peer address may change, we can identify that by STUN messages. + srs_error_t on_stun(SrsUdpMuxSocket* skt, SrsStunPacket* r); + srs_error_t on_dtls(char* data, int nb_data); + srs_error_t on_rtp(char* data, int nb_data); + srs_error_t on_rtcp(char* data, int nb_data); +private: + srs_error_t dispatch_rtcp(SrsRtcpCommon* rtcp); +public: + srs_error_t on_rtcp_feedback_twcc(char* buf, int nb_buf); + srs_error_t on_rtcp_feedback_remb(SrsRtcpPsfbCommon *rtcp); +public: + void set_hijacker(ISrsRtcConnectionHijacker* h); +public: + srs_error_t on_connection_established(); + srs_error_t on_dtls_alert(std::string type, std::string desc); + srs_error_t start_play(std::string stream_uri); + srs_error_t start_publish(std::string stream_uri); + bool is_alive(); + void alive(); + void update_sendonly_socket(SrsUdpMuxSocket* skt); +// interface ISrsHourGlass +public: + virtual srs_error_t notify(int type, srs_utime_t interval, srs_utime_t tick); +public: + // send rtcp + srs_error_t send_rtcp(char *data, int nb_data); + void check_send_nacks(SrsRtpNackForReceiver* nack, uint32_t ssrc, uint32_t& sent_nacks, uint32_t& timeout_nacks); + srs_error_t send_rtcp_rr(uint32_t ssrc, SrsRtpRingBuffer* rtp_queue, const uint64_t& last_send_systime, const SrsNtp& last_send_ntp); + srs_error_t send_rtcp_xr_rrtr(uint32_t ssrc); + srs_error_t send_rtcp_fb_pli(uint32_t ssrc, const SrsContextId& cid_of_subscriber); +public: + // Simulate the NACK to drop nn packets. + void simulate_nack_drop(int nn); + void simulate_player_drop_packet(SrsRtpHeader* h, int nn_bytes); + srs_error_t do_send_packets(const std::vector& pkts, SrsRtcPlayStreamStatistic& info); + // Directly set the status of play track, generally for init to set the default value. + void set_all_tracks_status(std::string stream_uri, bool is_publish, bool status); +private: + srs_error_t on_binding_request(SrsStunPacket* r); + // publish media capabilitiy negotiate + srs_error_t negotiate_publish_capability(SrsRequest* req, const SrsSdp& remote_sdp, SrsRtcStreamDescription* stream_desc); + srs_error_t generate_publish_local_sdp(SrsRequest* req, SrsSdp& local_sdp, SrsRtcStreamDescription* stream_desc, bool unified_plan); + // play media capabilitiy negotiate + //TODO: Use StreamDescription to negotiate and remove first negotiate_play_capability function + srs_error_t negotiate_play_capability(SrsRequest* req, const SrsSdp& remote_sdp, std::map& sub_relations); + srs_error_t negotiate_play_capability(SrsRequest* req, SrsRtcStreamDescription* req_stream_desc, std::map& sub_relations); + srs_error_t fetch_source_capability(SrsRequest* req, std::map& sub_relations); + srs_error_t generate_play_local_sdp(SrsRequest* req, SrsSdp& local_sdp, SrsRtcStreamDescription* stream_desc, bool unified_plan); + srs_error_t create_player(SrsRequest* request, std::map sub_relations); + srs_error_t create_publisher(SrsRequest* request, SrsRtcStreamDescription* stream_desc); +}; + +class ISrsRtcHijacker +{ +public: + ISrsRtcHijacker(); + virtual ~ISrsRtcHijacker(); +public: + // Initialize the hijacker. + virtual srs_error_t initialize() = 0; + // When create publisher, SDP is done, DTLS is not ready. + virtual srs_error_t on_create_publish(SrsRtcConnection* session, SrsRtcPublishStream* publisher, SrsRequest* req) = 0; + // When start publisher by RTC, SDP and DTLS are done. + virtual srs_error_t on_start_publish(SrsRtcConnection* session, SrsRtcPublishStream* publisher, SrsRequest* req) = 0; + // When stop publish by RTC. + virtual void on_stop_publish(SrsRtcConnection* session, SrsRtcPublishStream* publisher, SrsRequest* req) = 0; + // When got RTP plaintext packet. + virtual srs_error_t on_rtp_packet(SrsRtcConnection* session, SrsRtcPublishStream* publisher, SrsRequest* req, SrsRtpPacket2* pkt) = 0; + // When before play by RTC. (wait source to ready in cascade scenario) + virtual srs_error_t on_before_play(SrsRtcConnection* session, SrsRequest* req) = 0; + // When start player by RTC. + virtual srs_error_t on_start_play(SrsRtcConnection* session, SrsRtcPlayStream* player, SrsRequest* req) = 0; + // When start consuming for player for RTC. + virtual srs_error_t on_start_consume(SrsRtcConnection* session, SrsRtcPlayStream* player, SrsRequest* req, SrsRtcConsumer* consumer) = 0; +}; + +extern ISrsRtcHijacker* _srs_rtc_hijacker; + #endif diff --git a/trunk/src/app/srs_app_rtc_dtls.cpp b/trunk/src/app/srs_app_rtc_dtls.cpp new file mode 100644 index 000000000..cd877b360 --- /dev/null +++ b/trunk/src/app/srs_app_rtc_dtls.cpp @@ -0,0 +1,1050 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2013-2020 Winlin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include + +using namespace std; + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +// Defined in HTTP/HTTPS client. +extern int srs_verify_callback(int preverify_ok, X509_STORE_CTX *ctx); + +// Print the information of SSL, DTLS alert as such. +void ssl_on_info(const SSL* dtls, int where, int ret) +{ + SrsDtlsImpl* dtls_impl = (SrsDtlsImpl*)SSL_get_ex_data(dtls, 0); + srs_assert(dtls_impl); + + const char* method; + int w = where& ~SSL_ST_MASK; + if (w & SSL_ST_CONNECT) { + method = "SSL_connect"; + } else if (w & SSL_ST_ACCEPT) { + method = "SSL_accept"; + } else { + method = "undefined"; + } + + int r1 = SSL_get_error(dtls, ret); + if (where & SSL_CB_LOOP) { + srs_info("DTLS: method=%s state=%s(%s), where=%d, ret=%d, r1=%d", method, SSL_state_string(dtls), + SSL_state_string_long(dtls), where, ret, r1); + } else if (where & SSL_CB_ALERT) { + method = (where & SSL_CB_READ) ? "read":"write"; + + // @see https://www.openssl.org/docs/man1.0.2/man3/SSL_alert_type_string_long.html + string alert_type = SSL_alert_type_string_long(ret); + string alert_desc = SSL_alert_desc_string(ret); + + if (alert_type == "warning" && alert_desc == "CN") { + srs_warn("DTLS: SSL3 alert method=%s type=%s, desc=%s(%s), where=%d, ret=%d, r1=%d", method, alert_type.c_str(), + alert_desc.c_str(), SSL_alert_desc_string_long(ret), where, ret, r1); + } else { + srs_error("DTLS: SSL3 alert method=%s type=%s, desc=%s(%s), where=%d, ret=%d, r1=%d", method, alert_type.c_str(), + alert_desc.c_str(), SSL_alert_desc_string_long(ret), where, ret, r1); + } + + // Notify the DTLS to handle the ALERT message, which maybe means media connection disconnect. + dtls_impl->callback_by_ssl(alert_type, alert_desc); + } else if (where & SSL_CB_EXIT) { + if (ret == 0) { + srs_warn("DTLS: Fail method=%s state=%s(%s), where=%d, ret=%d, r1=%d", method, SSL_state_string(dtls), + SSL_state_string_long(dtls), where, ret, r1); + } else if (ret < 0) { + if (r1 != SSL_ERROR_NONE && r1 != SSL_ERROR_WANT_READ && r1 != SSL_ERROR_WANT_WRITE) { + srs_error("DTLS: Error method=%s state=%s(%s), where=%d, ret=%d, r1=%d", method, SSL_state_string(dtls), + SSL_state_string_long(dtls), where, ret, r1); + } else { + srs_info("DTLS: Error method=%s state=%s(%s), where=%d, ret=%d, r1=%d", method, SSL_state_string(dtls), + SSL_state_string_long(dtls), where, ret, r1); + } + } + } +} + +SSL_CTX* srs_build_dtls_ctx(SrsDtlsVersion version) +{ + SSL_CTX* dtls_ctx; +#if OPENSSL_VERSION_NUMBER < 0x10002000L // v1.0.2 + dtls_ctx = SSL_CTX_new(DTLSv1_method()); +#else + if (version == SrsDtlsVersion1_0) { + dtls_ctx = SSL_CTX_new(DTLSv1_method()); + } else if (version == SrsDtlsVersion1_2) { + dtls_ctx = SSL_CTX_new(DTLSv1_2_method()); + } else { + // SrsDtlsVersionAuto, use version-flexible DTLS methods + dtls_ctx = SSL_CTX_new(DTLS_method()); + } +#endif + + if (_srs_rtc_dtls_certificate->is_ecdsa()) { // By ECDSA, https://stackoverflow.com/a/6006898 +#if OPENSSL_VERSION_NUMBER >= 0x10002000L // v1.0.2 + // For ECDSA, we could set the curves list. + // @see https://www.openssl.org/docs/man1.0.2/man3/SSL_CTX_set1_curves_list.html + SSL_CTX_set1_curves_list(dtls_ctx, "P-521:P-384:P-256"); +#endif + + // For openssl <1.1, we must set the ECDH manually. + // @see https://stackoverrun.com/cn/q/10791887 +#if OPENSSL_VERSION_NUMBER < 0x10100000L // v1.1.x + #if OPENSSL_VERSION_NUMBER < 0x10002000L // v1.0.2 + SSL_CTX_set_tmp_ecdh(dtls_ctx, _srs_rtc_dtls_certificate->get_ecdsa_key()); + #else + SSL_CTX_set_ecdh_auto(dtls_ctx, 1); + #endif +#endif + } + + // Setup DTLS context. + if (true) { + // We use "ALL", while you can use "DEFAULT" means "ALL:!EXPORT:!LOW:!aNULL:!eNULL:!SSLv2" + // @see https://www.openssl.org/docs/man1.0.2/man1/ciphers.html + srs_assert(SSL_CTX_set_cipher_list(dtls_ctx, "ALL") == 1); + + // Setup the certificate. + srs_assert(SSL_CTX_use_certificate(dtls_ctx, _srs_rtc_dtls_certificate->get_cert()) == 1); + srs_assert(SSL_CTX_use_PrivateKey(dtls_ctx, _srs_rtc_dtls_certificate->get_public_key()) == 1); + + // Server will send Certificate Request. + // @see https://www.openssl.org/docs/man1.0.2/man3/SSL_CTX_set_verify.html + // TODO: FIXME: Config it, default to off to make the packet smaller. + SSL_CTX_set_verify(dtls_ctx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, srs_verify_callback); + // The depth count is "level 0:peer certificate", "level 1: CA certificate", + // "level 2: higher level CA certificate", and so on. + // @see https://www.openssl.org/docs/man1.0.2/man3/SSL_CTX_set_verify.html + SSL_CTX_set_verify_depth(dtls_ctx, 4); + + // Whether we should read as many input bytes as possible (for non-blocking reads) or not. + // @see https://www.openssl.org/docs/man1.0.2/man3/SSL_CTX_set_read_ahead.html + SSL_CTX_set_read_ahead(dtls_ctx, 1); + + // TODO: Maybe we can use SRTP-GCM in future. + // @see https://bugs.chromium.org/p/chromium/issues/detail?id=713701 + // @see https://groups.google.com/forum/#!topic/discuss-webrtc/PvCbWSetVAQ + // @remark Only support SRTP_AES128_CM_SHA1_80, please read ssl/d1_srtp.c + srs_assert(SSL_CTX_set_tlsext_use_srtp(dtls_ctx, "SRTP_AES128_CM_SHA1_80") == 0); + } + + return dtls_ctx; +} + +SrsDtlsCertificate::SrsDtlsCertificate() +{ + ecdsa_mode = true; + dtls_cert = NULL; + dtls_pkey = NULL; + eckey = NULL; +} + +SrsDtlsCertificate::~SrsDtlsCertificate() +{ + if (eckey) { + EC_KEY_free(eckey); + } + + if (dtls_pkey) { + EVP_PKEY_free(dtls_pkey); + } + + if (dtls_cert) { + X509_free(dtls_cert); + } +} + +srs_error_t SrsDtlsCertificate::initialize() +{ + srs_error_t err = srs_success; + + // Initialize once. + if (dtls_cert) { + return err; + } + +#if OPENSSL_VERSION_NUMBER < 0x10100000L // v1.1.x + // Initialize SSL library by registering algorithms + // The SSL_library_init() and OpenSSL_add_ssl_algorithms() functions were deprecated in OpenSSL 1.1.0 by OPENSSL_init_ssl(). + // @see https://www.openssl.org/docs/man1.1.0/man3/OpenSSL_add_ssl_algorithms.html + // @see https://web.archive.org/web/20150806185102/http://sctp.fh-muenster.de:80/dtls/dtls_udp_echo.c + OpenSSL_add_ssl_algorithms(); +#else + // As of version 1.1.0 OpenSSL will automatically allocate all resources that it needs so no explicit + // initialisation is required. Similarly it will also automatically deinitialise as required. + // @see https://www.openssl.org/docs/man1.1.0/man3/OPENSSL_init_ssl.html + // OPENSSL_init_ssl(); +#endif + + // Initialize SRTP first. + srs_assert(srtp_init() == 0); + + // Whether use ECDSA certificate. + ecdsa_mode = _srs_config->get_rtc_server_ecdsa(); + + // Create keys by RSA or ECDSA. + dtls_pkey = EVP_PKEY_new(); + srs_assert(dtls_pkey); + if (!ecdsa_mode) { // By RSA + RSA* rsa = RSA_new(); + srs_assert(rsa); + + // Initialize the big-number for private key. + BIGNUM* exponent = BN_new(); + srs_assert(exponent); + BN_set_word(exponent, RSA_F4); + + // Generates a key pair and stores it in the RSA structure provided in rsa. + // @see https://www.openssl.org/docs/man1.0.2/man3/RSA_generate_key_ex.html + int key_bits = 1024; + RSA_generate_key_ex(rsa, key_bits, exponent, NULL); + + // @see https://www.openssl.org/docs/man1.1.0/man3/EVP_PKEY_type.html + srs_assert(EVP_PKEY_set1_RSA(dtls_pkey, rsa) == 1); + + RSA_free(rsa); + BN_free(exponent); + } + if (ecdsa_mode) { // By ECDSA, https://stackoverflow.com/a/6006898 + eckey = EC_KEY_new(); + srs_assert(eckey); + + // Should use the curves in ClientHello.supported_groups + // For example: + // Supported Group: x25519 (0x001d) + // Supported Group: secp256r1 (0x0017) + // Supported Group: secp384r1 (0x0018) + // @remark The curve NID_secp256k1 is not secp256r1, k1 != r1. + // TODO: FIXME: Parse ClientHello and choose the curve. + // Note that secp256r1 in openssl is called NID_X9_62_prime256v1, not NID_secp256k1 + // @see https://stackoverflow.com/questions/41950056/openssl1-1-0-b-is-not-support-secp256r1openssl-ecparam-list-curves + EC_GROUP* ecgroup = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1); + //EC_GROUP* ecgroup = EC_GROUP_new_by_curve_name(NID_secp384r1); + srs_assert(ecgroup); +#if OPENSSL_VERSION_NUMBER < 0x10100000L // v1.1.x + // For openssl 1.0, we must set the group parameters, so that cert is ok. + // @see https://github.com/monero-project/monero/blob/master/contrib/epee/src/net_ssl.cpp#L225 + EC_GROUP_set_asn1_flag(ecgroup, OPENSSL_EC_NAMED_CURVE); +#endif + + srs_assert(EC_KEY_set_group(eckey, ecgroup) == 1); + srs_assert(EC_KEY_generate_key(eckey) == 1); + + // @see https://www.openssl.org/docs/man1.1.0/man3/EVP_PKEY_type.html + srs_assert(EVP_PKEY_set1_EC_KEY(dtls_pkey, eckey) == 1); + + EC_GROUP_free(ecgroup); + } + + // Create certificate, from previous generated pkey. + // TODO: Support ECDSA certificate. + dtls_cert = X509_new(); + srs_assert(dtls_cert); + if (true) { + X509_NAME* subject = X509_NAME_new(); + srs_assert(subject); + + int serial = rand(); + ASN1_INTEGER_set(X509_get_serialNumber(dtls_cert), serial); + + const std::string& aor = RTMP_SIG_SRS_DOMAIN; + X509_NAME_add_entry_by_txt(subject, "CN", MBSTRING_ASC, (unsigned char *) aor.data(), aor.size(), -1, 0); + + X509_set_issuer_name(dtls_cert, subject); + X509_set_subject_name(dtls_cert, subject); + + int expire_day = 365; + const long cert_duration = 60*60*24*expire_day; + + X509_gmtime_adj(X509_get_notBefore(dtls_cert), 0); + X509_gmtime_adj(X509_get_notAfter(dtls_cert), cert_duration); + + X509_set_version(dtls_cert, 2); + srs_assert(X509_set_pubkey(dtls_cert, dtls_pkey) == 1); + srs_assert(X509_sign(dtls_cert, dtls_pkey, EVP_sha1()) != 0); + + X509_NAME_free(subject); + } + + // Show DTLS fingerprint + if (true) { + char fp[100] = {0}; + char *p = fp; + unsigned char md[EVP_MAX_MD_SIZE]; + unsigned int n = 0; + + // TODO: FIXME: Unused variable. + /*int r = */X509_digest(dtls_cert, EVP_sha256(), md, &n); + + for (unsigned int i = 0; i < n; i++, ++p) { + sprintf(p, "%02X", md[i]); + p += 2; + + if(i < (n-1)) { + *p = ':'; + } else { + *p = '\0'; + } + } + + fingerprint.assign(fp, strlen(fp)); + srs_trace("fingerprint=%s", fingerprint.c_str()); + } + + return err; +} + +X509* SrsDtlsCertificate::get_cert() +{ + return dtls_cert; +} + +EVP_PKEY* SrsDtlsCertificate::get_public_key() +{ + return dtls_pkey; +} + +EC_KEY* SrsDtlsCertificate::get_ecdsa_key() +{ + return eckey; +} + +std::string SrsDtlsCertificate::get_fingerprint() +{ + return fingerprint; +} + +bool SrsDtlsCertificate::is_ecdsa() +{ + return ecdsa_mode; +} + +ISrsDtlsCallback::ISrsDtlsCallback() +{ +} + +ISrsDtlsCallback::~ISrsDtlsCallback() +{ +} + +SrsDtlsImpl::SrsDtlsImpl(ISrsDtlsCallback* callback) +{ + dtls_ctx = NULL; + dtls = NULL; + bio_in = NULL; + bio_out = NULL; + + callback_ = callback; + handshake_done_for_us = false; + + last_outgoing_packet_cache = new uint8_t[kRtpPacketSize]; + nn_last_outgoing_packet = 0; + nn_arq_packets = 0; + + version_ = SrsDtlsVersionAuto; +} + +SrsDtlsImpl::~SrsDtlsImpl() +{ + if (!handshake_done_for_us) { + srs_warn2(TAG_DTLS_HANG, "DTLS: Hang, done=%u, version=%d, arq=%u", handshake_done_for_us, + version_, nn_arq_packets); + } + + if (dtls_ctx) { + SSL_CTX_free(dtls_ctx); + dtls_ctx = NULL; + } + + if (dtls) { + // this function will free bio_in and bio_out + SSL_free(dtls); + dtls = NULL; + } + + srs_freepa(last_outgoing_packet_cache); +} + +srs_error_t SrsDtlsImpl::initialize(std::string version) +{ + srs_error_t err = srs_success; + + if (version == "dtls1.0") { + version_ = SrsDtlsVersion1_0; + } else if (version == "dtls1.2") { + version_ = SrsDtlsVersion1_2; + } else { + version_ = SrsDtlsVersionAuto; + } + + dtls_ctx = srs_build_dtls_ctx(version_); + + if ((dtls = SSL_new(dtls_ctx)) == NULL) { + return srs_error_new(ERROR_OpenSslCreateSSL, "SSL_new dtls"); + } + + SSL_set_ex_data(dtls, 0, this); + SSL_set_info_callback(dtls, ssl_on_info); + + if ((bio_in = BIO_new(BIO_s_mem())) == NULL) { + return srs_error_new(ERROR_OpenSslBIONew, "BIO_new in"); + } + + if ((bio_out = BIO_new(BIO_s_mem())) == NULL) { + BIO_free(bio_in); + return srs_error_new(ERROR_OpenSslBIONew, "BIO_new out"); + } + + SSL_set_bio(dtls, bio_in, bio_out); + + return err; +} + +srs_error_t SrsDtlsImpl::on_dtls(char* data, int nb_data) +{ + srs_error_t err = srs_success; + + if ((err = do_on_dtls(data, nb_data)) != srs_success) { + return srs_error_wrap(err, "on_dtls size=%u, data=[%s]", nb_data, + srs_string_dumps_hex(data, nb_data, 32).c_str()); + } + + return err; +} + +srs_error_t SrsDtlsImpl::do_on_dtls(char* data, int nb_data) +{ + srs_error_t err = srs_success; + + int r0 = 0; + // TODO: FIXME: Why reset it before writing? + if ((r0 = BIO_reset(bio_in)) != 1) { + return srs_error_new(ERROR_OpenSslBIOReset, "BIO_reset r0=%d", r0); + } + if ((r0 = BIO_reset(bio_out)) != 1) { + return srs_error_new(ERROR_OpenSslBIOReset, "BIO_reset r0=%d", r0); + } + + // Trace the detail of DTLS packet. + state_trace((uint8_t*)data, nb_data, true, r0, SSL_ERROR_NONE, false, false); + + if ((r0 = BIO_write(bio_in, data, nb_data)) <= 0) { + // TODO: 0 or -1 maybe block, use BIO_should_retry to check. + return srs_error_new(ERROR_OpenSslBIOWrite, "BIO_write r0=%d", r0); + } + + // Always do handshake, even the handshake is done, because the last DTLS packet maybe dropped, + // so we thought the DTLS is done, but client need us to retransmit the last packet. + if ((err = do_handshake()) != srs_success) { + return srs_error_wrap(err, "do handshake"); + } + + while (BIO_ctrl_pending(bio_in) > 0) { + char buf[8092]; + int nb = SSL_read(dtls, buf, sizeof(buf)); + if (nb <= 0) { + continue; + } + srs_trace("DTLS: read nb=%d, data=[%s]", nb, srs_string_dumps_hex(buf, nb, 32).c_str()); + + if ((err = callback_->on_dtls_application_data(buf, nb)) != srs_success) { + return srs_error_wrap(err, "on DTLS data, size=%u, data=[%s]", nb, + srs_string_dumps_hex(buf, nb, 32).c_str()); + } + } + + return err; +} + +srs_error_t SrsDtlsImpl::do_handshake() +{ + srs_error_t err = srs_success; + + // Do handshake and get the result. + int r0 = SSL_do_handshake(dtls); + int r1 = SSL_get_error(dtls, r0); + + // Fatal SSL error, for example, no available suite when peer is DTLS 1.0 while we are DTLS 1.2. + if (r0 < 0 && (r1 != SSL_ERROR_NONE && r1 != SSL_ERROR_WANT_READ && r1 != SSL_ERROR_WANT_WRITE)) { + return srs_error_new(ERROR_RTC_DTLS, "handshake r0=%d, r1=%d", r0, r1); + } + + // OK, Handshake is done, note that it maybe done many times. + if (r1 == SSL_ERROR_NONE) { + handshake_done_for_us = true; + } + + // The data to send out to peer. + uint8_t* data = NULL; + int size = BIO_get_mem_data(bio_out, &data); + + // Callback when got SSL original data. + bool cache = false; + on_ssl_out_data(data, size, cache); + state_trace((uint8_t*)data, size, false, r0, r1, cache, false); + + // Update the packet cache. + if (size > 0 && data != last_outgoing_packet_cache && size < kRtpPacketSize) { + memcpy(last_outgoing_packet_cache, data, size); + nn_last_outgoing_packet = size; + } + + // Callback for the final output data, before send-out. + if ((err = on_final_out_data(data, size)) != srs_success) { + return srs_error_wrap(err, "handle"); + } + + if (size > 0 && (err = callback_->write_dtls_data(data, size)) != srs_success) { + return srs_error_wrap(err, "dtls send size=%u, data=[%s]", size, + srs_string_dumps_hex((char*)data, size, 32).c_str()); + } + + if (handshake_done_for_us) { + if (((err = on_handshake_done()) != srs_success)) { + return srs_error_wrap(err, "done"); + } + } + + return err; +} + +void SrsDtlsImpl::state_trace(uint8_t* data, int length, bool incoming, int r0, int r1, bool cache, bool arq) +{ + // change_cipher_spec(20), alert(21), handshake(22), application_data(23) + // @see https://tools.ietf.org/html/rfc2246#section-6.2.1 + uint8_t content_type = 0; + if (length >= 1) { + content_type = (uint8_t)data[0]; + } + + uint16_t size = 0; + if (length >= 13) { + size = uint16_t(data[11])<<8 | uint16_t(data[12]); + } + + uint8_t handshake_type = 0; + if (length >= 14) { + handshake_type = (uint8_t)data[13]; + } + + srs_trace("DTLS: %s %s, done=%u, cache=%u, arq=%u/%u, r0=%d, r1=%d, len=%u, cnt=%u, size=%u, hs=%u", + (is_dtls_client()? "Active":"Passive"), (incoming? "RECV":"SEND"), handshake_done_for_us, cache, arq, + nn_arq_packets, r0, r1, length, content_type, size, handshake_type); +} + +const int SRTP_MASTER_KEY_KEY_LEN = 16; +const int SRTP_MASTER_KEY_SALT_LEN = 14; +srs_error_t SrsDtlsImpl::get_srtp_key(std::string& recv_key, std::string& send_key) +{ + srs_error_t err = srs_success; + + unsigned char material[SRTP_MASTER_KEY_LEN * 2] = {0}; // client(SRTP_MASTER_KEY_KEY_LEN + SRTP_MASTER_KEY_SALT_LEN) + server + static const string dtls_srtp_lable = "EXTRACTOR-dtls_srtp"; + if (!SSL_export_keying_material(dtls, material, sizeof(material), dtls_srtp_lable.c_str(), dtls_srtp_lable.size(), NULL, 0, 0)) { + return srs_error_new(ERROR_RTC_SRTP_INIT, "SSL export key r0=%lu", ERR_get_error()); + } + + size_t offset = 0; + + std::string client_master_key(reinterpret_cast(material), SRTP_MASTER_KEY_KEY_LEN); + offset += SRTP_MASTER_KEY_KEY_LEN; + std::string server_master_key(reinterpret_cast(material + offset), SRTP_MASTER_KEY_KEY_LEN); + offset += SRTP_MASTER_KEY_KEY_LEN; + std::string client_master_salt(reinterpret_cast(material + offset), SRTP_MASTER_KEY_SALT_LEN); + offset += SRTP_MASTER_KEY_SALT_LEN; + std::string server_master_salt(reinterpret_cast(material + offset), SRTP_MASTER_KEY_SALT_LEN); + + if (is_dtls_client()) { + recv_key = server_master_key + server_master_salt; + send_key = client_master_key + client_master_salt; + } else { + recv_key = client_master_key + client_master_salt; + send_key = server_master_key + server_master_salt; + } + + return err; +} + +void SrsDtlsImpl::callback_by_ssl(std::string type, std::string desc) +{ + srs_error_t err = srs_success; + if ((err = callback_->on_dtls_alert(type, desc)) != srs_success) { + srs_warn2(TAG_DTLS_ALERT, "DTLS: handler alert err %s", srs_error_desc(err).c_str()); + srs_freep(err); + } +} + +SrsDtlsClientImpl::SrsDtlsClientImpl(ISrsDtlsCallback* callback) : SrsDtlsImpl(callback) +{ + trd = NULL; + state_ = SrsDtlsStateInit; + + // The first wait and base interval for ARQ. + arq_interval = 10 * SRS_UTIME_MILLISECONDS; + + // Use step timeout for ARQ, the total timeout is sum(arq_to_ratios)*arq_interval. + // for example, if arq_interval is 10ms, arq_to_ratios is [3, 6, 9, 15, 20, 40, 80, 160], + // then total timeout is sum([3, 6, 9, 15, 20, 40, 80, 160]) * 10ms = 3330ms. + int ratios[] = {3, 6, 9, 15, 20, 40, 80, 160}; + srs_assert(sizeof(arq_to_ratios) == sizeof(ratios)); + memcpy(arq_to_ratios, ratios, sizeof(ratios)); +} + +SrsDtlsClientImpl::~SrsDtlsClientImpl() +{ + srs_freep(trd); +} + +srs_error_t SrsDtlsClientImpl::initialize(std::string version) +{ + srs_error_t err = srs_success; + + if ((err = SrsDtlsImpl::initialize(version)) != srs_success) { + return err; + } + + // Dtls setup active, as client role. + SSL_set_connect_state(dtls); + SSL_set_max_send_fragment(dtls, kRtpPacketSize); + + return err; +} + +srs_error_t SrsDtlsClientImpl::start_active_handshake() +{ + return do_handshake(); +} + +srs_error_t SrsDtlsClientImpl::on_dtls(char* data, int nb_data) +{ + srs_error_t err = srs_success; + + // When got packet, stop the ARQ if server in the first ARQ state SrsDtlsStateServerHello. + // @note But for ARQ state, we should never stop the ARQ, for example, we are in the second ARQ sate + // SrsDtlsStateServerDone, but we got previous late wrong packet ServeHello, which is not the expect + // packet SessionNewTicket, we should never stop the ARQ thread. + if (state_ == SrsDtlsStateServerHello) { + stop_arq(); + } + + if ((err = SrsDtlsImpl::on_dtls(data, nb_data)) != srs_success) { + return err; + } + + return err; +} + +void SrsDtlsClientImpl::on_ssl_out_data(uint8_t*& data, int& size, bool& cached) +{ + // DTLS client use ARQ thread to send cached packet. + cached = false; +} + +srs_error_t SrsDtlsClientImpl::on_final_out_data(uint8_t* data, int size) +{ + srs_error_t err = srs_success; + + // Driven ARQ and state for DTLS client. + // If we are sending client hello, change from init to new state. + if (state_ == SrsDtlsStateInit && size > 14 && data[13] == 1) { + state_ = SrsDtlsStateClientHello; + } + // If we are sending certificate, change from SrsDtlsStateServerHello to new state. + if (state_ == SrsDtlsStateServerHello && size > 14 && data[13] == 11) { + state_ = SrsDtlsStateClientCertificate; + } + + // Try to start the ARQ for client. + if ((state_ == SrsDtlsStateClientHello || state_ == SrsDtlsStateClientCertificate)) { + if (state_ == SrsDtlsStateClientHello) { + state_ = SrsDtlsStateServerHello; + } else if (state_ == SrsDtlsStateClientCertificate) { + state_ = SrsDtlsStateServerDone; + } + + if ((err = start_arq()) != srs_success) { + return srs_error_wrap(err, "start arq"); + } + } + + return err; +} + +srs_error_t SrsDtlsClientImpl::on_handshake_done() +{ + srs_error_t err = srs_success; + + // When handshake done, stop the ARQ. + state_ = SrsDtlsStateClientDone; + stop_arq(); + + // Notify connection the DTLS is done. + if (((err = callback_->on_dtls_handshake_done()) != srs_success)) { + return srs_error_wrap(err, "dtls done"); + } + + return err; +} + +bool SrsDtlsClientImpl::is_dtls_client() +{ + return true; +} + +srs_error_t SrsDtlsClientImpl::start_arq() +{ + srs_error_t err = srs_success; + + srs_info("start arq, state=%u", state_); + + // Dispose the previous ARQ thread. + srs_freep(trd); + trd = new SrsSTCoroutine("dtls", this, _srs_context->get_id()); + + // We should start the ARQ thread for DTLS client. + if ((err = trd->start()) != srs_success) { + return srs_error_wrap(err, "arq start"); + } + + return err; +} + +void SrsDtlsClientImpl::stop_arq() +{ + srs_info("stop arq, state=%u", state_); + srs_freep(trd); + srs_info("stop arq, done"); +} + +srs_error_t SrsDtlsClientImpl::cycle() +{ + srs_error_t err = srs_success; + + // Limit the max retry for ARQ. + for (int i = 0; i < (int)(sizeof(arq_to_ratios) / sizeof(int)); i++) { + srs_utime_t arq_to = arq_interval * arq_to_ratios[i]; + srs_usleep(arq_to); + + // We ignore any error for ARQ thread. + if ((err = trd->pull()) != srs_success) { + srs_freep(err); + return err; + } + + // If done, should stop ARQ. + if (handshake_done_for_us) { + return err; + } + + // For DTLS client ARQ, the state should be specified. + if (state_ != SrsDtlsStateServerHello && state_ != SrsDtlsStateServerDone) { + return err; + } + + // Try to retransmit the packet. + uint8_t* data = last_outgoing_packet_cache; + int size = nn_last_outgoing_packet; + + if (size) { + // Trace the detail of DTLS packet. + state_trace((uint8_t*)data, size, false, 1, SSL_ERROR_NONE, true, true); + nn_arq_packets++; + + if ((err = callback_->write_dtls_data(data, size)) != srs_success) { + return srs_error_wrap(err, "dtls send size=%u, data=[%s]", size, + srs_string_dumps_hex((char*)data, size, 32).c_str()); + } + } + + srs_info("arq cycle, done=%u, state=%u, retry=%d, interval=%dms, to=%dms, size=%d, nn=%d", handshake_done_for_us, + state_, i, srsu2msi(arq_interval), srsu2msi(arq_to), size, nn_arq_packets); + } + + return err; +} + +SrsDtlsServerImpl::SrsDtlsServerImpl(ISrsDtlsCallback* callback) : SrsDtlsImpl(callback) +{ +} + +SrsDtlsServerImpl::~SrsDtlsServerImpl() +{ +} + +srs_error_t SrsDtlsServerImpl::initialize(std::string version) +{ + srs_error_t err = srs_success; + + if ((err = SrsDtlsImpl::initialize(version)) != srs_success) { + return err; + } + + // Dtls setup passive, as server role. + SSL_set_accept_state(dtls); + + return err; +} + +srs_error_t SrsDtlsServerImpl::start_active_handshake() +{ + return srs_success; +} + +void SrsDtlsServerImpl::on_ssl_out_data(uint8_t*& data, int& size, bool& cached) +{ + // If outgoing packet is empty, we use the last cache. + // @remark Only for DTLS server, because DTLS client use ARQ thread to send cached packet. + if (size <= 0 && nn_last_outgoing_packet) { + size = nn_last_outgoing_packet; + data = last_outgoing_packet_cache; + nn_arq_packets++; + cached = true; + } +} + +srs_error_t SrsDtlsServerImpl::on_final_out_data(uint8_t* data, int size) +{ + return srs_success; +} + +srs_error_t SrsDtlsServerImpl::on_handshake_done() +{ + srs_error_t err = srs_success; + + // Notify connection the DTLS is done. + if (((err = callback_->on_dtls_handshake_done()) != srs_success)) { + return srs_error_wrap(err, "dtls done"); + } + + return err; +} + +bool SrsDtlsServerImpl::is_dtls_client() +{ + return false; +} + +SrsDtls::SrsDtls(ISrsDtlsCallback* callback) +{ + callback_ = callback; + impl = new SrsDtlsServerImpl(callback); +} + +SrsDtls::~SrsDtls() +{ + srs_freep(impl); +} + +srs_error_t SrsDtls::initialize(std::string role, std::string version) +{ + srs_freep(impl); + if (role == "active") { + impl = new SrsDtlsClientImpl(callback_); + } else { + impl = new SrsDtlsServerImpl(callback_); + } + + return impl->initialize(version); +} + +srs_error_t SrsDtls::start_active_handshake() +{ + return impl->start_active_handshake(); +} + +srs_error_t SrsDtls::on_dtls(char* data, int nb_data) +{ + return impl->on_dtls(data, nb_data); +} + +srs_error_t SrsDtls::get_srtp_key(std::string& recv_key, std::string& send_key) +{ + return impl->get_srtp_key(recv_key, send_key); +} + +SrsSRTP::SrsSRTP() +{ + recv_ctx_ = NULL; + send_ctx_ = NULL; +} + +SrsSRTP::~SrsSRTP() +{ + if (recv_ctx_) { + srtp_dealloc(recv_ctx_); + } + + if (send_ctx_) { + srtp_dealloc(send_ctx_); + } +} + +srs_error_t SrsSRTP::initialize(string recv_key, std::string send_key) +{ + srs_error_t err = srs_success; + + srtp_policy_t policy; + bzero(&policy, sizeof(policy)); + + // TODO: Maybe we can use SRTP-GCM in future. + // @see https://bugs.chromium.org/p/chromium/issues/detail?id=713701 + // @see https://groups.google.com/forum/#!topic/discuss-webrtc/PvCbWSetVAQ + srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtp); + srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtcp); + + policy.ssrc.value = 0; + // TODO: adjust window_size + policy.window_size = 8192; + policy.allow_repeat_tx = 1; + policy.next = NULL; + + // init recv context + policy.ssrc.type = ssrc_any_inbound; + uint8_t *rkey = new uint8_t[recv_key.size()]; + SrsAutoFreeA(uint8_t, rkey); + memcpy(rkey, recv_key.data(), recv_key.size()); + policy.key = rkey; + + srtp_err_status_t r0 = srtp_err_status_ok; + if ((r0 = srtp_create(&recv_ctx_, &policy)) != srtp_err_status_ok) { + return srs_error_new(ERROR_RTC_SRTP_INIT, "srtp create r0=%u", r0); + } + + policy.ssrc.type = ssrc_any_outbound; + uint8_t *skey = new uint8_t[send_key.size()]; + SrsAutoFreeA(uint8_t, skey); + memcpy(skey, send_key.data(), send_key.size()); + policy.key = skey; + + if ((r0 = srtp_create(&send_ctx_, &policy)) != srtp_err_status_ok) { + return srs_error_new(ERROR_RTC_SRTP_INIT, "srtp create r0=%u", r0); + } + + return err; +} + +srs_error_t SrsSRTP::protect_rtp(const char* plaintext, char* cipher, int& nb_cipher) +{ + srs_error_t err = srs_success; + + // If DTLS/SRTP is not ready, fail. + if (!send_ctx_) { + return srs_error_new(ERROR_RTC_SRTP_PROTECT, "not ready"); + } + + memcpy(cipher, plaintext, nb_cipher); + + srtp_err_status_t r0 = srtp_err_status_ok; + if ((r0 = srtp_protect(send_ctx_, cipher, &nb_cipher)) != srtp_err_status_ok) { + return srs_error_new(ERROR_RTC_SRTP_PROTECT, "rtp protect r0=%u", r0); + } + + return err; +} + +srs_error_t SrsSRTP::protect_rtcp(const char* plaintext, char* cipher, int& nb_cipher) +{ + srs_error_t err = srs_success; + + // If DTLS/SRTP is not ready, fail. + if (!send_ctx_) { + return srs_error_new(ERROR_RTC_SRTP_PROTECT, "not ready"); + } + + memcpy(cipher, plaintext, nb_cipher); + + srtp_err_status_t r0 = srtp_err_status_ok; + if ((r0 = srtp_protect_rtcp(send_ctx_, cipher, &nb_cipher)) != srtp_err_status_ok) { + return srs_error_new(ERROR_RTC_SRTP_PROTECT, "rtcp protect r0=%u", r0); + } + + return err; +} + +srs_error_t SrsSRTP::protect_rtp2(void* rtp_hdr, int* len_ptr) +{ + srs_error_t err = srs_success; + + // If DTLS/SRTP is not ready, fail. + if (!send_ctx_) { + return srs_error_new(ERROR_RTC_SRTP_PROTECT, "not ready"); + } + + srtp_err_status_t r0 = srtp_err_status_ok; + if ((r0 = srtp_protect(send_ctx_, rtp_hdr, len_ptr)) != srtp_err_status_ok) { + return srs_error_new(ERROR_RTC_SRTP_PROTECT, "rtp protect r0=%u", r0); + } + + return err; +} + +srs_error_t SrsSRTP::unprotect_rtp(const char* cipher, char* plaintext, int& nb_plaintext) +{ + srs_error_t err = srs_success; + + // If DTLS/SRTP is not ready, fail. + if (!recv_ctx_) { + return srs_error_new(ERROR_RTC_SRTP_UNPROTECT, "not ready"); + } + + memcpy(plaintext, cipher, nb_plaintext); + + srtp_err_status_t r0 = srtp_err_status_ok; + if ((r0 = srtp_unprotect(recv_ctx_, plaintext, &nb_plaintext)) != srtp_err_status_ok) { + return srs_error_new(ERROR_RTC_SRTP_UNPROTECT, "rtp unprotect r0=%u", r0); + } + + return err; +} + +srs_error_t SrsSRTP::unprotect_rtcp(const char* cipher, char* plaintext, int& nb_plaintext) +{ + srs_error_t err = srs_success; + + // If DTLS/SRTP is not ready, fail. + if (!recv_ctx_) { + return srs_error_new(ERROR_RTC_SRTP_UNPROTECT, "not ready"); + } + + memcpy(plaintext, cipher, nb_plaintext); + + srtp_err_status_t r0 = srtp_err_status_ok; + if ((r0 = srtp_unprotect_rtcp(recv_ctx_, plaintext, &nb_plaintext)) != srtp_err_status_ok) { + return srs_error_new(ERROR_RTC_SRTP_UNPROTECT, "rtcp unprotect r0=%u", r0); + } + + return err; +} + diff --git a/trunk/src/app/srs_app_rtc_dtls.hpp b/trunk/src/app/srs_app_rtc_dtls.hpp new file mode 100644 index 000000000..e6ddfaf3e --- /dev/null +++ b/trunk/src/app/srs_app_rtc_dtls.hpp @@ -0,0 +1,242 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2013-2020 Winlin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef SRS_APP_RTC_DTLS_HPP +#define SRS_APP_RTC_DTLS_HPP + +#include + +#include +#include + +#include +#include + +#include + +class SrsRequest; + +class SrsDtlsCertificate +{ +private: + std::string fingerprint; + bool ecdsa_mode; + X509* dtls_cert; + EVP_PKEY* dtls_pkey; + EC_KEY* eckey; +public: + SrsDtlsCertificate(); + virtual ~SrsDtlsCertificate(); +public: + // Initialize DTLS certificate. + srs_error_t initialize(); + // dtls_cert + X509* get_cert(); + // public key + EVP_PKEY* get_public_key(); + // ECDSA key + EC_KEY* get_ecdsa_key(); + // certificate fingerprint + std::string get_fingerprint(); + // whether is ecdsa + bool is_ecdsa(); +}; + +// @global config object. +extern SrsDtlsCertificate* _srs_rtc_dtls_certificate; + +// @remark: play the role of DTLS_CLIENT, will send handshake +// packet first. +enum SrsDtlsRole { + SrsDtlsRoleClient, + SrsDtlsRoleServer +}; + +// @remark: DTLS_10 will all be ignored, and only DTLS1_2 will be accepted, +// DTLS_10 Support will be completely removed in M84 or later. +// TODO(https://bugs.webrtc.org/10261). +enum SrsDtlsVersion { + SrsDtlsVersionAuto = -1, + SrsDtlsVersion1_0, + SrsDtlsVersion1_2 +}; + +class ISrsDtlsCallback +{ +public: + ISrsDtlsCallback(); + virtual ~ISrsDtlsCallback(); +public: + // DTLS handshake done callback. + virtual srs_error_t on_dtls_handshake_done() = 0; + // DTLS receive application data callback. + virtual srs_error_t on_dtls_application_data(const char* data, const int len) = 0; + // DTLS write dtls data. + virtual srs_error_t write_dtls_data(void* data, int size) = 0; + // Callback when DTLS Alert message. + virtual srs_error_t on_dtls_alert(std::string type, std::string desc) = 0; +}; + +// The state for DTLS client. +enum SrsDtlsState { + SrsDtlsStateInit, // Start. + SrsDtlsStateClientHello, // Should start ARQ thread. + SrsDtlsStateServerHello, // We are in the first ARQ state. + SrsDtlsStateClientCertificate, // Should start ARQ thread again. + SrsDtlsStateServerDone, // We are in the second ARQ state. + SrsDtlsStateClientDone, // Done. +}; + +class SrsDtlsImpl +{ +protected: + SSL_CTX* dtls_ctx; + SSL* dtls; + BIO* bio_in; + BIO* bio_out; + ISrsDtlsCallback* callback_; + // @remark: dtls_version_ default value is SrsDtlsVersionAuto. + SrsDtlsVersion version_; +protected: + // Whether the handhshake is done, for us only. + // @remark For us only, means peer maybe not done, we also need to handle the DTLS packet. + bool handshake_done_for_us; + // DTLS packet cache, only last out-going packet. + uint8_t* last_outgoing_packet_cache; + int nn_last_outgoing_packet; + // The stat for ARQ packets. + int nn_arq_packets; +public: + SrsDtlsImpl(ISrsDtlsCallback* callback); + virtual ~SrsDtlsImpl(); +public: + virtual srs_error_t initialize(std::string version); + virtual srs_error_t start_active_handshake() = 0; + virtual srs_error_t on_dtls(char* data, int nb_data); +protected: + srs_error_t do_on_dtls(char* data, int nb_data); + srs_error_t do_handshake(); + void state_trace(uint8_t* data, int length, bool incoming, int r0, int r1, bool cache, bool arq); +public: + srs_error_t get_srtp_key(std::string& recv_key, std::string& send_key); + void callback_by_ssl(std::string type, std::string desc); +protected: + virtual void on_ssl_out_data(uint8_t*& data, int& size, bool& cached) = 0; + virtual srs_error_t on_final_out_data(uint8_t* data, int size) = 0; + virtual srs_error_t on_handshake_done() = 0; + virtual bool is_dtls_client() = 0; +}; + +class SrsDtlsClientImpl : virtual public SrsDtlsImpl, virtual public ISrsCoroutineHandler +{ +private: + // ARQ thread, for role active(DTLS client). + // @note If passive(DTLS server), the ARQ is driven by DTLS client. + SrsCoroutine* trd; + // The DTLS-client state to drive the ARQ thread. + SrsDtlsState state_; + // The timeout for ARQ. + srs_utime_t arq_interval; + int arq_to_ratios[8]; +public: + SrsDtlsClientImpl(ISrsDtlsCallback* callback); + virtual ~SrsDtlsClientImpl(); +public: + virtual srs_error_t initialize(std::string version); + virtual srs_error_t start_active_handshake(); + virtual srs_error_t on_dtls(char* data, int nb_data); +protected: + virtual void on_ssl_out_data(uint8_t*& data, int& size, bool& cached); + virtual srs_error_t on_final_out_data(uint8_t* data, int size); + virtual srs_error_t on_handshake_done(); + virtual bool is_dtls_client(); +private: + srs_error_t start_arq(); + void stop_arq(); +public: + virtual srs_error_t cycle(); +}; + +class SrsDtlsServerImpl : public SrsDtlsImpl +{ +public: + SrsDtlsServerImpl(ISrsDtlsCallback* callback); + virtual ~SrsDtlsServerImpl(); +public: + virtual srs_error_t initialize(std::string version); + virtual srs_error_t start_active_handshake(); +protected: + virtual void on_ssl_out_data(uint8_t*& data, int& size, bool& cached); + virtual srs_error_t on_final_out_data(uint8_t* data, int size); + virtual srs_error_t on_handshake_done(); + virtual bool is_dtls_client(); +}; + +class SrsDtls +{ +private: + SrsDtlsImpl* impl; + ISrsDtlsCallback* callback_; +public: + SrsDtls(ISrsDtlsCallback* callback); + virtual ~SrsDtls(); +public: + srs_error_t initialize(std::string role, std::string version); +public: + // As DTLS client, start handshake actively, send the ClientHello packet. + srs_error_t start_active_handshake(); + // When got DTLS packet, may handshake packets or application data. + // @remark When we are passive(DTLS server), we start handshake when got DTLS packet. + srs_error_t on_dtls(char* data, int nb_data); +public: + srs_error_t get_srtp_key(std::string& recv_key, std::string& send_key); +}; + +class SrsSRTP +{ +private: + srtp_t recv_ctx_; + srtp_t send_ctx_; +public: + SrsSRTP(); + virtual ~SrsSRTP(); +public: + // Intialize srtp context with recv_key and send_key. + srs_error_t initialize(std::string recv_key, std::string send_key); +public: + // Encrypt the input plaintext to output cipher with nb_cipher bytes. + // @remark Note that the nb_cipher is the size of input plaintext, and + // it also is the length of output cipher when return. + srs_error_t protect_rtp(const char* plaintext, char* cipher, int& nb_cipher); + srs_error_t protect_rtcp(const char* plaintext, char* cipher, int& nb_cipher); + // Encrypt the input rtp_hdr with *len_ptr bytes. + // @remark the input plaintext and out cipher reuse rtp_hdr. + srs_error_t protect_rtp2(void* rtp_hdr, int* len_ptr); + // Decrypt the input cipher to output cipher with nb_cipher bytes. + // @remark Note that the nb_plaintext is the size of input cipher, and + // it also is the length of output plaintext when return. + srs_error_t unprotect_rtp(const char* cipher, char* plaintext, int& nb_plaintext); + srs_error_t unprotect_rtcp(const char* cipher, char* plaintext, int& nb_plaintext); +}; + +#endif diff --git a/trunk/src/app/srs_app_rtc_queue.cpp b/trunk/src/app/srs_app_rtc_queue.cpp new file mode 100644 index 000000000..efa3a6b75 --- /dev/null +++ b/trunk/src/app/srs_app_rtc_queue.cpp @@ -0,0 +1,296 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2013-2020 John + * + * 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 + +using namespace std; + +#include +#include +#include +#include + +SrsRtpRingBuffer::SrsRtpRingBuffer(int capacity) +{ + nn_seq_flip_backs = 0; + begin = end = 0; + capacity_ = (uint16_t)capacity; + initialized_ = false; + + queue_ = new SrsRtpPacket2*[capacity_]; + memset(queue_, 0, sizeof(SrsRtpPacket2*) * capacity); +} + +SrsRtpRingBuffer::~SrsRtpRingBuffer() +{ + for (int i = 0; i < capacity_; ++i) { + SrsRtpPacket2* pkt = queue_[i]; + srs_freep(pkt); + } + srs_freepa(queue_); +} + +bool SrsRtpRingBuffer::empty() +{ + return begin == end; +} + +int SrsRtpRingBuffer::size() +{ + int size = srs_rtp_seq_distance(begin, end); + srs_assert(size >= 0); + return size; +} + +void SrsRtpRingBuffer::advance_to(uint16_t seq) +{ + begin = seq; +} + +void SrsRtpRingBuffer::set(uint16_t at, SrsRtpPacket2* pkt) +{ + SrsRtpPacket2* p = queue_[at % capacity_]; + + if (p) { + srs_freep(p); + } + + queue_[at % capacity_] = pkt; +} + +void SrsRtpRingBuffer::remove(uint16_t at) +{ + set(at, NULL); +} + +uint32_t SrsRtpRingBuffer::get_extended_highest_sequence() +{ + return nn_seq_flip_backs * 65536 + end - 1; +} + +bool SrsRtpRingBuffer::update(uint16_t seq, uint16_t& nack_first, uint16_t& nack_last) +{ + if (!initialized_) { + initialized_ = true; + begin = seq; + end = seq + 1; + return true; + } + + // Normal sequence, seq follows high_. + if (srs_rtp_seq_distance(end, seq) >= 0) { + //TODO: FIXME: if diff_upper > limit_max_size clear? + // int16_t diff_upper = srs_rtp_seq_distance(end, seq) + // notify_nack_list_full() + nack_first = end; + nack_last = seq; + + // When distance(seq,high_)>0 and seq0 and 1<65535. + // TODO: FIXME: The first flip may be dropped. + if (seq < end) { + ++nn_seq_flip_backs; + } + end = seq + 1; + // TODO: FIXME: check whether is neccessary? + // srs_rtp_seq_distance(begin, end) > max_size + // advance_to(), srs_rtp_seq_distance(begin, end) < max_size; + return true; + } + + // Out-of-order sequence, seq before low_. + if (srs_rtp_seq_distance(seq, begin) > 0) { + nack_first = seq; + nack_last = begin; + begin = seq; + + // TODO: FIXME: Maybe should support startup drop. + return true; + // When startup, we may receive packets in chaos order. + // Because we don't know the ISN(initiazlie sequence number), the first packet + // we received maybe no the first packet client sent. + // @remark We only log a warning, because it seems ok for publisher. + //return false; + } + + return true; +} + +SrsRtpPacket2* SrsRtpRingBuffer::at(uint16_t seq) { + return queue_[seq % capacity_]; +} + +void SrsRtpRingBuffer::notify_nack_list_full() +{ + while(begin <= end) { + remove(begin); + ++begin; + } + + begin = end = 0; + initialized_ = false; +} + +void SrsRtpRingBuffer::notify_drop_seq(uint16_t seq) +{ + remove(seq); + advance_to(seq+1); +} + +SrsNackOption::SrsNackOption() +{ + max_count = 15; + max_alive_time = 1000 * SRS_UTIME_MILLISECONDS; + first_nack_interval = 10 * SRS_UTIME_MILLISECONDS; + nack_interval = 50 * SRS_UTIME_MILLISECONDS; + max_nack_interval = 500 * SRS_UTIME_MILLISECONDS; + min_nack_interval = 20 * SRS_UTIME_MILLISECONDS; + + nack_check_interval = 3 * SRS_UTIME_MILLISECONDS; + + //TODO: FIXME: audio and video using diff nack strategy + // video: + // max_alive_time = 1 * SRS_UTIME_SECONDS + // max_count = 15; + // nack_interval = 50 * SRS_UTIME_MILLISECONDS + // + // audio: + // DefaultRequestNackDelay = 30; //ms + // DefaultLostPacketLifeTime = 600; //ms + // FirstRequestInterval = 50;//ms +} + +SrsRtpNackInfo::SrsRtpNackInfo() +{ + generate_time_ = srs_update_system_time(); + pre_req_nack_time_ = 0; + req_nack_count_ = 0; +} + +SrsRtpNackForReceiver::SrsRtpNackForReceiver(SrsRtpRingBuffer* rtp, size_t queue_size) +{ + max_queue_size_ = queue_size; + rtp_ = rtp; + pre_check_time_ = 0; + rtt_ = 0; + + srs_info("max_queue_size=%u, nack opt: max_count=%d, max_alive_time=%us, first_nack_interval=%" PRId64 ", nack_interval=%" PRId64, + max_queue_size_, opts_.max_count, opts_.max_alive_time, opts_.first_nack_interval, opts_.nack_interval); +} + +SrsRtpNackForReceiver::~SrsRtpNackForReceiver() +{ +} + +void SrsRtpNackForReceiver::insert(uint16_t first, uint16_t last) +{ + for (uint16_t s = first; s != last; ++s) { + queue_[s] = SrsRtpNackInfo(); + } +} + +void SrsRtpNackForReceiver::remove(uint16_t seq) +{ + queue_.erase(seq); +} + +SrsRtpNackInfo* SrsRtpNackForReceiver::find(uint16_t seq) +{ + std::map::iterator iter = queue_.find(seq); + + if (iter == queue_.end()) { + return NULL; + } + + return &(iter->second); +} + +void SrsRtpNackForReceiver::check_queue_size() +{ + if (queue_.size() >= max_queue_size_) { + rtp_->notify_nack_list_full(); + queue_.clear(); + } +} + +void SrsRtpNackForReceiver::get_nack_seqs(SrsRtcpNack& seqs, uint32_t& timeout_nacks) +{ + // TODO: FIXME: Use packet as tick count, not clock. + srs_utime_t now = srs_update_system_time(); + + srs_utime_t interval = now - pre_check_time_; + if (interval < opts_.nack_check_interval) { + return; + } + pre_check_time_ = now; + + std::map::iterator iter = queue_.begin(); + while (iter != queue_.end()) { + const uint16_t& seq = iter->first; + SrsRtpNackInfo& nack_info = iter->second; + + int alive_time = now - nack_info.generate_time_; + if (alive_time > opts_.max_alive_time || nack_info.req_nack_count_ > opts_.max_count) { + ++timeout_nacks; + rtp_->notify_drop_seq(seq); + queue_.erase(iter++); + continue; + } + + // TODO:Statistics unorder packet. + if (now - nack_info.generate_time_ < opts_.first_nack_interval) { + break; + } + + srs_utime_t nack_interval = srs_max(opts_.min_nack_interval, opts_.nack_interval / 3); + if(opts_.nack_interval < 50 * SRS_UTIME_MILLISECONDS){ + nack_interval = srs_max(opts_.min_nack_interval, opts_.nack_interval); + } + + if (now - nack_info.pre_req_nack_time_ >= nack_interval ) { + ++nack_info.req_nack_count_; + nack_info.pre_req_nack_time_ = now; + seqs.add_lost_sn(seq); + } + + ++iter; + } +} + +void SrsRtpNackForReceiver::update_rtt(int rtt) +{ + rtt_ = rtt * SRS_UTIME_MILLISECONDS; + + if (rtt_ > opts_.nack_interval) { + opts_.nack_interval = opts_.nack_interval * 0.8 + rtt_ * 0.2; + } else { + opts_.nack_interval = rtt_; + } + + opts_.nack_interval = srs_min(opts_.nack_interval, opts_.max_nack_interval); +} + diff --git a/trunk/src/app/srs_app_rtc_queue.hpp b/trunk/src/app/srs_app_rtc_queue.hpp new file mode 100644 index 000000000..630f4c654 --- /dev/null +++ b/trunk/src/app/srs_app_rtc_queue.hpp @@ -0,0 +1,145 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2013-2020 John + * + * 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_RTC_QUEUE_HPP +#define SRS_APP_RTC_QUEUE_HPP + +#include + +#include +#include +#include + +#include +#include + +class SrsRtpPacket2; +class SrsRtpQueue; +class SrsRtpRingBuffer; + +// For UDP, the packets sequence may present as bellow: +// [seq1(done)|seq2|seq3 ... seq10|seq11(lost)|seq12|seq13] +// \___(head_sequence_) \ \___(highest_sequence_) +// \___(no received, in nack list) +// * seq1: The packet is done, we have already got and processed it. +// * seq2,seq3,...,seq10,seq12,seq13: Theses packets are in queue and wait to be processed. +// * seq10: This packet is lost or not received, we will put it in the nack list. +// We store the received packets in ring buffer. +class SrsRtpRingBuffer +{ +private: + // Capacity of the ring-buffer. + uint16_t capacity_; + // Ring bufer. + SrsRtpPacket2** queue_; + // Increase one when uint16 flip back, for get_extended_highest_sequence. + uint64_t nn_seq_flip_backs; + // Whether initialized, because we use uint16 so we can't use -1. + bool initialized_; +public: + // The begin iterator for ring buffer. + // For example, when got 1 elems, the begin is 0. + uint16_t begin; + // The end iterator for ring buffer. + // For example, when got 1 elems, the end is 1. + uint16_t end; +public: + SrsRtpRingBuffer(int capacity); + virtual ~SrsRtpRingBuffer(); +public: + // Whether the ring buffer is empty. + bool empty(); + // Get the count of elems in ring buffer. + int size(); + // Move the low position of buffer to seq. + void advance_to(uint16_t seq); + // Free the packet at position. + void set(uint16_t at, SrsRtpPacket2* pkt); + void remove(uint16_t at); + // The highest sequence number, calculate the flip back base. + uint32_t get_extended_highest_sequence(); + // Update the sequence, got the nack range by [first, last). + // @return If false, the seq is too old. + bool update(uint16_t seq, uint16_t& nack_first, uint16_t& nack_last); + // Get the packet by seq. + SrsRtpPacket2* at(uint16_t seq); +public: + // TODO: FIXME: Refine it? + void notify_nack_list_full(); + void notify_drop_seq(uint16_t seq); +}; + +struct SrsNackOption +{ + int max_count; + srs_utime_t max_alive_time; + srs_utime_t first_nack_interval; + srs_utime_t nack_interval; + + srs_utime_t max_nack_interval; + srs_utime_t min_nack_interval; + srs_utime_t nack_check_interval; + + SrsNackOption(); +}; + +struct SrsRtpNackInfo +{ + // Use to control the time of first nack req and the life of seq. + srs_utime_t generate_time_; + // Use to control nack interval. + srs_utime_t pre_req_nack_time_; + // Use to control nack times. + int req_nack_count_; + + SrsRtpNackInfo(); +}; + +class SrsRtpNackForReceiver +{ +private: + // Nack queue, seq order, oldest to newest. + std::map queue_; + // Max nack count. + size_t max_queue_size_; + SrsRtpRingBuffer* rtp_; + SrsNackOption opts_; +private: + srs_utime_t pre_check_time_; +private: + int rtt_; +public: + SrsRtpNackForReceiver(SrsRtpRingBuffer* rtp, size_t queue_size); + virtual ~SrsRtpNackForReceiver(); +public: + void insert(uint16_t first, uint16_t last); + void remove(uint16_t seq); + SrsRtpNackInfo* find(uint16_t seq); + void check_queue_size(); +public: + void get_nack_seqs(SrsRtcpNack& seqs, uint32_t& timeout_nacks); +public: + void update_rtt(int rtt); +}; + +#endif diff --git a/trunk/src/app/srs_app_sdp.cpp b/trunk/src/app/srs_app_rtc_sdp.cpp similarity index 77% rename from trunk/src/app/srs_app_sdp.cpp rename to trunk/src/app/srs_app_rtc_sdp.cpp index c67dbd9a4..facb8fbd1 100644 --- a/trunk/src/app/srs_app_sdp.cpp +++ b/trunk/src/app/srs_app_rtc_sdp.cpp @@ -21,31 +21,33 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include -using namespace std; +#include #include #include #include #include +#include +using namespace std; #include #include -const std::string kCRLF = "\\r\\n"; +// TODO: FIXME: Maybe we should use json.encode to escape it? +const std::string kCRLF = "\r\n"; #define FETCH(is,word) \ -if (! (is >> word)) {\ +if (!(is >> word)) {\ return srs_error_new(ERROR_RTC_SDP_DECODE, "fetch failed");\ }\ #define FETCH_WITH_DELIM(is,word,delim) \ -if (! getline(is,word,delim)) {\ +if (!getline(is,word,delim)) {\ return srs_error_new(ERROR_RTC_SDP_DECODE, "fetch with delim failed");\ }\ -static std::vector split_str(const std::string& str, const std::string& delim) +std::vector split_str(const std::string& str, const std::string& delim) { std::vector ret; size_t pre_pos = 0; @@ -68,7 +70,7 @@ static void skip_first_spaces(std::string& str) } } -srs_error_t parse_h264_fmtp(const std::string& fmtp, H264SpecificParam& h264_param) +srs_error_t srs_parse_h264_fmtp(const std::string& fmtp, H264SpecificParam& h264_param) { srs_error_t err = srs_success; std::vector vec = split_str(fmtp, ";"); @@ -111,6 +113,7 @@ SrsSessionInfo::~SrsSessionInfo() srs_error_t SrsSessionInfo::parse_attribute(const std::string& attribute, const std::string& value) { srs_error_t err = srs_success; + if (attribute == "ice-ufrag") { ice_ufrag_ = value; } else if (attribute == "ice-pwd") { @@ -134,23 +137,25 @@ srs_error_t SrsSessionInfo::parse_attribute(const std::string& attribute, const srs_error_t SrsSessionInfo::encode(std::ostringstream& os) { srs_error_t err = srs_success; - if (! ice_ufrag_.empty()) { + + if (!ice_ufrag_.empty()) { os << "a=ice-ufrag:" << ice_ufrag_ << kCRLF; } - if (! ice_pwd_.empty()) { + + if (!ice_pwd_.empty()) { os << "a=ice-pwd:" << ice_pwd_ << kCRLF; } - if (! ice_options_.empty()) { + + // For ICE-lite, we never set the trickle. + if (!ice_options_.empty()) { os << "a=ice-options:" << ice_options_ << kCRLF; - } else { - // @see: https://webrtcglossary.com/trickle-ice/ - // Trickle ICE is an optimization of the ICE specification for NAT traversal. - os << "a=ice-options:trickle" << kCRLF; } - if (! fingerprint_algo_.empty() && ! fingerprint_.empty()) { + + if (!fingerprint_algo_.empty() && ! fingerprint_.empty()) { os << "a=fingerprint:" << fingerprint_algo_ << " " << fingerprint_ << kCRLF; } - if (! setup_.empty()) { + + if (!setup_.empty()) { os << "a=setup:" << setup_ << kCRLF; } @@ -172,6 +177,16 @@ SrsSSRCInfo::SrsSSRCInfo() ssrc_ = 0; } +SrsSSRCInfo::SrsSSRCInfo(uint32_t ssrc, std::string cname, std::string stream_id, std::string track_id) +{ + ssrc_ = ssrc; + cname_ = cname; + msid_ = stream_id; + msid_tracker_ = track_id; + mslabel_ = msid_; + label_ = msid_tracker_; +} + SrsSSRCInfo::~SrsSSRCInfo() { } @@ -179,31 +194,67 @@ SrsSSRCInfo::~SrsSSRCInfo() srs_error_t SrsSSRCInfo::encode(std::ostringstream& os) { srs_error_t err = srs_success; + if (ssrc_ == 0) { return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid ssrc"); } os << "a=ssrc:" << ssrc_ << " cname:" << cname_ << kCRLF; - if (! msid_.empty()) { + if (!msid_.empty()) { os << "a=ssrc:" << ssrc_ << " msid:" << msid_; - if (! msid_tracker_.empty()) { + if (!msid_tracker_.empty()) { os << " " << msid_tracker_; } os << kCRLF; } - if (! mslabel_.empty()) { + if (!mslabel_.empty()) { os << "a=ssrc:" << ssrc_ << " mslabel:" << mslabel_ << kCRLF; } - if (! label_.empty()) { + if (!label_.empty()) { os << "a=ssrc:" << ssrc_ << " label:" << label_ << kCRLF; } return err; } +SrsSSRCGroup::SrsSSRCGroup() +{ +} + +SrsSSRCGroup::~SrsSSRCGroup() +{ +} + +SrsSSRCGroup::SrsSSRCGroup(const std::string& semantic, const std::vector& ssrcs) +{ + semantic_ = semantic; + ssrcs_ = ssrcs; +} + +srs_error_t SrsSSRCGroup::encode(std::ostringstream& os) +{ + srs_error_t err = srs_success; + + if (semantic_.empty()) { + return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid semantics"); + } + + if (ssrcs_.size() == 0) { + return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid ssrcs"); + } + + os << "a=ssrc-group:" << semantic_; + for (int i = 0; i < (int)ssrcs_.size(); i++) { + os << " " << ssrcs_[i]; + } + + return err; +} + SrsMediaPayloadType::SrsMediaPayloadType(int payload_type) { payload_type_ = payload_type; + clock_rate_ = 0; } SrsMediaPayloadType::~SrsMediaPayloadType() @@ -215,7 +266,7 @@ srs_error_t SrsMediaPayloadType::encode(std::ostringstream& os) srs_error_t err = srs_success; os << "a=rtpmap:" << payload_type_ << " " << encoding_name_ << "/" << clock_rate_; - if (! encoding_param_.empty()) { + if (!encoding_param_.empty()) { os << "/" << encoding_param_; } os << kCRLF; @@ -224,8 +275,11 @@ srs_error_t SrsMediaPayloadType::encode(std::ostringstream& os) os << "a=rtcp-fb:" << payload_type_ << " " << *iter << kCRLF; } - if (! format_specific_param_.empty()) { - os << "a=fmtp:" << payload_type_ << " " << format_specific_param_ << kCRLF; + if (!format_specific_param_.empty()) { + os << "a=fmtp:" << payload_type_ << " " << format_specific_param_ + // TODO: FIXME: Remove the test code bellow. + // << ";x-google-max-bitrate=6000;x-google-min-bitrate=5100;x-google-start-bitrate=5000" + << kCRLF; } return err; @@ -235,7 +289,9 @@ SrsMediaDesc::SrsMediaDesc(const std::string& type) { type_ = type; + port_ = 0; rtcp_mux_ = false; + rtcp_rsize_ = false; sendrecv_ = false; recvonly_ = false; @@ -262,8 +318,13 @@ vector SrsMediaDesc::find_media_with_encoding_name(const st { std::vector payloads; + std::string lower_name(encoding_name), upper_name(encoding_name); + transform(encoding_name.begin(), encoding_name.end(), lower_name.begin(), ::tolower); + transform(encoding_name.begin(), encoding_name.end(), upper_name.begin(), ::toupper); + for (size_t i = 0; i < payload_types_.size(); ++i) { - if (payload_types_[i].encoding_name_ == encoding_name) { + if (payload_types_[i].encoding_name_ == std::string(lower_name.c_str()) || + payload_types_[i].encoding_name_ == std::string(upper_name.c_str())) { payloads.push_back(payload_types_[i]); } } @@ -271,6 +332,20 @@ vector SrsMediaDesc::find_media_with_encoding_name(const st return payloads; } +srs_error_t SrsMediaDesc::update_msid(string id) +{ + srs_error_t err = srs_success; + + for(vector::iterator it = ssrc_infos_.begin(); it != ssrc_infos_.end(); ++it) { + SrsSSRCInfo& info = *it; + + info.msid_ = id; + info.mslabel_ = id; + } + + return err; +} + srs_error_t SrsMediaDesc::parse_line(const std::string& line) { srs_error_t err = srs_success; @@ -312,16 +387,19 @@ srs_error_t SrsMediaDesc::encode(std::ostringstream& os) } os << "a=mid:" << mid_ << kCRLF; - if (! msid_.empty()) { + if (!msid_.empty()) { os << "a=msid:" << msid_; - if (! msid_tracker_.empty()) { + if (!msid_tracker_.empty()) { os << " " << msid_tracker_; } os << kCRLF; } + for(map::iterator it = extmaps_.begin(); it != extmaps_.end(); ++it) { + os << "a=extmap:"<< it->first<< " "<< it->second<< kCRLF; + } if (sendonly_) { os << "a=sendonly" << kCRLF; } @@ -359,7 +437,7 @@ srs_error_t SrsMediaDesc::encode(std::ostringstream& os) int foundation = 0; int component_id = 1; /* RTP */ - for (std::vector::iterator iter = candidates_.begin(); iter != candidates_.end(); ++iter) { + for (std::vector::iterator iter = candidates_.begin(); iter != candidates_.end(); ++iter) { // @see: https://tools.ietf.org/html/draft-ietf-ice-rfc5245bis-00#section-4.2 uint32_t priority = (1<<24)*(126) + (1<<8)*(65535) + (1)*(256 - component_id); @@ -369,6 +447,8 @@ srs_error_t SrsMediaDesc::encode(std::ostringstream& os) << iter->ip_ << " " << iter->port_ << " typ " << iter->type_ << " generation 0" << kCRLF; + + srs_verbose("local SDP candidate line=%s", os.str().c_str()); } return err; @@ -389,8 +469,7 @@ srs_error_t SrsMediaDesc::parse_attribute(const std::string& content) } if (attribute == "extmap") { - // TODO:We don't parse "extmap" currently. - return 0; + return parse_attr_extmap(value); } else if (attribute == "rtpmap") { return parse_attr_rtpmap(value); } else if (attribute == "rtcp") { @@ -419,12 +498,26 @@ srs_error_t SrsMediaDesc::parse_attribute(const std::string& content) sendrecv_ = true; } else if (attribute == "inactive") { inactive_ = true; - } else { + } else { return session_info_.parse_attribute(attribute, value); } return err; } +srs_error_t SrsMediaDesc::parse_attr_extmap(const std::string& value) +{ + srs_error_t err = srs_success; + std::istringstream is(value); + int id = 0; + FETCH(is, id); + if(extmaps_.end() != extmaps_.find(id)) { + return srs_error_new(ERROR_RTC_SDP_DECODE, "duplicate ext id: %d", id); + } + string ext; + FETCH(is, ext); + extmaps_[id] = ext; + return err; +} srs_error_t SrsMediaDesc::parse_attr_rtpmap(const std::string& value) { @@ -523,7 +616,7 @@ srs_error_t SrsMediaDesc::parse_attr_mid(const std::string& value) std::istringstream is(value); // mid_ means m-line id FETCH(is, mid_); - srs_trace("mid=%s", mid_.c_str()); + srs_verbose("mid=%s", mid_.c_str()); return err; } @@ -563,6 +656,7 @@ srs_error_t SrsMediaDesc::parse_attr_ssrc(const std::string& value) ssrc_info.cname_ = ssrc_value; ssrc_info.ssrc_ = ssrc; } else if (ssrc_attr == "msid") { + // @see: https://tools.ietf.org/html/draft-alvestrand-mmusic-msid-00#section-2 std::vector vec = split_str(ssrc_value, " "); if (vec.empty()) { return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid ssrc line=%s", value.c_str()); @@ -592,10 +686,23 @@ srs_error_t SrsMediaDesc::parse_attr_ssrc_group(const std::string& value) std::string semantics; FETCH(is, semantics); - // TODO: ssrc group process - if (semantics == "FID") { + std::string ssrc_ids = is.str().substr(is.tellg()); + skip_first_spaces(ssrc_ids); + + std::vector vec = split_str(ssrc_ids, " "); + if (vec.size() == 0) { + return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid ssrc-group line=%s", value.c_str()); } + std::vector ssrcs; + for (size_t i = 0; i < vec.size(); ++i) { + std::istringstream in_stream(vec[i]); + uint32_t ssrc = 0; + in_stream >> ssrc; + ssrcs.push_back(ssrc); + } + ssrc_groups_.push_back(SrsSSRCGroup(semantics, ssrcs)); + return err; } @@ -649,11 +756,11 @@ srs_error_t SrsSdp::parse(const std::string& sdp_str) std::istringstream is(sdp_str); std::string line; while (getline(is, line)) { - srs_trace("%s", line.c_str()); + srs_verbose("%s", line.c_str()); if (line.size() < 2 || line[1] != '=') { return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid sdp line=%s", line.c_str()); } - if (! line.empty() && line[line.size()-1] == '\r') { + if (!line.empty() && line[line.size()-1] == '\r') { line.erase(line.size()-1, 1); } @@ -662,6 +769,31 @@ srs_error_t SrsSdp::parse(const std::string& sdp_str) } } + // The msid/tracker/mslabel is optional for SSRC, so we copy it when it's empty. + for (std::vector::iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) { + SrsMediaDesc& media_desc = *iter; + + for (size_t i = 0; i < media_desc.ssrc_infos_.size(); ++i) { + SrsSSRCInfo& ssrc_info = media_desc.ssrc_infos_.at(i); + + if (ssrc_info.msid_.empty()) { + ssrc_info.msid_ = media_desc.msid_; + } + + if (ssrc_info.msid_tracker_.empty()) { + ssrc_info.msid_tracker_ = media_desc.msid_tracker_; + } + + if (ssrc_info.mslabel_.empty()) { + ssrc_info.mslabel_ = media_desc.msid_; + } + + if (ssrc_info.label_.empty()) { + ssrc_info.label_ = media_desc.msid_tracker_; + } + } + } + return err; } @@ -676,7 +808,7 @@ srs_error_t SrsSdp::encode(std::ostringstream& os) // ice-lite is a minimal version of the ICE specification, intended for servers running on a public IP address. os << "a=ice-lite" << kCRLF; - if (! groups_.empty()) { + if (!groups_.empty()) { os << "a=group:" << group_policy_; for (std::vector::iterator iter = groups_.begin(); iter != groups_.end(); ++iter) { os << " " << *iter; @@ -703,42 +835,57 @@ srs_error_t SrsSdp::encode(std::ostringstream& os) return err; } -const SrsMediaDesc* SrsSdp::find_media_desc(const std::string& type) const +std::vector SrsSdp::find_media_descs(const std::string& type) { - for (std::vector::const_iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) { - if (iter->type_ == type) { - return &(*iter); + std::vector descs; + for (std::vector::iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) { + SrsMediaDesc* desc = &(*iter); + + if (desc->type_ == type) { + descs.push_back(desc); } } - return NULL; + return descs; } void SrsSdp::set_ice_ufrag(const std::string& ufrag) { for (std::vector::iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) { - iter->session_info_.ice_ufrag_ = ufrag; + SrsMediaDesc* desc = &(*iter); + desc->session_info_.ice_ufrag_ = ufrag; } } void SrsSdp::set_ice_pwd(const std::string& pwd) { for (std::vector::iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) { - iter->session_info_.ice_pwd_ = pwd; + SrsMediaDesc* desc = &(*iter); + desc->session_info_.ice_pwd_ = pwd; + } +} + +void SrsSdp::set_dtls_role(const std::string& dtls_role) +{ + for (std::vector::iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) { + SrsMediaDesc* desc = &(*iter); + desc->session_info_.setup_ = dtls_role; } } void SrsSdp::set_fingerprint_algo(const std::string& algo) { for (std::vector::iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) { - iter->session_info_.fingerprint_algo_ = algo; + SrsMediaDesc* desc = &(*iter); + desc->session_info_.fingerprint_algo_ = algo; } } void SrsSdp::set_fingerprint(const std::string& fingerprint) { for (std::vector::iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) { - iter->session_info_.fingerprint_ = fingerprint; + SrsMediaDesc* desc = &(*iter); + desc->session_info_.fingerprint_ = fingerprint; } } @@ -751,7 +898,8 @@ void SrsSdp::add_candidate(const std::string& ip, const int& port, const std::st candidate.type_ = type; for (std::vector::iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) { - iter->candidates_.push_back(candidate); + SrsMediaDesc* desc = &(*iter); + desc->candidates_.push_back(candidate); } } @@ -759,7 +907,8 @@ std::string SrsSdp::get_ice_ufrag() const { // Becaues we use BUNDLE, so we can choose the first element. for (std::vector::const_iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) { - return iter->session_info_.ice_ufrag_; + const SrsMediaDesc* desc = &(*iter); + return desc->session_info_.ice_ufrag_; } return ""; @@ -769,7 +918,19 @@ std::string SrsSdp::get_ice_pwd() const { // Becaues we use BUNDLE, so we can choose the first element. for (std::vector::const_iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) { - return iter->session_info_.ice_pwd_; + const SrsMediaDesc* desc = &(*iter); + return desc->session_info_.ice_pwd_; + } + + return ""; +} + +std::string SrsSdp::get_dtls_role() const +{ + // Becaues we use BUNDLE, so we can choose the first element. + for (std::vector::const_iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) { + const SrsMediaDesc* desc = &(*iter); + return desc->session_info_.setup_; } return ""; @@ -949,9 +1110,34 @@ srs_error_t SrsSdp::parse_media_description(const std::string& content) media_descs_.back().payload_types_.push_back(SrsMediaPayloadType(fmt)); } - if (! in_media_session_) { + if (!in_media_session_) { in_media_session_ = true; } return err; } + +bool SrsSdp::is_unified() const +{ + // TODO: FIXME: Maybe we should consider other situations. + return media_descs_.size() > 2; +} + +srs_error_t SrsSdp::update_msid(string id) +{ + srs_error_t err = srs_success; + + msids_.clear(); + msids_.push_back(id); + + for (vector::iterator it = media_descs_.begin(); it != media_descs_.end(); ++it) { + SrsMediaDesc& desc = *it; + + if ((err = desc.update_msid(id)) != srs_success) { + return srs_error_wrap(err, "desc %s update msid %s", desc.mid_.c_str(), id.c_str()); + } + } + + return err; +} + diff --git a/trunk/src/app/srs_app_sdp.hpp b/trunk/src/app/srs_app_rtc_sdp.hpp similarity index 80% rename from trunk/src/app/srs_app_sdp.hpp rename to trunk/src/app/srs_app_rtc_sdp.hpp index c0fb95321..372c893a1 100644 --- a/trunk/src/app/srs_app_sdp.hpp +++ b/trunk/src/app/srs_app_rtc_sdp.hpp @@ -21,8 +21,8 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef SRS_APP_SDP_HPP -#define SRS_APP_SDP_HPP +#ifndef SRS_APP_RTC_SDP_HPP +#define SRS_APP_RTC_SDP_HPP #include #include @@ -31,6 +31,18 @@ #include #include +#include +const std::string kTWCCExt = "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01"; + +// TDOO: FIXME: Rename it, and add utest. +extern std::vector split_str(const std::string& str, const std::string& delim); + +struct SrsSessionConfig +{ +public: + std::string dtls_role; + std::string dtls_version; +}; class SrsSessionInfo { @@ -55,6 +67,7 @@ class SrsSSRCInfo { public: SrsSSRCInfo(); + SrsSSRCInfo(uint32_t ssrc, std::string cname, std::string stream_id, std::string track_id); virtual ~SrsSSRCInfo(); public: srs_error_t encode(std::ostringstream& os); @@ -69,6 +82,17 @@ public: class SrsSSRCGroup { +public: + SrsSSRCGroup(); + SrsSSRCGroup(const std::string& usage, const std::vector& ssrcs); + virtual ~SrsSSRCGroup(); +public: + srs_error_t encode(std::ostringstream& os); +public: + // e.g FIX, FEC, SIM. + std::string semantic_; + // SSRCs of this type. + std::vector ssrcs_; }; struct H264SpecificParam @@ -78,7 +102,7 @@ struct H264SpecificParam std::string level_asymmerty_allow; }; -extern srs_error_t parse_h264_fmtp(const std::string& fmtp, H264SpecificParam& h264_param); +extern srs_error_t srs_parse_h264_fmtp(const std::string& fmtp, H264SpecificParam& h264_param); class SrsMediaPayloadType { @@ -115,6 +139,8 @@ public: srs_error_t encode(std::ostringstream& os); SrsMediaPayloadType* find_media_with_payload_type(int payload_type); std::vector find_media_with_encoding_name(const std::string& encoding_name) const; + const std::map& get_extmaps() const { return extmaps_; } + srs_error_t update_msid(std::string id); bool is_audio() const { return type_ == "audio"; } bool is_video() const { return type_ == "video"; } @@ -128,6 +154,7 @@ private: srs_error_t parse_attr_msid(const std::string& value); srs_error_t parse_attr_ssrc(const std::string& value); srs_error_t parse_attr_ssrc_group(const std::string& value); + srs_error_t parse_attr_extmap(const std::string& value); private: SrsSSRCInfo& fetch_or_create_ssrc_info(uint32_t ssrc); @@ -153,6 +180,7 @@ public: std::vector candidates_; std::vector ssrc_groups_; std::vector ssrc_infos_; + std::map extmaps_; }; class SrsSdp @@ -164,17 +192,18 @@ public: srs_error_t parse(const std::string& sdp_str); srs_error_t encode(std::ostringstream& os); public: -public: - const SrsMediaDesc* find_media_desc(const std::string& type) const; + std::vector find_media_descs(const std::string& type); public: void set_ice_ufrag(const std::string& ufrag); void set_ice_pwd(const std::string& pwd); + void set_dtls_role(const std::string& dtls_role); void set_fingerprint_algo(const std::string& algo); void set_fingerprint(const std::string& fingerprint); void add_candidate(const std::string& ip, const int& port, const std::string& type); std::string get_ice_ufrag() const; std::string get_ice_pwd() const; + std::string get_dtls_role() const; private: srs_error_t parse_line(const std::string& line); private: @@ -207,6 +236,7 @@ public: int64_t end_time_; SrsSessionInfo session_info_; + SrsSessionConfig session_config_; std::vector groups_; std::string group_policy_; @@ -216,6 +246,10 @@ public: // m-line, media sessions std::vector media_descs_; + + bool is_unified() const; + // TODO: FIXME: will be fixed when use single pc. + srs_error_t update_msid(std::string id); }; #endif diff --git a/trunk/src/app/srs_app_rtc_server.cpp b/trunk/src/app/srs_app_rtc_server.cpp new file mode 100644 index 000000000..0d9b747d0 --- /dev/null +++ b/trunk/src/app/srs_app_rtc_server.cpp @@ -0,0 +1,678 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2013-2020 John + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include + +using namespace std; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +SrsRtcBlackhole::SrsRtcBlackhole() +{ + blackhole = false; + blackhole_addr = NULL; + blackhole_stfd = NULL; +} + +SrsRtcBlackhole::~SrsRtcBlackhole() +{ + srs_close_stfd(blackhole_stfd); + srs_freep(blackhole_addr); +} + +srs_error_t SrsRtcBlackhole::initialize() +{ + srs_error_t err = srs_success; + + blackhole = _srs_config->get_rtc_server_black_hole(); + if (!blackhole) { + return err; + } + + string blackhole_ep = _srs_config->get_rtc_server_black_hole_addr(); + if (blackhole_ep.empty()) { + blackhole = false; + srs_warn("disable black hole for no endpoint"); + return err; + } + + string host; int port; + srs_parse_hostport(blackhole_ep, host, port); + + srs_freep(blackhole_addr); + blackhole_addr = new sockaddr_in(); + blackhole_addr->sin_family = AF_INET; + blackhole_addr->sin_addr.s_addr = inet_addr(host.c_str()); + blackhole_addr->sin_port = htons(port); + + int fd = socket(AF_INET, SOCK_DGRAM, 0); + blackhole_stfd = srs_netfd_open_socket(fd); + srs_assert(blackhole_stfd); + + srs_trace("RTC blackhole %s:%d, fd=%d", host.c_str(), port, fd); + + return err; +} + +void SrsRtcBlackhole::sendto(void* data, int len) +{ + if (!blackhole) { + return; + } + + // For blackhole, we ignore any error. + srs_sendto(blackhole_stfd, data, len, (sockaddr*)blackhole_addr, sizeof(sockaddr_in), SRS_UTIME_NO_TIMEOUT); +} + +SrsRtcBlackhole* _srs_blackhole = new SrsRtcBlackhole(); + +// @global dtls certficate for rtc module. +SrsDtlsCertificate* _srs_rtc_dtls_certificate = new SrsDtlsCertificate(); + +// TODO: Should support error response. +// For STUN packet, 0x00 is binding request, 0x01 is binding success response. +bool srs_is_stun(const uint8_t* data, size_t size) +{ + return size > 0 && (data[0] == 0 || data[0] == 1); +} + +// change_cipher_spec(20), alert(21), handshake(22), application_data(23) +// @see https://tools.ietf.org/html/rfc2246#section-6.2.1 +bool srs_is_dtls(const uint8_t* data, size_t len) +{ + return (len >= 13 && (data[0] > 19 && data[0] < 64)); +} + +// For RTP or RTCP, the V=2 which is in the high 2bits, 0xC0 (1100 0000) +bool srs_is_rtp_or_rtcp(const uint8_t* data, size_t len) +{ + return (len >= 12 && (data[0] & 0xC0) == 0x80); +} + +// For RTCP, PT is [128, 223] (or without marker [0, 95]). +// Literally, RTCP starts from 64 not 0, so PT is [192, 223] (or without marker [64, 95]). +// @note For RTP, the PT is [96, 127], or [224, 255] with marker. +bool srs_is_rtcp(const uint8_t* data, size_t len) +{ + return (len >= 12) && (data[0] & 0x80) && (data[1] >= 192 && data[1] <= 223); +} + +static std::vector get_candidate_ips() +{ + std::vector candidate_ips; + + string candidate = _srs_config->get_rtc_server_candidates(); + if (candidate != "*" && candidate != "0.0.0.0") { + candidate_ips.push_back(candidate); + return candidate_ips; + } + + // For * or 0.0.0.0, auto discovery expose ip addresses. + std::vector& ips = srs_get_local_ips(); + if (ips.empty()) { + return candidate_ips; + } + + // We try to find the best match candidates, no loopback. + string family = _srs_config->get_rtc_server_ip_family(); + for (int i = 0; i < (int)ips.size(); ++i) { + SrsIPAddress* ip = ips[i]; + if (ip->is_loopback) { + continue; + } + + if (family == "ipv4" && !ip->is_ipv4) { + continue; + } + if (family == "ipv6" && ip->is_ipv4) { + continue; + } + + candidate_ips.push_back(ip->ip); + srs_trace("Best matched ip=%s, ifname=%s", ip->ip.c_str(), ip->ifname.c_str()); + } + + if (!candidate_ips.empty()) { + return candidate_ips; + } + + // Then, we use the ipv4 address. + for (int i = 0; i < (int)ips.size(); ++i) { + SrsIPAddress* ip = ips[i]; + if (!ip->is_ipv4) { + continue; + } + + candidate_ips.push_back(ip->ip); + srs_trace("No best matched, use first ip=%s, ifname=%s", ip->ip.c_str(), ip->ifname.c_str()); + return candidate_ips; + } + + // We use the first one. + if (candidate_ips.empty()) { + SrsIPAddress* ip = ips[0]; + candidate_ips.push_back(ip->ip); + srs_warn("No best matched, use first ip=%s, ifname=%s", ip->ip.c_str(), ip->ifname.c_str()); + return candidate_ips; + } + + return candidate_ips; +} + +ISrsRtcServerHandler::ISrsRtcServerHandler() +{ +} + +ISrsRtcServerHandler::~ISrsRtcServerHandler() +{ +} + +ISrsRtcServerHijacker::ISrsRtcServerHijacker() +{ +} + +ISrsRtcServerHijacker::~ISrsRtcServerHijacker() +{ +} + +SrsRtcServer::SrsRtcServer() +{ + handler = NULL; + hijacker = NULL; + timer = new SrsHourGlass(this, 1 * SRS_UTIME_SECONDS); +} + +SrsRtcServer::~SrsRtcServer() +{ + srs_freep(timer); + + if (true) { + vector::iterator it; + for (it = listeners.begin(); it != listeners.end(); ++it) { + SrsUdpMuxListener* listener = *it; + srs_freep(listener); + } + } +} + +srs_error_t SrsRtcServer::initialize() +{ + srs_error_t err = srs_success; + + if ((err = timer->tick(5 * SRS_UTIME_SECONDS)) != srs_success) { + return srs_error_wrap(err, "hourglass tick"); + } + + if ((err = timer->start()) != srs_success) { + return srs_error_wrap(err, "start timer"); + } + + if ((err = _srs_blackhole->initialize()) != srs_success) { + return srs_error_wrap(err, "black hole"); + } + + srs_trace("RTC server init ok"); + + return err; +} + +void SrsRtcServer::set_handler(ISrsRtcServerHandler* h) +{ + handler = h; +} + +void SrsRtcServer::set_hijacker(ISrsRtcServerHijacker* h) +{ + hijacker = h; +} + +srs_error_t SrsRtcServer::listen_udp() +{ + srs_error_t err = srs_success; + + if (!_srs_config->get_rtc_server_enabled()) { + return err; + } + + int port = _srs_config->get_rtc_server_listen(); + if (port <= 0) { + return srs_error_new(ERROR_RTC_PORT, "invalid port=%d", port); + } + + string ip = srs_any_address_for_listener(); + srs_assert(listeners.empty()); + + int nn_listeners = _srs_config->get_rtc_server_reuseport(); + for (int i = 0; i < nn_listeners; i++) { + SrsUdpMuxListener* listener = new SrsUdpMuxListener(this, ip, port); + + if ((err = listener->listen()) != srs_success) { + srs_freep(listener); + return srs_error_wrap(err, "listen %s:%d", ip.c_str(), port); + } + + srs_trace("rtc listen at udp://%s:%d, fd=%d", ip.c_str(), port, listener->fd()); + listeners.push_back(listener); + } + + return err; +} + +srs_error_t SrsRtcServer::on_udp_packet(SrsUdpMuxSocket* skt) +{ + srs_error_t err = srs_success; + + string peer_id = skt->peer_id(); + char* data = skt->data(); int size = skt->size(); + + SrsRtcConnection* session = NULL; + if (true) { + ISrsResource* conn = _srs_rtc_manager->find_by_id(peer_id); + if (conn) { + // Switch to the session to write logs to the context. + session = dynamic_cast(conn); + session->switch_to_context(); + } + } + + // When got any packet, the session is alive now. + if (session) { + session->alive(); + } + + // Notify hijack to handle the UDP packet. + if (hijacker) { + bool consumed = false; + if ((err = hijacker->on_udp_packet(skt, session, &consumed)) != srs_success) { + return srs_error_wrap(err, "hijack consumed=%u", consumed); + } + + if (consumed) { + return err; + } + } + + // For STUN, the peer address may change. + if (srs_is_stun((uint8_t*)data, size)) { + SrsStunPacket ping; + if ((err = ping.decode(data, size)) != srs_success) { + return srs_error_wrap(err, "decode stun packet failed"); + } + srs_info("recv stun packet from %s, use-candidate=%d, ice-controlled=%d, ice-controlling=%d", + peer_id.c_str(), ping.get_use_candidate(), ping.get_ice_controlled(), ping.get_ice_controlling()); + + if (!session) { + session = find_session_by_username(ping.get_username()); + + // Switch to the session to write logs to the context. + if (session) { + session->switch_to_context(); + } + } + + // TODO: FIXME: For ICE trickle, we may get STUN packets before SDP answer, so maybe should response it. + if (!session) { + return srs_error_new(ERROR_RTC_STUN, "no session, stun username=%s, peer_id=%s", + ping.get_username().c_str(), peer_id.c_str()); + } + + return session->on_stun(skt, &ping); + } + + // For DTLS, RTCP or RTP, which does not support peer address changing. + if (!session) { + return srs_error_new(ERROR_RTC_STUN, "no session, peer_id=%s", peer_id.c_str()); + } + + if (srs_is_dtls((uint8_t*)data, size)) { + return session->on_dtls(data, size); + } else if (srs_is_rtp_or_rtcp((uint8_t*)data, size)) { + if (srs_is_rtcp((uint8_t*)data, size)) { + return session->on_rtcp(data, size); + } + return session->on_rtp(data, size); + } + + return srs_error_new(ERROR_RTC_UDP, "unknown packet"); +} + +srs_error_t SrsRtcServer::listen_api() +{ + srs_error_t err = srs_success; + + // TODO: FIXME: Fetch api from hybrid manager, not from SRS. + SrsHttpServeMux* http_api_mux = _srs_hybrid->srs()->instance()->api_server(); + + if ((err = http_api_mux->handle("/rtc/v1/play/", new SrsGoApiRtcPlay(this))) != srs_success) { + return srs_error_wrap(err, "handle play"); + } + + if ((err = http_api_mux->handle("/rtc/v1/publish/", new SrsGoApiRtcPublish(this))) != srs_success) { + return srs_error_wrap(err, "handle publish"); + } + +#ifdef SRS_SIMULATOR + if ((err = http_api_mux->handle("/rtc/v1/nack/", new SrsGoApiRtcNACK(this))) != srs_success) { + return srs_error_wrap(err, "handle nack"); + } +#endif + + return err; +} + +srs_error_t SrsRtcServer::create_session( + SrsRequest* req, const SrsSdp& remote_sdp, SrsSdp& local_sdp, const std::string& mock_eip, + bool publish, bool dtls, bool srtp, + SrsRtcConnection** psession +) { + srs_error_t err = srs_success; + + SrsContextId cid = _srs_context->get_id(); + + SrsRtcStream* source = NULL; + if ((err = _srs_rtc_sources->fetch_or_create(req, &source)) != srs_success) { + return srs_error_wrap(err, "create source"); + } + + if (publish && !source->can_publish()) { + return srs_error_new(ERROR_RTC_SOURCE_BUSY, "stream %s busy", req->get_stream_url().c_str()); + } + + // TODO: FIXME: add do_create_session to error process. + SrsRtcConnection* session = new SrsRtcConnection(this, cid); + if ((err = do_create_session(session, req, remote_sdp, local_sdp, mock_eip, publish, dtls, srtp)) != srs_success) { + srs_freep(session); + return srs_error_wrap(err, "create session"); + } + + *psession = session; + + return err; +} + +srs_error_t SrsRtcServer::do_create_session( + SrsRtcConnection* session, SrsRequest* req, const SrsSdp& remote_sdp, SrsSdp& local_sdp, const std::string& mock_eip, + bool publish, bool dtls, bool srtp +) +{ + srs_error_t err = srs_success; + + // first add publisher/player for negotiate sdp media info + if (publish) { + if ((err = session->add_publisher(req, remote_sdp, local_sdp)) != srs_success) { + return srs_error_wrap(err, "add publisher"); + } + } else { + if ((err = session->add_player(req, remote_sdp, local_sdp)) != srs_success) { + return srs_error_wrap(err, "add player"); + } + } + + // All tracks default as inactive, so we must enable them. + session->set_all_tracks_status(req->get_stream_url(), publish, true); + + std::string local_pwd = srs_random_str(32); + std::string local_ufrag = ""; + // TODO: FIXME: Rename for a better name, it's not an username. + std::string username = ""; + while (true) { + local_ufrag = srs_random_str(8); + + username = local_ufrag + ":" + remote_sdp.get_ice_ufrag(); + if (!_srs_rtc_manager->find_by_name(username)) { + break; + } + } + + local_sdp.set_ice_ufrag(local_ufrag); + local_sdp.set_ice_pwd(local_pwd); + local_sdp.set_fingerprint_algo("sha-256"); + local_sdp.set_fingerprint(_srs_rtc_dtls_certificate->get_fingerprint()); + + // We allows to mock the eip of server. + if (!mock_eip.empty()) { + local_sdp.add_candidate(mock_eip, _srs_config->get_rtc_server_listen(), "host"); + srs_trace("RTC: Use candidate mock_eip %s", mock_eip.c_str()); + } else { + std::vector candidate_ips = get_candidate_ips(); + for (int i = 0; i < (int)candidate_ips.size(); ++i) { + local_sdp.add_candidate(candidate_ips[i], _srs_config->get_rtc_server_listen(), "host"); + } + srs_trace("RTC: Use candidates %s", srs_join_vector_string(candidate_ips, ", ").c_str()); + } + + if (remote_sdp.get_dtls_role() == "active") { + local_sdp.set_dtls_role("passive"); + } else if (remote_sdp.get_dtls_role() == "passive") { + local_sdp.set_dtls_role("active"); + } else if (remote_sdp.get_dtls_role() == "actpass") { + local_sdp.set_dtls_role(local_sdp.session_config_.dtls_role); + } else { + // @see: https://tools.ietf.org/html/rfc4145#section-4.1 + // The default value of the setup attribute in an offer/answer exchange + // is 'active' in the offer and 'passive' in the answer. + local_sdp.set_dtls_role("passive"); + } + + session->set_remote_sdp(remote_sdp); + // We must setup the local SDP, then initialize the session object. + session->set_local_sdp(local_sdp); + session->set_state(WAITING_STUN); + + // Before session initialize, we must setup the local SDP. + if ((err = session->initialize(req, dtls, srtp, username)) != srs_success) { + return srs_error_wrap(err, "init"); + } + + // We allows username is optional, but it never empty here. + _srs_rtc_manager->add_with_name(username, session); + + return err; +} + +srs_error_t SrsRtcServer::create_session2(SrsRequest* req, SrsSdp& local_sdp, const std::string& mock_eip, bool unified_plan, SrsRtcConnection** psession) +{ + srs_error_t err = srs_success; + + SrsContextId cid = _srs_context->get_id(); + + std::string local_pwd = srs_random_str(32); + // TODO: FIXME: Collision detect. + std::string local_ufrag = srs_random_str(8); + + SrsRtcConnection* session = new SrsRtcConnection(this, cid); + // first add player for negotiate local sdp media info + if ((err = session->add_player2(req, unified_plan, local_sdp)) != srs_success) { + srs_freep(session); + return srs_error_wrap(err, "add player2"); + } + *psession = session; + + local_sdp.set_dtls_role("actpass"); + local_sdp.set_ice_ufrag(local_ufrag); + local_sdp.set_ice_pwd(local_pwd); + local_sdp.set_fingerprint_algo("sha-256"); + local_sdp.set_fingerprint(_srs_rtc_dtls_certificate->get_fingerprint()); + + // We allows to mock the eip of server. + if (!mock_eip.empty()) { + local_sdp.add_candidate(mock_eip, _srs_config->get_rtc_server_listen(), "host"); + srs_trace("RTC: Use candidate mock_eip %s", mock_eip.c_str()); + } else { + std::vector candidate_ips = get_candidate_ips(); + for (int i = 0; i < (int)candidate_ips.size(); ++i) { + local_sdp.add_candidate(candidate_ips[i], _srs_config->get_rtc_server_listen(), "host"); + } + srs_trace("RTC: Use candidates %s", srs_join_vector_string(candidate_ips, ", ").c_str()); + } + + session->set_local_sdp(local_sdp); + session->set_state(WAITING_ANSWER); + + return err; +} + +srs_error_t SrsRtcServer::setup_session2(SrsRtcConnection* session, SrsRequest* req, const SrsSdp& remote_sdp) +{ + srs_error_t err = srs_success; + + if (session->state() != WAITING_ANSWER) { + return err; + } + + // TODO: FIXME: Collision detect. + string username = session->get_local_sdp()->get_ice_ufrag() + ":" + remote_sdp.get_ice_ufrag(); + + if ((err = session->initialize(req, true, true, username)) != srs_success) { + return srs_error_wrap(err, "init"); + } + + // We allows username is optional, but it never empty here. + _srs_rtc_manager->add_with_name(username, session); + + session->set_remote_sdp(remote_sdp); + session->set_state(WAITING_STUN); + + return err; +} + +void SrsRtcServer::insert_into_id_sessions(const string& peer_id, SrsRtcConnection* session) +{ + _srs_rtc_manager->add_with_id(peer_id, session); +} + +SrsRtcConnection* SrsRtcServer::find_session_by_username(const std::string& username) +{ + ISrsResource* conn = _srs_rtc_manager->find_by_name(username); + return dynamic_cast(conn); +} + +srs_error_t SrsRtcServer::notify(int type, srs_utime_t interval, srs_utime_t tick) +{ + srs_error_t err = srs_success; + + // Alive RTC sessions, for stat. + int nn_rtc_conns = 0; + + // Check all sessions and dispose the dead sessions. + for (int i = 0; i < (int)_srs_rtc_manager->size(); i++) { + SrsRtcConnection* session = dynamic_cast(_srs_rtc_manager->at(i)); + if (!session || !session->is_alive() || session->disposing_) { + nn_rtc_conns++; + continue; + } + + SrsContextRestore(_srs_context->get_id()); + session->switch_to_context(); + + string username = session->username(); + srs_trace("RTC: session destroy by timeout, username=%s, summary: %s", username.c_str(), + session->stat_->summary().c_str()); + + // Use manager to free session and notify other objects. + _srs_rtc_manager->remove(session); + } + + // Ignore stats if no RTC connections. + if (!nn_rtc_conns) { + return err; + } + + // Show statistics for RTC server. + SrsProcSelfStat* u = srs_get_self_proc_stat(); + // Resident Set Size: number of pages the process has in real memory. + int memory = (int)(u->rss * 4 / 1024); + // TODO: FIXME: Show more data for RTC server. + srs_trace("RTC: Server conns=%u, cpu=%.2f%%, rss=%dMB", nn_rtc_conns, u->percent * 100, memory); + + return err; +} + +RtcServerAdapter::RtcServerAdapter() +{ + rtc = new SrsRtcServer(); +} + +RtcServerAdapter::~RtcServerAdapter() +{ + srs_freep(rtc); +} + +srs_error_t RtcServerAdapter::initialize() +{ + srs_error_t err = srs_success; + + if ((err = _srs_rtc_dtls_certificate->initialize()) != srs_success) { + return srs_error_wrap(err, "rtc dtls certificate initialize"); + } + + if ((err = rtc->initialize()) != srs_success) { + return srs_error_wrap(err, "rtc server initialize"); + } + + return err; +} + +srs_error_t RtcServerAdapter::run() +{ + srs_error_t err = srs_success; + + if ((err = rtc->listen_udp()) != srs_success) { + return srs_error_wrap(err, "listen udp"); + } + + if ((err = rtc->listen_api()) != srs_success) { + return srs_error_wrap(err, "listen api"); + } + + if ((err = _srs_rtc_manager->start()) != srs_success) { + return srs_error_wrap(err, "start manager"); + } + + return err; +} + +void RtcServerAdapter::stop() +{ +} + +SrsResourceManager* _srs_rtc_manager = new SrsResourceManager("RTC", true); + diff --git a/trunk/src/app/srs_app_rtc_server.hpp b/trunk/src/app/srs_app_rtc_server.hpp new file mode 100644 index 000000000..e1f7d7397 --- /dev/null +++ b/trunk/src/app/srs_app_rtc_server.hpp @@ -0,0 +1,152 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2013-2020 John + * + * 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_RTC_SERVER_HPP +#define SRS_APP_RTC_SERVER_HPP + +#include + +#include +#include +#include +#include +#include + +#include + +class SrsRtcServer; +class SrsHourGlass; +class SrsRtcConnection; +class SrsRequest; +class SrsSdp; +class SrsRtcStream; +class SrsResourceManager; + +// The UDP black hole, for developer to use wireshark to catch plaintext packets. +// For example, server receive UDP packets at udp://8000, and forward the plaintext packet to black hole, +// we can use wireshark to capture the plaintext. +class SrsRtcBlackhole +{ +public: + bool blackhole; +private: + sockaddr_in* blackhole_addr; + srs_netfd_t blackhole_stfd; +public: + SrsRtcBlackhole(); + virtual ~SrsRtcBlackhole(); +public: + srs_error_t initialize(); + void sendto(void* data, int len); +}; + +extern SrsRtcBlackhole* _srs_blackhole; + +// The handler for RTC server to call. +class ISrsRtcServerHandler +{ +public: + ISrsRtcServerHandler(); + virtual ~ISrsRtcServerHandler(); +public: + // When server detect the timeout for session object. + virtual void on_timeout(SrsRtcConnection* session) = 0; +}; + +// The hijacker to hook server. +class ISrsRtcServerHijacker +{ +public: + ISrsRtcServerHijacker(); + virtual ~ISrsRtcServerHijacker(); +public: + // If consumed set to true, server will ignore the packet. + virtual srs_error_t on_udp_packet(SrsUdpMuxSocket* skt, SrsRtcConnection* session, bool* pconsumed) = 0; +}; + +// The RTC server instance, listen UDP port, handle UDP packet, manage RTC connections. +class SrsRtcServer : virtual public ISrsUdpMuxHandler, virtual public ISrsHourGlass +{ +private: + SrsHourGlass* timer; + std::vector listeners; + ISrsRtcServerHandler* handler; + ISrsRtcServerHijacker* hijacker; +public: + SrsRtcServer(); + virtual ~SrsRtcServer(); +public: + virtual srs_error_t initialize(); + // Set the handler for server events. + void set_handler(ISrsRtcServerHandler* h); + void set_hijacker(ISrsRtcServerHijacker* h); +public: + // TODO: FIXME: Support gracefully quit. + // TODO: FIXME: Support reload. + srs_error_t listen_udp(); + virtual srs_error_t on_udp_packet(SrsUdpMuxSocket* skt); + srs_error_t listen_api(); +public: + // Peer start offering, we answer it. + srs_error_t create_session( + SrsRequest* req, const SrsSdp& remote_sdp, SrsSdp& local_sdp, const std::string& mock_eip, + bool publish, bool dtls, bool srtp, + SrsRtcConnection** psession + ); +private: + srs_error_t do_create_session( + SrsRtcConnection* session, SrsRequest* req, const SrsSdp& remote_sdp, SrsSdp& local_sdp, + const std::string& mock_eip, bool publish, bool dtls, bool srtp + ); +public: + // We start offering, create_session2 to generate offer, setup_session2 to handle answer. + srs_error_t create_session2(SrsRequest* req, SrsSdp& local_sdp, const std::string& mock_eip, bool unified_plan, SrsRtcConnection** psession); + srs_error_t setup_session2(SrsRtcConnection* session, SrsRequest* req, const SrsSdp& remote_sdp); +public: + void insert_into_id_sessions(const std::string& peer_id, SrsRtcConnection* session); +public: + SrsRtcConnection* find_session_by_username(const std::string& ufrag); +// interface ISrsHourGlass +public: + virtual srs_error_t notify(int type, srs_utime_t interval, srs_utime_t tick); +}; + +// The RTC server adapter. +class RtcServerAdapter : public ISrsHybridServer +{ +private: + SrsRtcServer* rtc; +public: + RtcServerAdapter(); + virtual ~RtcServerAdapter(); +public: + virtual srs_error_t initialize(); + virtual srs_error_t run(); + virtual void stop(); +}; + +// Manager for RTC connections. +extern SrsResourceManager* _srs_rtc_manager; + +#endif + diff --git a/trunk/src/app/srs_app_rtc_source.cpp b/trunk/src/app/srs_app_rtc_source.cpp new file mode 100644 index 000000000..979078b15 --- /dev/null +++ b/trunk/src/app/srs_app_rtc_source.cpp @@ -0,0 +1,2158 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2013-2020 John + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef SRS_FFMPEG_FIT +#include +#endif + +// Firefox defaults as 109, Chrome is 111. +const int kAudioPayloadType = 111; +const int kAudioChannel = 2; +const int kAudioSamplerate = 48000; + +// Firefox defaults as 126, Chrome is 102. +const int kVideoPayloadType = 102; +const int kVideoSamplerate = 90000; + +// An AAC packet may be transcoded to many OPUS packets. +const int kMaxOpusPackets = 8; +// The max size for each OPUS packet. +const int kMaxOpusPacketSize = 4096; + +// The RTP payload max size, reserved some paddings for SRTP as such: +// kRtpPacketSize = kRtpMaxPayloadSize + paddings +// For example, if kRtpPacketSize is 1500, recommend to set kRtpMaxPayloadSize to 1400, +// which reserves 100 bytes for SRTP or paddings. +const int kRtpMaxPayloadSize = kRtpPacketSize - 200; + +using namespace std; + +// TODO: Add this function into SrsRtpMux class. +srs_error_t aac_raw_append_adts_header(SrsSharedPtrMessage* shared_audio, SrsFormat* format, char** pbuf, int* pnn_buf) +{ + srs_error_t err = srs_success; + + if (format->is_aac_sequence_header()) { + return err; + } + + if (format->audio->nb_samples != 1) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "adts"); + } + + int nb_buf = format->audio->samples[0].size + 7; + char* buf = new char[nb_buf]; + SrsBuffer stream(buf, nb_buf); + + // TODO: Add comment. + stream.write_1bytes(0xFF); + stream.write_1bytes(0xF9); + stream.write_1bytes(((format->acodec->aac_object - 1) << 6) | ((format->acodec->aac_sample_rate & 0x0F) << 2) | ((format->acodec->aac_channels & 0x04) >> 2)); + stream.write_1bytes(((format->acodec->aac_channels & 0x03) << 6) | ((nb_buf >> 11) & 0x03)); + stream.write_1bytes((nb_buf >> 3) & 0xFF); + stream.write_1bytes(((nb_buf & 0x07) << 5) | 0x1F); + stream.write_1bytes(0xFC); + + stream.write_bytes(format->audio->samples[0].bytes, format->audio->samples[0].size); + + *pbuf = buf; + *pnn_buf = nb_buf; + + return err; +} + +uint64_t SrsNtp::kMagicNtpFractionalUnit = 1ULL << 32; + +SrsNtp::SrsNtp() +{ + system_ms_ = 0; + ntp_ = 0; + ntp_second_ = 0; + ntp_fractions_ = 0; +} + +SrsNtp::~SrsNtp() +{ +} + +SrsNtp SrsNtp::from_time_ms(uint64_t ms) +{ + SrsNtp srs_ntp; + srs_ntp.system_ms_ = ms; + srs_ntp.ntp_second_ = ms / 1000; + srs_ntp.ntp_fractions_ = (static_cast(ms % 1000 / 1000.0)) * kMagicNtpFractionalUnit; + srs_ntp.ntp_ = (static_cast(srs_ntp.ntp_second_) << 32) | srs_ntp.ntp_fractions_; + return srs_ntp; +} + +SrsNtp SrsNtp::to_time_ms(uint64_t ntp) +{ + SrsNtp srs_ntp; + srs_ntp.ntp_ = ntp; + srs_ntp.ntp_second_ = (ntp & 0xFFFFFFFF00000000ULL) >> 32; + srs_ntp.ntp_fractions_ = (ntp & 0x00000000FFFFFFFFULL); + srs_ntp.system_ms_ = (static_cast(srs_ntp.ntp_second_) * 1000) + + (static_cast(static_cast(srs_ntp.ntp_fractions_) * 1000.0) / kMagicNtpFractionalUnit); + return srs_ntp; +} + +SrsRtcConsumer::SrsRtcConsumer(SrsRtcStream* s) +{ + source = s; + should_update_source_id = false; + + mw_wait = srs_cond_new(); + mw_min_msgs = 0; + mw_waiting = false; +} + +SrsRtcConsumer::~SrsRtcConsumer() +{ + source->on_consumer_destroy(this); + + vector::iterator it; + for (it = queue.begin(); it != queue.end(); ++it) { + SrsRtpPacket2* pkt = *it; + srs_freep(pkt); + } + + srs_cond_destroy(mw_wait); +} + +void SrsRtcConsumer::update_source_id() +{ + should_update_source_id = true; +} + +srs_error_t SrsRtcConsumer::enqueue(SrsRtpPacket2* pkt) +{ + srs_error_t err = srs_success; + + queue.push_back(pkt); + + if (mw_waiting) { + if ((int)queue.size() > mw_min_msgs) { + srs_cond_signal(mw_wait); + mw_waiting = false; + return err; + } + } + + return err; +} + +srs_error_t SrsRtcConsumer::dump_packets(std::vector& pkts) +{ + srs_error_t err = srs_success; + + if (should_update_source_id) { + srs_trace("update source_id=%s/%s", source->source_id().c_str(), source->pre_source_id().c_str()); + should_update_source_id = false; + } + + queue.swap(pkts); + + return err; +} + +void SrsRtcConsumer::wait(int nb_msgs) +{ + mw_min_msgs = nb_msgs; + + // when duration ok, signal to flush. + if ((int)queue.size() > mw_min_msgs) { + return; + } + + // the enqueue will notify this cond. + mw_waiting = true; + + // use cond block wait for high performance mode. + srs_cond_wait(mw_wait); +} + +SrsRtcStreamManager::SrsRtcStreamManager() +{ + lock = NULL; +} + +SrsRtcStreamManager::~SrsRtcStreamManager() +{ + srs_mutex_destroy(lock); +} + +srs_error_t SrsRtcStreamManager::fetch_or_create(SrsRequest* r, SrsRtcStream** pps) +{ + srs_error_t err = srs_success; + + // Lazy create lock, because ST is not ready in SrsRtcStreamManager constructor. + if (!lock) { + lock = srs_mutex_new(); + } + + // Use lock to protect coroutine switch. + // @bug https://github.com/ossrs/srs/issues/1230 + SrsLocker(lock); + + SrsRtcStream* source = NULL; + if ((source = fetch(r)) != NULL) { + *pps = source; + return err; + } + + string stream_url = r->get_stream_url(); + string vhost = r->vhost; + + // should always not exists for create a source. + srs_assert (pool.find(stream_url) == pool.end()); + + srs_trace("new source, stream_url=%s", stream_url.c_str()); + + source = new SrsRtcStream(); + if ((err = source->initialize(r)) != srs_success) { + return srs_error_wrap(err, "init source %s", r->get_stream_url().c_str()); + } + + pool[stream_url] = source; + + *pps = source; + + return err; +} + +SrsRtcStream* SrsRtcStreamManager::fetch(SrsRequest* r) +{ + SrsRtcStream* source = NULL; + + string stream_url = r->get_stream_url(); + if (pool.find(stream_url) == pool.end()) { + return NULL; + } + + source = pool[stream_url]; + + // we always update the request of resource, + // for origin auth is on, the token in request maybe invalid, + // and we only need to update the token of request, it's simple. + source->update_auth(r); + + return source; +} + +SrsRtcStreamManager* _srs_rtc_sources = new SrsRtcStreamManager(); + +ISrsRtcPublishStream::ISrsRtcPublishStream() +{ +} + +ISrsRtcPublishStream::~ISrsRtcPublishStream() +{ +} + +ISrsRtcStreamEventHandler::ISrsRtcStreamEventHandler() +{ +} + +ISrsRtcStreamEventHandler::~ISrsRtcStreamEventHandler() +{ +} + +SrsRtcStream::SrsRtcStream() +{ + is_created_ = false; + is_delivering_packets_ = false; + + publish_stream_ = NULL; + stream_desc_ = NULL; + + req = NULL; + bridger_ = new SrsRtcDummyBridger(this); +} + +SrsRtcStream::~SrsRtcStream() +{ + // never free the consumers, + // for all consumers are auto free. + consumers.clear(); + + srs_freep(req); + srs_freep(bridger_); + srs_freep(stream_desc_); +} + +srs_error_t SrsRtcStream::initialize(SrsRequest* r) +{ + srs_error_t err = srs_success; + + req = r->copy(); + + return err; +} + +void SrsRtcStream::update_auth(SrsRequest* r) +{ + req->update_auth(r); +} + +srs_error_t SrsRtcStream::on_source_id_changed(SrsContextId id) +{ + srs_error_t err = srs_success; + + if (!_source_id.compare(id)) { + return err; + } + + if (_pre_source_id.empty()) { + _pre_source_id = id; + } + _source_id = id; + + // notice all consumer + std::vector::iterator it; + for (it = consumers.begin(); it != consumers.end(); ++it) { + SrsRtcConsumer* consumer = *it; + consumer->update_source_id(); + } + + return err; +} + +SrsContextId SrsRtcStream::source_id() +{ + return _source_id; +} + +SrsContextId SrsRtcStream::pre_source_id() +{ + return _pre_source_id; +} + +ISrsSourceBridger* SrsRtcStream::bridger() +{ + return bridger_; +} + +srs_error_t SrsRtcStream::create_consumer(SrsRtcConsumer*& consumer) +{ + srs_error_t err = srs_success; + + consumer = new SrsRtcConsumer(this); + consumers.push_back(consumer); + + // TODO: FIXME: Implements edge cluster. + + return err; +} + +srs_error_t SrsRtcStream::consumer_dumps(SrsRtcConsumer* consumer, bool ds, bool dm, bool dg) +{ + srs_error_t err = srs_success; + + // print status. + srs_trace("create consumer, no gop cache"); + + return err; +} + +void SrsRtcStream::on_consumer_destroy(SrsRtcConsumer* consumer) +{ + std::vector::iterator it; + it = std::find(consumers.begin(), consumers.end(), consumer); + if (it != consumers.end()) { + consumers.erase(it); + } + + // When all consumers finished, notify publisher to handle it. + if (publish_stream_ && consumers.empty()) { + for (size_t i = 0; i < event_handlers_.size(); i++) { + ISrsRtcStreamEventHandler* h = event_handlers_.at(i); + h->on_consumers_finished(); + } + } +} + +bool SrsRtcStream::can_publish() +{ + return !is_created_; +} + +void SrsRtcStream::set_stream_created() +{ + srs_assert(!is_created_ && !is_delivering_packets_); + is_created_ = true; +} + +srs_error_t SrsRtcStream::on_publish() +{ + srs_error_t err = srs_success; + + // update the request object. + srs_assert(req); + + // For RTC, DTLS is done, and we are ready to deliver packets. + // @note For compatible with RTMP, we also set the is_created_, it MUST be created here. + is_created_ = true; + is_delivering_packets_ = true; + + // whatever, the publish thread is the source or edge source, + // save its id to srouce id. + if ((err = on_source_id_changed(_srs_context->get_id())) != srs_success) { + return srs_error_wrap(err, "source id change"); + } + + // Create a new bridger, because it's been disposed when unpublish. +#ifdef SRS_FFMPEG_FIT + SrsRtcFromRtmpBridger* impl = new SrsRtcFromRtmpBridger(this); + if ((err = impl->initialize(req)) != srs_success) { + return srs_error_wrap(err, "bridge initialize"); + } + + bridger_->setup(impl); +#endif + + // TODO: FIXME: Handle by statistic. + + return err; +} + +void SrsRtcStream::on_unpublish() +{ + // ignore when already unpublished. + if (!is_created_) { + return; + } + + srs_trace("cleanup when unpublish, created=%u, deliver=%u", is_created_, is_delivering_packets_); + + is_created_ = false; + is_delivering_packets_ = false; + + if (!_source_id.empty()) { + _pre_source_id = _source_id; + } + _source_id = SrsContextId(); + + for (size_t i = 0; i < event_handlers_.size(); i++) { + ISrsRtcStreamEventHandler* h = event_handlers_.at(i); + h->on_unpublish(); + } + + // release unpublish stream description. + set_stream_desc(NULL); + + // Dispose the impl of bridger, to free memory. +#ifdef SRS_FFMPEG_FIT + bridger_->setup(NULL); +#endif + + // TODO: FIXME: Handle by statistic. +} + +void SrsRtcStream::subscribe(ISrsRtcStreamEventHandler* h) +{ + if (std::find(event_handlers_.begin(), event_handlers_.end(), h) == event_handlers_.end()) { + event_handlers_.push_back(h); + } +} + +void SrsRtcStream::unsubscribe(ISrsRtcStreamEventHandler* h) +{ + std::vector::iterator it; + it = std::find(event_handlers_.begin(), event_handlers_.end(), h); + if (it != event_handlers_.end()) { + event_handlers_.erase(it); + } +} + +ISrsRtcPublishStream* SrsRtcStream::publish_stream() +{ + return publish_stream_; +} + +void SrsRtcStream::set_publish_stream(ISrsRtcPublishStream* v) +{ + publish_stream_ = v; +} + +srs_error_t SrsRtcStream::on_rtp(SrsRtpPacket2* pkt) +{ + srs_error_t err = srs_success; + + for (int i = 0; i < (int)consumers.size(); i++) { + SrsRtcConsumer* consumer = consumers.at(i); + if ((err = consumer->enqueue(pkt->copy())) != srs_success) { + return srs_error_wrap(err, "consume message"); + } + } + + return err; +} + +bool SrsRtcStream::has_stream_desc() +{ + return stream_desc_; +} + +void SrsRtcStream::set_stream_desc(SrsRtcStreamDescription* stream_desc) +{ + srs_freep(stream_desc_); + + if (stream_desc) { + stream_desc_ = stream_desc->copy(); + } +} + +std::vector SrsRtcStream::get_track_desc(std::string type, std::string media_name) +{ + std::vector track_descs; + if (!stream_desc_) { + return track_descs; + } + + if (type == "audio") { + if (stream_desc_->audio_track_desc_->media_->name_ == media_name) { + track_descs.push_back(stream_desc_->audio_track_desc_); + } + } + + if (type == "video") { + std::vector::iterator it = stream_desc_->video_track_descs_.begin(); + while (it != stream_desc_->video_track_descs_.end() ){ + track_descs.push_back(*it); + ++it; + } + } + + return track_descs; +} + +#ifdef SRS_FFMPEG_FIT +SrsRtcFromRtmpBridger::SrsRtcFromRtmpBridger(SrsRtcStream* source) +{ + req = NULL; + source_ = source; + format = new SrsRtmpFormat(); + codec = new SrsAudioRecode(SrsAudioCodecIdAAC, SrsAudioCodecIdOpus, kAudioChannel, kAudioSamplerate); + discard_aac = false; + discard_bframe = false; + merge_nalus = false; + meta = new SrsMetaCache(); + audio_timestamp = 0; + audio_sequence = 0; + video_sequence = 0; + + SrsRtcStreamDescription* stream_desc = new SrsRtcStreamDescription(); + SrsAutoFree(SrsRtcStreamDescription, stream_desc); + + // audio track description + if (true) { + SrsRtcTrackDescription* audio_track_desc = new SrsRtcTrackDescription(); + stream_desc->audio_track_desc_ = audio_track_desc; + + audio_track_desc->type_ = "audio"; + audio_track_desc->id_ = "audio-" + srs_random_str(8); + + audio_ssrc = SrsRtcSSRCGenerator::instance()->generate_ssrc(); + audio_track_desc->ssrc_ = audio_ssrc; + audio_track_desc->direction_ = "recvonly"; + + audio_track_desc->media_ = new SrsAudioPayload(kAudioPayloadType, "opus", kAudioSamplerate, kAudioChannel); + } + + // video track description + if (true) { + SrsRtcTrackDescription* video_track_desc = new SrsRtcTrackDescription(); + stream_desc->video_track_descs_.push_back(video_track_desc); + + video_track_desc->type_ = "video"; + video_track_desc->id_ = "video-" + srs_random_str(8); + + video_ssrc = SrsRtcSSRCGenerator::instance()->generate_ssrc(); + video_track_desc->ssrc_ = video_ssrc; + video_track_desc->direction_ = "recvonly"; + + SrsVideoPayload* video_payload = new SrsVideoPayload(kVideoPayloadType, "H264", kVideoSamplerate); + video_track_desc->media_ = video_payload; + + video_payload->set_h264_param_desc("level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f"); + } + + // If the stream description has already been setup by RTC publisher, + // we should ignore and it's ok, because we only need to setup it for bridger. + if (!source_->has_stream_desc()) { + source_->set_stream_desc(stream_desc); + } +} + +SrsRtcFromRtmpBridger::~SrsRtcFromRtmpBridger() +{ + srs_freep(format); + srs_freep(codec); + srs_freep(meta); +} + +srs_error_t SrsRtcFromRtmpBridger::initialize(SrsRequest* r) +{ + srs_error_t err = srs_success; + + req = r; + + if ((err = format->initialize()) != srs_success) { + return srs_error_wrap(err, "format initialize"); + } + + if ((err = codec->initialize()) != srs_success) { + return srs_error_wrap(err, "init codec"); + } + + // TODO: FIXME: Support reload. + discard_aac = _srs_config->get_rtc_aac_discard(req->vhost); + discard_bframe = _srs_config->get_rtc_bframe_discard(req->vhost); + merge_nalus = _srs_config->get_rtc_server_merge_nalus(); + srs_trace("RTC bridge from RTMP, discard_aac=%d, discard_bframe=%d, merge_nalus=%d", + discard_aac, discard_bframe, merge_nalus); + + return err; +} + +srs_error_t SrsRtcFromRtmpBridger::on_publish() +{ + srs_error_t err = srs_success; + + // TODO: FIXME: Should sync with bridger? + if ((err = source_->on_publish()) != srs_success) { + return srs_error_wrap(err, "source publish"); + } + + // Reset the metadata cache, to make VLC happy when disable/enable stream. + // @see https://github.com/ossrs/srs/issues/1630#issuecomment-597979448 + meta->clear(); + + return err; +} + +void SrsRtcFromRtmpBridger::on_unpublish() +{ + // Reset the metadata cache, to make VLC happy when disable/enable stream. + // @see https://github.com/ossrs/srs/issues/1630#issuecomment-597979448 + meta->update_previous_vsh(); + meta->update_previous_ash(); + + // @remark This bridger might be disposed here, so never use it. + // TODO: FIXME: Should sync with bridger? + source_->on_unpublish(); +} + +srs_error_t SrsRtcFromRtmpBridger::on_audio(SrsSharedPtrMessage* msg) +{ + srs_error_t err = srs_success; + + // TODO: FIXME: Support parsing OPUS for RTC. + if ((err = format->on_audio(msg)) != srs_success) { + return srs_error_wrap(err, "format consume audio"); + } + + // Ignore if no format->acodec, it means the codec is not parsed, or unknown codec. + // @issue https://github.com/ossrs/srs/issues/1506#issuecomment-562079474 + if (!format->acodec) { + return err; + } + + // ts support audio codec: aac/mp3 + SrsAudioCodecId acodec = format->acodec->id; + if (acodec != SrsAudioCodecIdAAC && acodec != SrsAudioCodecIdMP3) { + return err; + } + + // When drop aac audio packet, never transcode. + if (discard_aac && acodec == SrsAudioCodecIdAAC) { + return err; + } + + // ignore sequence header + srs_assert(format->audio); + + char* adts_audio = NULL; + int nn_adts_audio = 0; + // TODO: FIXME: Reserve 7 bytes header when create shared message. + if ((err = aac_raw_append_adts_header(msg, format, &adts_audio, &nn_adts_audio)) != srs_success) { + return srs_error_wrap(err, "aac append header"); + } + + if (adts_audio) { + err = transcode(adts_audio, nn_adts_audio); + srs_freep(adts_audio); + } + + return err; +} + +srs_error_t SrsRtcFromRtmpBridger::transcode(char* adts_audio, int nn_adts_audio) +{ + srs_error_t err = srs_success; + + // Opus packet cache. + static char* opus_payloads[kMaxOpusPackets]; + + static bool initialized = false; + if (!initialized) { + initialized = true; + + static char opus_packets_cache[kMaxOpusPackets][kMaxOpusPacketSize]; + opus_payloads[0] = &opus_packets_cache[0][0]; + for (int i = 1; i < kMaxOpusPackets; i++) { + opus_payloads[i] = opus_packets_cache[i]; + } + } + + // Transcode an aac packet to many opus packets. + SrsSample aac; + aac.bytes = adts_audio; + aac.size = nn_adts_audio; + + int nn_opus_packets = 0; + int opus_sizes[kMaxOpusPackets]; + if ((err = codec->transcode(&aac, opus_payloads, opus_sizes, nn_opus_packets)) != srs_success) { + return srs_error_wrap(err, "recode error"); + } + + // Save OPUS packets in shared message. + if (nn_opus_packets <= 0) { + return err; + } + + int nn_max_extra_payload = 0; + for (int i = 0; i < nn_opus_packets; i++) { + char* data = (char*)opus_payloads[i]; + int size = (int)opus_sizes[i]; + + // TODO: FIXME: Use it to padding audios. + nn_max_extra_payload = srs_max(nn_max_extra_payload, size); + + SrsRtpPacket2* pkt = NULL; + SrsAutoFree(SrsRtpPacket2, pkt); + + if ((err = package_opus(data, size, &pkt)) != srs_success) { + return srs_error_wrap(err, "package opus"); + } + + if ((err = source_->on_rtp(pkt)) != srs_success) { + return srs_error_wrap(err, "consume opus"); + } + } + + return err; +} + +srs_error_t SrsRtcFromRtmpBridger::package_opus(char* data, int size, SrsRtpPacket2** ppkt) +{ + srs_error_t err = srs_success; + + SrsRtpPacket2* pkt = new SrsRtpPacket2(); + pkt->header.set_payload_type(kAudioPayloadType); + pkt->header.set_ssrc(audio_ssrc); + pkt->frame_type = SrsFrameTypeAudio; + pkt->header.set_marker(true); + pkt->header.set_sequence(audio_sequence++); + pkt->header.set_timestamp(audio_timestamp); + + // TODO: FIXME: Why 960? Need Refactoring? + audio_timestamp += 960; + + SrsRtpRawPayload* raw = new SrsRtpRawPayload(); + pkt->payload = raw; + + raw->payload = new char[size]; + raw->nn_payload = size; + memcpy(raw->payload, data, size); + + pkt->shared_msg = new SrsSharedPtrMessage(); + pkt->shared_msg->wrap(raw->payload, size); + + *ppkt = pkt; + + return err; +} + +srs_error_t SrsRtcFromRtmpBridger::on_video(SrsSharedPtrMessage* msg) +{ + srs_error_t err = srs_success; + + // cache the sequence header if h264 + bool is_sequence_header = SrsFlvVideo::sh(msg->payload, msg->size); + if (is_sequence_header && (err = meta->update_vsh(msg)) != srs_success) { + return srs_error_wrap(err, "meta update video"); + } + + if ((err = format->on_video(msg)) != srs_success) { + return srs_error_wrap(err, "format consume video"); + } + + bool has_idr = false; + vector samples; + if ((err = filter(msg, format, has_idr, samples)) != srs_success) { + return srs_error_wrap(err, "filter video"); + } + int nn_samples = (int)samples.size(); + + // Well, for each IDR, we append a SPS/PPS before it, which is packaged in STAP-A. + if (has_idr) { + SrsRtpPacket2* pkt = NULL; + SrsAutoFree(SrsRtpPacket2, pkt); + + if ((err = package_stap_a(source_, msg, &pkt)) != srs_success) { + return srs_error_wrap(err, "package stap-a"); + } + + if ((err = source_->on_rtp(pkt)) != srs_success) { + return srs_error_wrap(err, "consume sps/pps"); + } + } + + // If merge Nalus, we pcakges all NALUs(samples) as one NALU, in a RTP or FUA packet. + vector pkts; + if (merge_nalus && nn_samples > 1) { + if ((err = package_nalus(msg, samples, pkts)) != srs_success) { + return srs_error_wrap(err, "package nalus as one"); + } + } else { + // By default, we package each NALU(sample) to a RTP or FUA packet. + for (int i = 0; i < nn_samples; i++) { + SrsSample* sample = samples[i]; + + // We always ignore bframe here, if config to discard bframe, + // the bframe flag will not be set. + if (sample->bframe) { + continue; + } + + if (sample->size <= kRtpMaxPayloadSize) { + if ((err = package_single_nalu(msg, sample, pkts)) != srs_success) { + return srs_error_wrap(err, "package single nalu"); + } + } else { + if ((err = package_fu_a(msg, sample, kRtpMaxPayloadSize, pkts)) != srs_success) { + return srs_error_wrap(err, "package fu-a"); + } + } + } + } + + if (pkts.size() > 0) { + pkts.back()->header.set_marker(true); + } + + return consume_packets(pkts); +} + +srs_error_t SrsRtcFromRtmpBridger::filter(SrsSharedPtrMessage* msg, SrsFormat* format, bool& has_idr, vector& samples) +{ + srs_error_t err = srs_success; + + // If IDR, we will insert SPS/PPS before IDR frame. + if (format->video && format->video->has_idr) { + has_idr = true; + } + + // Update samples to shared frame. + for (int i = 0; i < format->video->nb_samples; ++i) { + SrsSample* sample = &format->video->samples[i]; + + // Because RTC does not support B-frame, so we will drop them. + // TODO: Drop B-frame in better way, which not cause picture corruption. + if (discard_bframe) { + if ((err = sample->parse_bframe()) != srs_success) { + return srs_error_wrap(err, "parse bframe"); + } + if (sample->bframe) { + continue; + } + } + + samples.push_back(sample); + } + + return err; +} + +srs_error_t SrsRtcFromRtmpBridger::package_stap_a(SrsRtcStream* source, SrsSharedPtrMessage* msg, SrsRtpPacket2** ppkt) +{ + srs_error_t err = srs_success; + + SrsFormat* format = meta->vsh_format(); + if (!format || !format->vcodec) { + return err; + } + + // Note that the sps/pps may change, so we should copy it. + const vector& sps = format->vcodec->sequenceParameterSetNALUnit; + const vector& pps = format->vcodec->pictureParameterSetNALUnit; + if (sps.empty() || pps.empty()) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "sps/pps empty"); + } + + SrsRtpPacket2* pkt = new SrsRtpPacket2(); + pkt->header.set_payload_type(kVideoPayloadType); + pkt->header.set_ssrc(video_ssrc); + pkt->frame_type = SrsFrameTypeVideo; + pkt->nalu_type = (SrsAvcNaluType)kStapA; + pkt->header.set_marker(false); + pkt->header.set_sequence(video_sequence++); + pkt->header.set_timestamp(msg->timestamp * 90); + + SrsRtpSTAPPayload* stap = new SrsRtpSTAPPayload(); + pkt->payload = stap; + + uint8_t header = sps[0]; + stap->nri = (SrsAvcNaluType)header; + + // Copy the SPS/PPS bytes, because it may change. + int size = (int)(sps.size() + pps.size()); + char* payload = new char[size]; + pkt->shared_msg = new SrsSharedPtrMessage(); + pkt->shared_msg->wrap(payload, size); + + if (true) { + SrsSample* sample = new SrsSample(); + sample->bytes = payload; + sample->size = (int)sps.size(); + stap->nalus.push_back(sample); + + memcpy(payload, (char*)&sps[0], sps.size()); + payload += (int)sps.size(); + } + + if (true) { + SrsSample* sample = new SrsSample(); + sample->bytes = payload; + sample->size = (int)pps.size(); + stap->nalus.push_back(sample); + + memcpy(payload, (char*)&pps[0], pps.size()); + payload += (int)pps.size(); + } + + *ppkt = pkt; + srs_info("RTC STAP-A seq=%u, sps %d, pps %d bytes", pkt->header.get_sequence(), sps.size(), pps.size()); + + return err; +} + +srs_error_t SrsRtcFromRtmpBridger::package_nalus(SrsSharedPtrMessage* msg, const vector& samples, vector& pkts) +{ + srs_error_t err = srs_success; + + SrsRtpRawNALUs* raw = new SrsRtpRawNALUs(); + SrsAvcNaluType first_nalu_type = SrsAvcNaluTypeReserved; + + for (int i = 0; i < (int)samples.size(); i++) { + SrsSample* sample = samples[i]; + + // We always ignore bframe here, if config to discard bframe, + // the bframe flag will not be set. + if (sample->bframe) { + continue; + } + + if (!sample->size) { + continue; + } + + if (first_nalu_type == SrsAvcNaluTypeReserved) { + first_nalu_type = SrsAvcNaluType((uint8_t)(sample->bytes[0] & kNalTypeMask)); + } + + raw->push_back(sample->copy()); + } + + // Ignore empty. + int nn_bytes = raw->nb_bytes(); + if (nn_bytes <= 0) { + srs_freep(raw); + return err; + } + + if (nn_bytes < kRtpMaxPayloadSize) { + // Package NALUs in a single RTP packet. + SrsRtpPacket2* pkt = new SrsRtpPacket2(); + pkt->header.set_payload_type(kVideoPayloadType); + pkt->header.set_ssrc(video_ssrc); + pkt->frame_type = SrsFrameTypeVideo; + pkt->nalu_type = (SrsAvcNaluType)first_nalu_type; + pkt->header.set_sequence(video_sequence++); + pkt->header.set_timestamp(msg->timestamp * 90); + pkt->payload = raw; + pkt->shared_msg = msg->copy(); + pkts.push_back(pkt); + } else { + // We must free it, should never use RTP packets to free it, + // because more than one RTP packet will refer to it. + SrsAutoFree(SrsRtpRawNALUs, raw); + + // Package NALUs in FU-A RTP packets. + int fu_payload_size = kRtpMaxPayloadSize; + + // The first byte is store in FU-A header. + uint8_t header = raw->skip_first_byte(); + uint8_t nal_type = header & kNalTypeMask; + int nb_left = nn_bytes - 1; + + int num_of_packet = 1 + (nn_bytes - 1) / fu_payload_size; + for (int i = 0; i < num_of_packet; ++i) { + int packet_size = srs_min(nb_left, fu_payload_size); + + SrsRtpFUAPayload* fua = new SrsRtpFUAPayload(); + if ((err = raw->read_samples(fua->nalus, packet_size)) != srs_success) { + srs_freep(fua); + return srs_error_wrap(err, "read samples %d bytes, left %d, total %d", packet_size, nb_left, nn_bytes); + } + + SrsRtpPacket2* pkt = new SrsRtpPacket2(); + pkt->header.set_payload_type(kVideoPayloadType); + pkt->header.set_ssrc(video_ssrc); + pkt->frame_type = SrsFrameTypeVideo; + pkt->nalu_type = (SrsAvcNaluType)kFuA; + pkt->header.set_sequence(video_sequence++); + pkt->header.set_timestamp(msg->timestamp * 90); + + fua->nri = (SrsAvcNaluType)header; + fua->nalu_type = (SrsAvcNaluType)nal_type; + fua->start = bool(i == 0); + fua->end = bool(i == num_of_packet - 1); + + pkt->payload = fua; + pkt->shared_msg = msg->copy(); + pkts.push_back(pkt); + + nb_left -= packet_size; + } + } + + return err; +} + +// Single NAL Unit Packet @see https://tools.ietf.org/html/rfc6184#section-5.6 +srs_error_t SrsRtcFromRtmpBridger::package_single_nalu(SrsSharedPtrMessage* msg, SrsSample* sample, vector& pkts) +{ + srs_error_t err = srs_success; + + SrsRtpPacket2* pkt = new SrsRtpPacket2(); + pkt->header.set_payload_type(kVideoPayloadType); + pkt->header.set_ssrc(video_ssrc); + pkt->frame_type = SrsFrameTypeVideo; + pkt->header.set_sequence(video_sequence++); + pkt->header.set_timestamp(msg->timestamp * 90); + + SrsRtpRawPayload* raw = new SrsRtpRawPayload(); + pkt->payload = raw; + + raw->payload = sample->bytes; + raw->nn_payload = sample->size; + + pkt->shared_msg = msg->copy(); + pkts.push_back(pkt); + + return err; +} + +srs_error_t SrsRtcFromRtmpBridger::package_fu_a(SrsSharedPtrMessage* msg, SrsSample* sample, int fu_payload_size, vector& pkts) +{ + srs_error_t err = srs_success; + + char* p = sample->bytes + 1; + int nb_left = sample->size - 1; + uint8_t header = sample->bytes[0]; + uint8_t nal_type = header & kNalTypeMask; + + int num_of_packet = 1 + (sample->size - 1) / fu_payload_size; + for (int i = 0; i < num_of_packet; ++i) { + int packet_size = srs_min(nb_left, fu_payload_size); + + SrsRtpPacket2* pkt = new SrsRtpPacket2(); + pkt->header.set_payload_type(kVideoPayloadType); + pkt->header.set_ssrc(video_ssrc); + pkt->frame_type = SrsFrameTypeVideo; + pkt->header.set_sequence(video_sequence++); + pkt->header.set_timestamp(msg->timestamp * 90); + + SrsRtpFUAPayload2* fua = new SrsRtpFUAPayload2(); + pkt->payload = fua; + + fua->nri = (SrsAvcNaluType)header; + fua->nalu_type = (SrsAvcNaluType)nal_type; + fua->start = bool(i == 0); + fua->end = bool(i == num_of_packet - 1); + + fua->payload = p; + fua->size = packet_size; + + pkt->shared_msg = msg->copy(); + pkts.push_back(pkt); + + p += packet_size; + nb_left -= packet_size; + } + + return err; +} + +srs_error_t SrsRtcFromRtmpBridger::consume_packets(vector& pkts) +{ + srs_error_t err = srs_success; + + // TODO: FIXME: Consume a range of packets. + for (int i = 0; i < (int)pkts.size(); i++) { + SrsRtpPacket2* pkt = pkts[i]; + if ((err = source_->on_rtp(pkt)) != srs_success) { + err = srs_error_wrap(err, "consume sps/pps"); + break; + } + } + + for (int i = 0; i < (int)pkts.size(); i++) { + SrsRtpPacket2* pkt = pkts[i]; + srs_freep(pkt); + } + + return err; +} +#endif + +SrsRtcDummyBridger::SrsRtcDummyBridger(SrsRtcStream* s) +{ + rtc_ = s; + impl_ = NULL; +} + +SrsRtcDummyBridger::~SrsRtcDummyBridger() +{ + srs_freep(impl_); +} + +srs_error_t SrsRtcDummyBridger::on_publish() +{ + if (impl_) { + return impl_->on_publish(); + } + return rtc_->on_publish(); +} + +srs_error_t SrsRtcDummyBridger::on_audio(SrsSharedPtrMessage* audio) +{ + if (impl_) { + return impl_->on_audio(audio); + } + return srs_success; +} + +srs_error_t SrsRtcDummyBridger::on_video(SrsSharedPtrMessage* video) +{ + if (impl_) { + return impl_->on_video(video); + } + return srs_success; +} + +void SrsRtcDummyBridger::on_unpublish() +{ + if (impl_) { + impl_->on_unpublish(); + return; + } + rtc_->on_unpublish(); +} + +void SrsRtcDummyBridger::setup(ISrsSourceBridger* impl) +{ + srs_freep(impl_); + impl_ = impl; +} + +SrsCodecPayload::SrsCodecPayload() +{ + pt_of_publisher_ = pt_ = 0; + sample_ = 0; +} + +SrsCodecPayload::SrsCodecPayload(uint8_t pt, std::string encode_name, int sample) +{ + pt_of_publisher_ = pt_ = pt; + name_ = encode_name; + sample_ = sample; +} + +SrsCodecPayload::~SrsCodecPayload() +{ +} + +SrsCodecPayload* SrsCodecPayload::copy() +{ + SrsCodecPayload* cp = new SrsCodecPayload(); + + cp->type_ = type_; + cp->pt_ = pt_; + cp->pt_of_publisher_ = pt_of_publisher_; + cp->name_ = name_; + cp->sample_ = sample_; + cp->rtcp_fbs_ = rtcp_fbs_; + + return cp; +} + +SrsMediaPayloadType SrsCodecPayload::generate_media_payload_type() +{ + SrsMediaPayloadType media_payload_type(pt_); + + media_payload_type.encoding_name_ = name_; + media_payload_type.clock_rate_ = sample_; + media_payload_type.rtcp_fb_ = rtcp_fbs_; + + return media_payload_type; +} + +SrsVideoPayload::SrsVideoPayload() +{ + type_ = "video"; +} + +SrsVideoPayload::SrsVideoPayload(uint8_t pt, std::string encode_name, int sample) + :SrsCodecPayload(pt, encode_name, sample) +{ + type_ = "video"; + h264_param_.profile_level_id = ""; + h264_param_.packetization_mode = ""; + h264_param_.level_asymmerty_allow = ""; +} + +SrsVideoPayload::~SrsVideoPayload() +{ +} + +SrsVideoPayload* SrsVideoPayload::copy() +{ + SrsVideoPayload* cp = new SrsVideoPayload(); + + cp->type_ = type_; + cp->pt_ = pt_; + cp->pt_of_publisher_ = pt_of_publisher_; + cp->name_ = name_; + cp->sample_ = sample_; + cp->rtcp_fbs_ = rtcp_fbs_; + cp->h264_param_ = h264_param_; + + return cp; +} + +SrsMediaPayloadType SrsVideoPayload::generate_media_payload_type() +{ + SrsMediaPayloadType media_payload_type(pt_); + + media_payload_type.encoding_name_ = name_; + media_payload_type.clock_rate_ = sample_; + media_payload_type.rtcp_fb_ = rtcp_fbs_; + + std::ostringstream format_specific_param; + if (!h264_param_.level_asymmerty_allow.empty()) { + format_specific_param << "level-asymmetry-allowed=" << h264_param_.level_asymmerty_allow; + } + if (!h264_param_.packetization_mode.empty()) { + format_specific_param << ";packetization-mode=" << h264_param_.packetization_mode; + } + if (!h264_param_.profile_level_id.empty()) { + format_specific_param << ";profile-level-id=" << h264_param_.profile_level_id; + } + + media_payload_type.format_specific_param_ = format_specific_param.str(); + + return media_payload_type; +} + +srs_error_t SrsVideoPayload::set_h264_param_desc(std::string fmtp) +{ + srs_error_t err = srs_success; + + // For example: level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f + std::vector attributes = split_str(fmtp, ";"); + + for (size_t i = 0; i < attributes.size(); ++i) { + std::string attribute = attributes.at(i); + + std::vector kv = split_str(attribute, "="); + if (kv.size() != 2) { + return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid h264 param=%s", attribute.c_str()); + } + + if (kv[0] == "profile-level-id") { + h264_param_.profile_level_id = kv[1]; + } else if (kv[0] == "packetization-mode") { + // 6.3. Non-Interleaved Mode + // This mode is in use when the value of the OPTIONAL packetization-mode + // media type parameter is equal to 1. This mode SHOULD be supported. + // It is primarily intended for low-delay applications. Only single NAL + // unit packets, STAP-As, and FU-As MAY be used in this mode. STAP-Bs, + // MTAPs, and FU-Bs MUST NOT be used. The transmission order of NAL + // units MUST comply with the NAL unit decoding order. + // @see https://tools.ietf.org/html/rfc6184#section-6.3 + h264_param_.packetization_mode = kv[1]; + } else if (kv[0] == "level-asymmetry-allowed") { + h264_param_.level_asymmerty_allow = kv[1]; + } else { + return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid h264 param=%s", kv[0].c_str()); + } + } + + return err; +} + +SrsAudioPayload::SrsAudioPayload() +{ + channel_ = 0; +} + +SrsAudioPayload::SrsAudioPayload(uint8_t pt, std::string encode_name, int sample, int channel) + :SrsCodecPayload(pt, encode_name, sample) +{ + type_ = "audio"; + channel_ = channel; + opus_param_.minptime = 0; + opus_param_.use_inband_fec = false; + opus_param_.usedtx = false; +} + +SrsAudioPayload::~SrsAudioPayload() +{ +} + +SrsAudioPayload* SrsAudioPayload::copy() +{ + SrsAudioPayload* cp = new SrsAudioPayload(); + + cp->type_ = type_; + cp->pt_ = pt_; + cp->pt_of_publisher_ = pt_of_publisher_; + cp->name_ = name_; + cp->sample_ = sample_; + cp->rtcp_fbs_ = rtcp_fbs_; + cp->channel_ = channel_; + cp->opus_param_ = opus_param_; + + return cp; +} + +SrsMediaPayloadType SrsAudioPayload::generate_media_payload_type() +{ + SrsMediaPayloadType media_payload_type(pt_); + + media_payload_type.encoding_name_ = name_; + media_payload_type.clock_rate_ = sample_; + if (channel_ != 0) { + media_payload_type.encoding_param_ = srs_int2str(channel_); + } + media_payload_type.rtcp_fb_ = rtcp_fbs_; + + std::ostringstream format_specific_param; + if (opus_param_.minptime) { + format_specific_param << "minptime=" << opus_param_.minptime; + } + if (opus_param_.use_inband_fec) { + format_specific_param << ";useinbandfec=1"; + } + if (opus_param_.usedtx) { + format_specific_param << ";usedtx=1"; + } + media_payload_type.format_specific_param_ = format_specific_param.str(); + + return media_payload_type; +} + +srs_error_t SrsAudioPayload::set_opus_param_desc(std::string fmtp) +{ + srs_error_t err = srs_success; + std::vector vec = split_str(fmtp, ";"); + for (size_t i = 0; i < vec.size(); ++i) { + std::vector kv = split_str(vec[i], "="); + if (kv.size() == 2) { + if (kv[0] == "minptime") { + opus_param_.minptime = (int)::atol(kv[1].c_str()); + } else if (kv[0] == "useinbandfec") { + opus_param_.use_inband_fec = (kv[1] == "1") ? true : false; + } else if (kv[0] == "usedtx") { + opus_param_.usedtx = (kv[1] == "1") ? true : false; + } + } else { + return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid opus param=%s", vec[i].c_str()); + } + } + + return err; +} + +SrsRedPayload::SrsRedPayload() +{ + channel_ = 0; +} + +SrsRedPayload::SrsRedPayload(uint8_t pt, std::string encode_name, int sample, int channel) + :SrsCodecPayload(pt, encode_name, sample) +{ + channel_ = channel; +} + +SrsRedPayload::~SrsRedPayload() +{ +} + +SrsRedPayload* SrsRedPayload::copy() +{ + SrsRedPayload* cp = new SrsRedPayload(); + + cp->type_ = type_; + cp->pt_ = pt_; + cp->pt_of_publisher_ = pt_of_publisher_; + cp->name_ = name_; + cp->sample_ = sample_; + cp->rtcp_fbs_ = rtcp_fbs_; + cp->channel_ = channel_; + + return cp; +} + +SrsMediaPayloadType SrsRedPayload::generate_media_payload_type() +{ + SrsMediaPayloadType media_payload_type(pt_); + + media_payload_type.encoding_name_ = name_; + media_payload_type.clock_rate_ = sample_; + if (channel_ != 0) { + media_payload_type.encoding_param_ = srs_int2str(channel_); + } + media_payload_type.rtcp_fb_ = rtcp_fbs_; + + return media_payload_type; +} + +SrsRtxPayloadDes::SrsRtxPayloadDes() +{ +} + +SrsRtxPayloadDes::SrsRtxPayloadDes(uint8_t pt, uint8_t apt):SrsCodecPayload(pt, "rtx", 8000), apt_(apt) +{ +} + +SrsRtxPayloadDes::~SrsRtxPayloadDes() +{ +} + +SrsRtxPayloadDes* SrsRtxPayloadDes::copy() +{ + SrsRtxPayloadDes* cp = new SrsRtxPayloadDes(); + + cp->type_ = type_; + cp->pt_ = pt_; + cp->pt_of_publisher_ = pt_of_publisher_; + cp->name_ = name_; + cp->sample_ = sample_; + cp->rtcp_fbs_ = rtcp_fbs_; + cp->apt_ = apt_; + + return cp; +} + +SrsMediaPayloadType SrsRtxPayloadDes::generate_media_payload_type() +{ + SrsMediaPayloadType media_payload_type(pt_); + + media_payload_type.encoding_name_ = name_; + media_payload_type.clock_rate_ = sample_; + std::ostringstream format_specific_param; + format_specific_param << "fmtp:" << pt_ << " apt="<< apt_; + + media_payload_type.format_specific_param_ = format_specific_param.str(); + + return media_payload_type; +} + +SrsRtcTrackDescription::SrsRtcTrackDescription() +{ + ssrc_ = 0; + rtx_ssrc_ = 0; + fec_ssrc_ = 0; + is_active_ = false; + + media_ = NULL; + red_ = NULL; + rtx_ = NULL; + ulpfec_ = NULL; +} + +SrsRtcTrackDescription::~SrsRtcTrackDescription() +{ + srs_freep(media_); + srs_freep(red_); + srs_freep(rtx_); + srs_freep(ulpfec_); +} + +bool SrsRtcTrackDescription::has_ssrc(uint32_t ssrc) +{ + if (!is_active_) { + return false; + } + + if (ssrc == ssrc_ || ssrc == rtx_ssrc_ || ssrc == fec_ssrc_) { + return true; + } + + return false; +} + +void SrsRtcTrackDescription::add_rtp_extension_desc(int id, std::string uri) +{ + extmaps_[id] = uri; +} + +void SrsRtcTrackDescription::set_direction(std::string direction) +{ + direction_ = direction; +} + +void SrsRtcTrackDescription::set_codec_payload(SrsCodecPayload* payload) +{ + media_ = payload; +} + +void SrsRtcTrackDescription::create_auxiliary_payload(const std::vector payloads) +{ + if (!payloads.size()) { + return; + } + + SrsMediaPayloadType payload = payloads.at(0); + if (payload.encoding_name_ == "red"){ + srs_freep(red_); + red_ = new SrsRedPayload(payload.payload_type_, "red", payload.clock_rate_, ::atol(payload.encoding_param_.c_str())); + } else if (payload.encoding_name_ == "rtx") { + srs_freep(rtx_); + // TODO: FIXME: Rtx clock_rate should be payload.clock_rate_ + rtx_ = new SrsRtxPayloadDes(payload.payload_type_, ::atol(payload.encoding_param_.c_str())); + } else if (payload.encoding_name_ == "ulpfec") { + srs_freep(ulpfec_); + ulpfec_ = new SrsCodecPayload(payload.payload_type_, "ulpfec", payload.clock_rate_); + } +} + +void SrsRtcTrackDescription::set_rtx_ssrc(uint32_t ssrc) +{ + rtx_ssrc_ = ssrc; +} + +void SrsRtcTrackDescription::set_fec_ssrc(uint32_t ssrc) +{ + fec_ssrc_ = ssrc; +} + +void SrsRtcTrackDescription::set_mid(std::string mid) +{ + mid_ = mid; +} + +int SrsRtcTrackDescription::get_rtp_extension_id(std::string uri) +{ + for(std::map::iterator it = extmaps_.begin(); it != extmaps_.end(); ++it) { + if(uri == it->second) { + return it->first; + } + } + + return 0; +} + +SrsRtcTrackDescription* SrsRtcTrackDescription::copy() +{ + SrsRtcTrackDescription* cp = new SrsRtcTrackDescription(); + + cp->type_ = type_; + cp->id_ = id_; + cp->ssrc_ = ssrc_; + cp->fec_ssrc_ = fec_ssrc_; + cp->rtx_ssrc_ = rtx_ssrc_; + cp->extmaps_ = extmaps_; + cp->direction_ = direction_; + cp->mid_ = mid_; + cp->msid_ = msid_; + cp->is_active_ = is_active_; + cp->media_ = media_ ? media_->copy():NULL; + cp->red_ = red_ ? red_->copy():NULL; + cp->rtx_ = rtx_ ? rtx_->copy():NULL; + cp->ulpfec_ = ulpfec_ ? ulpfec_->copy():NULL; + + return cp; +} + +SrsRtcStreamDescription::SrsRtcStreamDescription() +{ + audio_track_desc_ = NULL; +} + +SrsRtcStreamDescription::~SrsRtcStreamDescription() +{ + srs_freep(audio_track_desc_); + + for (int i = 0; i < (int)video_track_descs_.size(); ++i) { + srs_freep(video_track_descs_.at(i)); + } + video_track_descs_.clear(); +} + +SrsRtcStreamDescription* SrsRtcStreamDescription::copy() +{ + SrsRtcStreamDescription* stream_desc = new SrsRtcStreamDescription(); + + if (audio_track_desc_) { + stream_desc->audio_track_desc_ = audio_track_desc_->copy(); + } + + for (int i = 0; i < (int)video_track_descs_.size(); ++i) { + stream_desc->video_track_descs_.push_back(video_track_descs_.at(i)->copy()); + } + + return stream_desc; +} + +SrsRtcTrackDescription* SrsRtcStreamDescription::find_track_description_by_ssrc(uint32_t ssrc) +{ + if (audio_track_desc_ && audio_track_desc_->has_ssrc(ssrc)) { + return audio_track_desc_; + } + + for (int i = 0; i < (int)video_track_descs_.size(); ++i) { + if (video_track_descs_.at(i)->has_ssrc(ssrc)) { + return video_track_descs_.at(i); + } + } + + return NULL; +} + +SrsRtcTrackStatistic::SrsRtcTrackStatistic() +{ + packets = 0; + last_packets = 0; + bytes = 0; + last_bytes = 0; + nacks = 0; + last_nacks = 0; + padding_packets = 0; + last_padding_packets = 0; + padding_bytes = 0; + last_padding_bytes = 0; + replay_packets = 0; + last_replay_packets = 0; + replay_bytes = 0; + last_replay_bytes = 0; +} + +SrsRtcRecvTrack::SrsRtcRecvTrack(SrsRtcConnection* session, SrsRtcTrackDescription* track_desc, bool is_audio) +{ + session_ = session; + track_desc_ = track_desc->copy(); + statistic_ = new SrsRtcTrackStatistic(); + + if (is_audio) { + rtp_queue_ = new SrsRtpRingBuffer(100); + nack_receiver_ = new SrsRtpNackForReceiver(rtp_queue_, 100 * 2 / 3); + } else { + rtp_queue_ = new SrsRtpRingBuffer(1000); + nack_receiver_ = new SrsRtpNackForReceiver(rtp_queue_, 1000 * 2 / 3); + } + + last_sender_report_sys_time = 0; +} + +SrsRtcRecvTrack::~SrsRtcRecvTrack() +{ + srs_freep(rtp_queue_); + srs_freep(nack_receiver_); + srs_freep(track_desc_); + srs_freep(statistic_); +} + +bool SrsRtcRecvTrack::has_ssrc(uint32_t ssrc) +{ + return track_desc_->has_ssrc(ssrc); +} + +uint32_t SrsRtcRecvTrack::get_ssrc() +{ + return track_desc_->ssrc_; +} + +void SrsRtcRecvTrack::update_rtt(int rtt) +{ + nack_receiver_->update_rtt(rtt); +} + +void SrsRtcRecvTrack::update_send_report_time(const SrsNtp& ntp) +{ + last_sender_report_ntp = ntp; + last_sender_report_sys_time = srs_update_system_time();; +} + +srs_error_t SrsRtcRecvTrack::send_rtcp_rr() +{ + srs_error_t err = srs_success; + + uint32_t ssrc = track_desc_->ssrc_; + const uint64_t& last_time = last_sender_report_sys_time; + if ((err = session_->send_rtcp_rr(ssrc, rtp_queue_, last_time, last_sender_report_ntp)) != srs_success) { + return srs_error_wrap(err, "ssrc=%u, last_time=%" PRId64, ssrc, last_time); + } + + return err; +} + +srs_error_t SrsRtcRecvTrack::send_rtcp_xr_rrtr() +{ + srs_error_t err = srs_success; + + if ((err = session_->send_rtcp_xr_rrtr(track_desc_->ssrc_)) != srs_success) { + return srs_error_wrap(err, "ssrc=%u", track_desc_->ssrc_); + } + + return err; +} + +bool SrsRtcRecvTrack::set_track_status(bool active) +{ + bool previous_status = track_desc_->is_active_; + track_desc_->is_active_ = active; + return previous_status; +} + +bool SrsRtcRecvTrack::get_track_status() +{ + return track_desc_->is_active_; +} + +std::string SrsRtcRecvTrack::get_track_id() +{ + return track_desc_->id_; +} + +srs_error_t SrsRtcRecvTrack::on_nack(SrsRtpPacket2* pkt) +{ + srs_error_t err = srs_success; + + uint16_t seq = pkt->header.get_sequence(); + SrsRtpNackInfo* nack_info = nack_receiver_->find(seq); + if (nack_info) { + // seq had been received. + nack_receiver_->remove(seq); + return err; + } + + // insert check nack list + uint16_t nack_first = 0, nack_last = 0; + if (!rtp_queue_->update(seq, nack_first, nack_last)) { + srs_warn("NACK: too old seq %u, range [%u, %u]", seq, rtp_queue_->begin, rtp_queue_->end); + } + if (srs_rtp_seq_distance(nack_first, nack_last) > 0) { + srs_trace("NACK: update seq=%u, nack range [%u, %u]", seq, nack_first, nack_last); + nack_receiver_->insert(nack_first, nack_last); + nack_receiver_->check_queue_size(); + } + + // insert into video_queue and audio_queue + rtp_queue_->set(seq, pkt->copy()); + + return err; +} + +srs_error_t SrsRtcRecvTrack::do_check_send_nacks(uint32_t& timeout_nacks) +{ + srs_error_t err = srs_success; + + uint32_t sent_nacks = 0; + session_->check_send_nacks(nack_receiver_, track_desc_->ssrc_, sent_nacks, timeout_nacks); + statistic_->nacks += sent_nacks; + + return err; +} + +SrsRtcAudioRecvTrack::SrsRtcAudioRecvTrack(SrsRtcConnection* session, SrsRtcTrackDescription* track_desc) + : SrsRtcRecvTrack(session, track_desc, true) +{ +} + +SrsRtcAudioRecvTrack::~SrsRtcAudioRecvTrack() +{ +} + +srs_error_t SrsRtcAudioRecvTrack::on_rtp(SrsRtcStream* source, SrsRtpPacket2* pkt) +{ + srs_error_t err = srs_success; + + // connection level statistic + session_->stat_->nn_in_audios++; + + // track level statistic + statistic_->packets++; + statistic_->bytes += pkt->nb_bytes(); + + if ((err = source->on_rtp(pkt)) != srs_success) { + return srs_error_wrap(err, "source on rtp"); + } + + // For NACK to handle packet. + if ((err = on_nack(pkt)) != srs_success) { + return srs_error_wrap(err, "on nack"); + } + + return err; +} + +srs_error_t SrsRtcAudioRecvTrack::check_send_nacks() +{ + srs_error_t err = srs_success; + + uint32_t timeout_nacks = 0; + if ((err = do_check_send_nacks(timeout_nacks)) != srs_success) { + return srs_error_wrap(err, "audio"); + } + + return err; +} + +SrsRtcVideoRecvTrack::SrsRtcVideoRecvTrack(SrsRtcConnection* session, SrsRtcTrackDescription* track_desc) + : SrsRtcRecvTrack(session, track_desc, false) +{ +} + +SrsRtcVideoRecvTrack::~SrsRtcVideoRecvTrack() +{ +} + +srs_error_t SrsRtcVideoRecvTrack::on_rtp(SrsRtcStream* source, SrsRtpPacket2* pkt) +{ + srs_error_t err = srs_success; + + // connection level statistic + session_->stat_->nn_in_videos++; + + // track level statistic + statistic_->packets++; + statistic_->bytes += pkt->nb_bytes(); + + pkt->frame_type = SrsFrameTypeVideo; + + if ((err = source->on_rtp(pkt)) != srs_success) { + return srs_error_wrap(err, "source on rtp"); + } + + // For NACK to handle packet. + if ((err = on_nack(pkt)) != srs_success) { + return srs_error_wrap(err, "on nack"); + } + + return err; +} + +srs_error_t SrsRtcVideoRecvTrack::check_send_nacks() +{ + srs_error_t err = srs_success; + + uint32_t timeout_nacks = 0; + if ((err = do_check_send_nacks(timeout_nacks)) != srs_success) { + return srs_error_wrap(err, "video"); + } + + // If NACK timeout, start PLI if not requesting. + if (timeout_nacks == 0) { + return err; + } + + srs_trace2(TAG_MAYBE, "RTC: NACK timeout=%u, request PLI, track=%s, ssrc=%u", timeout_nacks, + track_desc_->id_.c_str(), track_desc_->ssrc_); + + return err; +} + +SrsRtcSendTrack::SrsRtcSendTrack(SrsRtcConnection* session, SrsRtcTrackDescription* track_desc, bool is_audio) +{ + session_ = session; + track_desc_ = track_desc->copy(); + statistic_ = new SrsRtcTrackStatistic(); + + if (is_audio) { + rtp_queue_ = new SrsRtpRingBuffer(100); + } else { + rtp_queue_ = new SrsRtpRingBuffer(1000); + } + + nack_epp = new SrsErrorPithyPrint(); +} + +SrsRtcSendTrack::~SrsRtcSendTrack() +{ + srs_freep(rtp_queue_); + srs_freep(track_desc_); + srs_freep(statistic_); + srs_freep(nack_epp); +} + +bool SrsRtcSendTrack::has_ssrc(uint32_t ssrc) +{ + return track_desc_->has_ssrc(ssrc); +} + +SrsRtpPacket2* SrsRtcSendTrack::fetch_rtp_packet(uint16_t seq) +{ + SrsRtpPacket2* pkt = rtp_queue_->at(seq); + + if (pkt == NULL) { + return pkt; + } + + // For NACK, it sequence must match exactly, or it cause SRTP fail. + // Return packet only when sequence is equal. + if (pkt->header.get_sequence() == seq) { + return pkt; + } + + // Ignore if sequence not match. + uint32_t nn = 0; + if (nack_epp->can_print(pkt->header.get_ssrc(), &nn)) { + srs_trace("RTC: NACK miss seq=%u, require_seq=%u, ssrc=%u, ts=%u, count=%u/%u, %d bytes", seq, pkt->header.get_sequence(), + pkt->header.get_ssrc(), pkt->header.get_timestamp(), nn, nack_epp->nn_count, pkt->nb_bytes()); + } + return NULL; +} + +// TODO: FIXME: Should refine logs, set tracks in a time. +bool SrsRtcSendTrack::set_track_status(bool active) +{ + bool previous_status = track_desc_->is_active_; + track_desc_->is_active_ = active; + return previous_status; +} + +bool SrsRtcSendTrack::get_track_status() +{ + return track_desc_->is_active_; +} + +std::string SrsRtcSendTrack::get_track_id() +{ + return track_desc_->id_; +} + +void SrsRtcSendTrack::on_recv_nack() +{ + SrsRtcTrackStatistic* statistic = statistic_; + + statistic->nacks++; +} + +SrsRtcAudioSendTrack::SrsRtcAudioSendTrack(SrsRtcConnection* session, SrsRtcTrackDescription* track_desc) + : SrsRtcSendTrack(session, track_desc, true) +{ +} + +SrsRtcAudioSendTrack::~SrsRtcAudioSendTrack() +{ +} + +srs_error_t SrsRtcAudioSendTrack::on_rtp(SrsRtpPacket2* pkt, SrsRtcPlayStreamStatistic& info) +{ + srs_error_t err = srs_success; + + if (!track_desc_->is_active_) { + return err; + } + + pkt->header.set_ssrc(track_desc_->ssrc_); + + // Should update PT, because subscriber may use different PT to publisher. + if (track_desc_->media_ && pkt->header.get_payload_type() == track_desc_->media_->pt_of_publisher_) { + // If PT is media from publisher, change to PT of media for subscriber. + pkt->header.set_payload_type(track_desc_->media_->pt_); + } else if (track_desc_->red_ && pkt->header.get_payload_type() == track_desc_->red_->pt_of_publisher_) { + // If PT is RED from publisher, change to PT of RED for subscriber. + pkt->header.set_payload_type(track_desc_->red_->pt_); + } else { + // TODO: FIXME: Should update PT for RTX. + } + + // Put rtp packet to NACK/ARQ queue + if (true) { + SrsRtpPacket2* nack = pkt->copy(); + rtp_queue_->set(nack->header.get_sequence(), nack); + } + + // Update stats. + info.nn_bytes += pkt->nb_bytes(); + info.nn_audios++; + session_->stat_->nn_out_audios++; + + // track level statistic + // TODO: FIXME: if send packets failed, statistic is no correct. + statistic_->packets++; + statistic_->bytes += pkt->nb_bytes(); + + if (true) { + std::vector pkts; + pkts.push_back(pkt); + + if ((err = session_->do_send_packets(pkts, info)) != srs_success) { + return srs_error_wrap(err, "raw send"); + } + } + + return err; +} + +srs_error_t SrsRtcAudioSendTrack::on_rtcp(SrsRtpPacket2* pkt) +{ + srs_error_t err = srs_success; + // process rtcp + return err; +} + +SrsRtcVideoSendTrack::SrsRtcVideoSendTrack(SrsRtcConnection* session, SrsRtcTrackDescription* track_desc) + : SrsRtcSendTrack(session, track_desc, false) +{ +} + +SrsRtcVideoSendTrack::~SrsRtcVideoSendTrack() +{ +} + +srs_error_t SrsRtcVideoSendTrack::on_rtp(SrsRtpPacket2* pkt, SrsRtcPlayStreamStatistic& info) +{ + srs_error_t err = srs_success; + + if (!track_desc_->is_active_) { + return err; + } + + SrsRtcTrackStatistic* statistic = statistic_; + + pkt->header.set_ssrc(track_desc_->ssrc_); + + // Should update PT, because subscriber may use different PT to publisher. + if (track_desc_->media_ && pkt->header.get_payload_type() == track_desc_->media_->pt_of_publisher_) { + // If PT is media from publisher, change to PT of media for subscriber. + pkt->header.set_payload_type(track_desc_->media_->pt_); + } else if (track_desc_->red_ && pkt->header.get_payload_type() == track_desc_->red_->pt_of_publisher_) { + // If PT is RED from publisher, change to PT of RED for subscriber. + pkt->header.set_payload_type(track_desc_->red_->pt_); + } else { + // TODO: FIXME: Should update PT for RTX. + } + + // Put rtp packet to NACK/ARQ queue + if (true) { + SrsRtpPacket2* nack = pkt->copy(); + rtp_queue_->set(nack->header.get_sequence(), nack); + } + + // Update stats. + info.nn_bytes += pkt->nb_bytes(); + info.nn_videos++; + session_->stat_->nn_out_videos++; + + // track level statistic + // TODO: FIXME: if send packets failed, statistic is no correct. + statistic->packets++; + statistic->bytes += pkt->nb_bytes(); + + if (true) { + std::vector pkts; + pkts.push_back(pkt); + + if ((err = session_->do_send_packets(pkts, info)) != srs_success) { + return srs_error_wrap(err, "raw send"); + } + } + + return err; +} + +srs_error_t SrsRtcVideoSendTrack::on_rtcp(SrsRtpPacket2* pkt) +{ + srs_error_t err = srs_success; + // process rtcp + return err; +} + +SrsRtcSSRCGenerator* SrsRtcSSRCGenerator::_instance = NULL; + +SrsRtcSSRCGenerator::SrsRtcSSRCGenerator() +{ + ssrc_num = 0; +} + +SrsRtcSSRCGenerator::~SrsRtcSSRCGenerator() +{ +} + +SrsRtcSSRCGenerator* SrsRtcSSRCGenerator::instance() +{ + if (!_instance) { + _instance = new SrsRtcSSRCGenerator(); + } + return _instance; +} + +uint32_t SrsRtcSSRCGenerator::generate_ssrc() +{ + if (!ssrc_num) { + ssrc_num = ::getpid() * 10000 + ::getpid() * 100 + ::getpid(); + } + + return ++ssrc_num; +} + diff --git a/trunk/src/app/srs_app_rtc_source.hpp b/trunk/src/app/srs_app_rtc_source.hpp new file mode 100644 index 000000000..bd6328aa7 --- /dev/null +++ b/trunk/src/app/srs_app_rtc_source.hpp @@ -0,0 +1,618 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2013-2020 John + * + * 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_RTC_SOURCE_HPP +#define SRS_APP_RTC_SOURCE_HPP + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +class SrsRequest; +class SrsMetaCache; +class SrsSharedPtrMessage; +class SrsCommonMessage; +class SrsMessageArray; +class SrsRtcStream; +class SrsRtcFromRtmpBridger; +class SrsAudioRecode; +class SrsRtpPacket2; +class SrsSample; +class SrsRtcStreamDescription; +class SrsRtcTrackDescription; +class SrsRtcConnection; +class SrsRtpRingBuffer; +class SrsRtpNackForReceiver; +class SrsJsonObject; +class SrsRtcPlayStreamStatistic; +class SrsErrorPithyPrint; +class SrsRtcDummyBridger; + +class SrsNtp +{ +public: + uint64_t system_ms_; + uint64_t ntp_; + uint32_t ntp_second_; + uint32_t ntp_fractions_; +public: + SrsNtp(); + virtual ~SrsNtp(); +public: + static SrsNtp from_time_ms(uint64_t ms); + static SrsNtp to_time_ms(uint64_t ntp); +public: + static uint64_t kMagicNtpFractionalUnit; +}; + +class SrsRtcConsumer +{ +private: + SrsRtcStream* source; + std::vector queue; + // when source id changed, notice all consumers + bool should_update_source_id; + // The cond wait for mw. + // @see https://github.com/ossrs/srs/issues/251 + srs_cond_t mw_wait; + bool mw_waiting; + int mw_min_msgs; +public: + SrsRtcConsumer(SrsRtcStream* s); + virtual ~SrsRtcConsumer(); +public: + // When source id changed, notice client to print. + virtual void update_source_id(); + // Put RTP packet into queue. + // @note We do not drop packet here, but drop it in sender. + srs_error_t enqueue(SrsRtpPacket2* pkt); + // Get all RTP packets from queue. + virtual srs_error_t dump_packets(std::vector& pkts); + // Wait for at-least some messages incoming in queue. + virtual void wait(int nb_msgs); +}; + +class SrsRtcStreamManager +{ +private: + srs_mutex_t lock; + std::map pool; +public: + SrsRtcStreamManager(); + virtual ~SrsRtcStreamManager(); +public: + // create source when fetch from cache failed. + // @param r the client request. + // @param pps the matched source, if success never be NULL. + virtual srs_error_t fetch_or_create(SrsRequest* r, SrsRtcStream** pps); +private: + // Get the exists source, NULL when not exists. + // update the request and return the exists source. + virtual SrsRtcStream* fetch(SrsRequest* r); +}; + +// Global singleton instance. +extern SrsRtcStreamManager* _srs_rtc_sources; + +// A publish stream interface, for source to callback with. +class ISrsRtcPublishStream +{ +public: + ISrsRtcPublishStream(); + virtual ~ISrsRtcPublishStream(); +public: + // Request keyframe(PLI) from publisher, for fresh consumer. + virtual void request_keyframe(uint32_t ssrc) = 0; +}; + +class ISrsRtcStreamEventHandler +{ +public: + ISrsRtcStreamEventHandler(); + virtual ~ISrsRtcStreamEventHandler(); +public: + // stream unpublish, sync API. + virtual void on_unpublish() = 0; + // no player subscribe this stream, sync API + virtual void on_consumers_finished() = 0; +}; + +// A Source is a stream, to publish and to play with, binding to SrsRtcPublishStream and SrsRtcPlayStream. +class SrsRtcStream +{ +private: + // For publish, it's the publish client id. + // For edge, it's the edge ingest id. + // when source id changed, for example, the edge reconnect, + // invoke the on_source_id_changed() to let all clients know. + SrsContextId _source_id; + // previous source id. + SrsContextId _pre_source_id; + SrsRequest* req; + ISrsRtcPublishStream* publish_stream_; + // Transmux RTMP to RTC. + SrsRtcDummyBridger* bridger_; + // Steam description for this steam. + SrsRtcStreamDescription* stream_desc_; +private: + // To delivery stream to clients. + std::vector consumers; + // Whether stream is created, that is, SDP is done. + bool is_created_; + // Whether stream is delivering data, that is, DTLS is done. + bool is_delivering_packets_; + // Notify stream event to event handler + std::vector event_handlers_; +public: + SrsRtcStream(); + virtual ~SrsRtcStream(); +public: + virtual srs_error_t initialize(SrsRequest* r); + // Update the authentication information in request. + virtual void update_auth(SrsRequest* r); + // The source id changed. + virtual srs_error_t on_source_id_changed(SrsContextId id); + // Get current source id. + virtual SrsContextId source_id(); + virtual SrsContextId pre_source_id(); + // Get the bridger. + ISrsSourceBridger* bridger(); +public: + // Create consumer + // @param consumer, output the create consumer. + virtual srs_error_t create_consumer(SrsRtcConsumer*& consumer); + // Dumps packets in cache to consumer. + // @param ds, whether dumps the sequence header. + // @param dm, whether dumps the metadata. + // @param dg, whether dumps the gop cache. + virtual srs_error_t consumer_dumps(SrsRtcConsumer* consumer, bool ds = true, bool dm = true, bool dg = true); + virtual void on_consumer_destroy(SrsRtcConsumer* consumer); + // Whether we can publish stream to the source, return false if it exists. + // @remark Note that when SDP is done, we set the stream is not able to publish. + virtual bool can_publish(); + // For RTC, the stream is created when SDP is done, and then do DTLS + virtual void set_stream_created(); + // When start publish stream. + virtual srs_error_t on_publish(); + // When stop publish stream. + virtual void on_unpublish(); +public: + // For event handler + void subscribe(ISrsRtcStreamEventHandler* h); + void unsubscribe(ISrsRtcStreamEventHandler* h); +public: + // Get and set the publisher, passed to consumer to process requests such as PLI. + ISrsRtcPublishStream* publish_stream(); + void set_publish_stream(ISrsRtcPublishStream* v); + // Consume the shared RTP packet, user must free it. + srs_error_t on_rtp(SrsRtpPacket2* pkt); + // Set and get stream description for souce + bool has_stream_desc(); + void set_stream_desc(SrsRtcStreamDescription* stream_desc); + std::vector get_track_desc(std::string type, std::string media_type); +}; + +#ifdef SRS_FFMPEG_FIT +class SrsRtcFromRtmpBridger : public ISrsSourceBridger +{ +private: + SrsRequest* req; + SrsRtcStream* source_; + // The format, codec information. + SrsRtmpFormat* format; + // The metadata cache. + SrsMetaCache* meta; +private: + bool discard_aac; + SrsAudioRecode* codec; + bool discard_bframe; + bool merge_nalus; + uint32_t audio_timestamp; + uint16_t audio_sequence; + uint16_t video_sequence; + uint32_t audio_ssrc; + uint32_t video_ssrc; +public: + SrsRtcFromRtmpBridger(SrsRtcStream* source); + virtual ~SrsRtcFromRtmpBridger(); +public: + virtual srs_error_t initialize(SrsRequest* r); + virtual srs_error_t on_publish(); + virtual void on_unpublish(); + virtual srs_error_t on_audio(SrsSharedPtrMessage* msg); +private: + srs_error_t transcode(char* adts_audio, int nn_adts_audio); + srs_error_t package_opus(char* data, int size, SrsRtpPacket2** ppkt); +public: + virtual srs_error_t on_video(SrsSharedPtrMessage* msg); +private: + srs_error_t filter(SrsSharedPtrMessage* msg, SrsFormat* format, bool& has_idr, std::vector& samples); + srs_error_t package_stap_a(SrsRtcStream* source, SrsSharedPtrMessage* msg, SrsRtpPacket2** ppkt); + srs_error_t package_nalus(SrsSharedPtrMessage* msg, const std::vector& samples, std::vector& pkts); + srs_error_t package_single_nalu(SrsSharedPtrMessage* msg, SrsSample* sample, std::vector& pkts); + srs_error_t package_fu_a(SrsSharedPtrMessage* msg, SrsSample* sample, int fu_payload_size, std::vector& pkts); + srs_error_t consume_packets(std::vector& pkts); +}; +#endif + +class SrsRtcDummyBridger : public ISrsSourceBridger +{ +private: + SrsRtcStream* rtc_; + // The optional implementation bridger, ignore if NULL. + ISrsSourceBridger* impl_; +public: + SrsRtcDummyBridger(SrsRtcStream* s); + virtual ~SrsRtcDummyBridger(); +public: + virtual srs_error_t on_publish(); + virtual srs_error_t on_audio(SrsSharedPtrMessage* audio); + virtual srs_error_t on_video(SrsSharedPtrMessage* video); + virtual void on_unpublish(); +public: + // Setup a new implementation bridger, which might be NULL to free previous one. + void setup(ISrsSourceBridger* impl); +}; + +// TODO: FIXME: Rename it. +class SrsCodecPayload +{ +public: + std::string type_; + uint8_t pt_; + // for publish, equals to PT of itself; + // for subscribe, is the PT of publisher; + uint8_t pt_of_publisher_; + std::string name_; + int sample_; + + std::vector rtcp_fbs_; +public: + SrsCodecPayload(); + SrsCodecPayload(uint8_t pt, std::string encode_name, int sample); + virtual ~SrsCodecPayload(); +public: + virtual SrsCodecPayload* copy(); + virtual SrsMediaPayloadType generate_media_payload_type(); +}; + +// TODO: FIXME: Rename it. +class SrsVideoPayload : public SrsCodecPayload +{ +public: + struct H264SpecificParameter + { + std::string profile_level_id; + std::string packetization_mode; + std::string level_asymmerty_allow; + }; + H264SpecificParameter h264_param_; + +public: + SrsVideoPayload(); + SrsVideoPayload(uint8_t pt, std::string encode_name, int sample); + virtual ~SrsVideoPayload(); +public: + virtual SrsVideoPayload* copy(); + virtual SrsMediaPayloadType generate_media_payload_type(); +public: + srs_error_t set_h264_param_desc(std::string fmtp); +}; + +// TODO: FIXME: Rename it. +class SrsAudioPayload : public SrsCodecPayload +{ + struct SrsOpusParameter + { + int minptime; + bool use_inband_fec; + bool usedtx; + + SrsOpusParameter() { + minptime = 0; + use_inband_fec = false; + usedtx = false; + } + }; + +public: + int channel_; + SrsOpusParameter opus_param_; +public: + SrsAudioPayload(); + SrsAudioPayload(uint8_t pt, std::string encode_name, int sample, int channel); + virtual ~SrsAudioPayload(); +public: + virtual SrsAudioPayload* copy(); + virtual SrsMediaPayloadType generate_media_payload_type(); +public: + srs_error_t set_opus_param_desc(std::string fmtp); +}; + +// TODO: FIXME: Rename it. +class SrsRedPayload : public SrsCodecPayload +{ +public: + int channel_; +public: + SrsRedPayload(); + SrsRedPayload(uint8_t pt, std::string encode_name, int sample, int channel); + virtual ~SrsRedPayload(); +public: + virtual SrsRedPayload* copy(); + virtual SrsMediaPayloadType generate_media_payload_type(); +}; + +class SrsRtxPayloadDes : public SrsCodecPayload +{ +public: + uint8_t apt_; +public: + SrsRtxPayloadDes(); + SrsRtxPayloadDes(uint8_t pt, uint8_t apt); + virtual ~SrsRtxPayloadDes(); + +public: + virtual SrsRtxPayloadDes* copy(); + virtual SrsMediaPayloadType generate_media_payload_type(); +}; + +class SrsRtcTrackDescription +{ +public: + // type: audio, video + std::string type_; + // track_id + std::string id_; + // ssrc is the primary ssrc for this track, + // if sdp has ssrc-group, it is the first ssrc of the ssrc-group + uint32_t ssrc_; + // rtx ssrc is the second ssrc of "FEC" src-group, + // if no rtx ssrc, rtx_ssrc_ = 0. + uint32_t fec_ssrc_; + // rtx ssrc is the second ssrc of "FID" src-group, + // if no rtx ssrc, rtx_ssrc_ = 0. + uint32_t rtx_ssrc_; + // key: rtp header extension id, value: rtp header extension uri. + std::map extmaps_; + // Whether this track active. default: active. + bool is_active_; + // direction + std::string direction_; + // mid is used in BOUNDLE + std::string mid_; + // msid_: track stream id + std::string msid_; + + // meida payload, such as opus, h264. + SrsCodecPayload* media_; + SrsCodecPayload* red_; + SrsCodecPayload* rtx_; + SrsCodecPayload* ulpfec_; +public: + SrsRtcTrackDescription(); + virtual ~SrsRtcTrackDescription(); +public: + // whether or not the track has ssrc. + // for example: + // we need check track has the ssrc in the ssrc_group, then add ssrc_group to the track, + bool has_ssrc(uint32_t ssrc); +public: + void add_rtp_extension_desc(int id, std::string uri); + void set_direction(std::string direction); + void set_codec_payload(SrsCodecPayload* payload); + // auxiliary paylod include red, rtx, ulpfec. + void create_auxiliary_payload(const std::vector payload_types); + void set_rtx_ssrc(uint32_t ssrc); + void set_fec_ssrc(uint32_t ssrc); + void set_mid(std::string mid); + int get_rtp_extension_id(std::string uri); +public: + SrsRtcTrackDescription* copy(); +}; + +class SrsRtcStreamDescription +{ +public: + // the id for this stream; + std::string id_; + + SrsRtcTrackDescription* audio_track_desc_; + std::vector video_track_descs_; +public: + SrsRtcStreamDescription(); + virtual ~SrsRtcStreamDescription(); + +public: + SrsRtcStreamDescription* copy(); + SrsRtcTrackDescription* find_track_description_by_ssrc(uint32_t ssrc); +}; + +class SrsRtcTrackStatistic +{ +public: + // packets received or sent. + uint32_t packets; + // packets received or sent at last statistic time. + uint32_t last_packets; + // bytes received or sent. + uint64_t bytes; + // bytes received or sent at last statistic time. + uint32_t last_bytes; + + // nacks received or sent. + uint32_t nacks; + // nacks received or sent at last statistic time. + uint32_t last_nacks; + + // padding packets received or sent. + uint32_t padding_packets; + // padding packets received or sent at last statistic time. + uint32_t last_padding_packets; + // padding bytes received or sent. + uint32_t padding_bytes; + // padding bytes received or sent at last statistic time. + uint32_t last_padding_bytes; + + // replay packets received or sent. + uint32_t replay_packets; + // replay packets received or sent at last statistic time. + uint32_t last_replay_packets; + // replay bytes received or sent. + uint64_t replay_bytes; + // replay bytes received or sent at last statistic time. + uint64_t last_replay_bytes; + +public: + SrsRtcTrackStatistic(); +}; + +class SrsRtcRecvTrack +{ +protected: + SrsRtcTrackDescription* track_desc_; + SrsRtcTrackStatistic* statistic_; + + SrsRtcConnection* session_; + SrsRtpRingBuffer* rtp_queue_; + SrsRtpNackForReceiver* nack_receiver_; + + // send report ntp and received time. + SrsNtp last_sender_report_ntp; + uint64_t last_sender_report_sys_time; +public: + SrsRtcRecvTrack(SrsRtcConnection* session, SrsRtcTrackDescription* stream_descs, bool is_audio); + virtual ~SrsRtcRecvTrack(); +public: + bool has_ssrc(uint32_t ssrc); + uint32_t get_ssrc(); + void update_rtt(int rtt); + void update_send_report_time(const SrsNtp& ntp); + srs_error_t send_rtcp_rr(); + srs_error_t send_rtcp_xr_rrtr(); + bool set_track_status(bool active); + bool get_track_status(); + std::string get_track_id(); +protected: + srs_error_t on_nack(SrsRtpPacket2* pkt); +public: + virtual srs_error_t on_rtp(SrsRtcStream* source, SrsRtpPacket2* pkt) = 0; + virtual srs_error_t check_send_nacks() = 0; +protected: + virtual srs_error_t do_check_send_nacks(uint32_t& timeout_nacks); +}; + +class SrsRtcAudioRecvTrack : public SrsRtcRecvTrack +{ +public: + SrsRtcAudioRecvTrack(SrsRtcConnection* session, SrsRtcTrackDescription* track_desc); + virtual ~SrsRtcAudioRecvTrack(); +public: + virtual srs_error_t on_rtp(SrsRtcStream* source, SrsRtpPacket2* pkt); + virtual srs_error_t check_send_nacks(); +}; + +class SrsRtcVideoRecvTrack : public SrsRtcRecvTrack +{ +public: + SrsRtcVideoRecvTrack(SrsRtcConnection* session, SrsRtcTrackDescription* stream_descs); + virtual ~SrsRtcVideoRecvTrack(); +public: + virtual srs_error_t on_rtp(SrsRtcStream* source, SrsRtpPacket2* pkt); + virtual srs_error_t check_send_nacks(); +}; + +class SrsRtcSendTrack +{ +protected: + // send track description + SrsRtcTrackDescription* track_desc_; + SrsRtcTrackStatistic* statistic_; +protected: + // The owner connection for this track. + SrsRtcConnection* session_; + // NACK ARQ ring buffer. + SrsRtpRingBuffer* rtp_queue_; +private: + // The pithy print for special stage. + SrsErrorPithyPrint* nack_epp; +public: + SrsRtcSendTrack(SrsRtcConnection* session, SrsRtcTrackDescription* track_desc, bool is_audio); + virtual ~SrsRtcSendTrack(); +public: + bool has_ssrc(uint32_t ssrc); + SrsRtpPacket2* fetch_rtp_packet(uint16_t seq); + bool set_track_status(bool active); + bool get_track_status(); + std::string get_track_id(); +public: + virtual srs_error_t on_rtp(SrsRtpPacket2* pkt, SrsRtcPlayStreamStatistic& info) = 0; + virtual srs_error_t on_rtcp(SrsRtpPacket2* pkt) = 0; + virtual void on_recv_nack(); +}; + +class SrsRtcAudioSendTrack : public SrsRtcSendTrack +{ +public: + SrsRtcAudioSendTrack(SrsRtcConnection* session, SrsRtcTrackDescription* track_desc); + virtual ~SrsRtcAudioSendTrack(); +public: + virtual srs_error_t on_rtp(SrsRtpPacket2* pkt, SrsRtcPlayStreamStatistic& info); + virtual srs_error_t on_rtcp(SrsRtpPacket2* pkt); +}; + +class SrsRtcVideoSendTrack : public SrsRtcSendTrack +{ +public: + SrsRtcVideoSendTrack(SrsRtcConnection* session, SrsRtcTrackDescription* track_desc); + virtual ~SrsRtcVideoSendTrack(); +public: + virtual srs_error_t on_rtp(SrsRtpPacket2* pkt, SrsRtcPlayStreamStatistic& info); + virtual srs_error_t on_rtcp(SrsRtpPacket2* pkt); +}; + +class SrsRtcSSRCGenerator +{ +private: + static SrsRtcSSRCGenerator* _instance; +private: + uint32_t ssrc_num; +private: + SrsRtcSSRCGenerator(); + virtual ~SrsRtcSSRCGenerator(); +public: + static SrsRtcSSRCGenerator* instance(); + uint32_t generate_ssrc(); +}; + +#endif + diff --git a/trunk/src/app/srs_app_rtmp_conn.cpp b/trunk/src/app/srs_app_rtmp_conn.cpp index 0f8b1012a..e41b1ec47 100644 --- a/trunk/src/app/srs_app_rtmp_conn.cpp +++ b/trunk/src/app/srs_app_rtmp_conn.cpp @@ -81,13 +81,13 @@ SrsSimpleRtmpClient::~SrsSimpleRtmpClient() srs_error_t SrsSimpleRtmpClient::connect_app() { - std::vector ips = srs_get_local_ips(); + std::vector& ips = srs_get_local_ips(); assert(_srs_config->get_stats_network() < (int)ips.size()); - std::string local_ip = ips[_srs_config->get_stats_network()]; + SrsIPAddress* local_ip = ips[_srs_config->get_stats_network()]; bool debug_srs_upnode = _srs_config->get_debug_srs_upnode(req->vhost); - return do_connect_app(local_ip, debug_srs_upnode); + return do_connect_app(local_ip->ip, debug_srs_upnode); } SrsClientInfo::SrsClientInfo() @@ -104,9 +104,23 @@ SrsClientInfo::~SrsClientInfo() srs_freep(res); } -SrsRtmpConn::SrsRtmpConn(SrsServer* svr, srs_netfd_t c, string cip) : SrsConnection(svr, c, cip) +SrsRtmpConn::SrsRtmpConn(SrsServer* svr, srs_netfd_t c, string cip, int cport) { + // Create a identify for this client. + _srs_context->set_id(_srs_context->generate_id()); + server = svr; + + stfd = c; + skt = new SrsTcpConnection(c); + manager = svr; + ip = cip; + port = cport; + create_time = srsu2ms(srs_get_system_time()); + clk = new SrsWallClock(); + kbps = new SrsKbps(clk); + kbps->set_io(skt, skt); + trd = new SrsSTCoroutine("rtmp", this, _srs_context->get_id()); rtmp = new SrsRtmpServer(skt); refer = new SrsRefer(); @@ -121,6 +135,9 @@ SrsRtmpConn::SrsRtmpConn(SrsServer* svr, srs_netfd_t c, string cip) : SrsConnect send_min_interval = 0; tcp_nodelay = false; info = new SrsClientInfo(); + + publish_1stpkt_timeout = 0; + publish_normal_timeout = 0; _srs_config->subscribe(this); } @@ -128,6 +145,17 @@ SrsRtmpConn::SrsRtmpConn(SrsServer* svr, srs_netfd_t c, string cip) : SrsConnect SrsRtmpConn::~SrsRtmpConn() { _srs_config->unsubscribe(this); + + trd->interrupt(); + // wakeup the handler which need to notice. + if (wakable) { + wakable->wakeup(); + } + srs_freep(trd); + + srs_freep(kbps); + srs_freep(clk); + srs_freep(skt); srs_freep(info); srs_freep(rtmp); @@ -136,14 +164,9 @@ SrsRtmpConn::~SrsRtmpConn() srs_freep(security); } -void SrsRtmpConn::dispose() +std::string SrsRtmpConn::desc() { - SrsConnection::dispose(); - - // wakeup the handler which need to notice. - if (wakable) { - wakable->wakeup(); - } + return "RtmpConn"; } // TODO: return detail message when error for client. @@ -151,7 +174,7 @@ srs_error_t SrsRtmpConn::do_cycle() { srs_error_t err = srs_success; - srs_trace("RTMP client ip=%s, fd=%d", ip.c_str(), srs_netfd_fileno(stfd)); + srs_trace("RTMP client ip=%s:%d, fd=%d", ip.c_str(), port, srs_netfd_fileno(stfd)); rtmp->set_recv_timeout(SRS_CONSTS_RTMP_TIMEOUT); rtmp->set_send_timeout(SRS_CONSTS_RTMP_TIMEOUT); @@ -267,7 +290,7 @@ srs_error_t SrsRtmpConn::on_reload_vhost_play(string vhost) mw_msgs = _srs_config->get_mw_msgs(req->vhost, realtime); mw_sleep = _srs_config->get_mw_sleep(req->vhost); - set_socket_buffer(mw_sleep); + skt->set_socket_buffer(mw_sleep); return err; } @@ -305,7 +328,7 @@ srs_error_t SrsRtmpConn::on_reload_vhost_realtime(string vhost) mw_msgs = _srs_config->get_mw_msgs(req->vhost, realtime); mw_sleep = _srs_config->get_mw_sleep(req->vhost); - set_socket_buffer(mw_sleep); + skt->set_socket_buffer(mw_sleep); return err; } @@ -515,8 +538,8 @@ srs_error_t SrsRtmpConn::stream_service_cycle() } bool enabled_cache = _srs_config->get_gop_cache(req->vhost); - srs_trace("source url=%s, ip=%s, cache=%d, is_edge=%d, source_id=[%d][%d]", - req->get_stream_url().c_str(), ip.c_str(), enabled_cache, info->edge, ::getpid(), source->source_id()); + srs_trace("source url=%s, ip=%s, cache=%d, is_edge=%d, source_id=%s/%s", + req->get_stream_url().c_str(), ip.c_str(), enabled_cache, info->edge, source->source_id().c_str(), source->pre_source_id().c_str()); source->set_cache(enabled_cache); switch (info->type) { @@ -654,10 +677,13 @@ srs_error_t SrsRtmpConn::playing(SrsSource* source) // Create a consumer of source. SrsConsumer* consumer = NULL; - if ((err = source->create_consumer(this, consumer)) != srs_success) { + SrsAutoFree(SrsConsumer, consumer); + if ((err = source->create_consumer(consumer)) != srs_success) { return srs_error_wrap(err, "rtmp: create consumer"); } - SrsAutoFree(SrsConsumer, consumer); + if ((err = source->consumer_dumps(consumer)) != srs_success) { + return srs_error_wrap(err, "rtmp: dumps consumer"); + } // Use receiving thread to receive packets from peer. // @see: https://github.com/ossrs/srs/issues/217 @@ -704,7 +730,7 @@ srs_error_t SrsRtmpConn::do_playing(SrsSource* source, SrsConsumer* consumer, Sr // when mw_sleep changed, resize the socket send buffer. mw_msgs = _srs_config->get_mw_msgs(req->vhost, realtime); mw_sleep = _srs_config->get_mw_sleep(req->vhost); - set_socket_buffer(mw_sleep); + skt->set_socket_buffer(mw_sleep); // initialize the send_min_interval send_min_interval = _srs_config->get_send_min_interval(req->vhost); @@ -1125,7 +1151,7 @@ void SrsRtmpConn::set_sock_options() if (nvalue != tcp_nodelay) { tcp_nodelay = nvalue; - srs_error_t err = set_tcp_nodelay(tcp_nodelay); + srs_error_t err = skt->set_tcp_nodelay(tcp_nodelay); if (err != srs_success) { srs_warn("ignore err %s", srs_error_desc(err).c_str()); srs_freep(err); @@ -1399,3 +1425,68 @@ void SrsRtmpConn::http_hooks_on_stop() return; } +srs_error_t SrsRtmpConn::start() +{ + srs_error_t err = srs_success; + + if ((err = skt->initialize()) != srs_success) { + return srs_error_wrap(err, "init socket"); + } + + if ((err = trd->start()) != srs_success) { + return srs_error_wrap(err, "coroutine"); + } + + return err; +} + +srs_error_t SrsRtmpConn::cycle() +{ + srs_error_t err = do_cycle(); + + // Notify manager to remove it. + // Note that we create this object, so we use manager to remove it. + manager->remove(this); + + // success. + if (err == srs_success) { + srs_trace("client finished."); + return err; + } + + // It maybe success with message. + if (srs_error_code(err) == ERROR_SUCCESS) { + srs_trace("client finished%s.", srs_error_summary(err).c_str()); + srs_freep(err); + return err; + } + + // client close peer. + // TODO: FIXME: Only reset the error when client closed it. + if (srs_is_client_gracefully_close(err)) { + srs_warn("client disconnect peer. ret=%d", srs_error_code(err)); + } else if (srs_is_server_gracefully_close(err)) { + srs_warn("server disconnect. ret=%d", srs_error_code(err)); + } else { + srs_error("serve error %s", srs_error_desc(err).c_str()); + } + + srs_freep(err); + return srs_success; +} + +string SrsRtmpConn::remote_ip() +{ + return ip; +} + +const SrsContextId& SrsRtmpConn::get_id() +{ + return trd->cid(); +} + +void SrsRtmpConn::expire() +{ + trd->interrupt(); +} + diff --git a/trunk/src/app/srs_app_rtmp_conn.hpp b/trunk/src/app/srs_app_rtmp_conn.hpp index c58111f04..1151378b7 100644 --- a/trunk/src/app/srs_app_rtmp_conn.hpp +++ b/trunk/src/app/srs_app_rtmp_conn.hpp @@ -83,7 +83,8 @@ public: }; // The client provides the main logic control for RTMP clients. -class SrsRtmpConn : virtual public SrsConnection, virtual public ISrsReloadHandler +class SrsRtmpConn : virtual public ISrsStartableConneciton, virtual public ISrsReloadHandler + , virtual public ISrsCoroutineHandler, virtual public ISrsExpire { // For the thread to directly access any field of connection. friend class SrsPublishRecvThread; @@ -116,11 +117,32 @@ private: bool tcp_nodelay; // About the rtmp client. SrsClientInfo* info; +private: + srs_netfd_t stfd; + SrsTcpConnection* skt; + // Each connection start a green thread, + // when thread stop, the connection will be delete by server. + SrsCoroutine* trd; + // The manager object to manage the connection. + ISrsResourceManager* manager; + // The ip and port of client. + std::string ip; + int port; + // The connection total kbps. + // not only the rtmp or http connection, all type of connection are + // need to statistic the kbps of io. + // The SrsStatistic will use it indirectly to statistic the bytes delta of current connection. + SrsKbps* kbps; + SrsWallClock* clk; + // The create time in milliseconds. + // for current connection to log self create time and calculate the living time. + int64_t create_time; public: - SrsRtmpConn(SrsServer* svr, srs_netfd_t c, std::string cip); + SrsRtmpConn(SrsServer* svr, srs_netfd_t c, std::string cip, int port); virtual ~SrsRtmpConn(); +// Interface ISrsResource. public: - virtual void dispose(); + virtual std::string desc(); protected: virtual srs_error_t do_cycle(); // Interface ISrsReloadHandler @@ -163,6 +185,30 @@ private: virtual void http_hooks_on_unpublish(); virtual srs_error_t http_hooks_on_play(); virtual void http_hooks_on_stop(); +// Extract APIs from SrsTcpConnection. +// Interface ISrsStartable +public: + // Start the client green thread. + // when server get a client from listener, + // 1. server will create an concrete connection(for instance, RTMP connection), + // 2. then add connection to its connection manager, + // 3. start the client thread by invoke this start() + // when client cycle thread stop, invoke the on_thread_stop(), which will use server + // To remove the client by server->remove(this). + virtual srs_error_t start(); +// Interface ISrsOneCycleThreadHandler +public: + // The thread cycle function, + // when serve connection completed, terminate the loop which will terminate the thread, + // thread will invoke the on_thread_stop() when it terminated. + virtual srs_error_t cycle(); +// Interface ISrsConnection. +public: + virtual std::string remote_ip(); + virtual const SrsContextId& get_id(); +// Interface ISrsExpire. +public: + virtual void expire(); }; #endif diff --git a/trunk/src/app/srs_app_rtsp.cpp b/trunk/src/app/srs_app_rtsp.cpp index 8c6967115..1a936808c 100644 --- a/trunk/src/app/srs_app_rtsp.cpp +++ b/trunk/src/app/srs_app_rtsp.cpp @@ -120,9 +120,11 @@ srs_error_t SrsRtpConn::on_udp_packet(const sockaddr* from, const int fromlen, c // always free it. SrsAutoFree(SrsRtpPacket, cache); - - if ((err = rtsp->on_rtp_packet(cache, stream_id)) != srs_success) { - return srs_error_wrap(err, "process rtp packet"); + + err = rtsp->on_rtp_packet(cache, stream_id); + if (err != srs_success) { + srs_warn("ignore RTP packet err %s", srs_error_desc(err).c_str()); + srs_freep(err); } return err; @@ -191,6 +193,11 @@ SrsRtspConn::SrsRtspConn(SrsRtspCaster* c, srs_netfd_t fd, std::string o) skt = new SrsStSocket(); rtsp = new SrsRtspStack(skt); trd = new SrsSTCoroutine("rtsp", this); + + audio_id = 0; + video_id = 0; + audio_sample_rate = 0; + audio_channel = 0; req = NULL; sdk = NULL; @@ -221,6 +228,9 @@ SrsRtspConn::~SrsRtspConn() srs_freep(vjitter); srs_freep(ajitter); + + srs_freep(avc); + srs_freep(aac); srs_freep(acodec); srs_freep(acache); } @@ -246,16 +256,29 @@ std::string SrsRtspConn::remote_ip() return ""; } +std::string SrsRtspConn::desc() +{ + return "RtspConn"; +} + +const SrsContextId& SrsRtspConn::get_id() +{ + return _srs_context->get_id(); +} + srs_error_t SrsRtspConn::do_cycle() { srs_error_t err = srs_success; // retrieve ip of client. - std::string ip = srs_get_peer_ip(srs_netfd_fileno(stfd)); + int fd = srs_netfd_fileno(stfd); + std::string ip = srs_get_peer_ip(fd); + int port = srs_get_peer_port(fd); + if (ip.empty() && !_srs_config->empty_ip_ok()) { srs_warn("empty ip for fd=%d", srs_netfd_fileno(stfd)); } - srs_trace("rtsp: serve %s", ip.c_str()); + srs_trace("rtsp: serve %s:%d", ip.c_str(), port); // consume all rtsp messages. while (true) { @@ -495,54 +518,62 @@ srs_error_t SrsRtspConn::write_sequence_header() } // generate audio sh by audio specific config. - if (true) { - std::string sh = aac_specific_config; - - SrsFormat* format = new SrsFormat(); - SrsAutoFree(SrsFormat, format); - - if ((err = format->on_aac_sequence_header((char*)sh.c_str(), (int)sh.length())) != srs_success) { - return srs_error_wrap(err, "on aac sequence header"); - } - - SrsAudioCodecConfig* dec = format->acodec; - - acodec->sound_format = SrsAudioCodecIdAAC; - acodec->sound_type = (dec->aac_channels == 2)? SrsAudioChannelsStereo : SrsAudioChannelsMono; - acodec->sound_size = SrsAudioSampleBits16bit; - acodec->aac_packet_type = 0; - - 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[dec->aac_sample_rate]) { - case 11025: - acodec->sound_rate = SrsAudioSampleRate11025; - break; - case 22050: - acodec->sound_rate = SrsAudioSampleRate22050; - break; - case 44100: - acodec->sound_rate = SrsAudioSampleRate44100; - break; - default: - break; - }; - - if ((err = write_audio_raw_frame((char*)sh.data(), (int)sh.length(), acodec, (uint32_t)dts)) != srs_success) { - return srs_error_wrap(err, "write audio raw frame"); - } + if (aac_specific_config.empty()) { + srs_warn("no audio asc"); + return err; } - + + std::string sh = aac_specific_config; + + SrsFormat* format = new SrsFormat(); + SrsAutoFree(SrsFormat, format); + + if ((err = format->on_aac_sequence_header((char*)sh.c_str(), (int)sh.length())) != srs_success) { + return srs_error_wrap(err, "on aac sequence header"); + } + + SrsAudioCodecConfig* dec = format->acodec; + + acodec->sound_format = SrsAudioCodecIdAAC; + acodec->sound_type = (dec->aac_channels == 2)? SrsAudioChannelsStereo : SrsAudioChannelsMono; + acodec->sound_size = SrsAudioSampleBits16bit; + acodec->aac_packet_type = 0; + + 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[dec->aac_sample_rate]) { + case 11025: + acodec->sound_rate = SrsAudioSampleRate11025; + break; + case 22050: + acodec->sound_rate = SrsAudioSampleRate22050; + break; + case 44100: + acodec->sound_rate = SrsAudioSampleRate44100; + break; + default: + break; + }; + + if ((err = write_audio_raw_frame((char*)sh.data(), (int)sh.length(), acodec, (uint32_t)dts)) != srs_success) { + return srs_error_wrap(err, "write audio raw frame"); + } + return err; } srs_error_t SrsRtspConn::write_h264_sps_pps(uint32_t dts, uint32_t pts) { srs_error_t err = srs_success; + + if (h264_sps.empty() || h264_pps.empty()) { + srs_warn("no sps=%dB or pps=%dB", (int)h264_sps.size(), (int)h264_pps.size()); + return err; + } // h264 raw to h264 packet. std::string sh; @@ -687,10 +718,11 @@ void SrsRtspConn::close() SrsRtspCaster::SrsRtspCaster(SrsConfDirective* c) { // TODO: FIXME: support reload. + engine = _srs_config->get_stream_caster_engine(c); output = _srs_config->get_stream_caster_output(c); local_port_min = _srs_config->get_stream_caster_rtp_port_min(c); local_port_max = _srs_config->get_stream_caster_rtp_port_max(c); - manager = new SrsCoroutineManager(); + manager = new SrsResourceManager("CRTSP"); } SrsRtspCaster::~SrsRtspCaster() @@ -728,7 +760,7 @@ srs_error_t SrsRtspCaster::alloc_port(int* pport) break; } } - srs_info("rtsp: alloc port=%d-%d", *pport, *pport + 1); + srs_trace("rtsp: %s alloc port=%d-%d", engine.c_str(), *pport, *pport + 1); return err; } @@ -738,7 +770,7 @@ void SrsRtspCaster::free_port(int lpmin, int lpmax) for (int i = lpmin; i < lpmax; i++) { used_ports[i] = false; } - srs_trace("rtsp: free rtp port=%d-%d", lpmin, lpmax); + srs_trace("rtsp: %s free rtp port=%d-%d", engine.c_str(), lpmin, lpmax); } srs_error_t SrsRtspCaster::on_tcp_client(srs_netfd_t stfd) diff --git a/trunk/src/app/srs_app_rtsp.hpp b/trunk/src/app/srs_app_rtsp.hpp index 0ca748535..ca79a359e 100644 --- a/trunk/src/app/srs_app_rtsp.hpp +++ b/trunk/src/app/srs_app_rtsp.hpp @@ -31,8 +31,8 @@ #include #include -#include #include +#include class SrsStSocket; class SrsRtspConn; @@ -51,6 +51,7 @@ class SrsAudioFrame; class SrsSimpleStream; class SrsPithyPrint; class SrsSimpleRtmpClient; +class SrsResourceManager; // A rtp connection which transport a stream. class SrsRtpConn: public ISrsUdpHandler @@ -143,7 +144,11 @@ public: virtual ~SrsRtspConn(); public: virtual srs_error_t serve(); +// Interface ISrsConnection. +public: virtual std::string remote_ip(); + virtual const SrsContextId& get_id(); + virtual std::string desc(); private: virtual srs_error_t do_cycle(); // internal methods @@ -173,6 +178,7 @@ private: class SrsRtspCaster : public ISrsTcpHandler { private: + std::string engine; std::string output; int local_port_min; int local_port_max; @@ -180,7 +186,7 @@ private: std::map used_ports; private: std::vector clients; - SrsCoroutineManager* manager; + SrsResourceManager* manager; public: SrsRtspCaster(SrsConfDirective* c); virtual ~SrsRtspCaster(); diff --git a/trunk/src/app/srs_app_server.cpp b/trunk/src/app/srs_app_server.cpp index 224f4790e..f2c2c314f 100644 --- a/trunk/src/app/srs_app_server.cpp +++ b/trunk/src/app/srs_app_server.cpp @@ -30,7 +30,7 @@ #include #include #include -#ifndef SRS_AUTO_OSX +#ifndef SRS_OSX #include #endif using namespace std; @@ -52,7 +52,6 @@ using namespace std; #include #include #include -#include #include #include #include @@ -105,8 +104,12 @@ std::string srs_listener_type2string(SrsListenerType type) return "RTMP"; case SrsListenerHttpApi: return "HTTP-API"; + case SrsListenerHttpsApi: + return "HTTPS-API"; case SrsListenerHttpStream: return "HTTP-Server"; + case SrsListenerHttpsStream: + return "HTTP-Server"; case SrsListenerMpegTsOverUdp: return "MPEG-TS over UDP"; case SrsListenerRtsp: @@ -349,7 +352,7 @@ SrsUdpCasterListener::~SrsUdpCasterListener() srs_freep(caster); } -#ifdef SRS_AUTO_GB28181 +#ifdef SRS_GB28181 SrsGb28181Listener::SrsGb28181Listener(SrsServer* svr, SrsListenerType t, SrsConfDirective* c) : SrsUdpStreamListener(svr, t, NULL) { @@ -370,6 +373,62 @@ SrsGb28181Listener::~SrsGb28181Listener() srs_freep(caster); } +SrsGb28181TcpListener::SrsGb28181TcpListener(SrsServer* svr, SrsListenerType t, SrsConfDirective* c) : SrsListener(svr, t) +{ + // the caller already ensure the type is ok, + // we just assert here for unknown stream caster. + srs_assert(type == SrsListenerGb28181RtpMux); + + caster = new SrsGb28181Caster(c); + listener = NULL; +} + +SrsGb28181TcpListener::~SrsGb28181TcpListener() +{ + srs_freep(caster); + srs_freep(listener); +} + +srs_error_t SrsGb28181TcpListener::listen(std::string i, int p) +{ + srs_error_t err = srs_success; + + // the caller already ensure the type is ok, + // we just assert here for unknown stream caster. + srs_assert(type == SrsListenerGb28181RtpMux); + + ip = i; + port = p; + + if ((err = caster->initialize()) != srs_success) { + return srs_error_wrap(err, "init caster"); + } + + srs_freep(listener); + listener = new SrsTcpListener(this, ip, port); + + if ((err = listener->listen()) != srs_success) { + return srs_error_wrap(err, "rtsp listen %s:%d", ip.c_str(), port); + } + + string v = srs_listener_type2string(type); + + return err; +} + +srs_error_t SrsGb28181TcpListener::on_tcp_client(srs_netfd_t stfd) +{ + int fd = srs_netfd_fileno(stfd); + string ip = srs_get_peer_ip(fd); + + srs_error_t err = caster->on_tcp_client(stfd); + if (err != srs_success) { + srs_warn("accept client failed, err is %s", srs_error_desc(err).c_str()); + srs_freep(err); + } + return srs_success; +} + #endif SrsSignalManager* SrsSignalManager::instance = NULL; @@ -380,7 +439,7 @@ SrsSignalManager::SrsSignalManager(SrsServer* s) server = s; sig_pipe[0] = sig_pipe[1] = -1; - trd = new SrsSTCoroutine("signal", this); + trd = new SrsSTCoroutine("signal", this, _srs_context->get_id()); signal_read_stfd = NULL; } @@ -514,7 +573,7 @@ srs_error_t SrsInotifyWorker::start() { srs_error_t err = srs_success; -#ifndef SRS_AUTO_OSX +#ifndef SRS_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()) { @@ -539,7 +598,7 @@ srs_error_t SrsInotifyWorker::start() } if (((err = srs_fd_closeexec(fd))) != srs_success) { - return srs_error_new(ERROR_INOTIFY_OPENFD, "closeexec fd=%d", fd); + return srs_error_wrap(err, "closeexec fd=%d", fd); } // /* the following are legal, implemented events that user-space can watch for */ @@ -594,7 +653,7 @@ srs_error_t SrsInotifyWorker::cycle() { srs_error_t err = srs_success; -#ifndef SRS_AUTO_OSX +#ifndef SRS_OSX string config_path = _srs_config->config(); string config_file = srs_path_basename(config_path); string k8s_file = "..data"; @@ -657,7 +716,7 @@ SrsServer::SrsServer() pid_fd = -1; signal_manager = new SrsSignalManager(this); - conn_manager = new SrsCoroutineManager(); + conn_manager = new SrsResourceManager("TCP", true); handler = NULL; ppid = ::getppid(); @@ -695,7 +754,7 @@ void SrsServer::destroy() srs_freep(signal_manager); srs_freep(conn_manager); -#ifdef SRS_AUTO_GB28181 +#ifdef SRS_GB28181 //free global gb28181 manager srs_freep(_srs_gb28181); #endif @@ -708,7 +767,9 @@ void SrsServer::dispose() // prevent fresh clients. close_listeners(SrsListenerRtmpStream); close_listeners(SrsListenerHttpApi); + close_listeners(SrsListenerHttpsApi); close_listeners(SrsListenerHttpStream); + close_listeners(SrsListenerHttpsStream); close_listeners(SrsListenerMpegTsOverUdp); close_listeners(SrsListenerRtsp); close_listeners(SrsListenerFlv); @@ -721,7 +782,7 @@ void SrsServer::dispose() // @remark don't dispose all connections, for too slow. -#ifdef SRS_AUTO_MEM_WATCH +#ifdef SRS_MEM_WATCH srs_memory_report(); #endif } @@ -737,7 +798,9 @@ void SrsServer::gracefully_dispose() // prevent fresh clients. close_listeners(SrsListenerRtmpStream); close_listeners(SrsListenerHttpApi); + close_listeners(SrsListenerHttpsApi); close_listeners(SrsListenerHttpStream); + close_listeners(SrsListenerHttpsStream); close_listeners(SrsListenerMpegTsOverUdp); close_listeners(SrsListenerRtsp); close_listeners(SrsListenerFlv); @@ -750,20 +813,20 @@ void SrsServer::gracefully_dispose() // Wait for connections to quit. // While gracefully quiting, user can requires SRS to fast quit. int wait_step = 1; - while (!conns.empty() && !signal_fast_quit) { - for (int i = 0; i < wait_step && !conns.empty() && !signal_fast_quit; i++) { + while (!conn_manager->empty() && !signal_fast_quit) { + for (int i = 0; i < wait_step && !conn_manager->empty() && !signal_fast_quit; i++) { srs_usleep(1000 * SRS_UTIME_MILLISECONDS); } wait_step = (wait_step * 2) % 33; - srs_trace("wait for %d conns to quit", conns.size()); + srs_trace("wait for %d conns to quit", (int)conn_manager->size()); } // dispose the source for hls and dvr. _srs_sources->dispose(); srs_trace("source disposed"); -#ifdef SRS_AUTO_MEM_WATCH +#ifdef SRS_MEM_WATCH srs_memory_report(); #endif @@ -804,29 +867,14 @@ 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. -#define __MMAP_MAX_CONNECTIONS 32756 - if (_srs_config->get_max_connections() > __MMAP_MAX_CONNECTIONS) { - srs_error("st mmap for stack allocation must <= %d threads, " - "@see Makefile of st for MALLOC_STACK, please build st manually by " - "\"make EXTRA_CFLAGS=-DMALLOC_STACK linux-debug\"", __MMAP_MAX_CONNECTIONS); - return srs_error_new(ERROR_ST_EXCEED_THREADS, "%d exceed max %d threads", - _srs_config->get_max_connections(), __MMAP_MAX_CONNECTIONS); - } - - // set current log id. - _srs_context->generate_id(); - // check asprocess. bool asprocess = _srs_config->get_asprocess(); if (asprocess && ppid == 1) { return srs_error_new(ERROR_SYSTEM_ASSERT_FAILED, "ppid=%d illegal for asprocess", ppid); } - srs_trace("server main cid=%d, pid=%d, ppid=%d, asprocess=%d", - _srs_context->get_id(), ::getpid(), ppid, asprocess); + srs_trace("server main cid=%s, pid=%d, ppid=%d, asprocess=%d", + _srs_context->get_id().c_str(), ::getpid(), ppid, asprocess); return err; } @@ -880,7 +928,7 @@ srs_error_t SrsServer::acquire_pid_file() // write the pid string pid = srs_int2str(getpid()); if (write(fd, pid.c_str(), pid.length()) != (int)pid.length()) { - return srs_error_new(ERROR_SYSTEM_PID_WRITE_FILE, "write pid=%d to file=%s", pid.c_str(), pid_file.c_str()); + return srs_error_new(ERROR_SYSTEM_PID_WRITE_FILE, "write pid=%s to file=%s", pid.c_str(), pid_file.c_str()); } // auto close when fork child process. @@ -910,10 +958,18 @@ srs_error_t SrsServer::listen() if ((err = listen_http_api()) != srs_success) { return srs_error_wrap(err, "http api listen"); } + + if ((err = listen_https_api()) != srs_success) { + return srs_error_wrap(err, "https api listen"); + } if ((err = listen_http_stream()) != srs_success) { return srs_error_wrap(err, "http stream listen"); } + + if ((err = listen_https_stream()) != srs_success) { + return srs_error_wrap(err, "https stream listen"); + } if ((err = listen_stream_caster()) != srs_success) { return srs_error_wrap(err, "stream caster listen"); @@ -941,8 +997,8 @@ srs_error_t SrsServer::http_handle() { srs_error_t err = srs_success; - if ((err = http_api_mux->handle("/", new SrsHttpNotFoundHandler())) != srs_success) { - return srs_error_wrap(err, "handle not found"); + if ((err = http_api_mux->handle("/", new SrsGoApiRoot())) != srs_success) { + return srs_error_wrap(err, "handle /"); } if ((err = http_api_mux->handle("/api/", new SrsGoApiApi())) != srs_success) { return srs_error_wrap(err, "handle api"); @@ -992,7 +1048,7 @@ srs_error_t SrsServer::http_handle() if ((err = http_api_mux->handle("/api/v1/perf", new SrsGoApiPerf())) != srs_success) { return srs_error_wrap(err, "handle perf"); } -#ifdef SRS_AUTO_GB28181 +#ifdef SRS_GB28181 if ((err = http_api_mux->handle("/api/v1/gb28181", new SrsGoApiGb28181())) != srs_success) { return srs_error_wrap(err, "handle raw"); } @@ -1015,7 +1071,7 @@ srs_error_t SrsServer::http_handle() return srs_error_wrap(err, "handle tests errors for error.srs.com"); } -#ifdef SRS_AUTO_GPERF +#ifdef SRS_GPERF // The test api for get tcmalloc stats. // @see Memory Introspection in https://gperftools.github.io/gperftools/tcmalloc.html if ((err = http_api_mux->handle("/api/v1/tcmalloc", new SrsGoApiTcmalloc())) != srs_success) { @@ -1058,7 +1114,7 @@ srs_error_t SrsServer::cycle() // Do server main cycle. err = do_cycle(); -#ifdef SRS_AUTO_GPERF_MC +#ifdef SRS_GPERF_MC destroy(); // remark, for gmc, never invoke the exit(). @@ -1074,7 +1130,7 @@ srs_error_t SrsServer::cycle() // fast quit, do some essential cleanup. if (signal_fast_quit) { - dispose(); + dispose(); // TODO: FIXME: Rename to essential_dispose. srs_trace("srs disposed"); } @@ -1104,15 +1160,20 @@ void SrsServer::on_signal(int signo) return; } -#ifndef SRS_AUTO_GPERF_MC +#ifndef SRS_GPERF_MC if (signo == SRS_SIGNAL_REOPEN_LOG) { _srs_log->reopen(); + + if (handler) { + handler->on_logrotate(); + } + srs_warn("reopen log file, signo=%d", signo); return; } #endif -#ifdef SRS_AUTO_GPERF_MC +#ifdef SRS_GPERF_MC if (signo == SRS_SIGNAL_REOPEN_LOG) { signal_gmc_stop = true; srs_warn("for gmc, the SIGUSR1 used as SIGINT, signo=%d", signo); @@ -1126,11 +1187,11 @@ void SrsServer::on_signal(int signo) } if (signo == SIGINT) { -#ifdef SRS_AUTO_GPERF_MC +#ifdef SRS_GPERF_MC srs_trace("gmc is on, main cycle will terminate normally, signo=%d", signo); signal_gmc_stop = true; #else - #ifdef SRS_AUTO_MEM_WATCH + #ifdef SRS_MEM_WATCH srs_memory_report(); #endif #endif @@ -1206,7 +1267,7 @@ srs_error_t SrsServer::do_cycle() // if user interrupt the program, exit to check mem leak. // but, if gperf, use reload to ensure main return normally, // because directly exit will cause core-dump. -#ifdef SRS_AUTO_GPERF_MC +#ifdef SRS_GPERF_MC if (signal_gmc_stop) { srs_warn("gmc got singal to stop server."); return err; @@ -1336,6 +1397,29 @@ srs_error_t SrsServer::listen_http_api() return err; } +srs_error_t SrsServer::listen_https_api() +{ + srs_error_t err = srs_success; + + close_listeners(SrsListenerHttpsApi); + if (_srs_config->get_https_api_enabled()) { + SrsListener* listener = new SrsBufferListener(this, SrsListenerHttpsApi); + listeners.push_back(listener); + + std::string ep = _srs_config->get_https_api_listen(); + + std::string ip; + int port; + srs_parse_endpoint(ep, ip, port); + + if ((err = listener->listen(ip, port)) != srs_success) { + return srs_error_wrap(err, "https api listen %s:%d", ip.c_str(), port); + } + } + + return err; +} + srs_error_t SrsServer::listen_http_stream() { srs_error_t err = srs_success; @@ -1359,7 +1443,30 @@ srs_error_t SrsServer::listen_http_stream() return err; } -#ifdef SRS_AUTO_GB28181 +srs_error_t SrsServer::listen_https_stream() +{ + srs_error_t err = srs_success; + + close_listeners(SrsListenerHttpsStream); + if (_srs_config->get_https_stream_enabled()) { + SrsListener* listener = new SrsBufferListener(this, SrsListenerHttpsStream); + listeners.push_back(listener); + + std::string ep = _srs_config->get_https_stream_listen(); + + std::string ip; + int port; + srs_parse_endpoint(ep, ip, port); + + if ((err = listener->listen(ip, port)) != srs_success) { + return srs_error_wrap(err, "https stream listen %s:%d", ip.c_str(), port); + } + } + + return err; +} + +#ifdef SRS_GB28181 srs_error_t SrsServer::listen_gb28181_sip(SrsConfDirective* stream_caster) { srs_error_t err = srs_success; @@ -1409,11 +1516,11 @@ 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); +#ifdef SRS_GB28181 } else if (srs_stream_caster_is_gb28181(caster)) { -#ifdef SRS_AUTO_GB28181 //init global gb28181 manger if (_srs_gb28181 == NULL){ - _srs_gb28181 = new SrsGb28181Manger(stream_caster); + _srs_gb28181 = new SrsGb28181Manger(this, stream_caster); if ((err = _srs_gb28181->initialize()) != srs_success){ return err; } @@ -1427,7 +1534,11 @@ srs_error_t SrsServer::listen_stream_caster() } //gb28181 stream listener - listener = new SrsGb28181Listener(this, SrsListenerGb28181RtpMux, stream_caster); + if (!_srs_config->get_stream_caster_tcp_enable(stream_caster)) { + listener = new SrsGb28181Listener(this, SrsListenerGb28181RtpMux, stream_caster); + } else { + listener = new SrsGb28181TcpListener(this, SrsListenerGb28181RtpMux, stream_caster); + } #else srs_warn("gb28181 is disabled, please enable it by: ./configure --with-gb28181"); continue; @@ -1472,12 +1583,13 @@ void SrsServer::resample_kbps() SrsStatistic* stat = SrsStatistic::instance(); // collect delta from all clients. - for (std::vector::iterator it = conns.begin(); it != conns.end(); ++it) { - SrsConnection* conn = *it; + for (int i = 0; i < (int)conn_manager->size(); i++) { + ISrsResource* c = conn_manager->at(i); + ISrsKbpsDelta* conn = dynamic_cast(conn_manager->at(i)); // add delta of connection to server kbps., // for next sample() of server kbps can get the stat. - stat->kbps_add_delta(conn); + stat->kbps_add_delta(c->get_id(), conn); } // TODO: FXME: support all other connections. @@ -1485,29 +1597,27 @@ void SrsServer::resample_kbps() // sample the kbps, get the stat. SrsKbps* kbps = stat->kbps_sample(); - srs_update_rtmp_server((int)conns.size(), kbps); + srs_update_rtmp_server((int)conn_manager->size(), kbps); } srs_error_t SrsServer::accept_client(SrsListenerType type, srs_netfd_t stfd) { srs_error_t err = srs_success; - SrsConnection* conn = NULL; + ISrsStartableConneciton* conn = NULL; - if ((err = fd2conn(type, stfd, &conn)) != srs_success) { + if ((err = fd_to_resource(type, stfd, &conn)) != srs_success) { if (srs_error_code(err) == ERROR_SOCKET_GET_PEER_IP && _srs_config->empty_ip_ok()) { srs_close_stfd(stfd); srs_error_reset(err); return srs_success; } - return srs_error_wrap(err, "fd2conn"); + return srs_error_wrap(err, "fd to resource"); } srs_assert(conn); // directly enqueue, the cycle thread will remove the client. - conns.push_back(conn); - - // cycle will start process thread and when finished remove the client. - // @remark never use the conn, for it maybe destroyed. + conn_manager->add(conn); + if ((err = conn->start()) != srs_success) { return srs_error_wrap(err, "start conn coroutine"); } @@ -1520,12 +1630,13 @@ SrsHttpServeMux* SrsServer::api_server() return http_api_mux; } -srs_error_t SrsServer::fd2conn(SrsListenerType type, srs_netfd_t stfd, SrsConnection** pconn) +srs_error_t SrsServer::fd_to_resource(SrsListenerType type, srs_netfd_t stfd, ISrsStartableConneciton** pr) { srs_error_t err = srs_success; int fd = srs_netfd_fileno(stfd); string ip = srs_get_peer_ip(fd); + int port = srs_get_peer_port(fd); // for some keep alive application, for example, the keepalived, // will send some tcp packet which we cann't got the ip, @@ -1536,14 +1647,13 @@ srs_error_t SrsServer::fd2conn(SrsListenerType type, srs_netfd_t stfd, SrsConnec // check connection limitation. int max_connections = _srs_config->get_max_connections(); - if (handler && (err = handler->on_accept_client(max_connections, (int)conns.size())) != srs_success) { - return srs_error_wrap(err, "drop client fd=%d, max=%d, cur=%d for err: %s", - fd, max_connections, (int)conns.size(), srs_error_desc(err).c_str()); + if (handler && (err = handler->on_accept_client(max_connections, (int)conn_manager->size())) != srs_success) { + return srs_error_wrap(err, "drop client fd=%d, ip=%s:%d, max=%d, cur=%d for err: %s", + fd, ip.c_str(), port, max_connections, (int)conn_manager->size(), srs_error_desc(err).c_str()); } - if ((int)conns.size() >= max_connections) { - return srs_error_new(ERROR_EXCEED_CONNECTIONS, - "drop fd=%d, max=%d, cur=%d for exceed connection limits", - fd, max_connections, (int)conns.size()); + if ((int)conn_manager->size() >= max_connections) { + return srs_error_new(ERROR_EXCEED_CONNECTIONS, "drop fd=%d, ip=%s:%d, max=%d, cur=%d for exceed connection limits", + fd, ip.c_str(), port, max_connections, (int)conn_manager->size()); } // avoid fd leak when fork. @@ -1558,15 +1668,22 @@ srs_error_t SrsServer::fd2conn(SrsListenerType type, srs_netfd_t stfd, SrsConnec return srs_error_new(ERROR_SYSTEM_PID_SET_FILE_INFO, "fcntl F_SETFD error! fd=%d", fd); } } + + // The context id may change during creating the bellow objects. + SrsContextRestore(_srs_context->get_id()); if (type == SrsListenerRtmpStream) { - *pconn = new SrsRtmpConn(this, stfd, ip); + *pr = new SrsRtmpConn(this, stfd, ip, port); } else if (type == SrsListenerHttpApi) { - *pconn = new SrsHttpApi(this, stfd, http_api_mux, ip); + *pr = new SrsHttpApi(false, this, stfd, http_api_mux, ip, port); + } else if (type == SrsListenerHttpsApi) { + *pr = new SrsHttpApi(true, this, stfd, http_api_mux, ip, port); } else if (type == SrsListenerHttpStream) { - *pconn = new SrsResponseOnlyHttpConn(this, stfd, http_server, ip); + *pr = new SrsResponseOnlyHttpConn(false, this, stfd, http_server, ip, port); + } else if (type == SrsListenerHttpsStream) { + *pr = new SrsResponseOnlyHttpConn(true, this, stfd, http_server, ip, port); } else { - srs_warn("close for no service handler. fd=%d, ip=%s", fd, ip.c_str()); + srs_warn("close for no service handler. fd=%d, ip=%s:%d", fd, ip.c_str(), port); srs_close_stfd(stfd); return err; } @@ -1574,25 +1691,14 @@ srs_error_t SrsServer::fd2conn(SrsListenerType type, srs_netfd_t stfd, SrsConnec return err; } -void SrsServer::remove(ISrsConnection* c) +void SrsServer::remove(ISrsResource* c) { - SrsConnection* conn = dynamic_cast(c); - std::vector::iterator it = std::find(conns.begin(), conns.end(), conn); - - // removed by destroy, ignore. - if (it == conns.end()) { - srs_warn("server moved connection, ignore."); - return; - } - - conns.erase(it); - - srs_info("conn removed. conns=%d", (int)conns.size()); - + ISrsStartableConneciton* conn = dynamic_cast(c); + SrsStatistic* stat = SrsStatistic::instance(); - stat->kbps_add_delta(conn); - stat->on_disconnect(conn->srs_id()); - + stat->kbps_add_delta(c->get_id(), conn); + stat->on_disconnect(c->get_id()); + // use manager to free it async. conn_manager->remove(c); } @@ -1659,6 +1765,10 @@ srs_error_t SrsServer::on_reload_http_api_enabled() if ((err = listen_http_api()) != srs_success) { return srs_error_wrap(err, "reload http_api"); } + + if ((err = listen_https_api()) != srs_success) { + return srs_error_wrap(err, "reload https_api"); + } return err; } @@ -1666,6 +1776,7 @@ srs_error_t SrsServer::on_reload_http_api_enabled() srs_error_t SrsServer::on_reload_http_api_disabled() { close_listeners(SrsListenerHttpApi); + close_listeners(SrsListenerHttpsApi); return srs_success; } @@ -1676,6 +1787,10 @@ srs_error_t SrsServer::on_reload_http_stream_enabled() if ((err = listen_http_stream()) != srs_success) { return srs_error_wrap(err, "reload http_stream enabled"); } + + if ((err = listen_https_stream()) != srs_success) { + return srs_error_wrap(err, "reload https_stream enabled"); + } return err; } @@ -1683,6 +1798,7 @@ srs_error_t SrsServer::on_reload_http_stream_enabled() srs_error_t SrsServer::on_reload_http_stream_disabled() { close_listeners(SrsListenerHttpStream); + close_listeners(SrsListenerHttpsStream); return srs_success; } diff --git a/trunk/src/app/srs_app_server.hpp b/trunk/src/app/srs_app_server.hpp index 1147a5a35..60a69d517 100644 --- a/trunk/src/app/srs_app_server.hpp +++ b/trunk/src/app/srs_app_server.hpp @@ -40,7 +40,6 @@ #include class SrsServer; -class SrsConnection; class SrsHttpServeMux; class SrsHttpServer; class SrsIngester; @@ -53,7 +52,7 @@ class SrsUdpListener; class SrsTcpListener; class SrsAppCasterFlv; class SrsRtspCaster; -class SrsCoroutineManager; +class SrsResourceManager; class SrsGb28181Caster; @@ -77,6 +76,10 @@ enum SrsListenerType SrsListenerGb28181RtpMux = 6, // UDP gb28181 sip server SrsListenerGb28181Sip = 7, + // HTTPS api, + SrsListenerHttpsApi = 8, + // HTTPS stream, + SrsListenerHttpsStream = 9, }; // A common tcp listener, for RTMP/HTTP server. @@ -164,7 +167,7 @@ public: virtual ~SrsUdpCasterListener(); }; -#ifdef SRS_AUTO_GB28181 +#ifdef SRS_GB28181 // A UDP gb28181 listener, for sip and rtp stream mux server. class SrsGb28181Listener : public SrsUdpStreamListener @@ -174,6 +177,21 @@ public: virtual ~SrsGb28181Listener(); }; +class SrsGb28181TcpListener : virtual public SrsListener, virtual public ISrsTcpHandler +{ +private: + SrsTcpListener* listener; + SrsGb28181Caster* caster; +public: + SrsGb28181TcpListener(SrsServer* svr, SrsListenerType t, SrsConfDirective* c); + virtual ~SrsGb28181TcpListener(); +public: + virtual srs_error_t listen(std::string i, int p); +// Interface ISrsTcpHandler +public: + virtual srs_error_t on_tcp_client(srs_netfd_t stfd); +}; + #endif // Convert signal to io, @@ -236,26 +254,27 @@ public: virtual srs_error_t on_cycle() = 0; // Callback the handler when got client. virtual srs_error_t on_accept_client(int max, int cur) = 0; + // Callback for logrotate. + virtual void on_logrotate() = 0; }; +// TODO: FIXME: Rename to SrsLiveServer. // SRS RTMP server, initialize and listen, start connection service thread, destroy client. -class SrsServer : virtual public ISrsReloadHandler, virtual public ISrsSourceHandler, virtual public IConnectionManager +class SrsServer : virtual public ISrsReloadHandler, virtual public ISrsSourceHandler, virtual public ISrsResourceManager { private: - // TODO: FIXME: rename to http_api + // TODO: FIXME: Extract an HttpApiServer. SrsHttpServeMux* http_api_mux; SrsHttpServer* http_server; SrsHttpHeartbeat* http_heartbeat; SrsIngester* ingester; - SrsCoroutineManager* conn_manager; + SrsResourceManager* conn_manager; private: // The pid file fd, lock the file write when server is running. // @remark the init.d script should cleanup the pid file, when stop service, // for the server never delete the file; when system startup, the pid in pid file // maybe valid but the process is not SRS, the init.d script will never start server. int pid_fd; - // All connections, connection manager - std::vector conns; // All listners, listener manager. std::vector listeners; // Signal manager which convert gignal to io message. @@ -321,9 +340,11 @@ private: // listen at specified protocol. virtual srs_error_t listen_rtmp(); virtual srs_error_t listen_http_api(); + virtual srs_error_t listen_https_api(); virtual srs_error_t listen_http_stream(); + virtual srs_error_t listen_https_stream(); virtual srs_error_t listen_stream_caster(); -#ifdef SRS_AUTO_GB28181 +#ifdef SRS_GB28181 virtual srs_error_t listen_gb28181_sip(SrsConfDirective* c); #endif // Close the listeners for specified type, @@ -341,13 +362,13 @@ public: // TODO: FIXME: Fetch from hybrid server manager. virtual SrsHttpServeMux* api_server(); private: - virtual srs_error_t fd2conn(SrsListenerType type, srs_netfd_t stfd, SrsConnection** pconn); -// Interface IConnectionManager + virtual srs_error_t fd_to_resource(SrsListenerType type, srs_netfd_t stfd, ISrsStartableConneciton** pr); +// Interface ISrsResourceManager public: // A callback for connection to remove itself. // When connection thread cycle terminated, callback this to delete connection. - // @see SrsConnection.on_thread_stop(). - virtual void remove(ISrsConnection* c); + // @see SrsTcpConnection.on_thread_stop(). + virtual void remove(ISrsResource* c); // Interface ISrsReloadHandler. public: virtual srs_error_t on_reload_listen(); diff --git a/trunk/src/app/srs_app_source.cpp b/trunk/src/app/srs_app_source.cpp index c0548708f..3d317eb2d 100755 --- a/trunk/src/app/srs_app_source.cpp +++ b/trunk/src/app/srs_app_source.cpp @@ -31,7 +31,7 @@ using namespace std; #include #include #include -#include +#include #include #include #include @@ -50,9 +50,7 @@ using namespace std; #include #include #include -#ifdef SRS_AUTO_RTC -#include -#endif +#include #define CONST_MAX_JITTER_MS 250 #define CONST_MAX_JITTER_MS_NEG -250 @@ -68,7 +66,7 @@ using namespace std; // the time to cleanup source. #define SRS_SOURCE_CLEANUP (30 * SRS_UTIME_SECONDS) -int _srs_time_jitter_string2int(std::string time_jitter) +int srs_time_jitter_string2int(std::string time_jitter) { if (time_jitter == "full") { return SrsRtmpJitterAlgorithmFULL; @@ -269,17 +267,11 @@ void SrsMessageQueue::set_queue_size(srs_utime_t queue_size) max_queue_size = queue_size; } -srs_error_t SrsMessageQueue::enqueue(SrsSharedPtrMessage* msg, bool* is_overflow, bool pass_timestamp) +srs_error_t SrsMessageQueue::enqueue(SrsSharedPtrMessage* msg, bool* is_overflow) { srs_error_t err = srs_success; msgs.push_back(msg); - - // For RTC, we never care about the timestamp and duration, so we never shrink queue here, - // but we will drop messages in each consumer coroutine. - if (pass_timestamp) { - return err; - } if (msg->is_av()) { if (av_start_time == -1) { @@ -288,6 +280,10 @@ srs_error_t SrsMessageQueue::enqueue(SrsSharedPtrMessage* msg, bool* is_overflow av_end_time = srs_utime_t(msg->timestamp * SRS_UTIME_MILLISECONDS); } + + if (max_queue_size <= 0) { + return err; + } while (av_end_time - av_start_time > max_queue_size) { // notice the caller queue already overflow and shrinked. @@ -301,7 +297,7 @@ srs_error_t SrsMessageQueue::enqueue(SrsSharedPtrMessage* msg, bool* is_overflow return err; } -srs_error_t SrsMessageQueue::dump_packets(int max_count, SrsSharedPtrMessage** pmsgs, int& count, bool pass_timestamp) +srs_error_t SrsMessageQueue::dump_packets(int max_count, SrsSharedPtrMessage** pmsgs, int& count) { srs_error_t err = srs_success; @@ -316,13 +312,9 @@ srs_error_t SrsMessageQueue::dump_packets(int max_count, SrsSharedPtrMessage** p SrsSharedPtrMessage** omsgs = msgs.data(); memcpy(pmsgs, omsgs, count * sizeof(SrsSharedPtrMessage*)); - // For RTC, we enable pass_timestamp mode, which never care about the timestamp and duration, - // so we do not have to update the start time here. - if (!pass_timestamp) { - SrsSharedPtrMessage* last = omsgs[count - 1]; - av_start_time = srs_utime_t(last->timestamp * SRS_UTIME_MILLISECONDS); - } - + SrsSharedPtrMessage* last = omsgs[count - 1]; + av_start_time = srs_utime_t(last->timestamp * SRS_UTIME_MILLISECONDS); + if (count >= nb_msgs) { // the pmsgs is big enough and clear msgs at most time. msgs.clear(); @@ -426,10 +418,9 @@ ISrsWakable::~ISrsWakable() { } -SrsConsumer::SrsConsumer(SrsSource* s, SrsConnection* c) +SrsConsumer::SrsConsumer(SrsSource* s) { source = s; - conn = c; paused = false; jitter = new SrsRtmpJitter(); queue = new SrsMessageQueue(); @@ -441,8 +432,6 @@ SrsConsumer::SrsConsumer(SrsSource* s, SrsConnection* c) mw_duration = 0; mw_waiting = false; #endif - - pass_timestamp = false; } SrsConsumer::~SrsConsumer() @@ -477,33 +466,19 @@ srs_error_t SrsConsumer::enqueue(SrsSharedPtrMessage* shared_msg, bool atc, SrsR SrsSharedPtrMessage* msg = shared_msg->copy(); - // For RTC, we enable pass_timestamp mode, which never correct or depends on monotonic increasing of - // timestamp. And in RTC, the audio and video timebase can be different, so we ignore time_jitter here. - if (!pass_timestamp && !atc) { + if (!atc) { if ((err = jitter->correct(msg, ag)) != srs_success) { return srs_error_wrap(err, "consume message"); } } - // Put message in queue, here we may enable pass_timestamp mode. - if ((err = queue->enqueue(msg, NULL, pass_timestamp)) != srs_success) { + if ((err = queue->enqueue(msg, NULL)) != srs_success) { return srs_error_wrap(err, "enqueue message"); } #ifdef SRS_PERF_QUEUE_COND_WAIT // fire the mw when msgs is enough. if (mw_waiting) { - // For RTC, we use pass_timestamp mode, we don't care about the timestamp in queue, - // so we only check the messages in queue. - if (pass_timestamp) { - if (queue->size() > mw_min_msgs) { - srs_cond_signal(mw_wait); - mw_waiting = false; - return err; - } - return err; - } - // For RTMP, we wait for messages and duration. srs_utime_t duration = queue->duration(); bool match_min_msgs = queue->size() > mw_min_msgs; @@ -544,7 +519,7 @@ srs_error_t SrsConsumer::dump_packets(SrsMessageArray* msgs, int& count) count = 0; if (should_update_source_id) { - srs_trace("update source_id=%d[%d]", source->source_id(), source->source_id()); + srs_trace("update source_id=%s/%s", source->source_id().c_str(), source->pre_source_id().c_str()); should_update_source_id = false; } @@ -554,7 +529,7 @@ srs_error_t SrsConsumer::dump_packets(SrsMessageArray* msgs, int& count) } // pump msgs from queue. - if ((err = queue->dump_packets(max, msgs->msgs, count, pass_timestamp)) != srs_success) { + if ((err = queue->dump_packets(max, msgs->msgs, count)) != srs_success) { return srs_error_wrap(err, "dump packets"); } @@ -843,58 +818,6 @@ SrsSharedPtrMessage* SrsMixQueue::pop() return msg; } -#ifdef SRS_AUTO_RTC -SrsRtpPacketQueue::SrsRtpPacketQueue() -{ -} - -SrsRtpPacketQueue::~SrsRtpPacketQueue() -{ - clear(); -} - -void SrsRtpPacketQueue::clear() -{ - map::iterator iter = pkt_queue.begin(); - while (iter != pkt_queue.end()) { - srs_freep(iter->second); - pkt_queue.erase(iter++); - } -} - -void SrsRtpPacketQueue::push(std::vector& pkts) -{ - for (int i = 0; i < (int)pkts.size(); ++i) { - insert(pkts[i]->rtp_header.get_sequence(), pkts[i]); - } -} - -void SrsRtpPacketQueue::insert(const uint16_t& sequence, SrsRtpSharedPacket* pkt) -{ - pkt_queue.insert(make_pair(sequence, pkt->copy())); - // TODO: 3000 is magic number. - if (pkt_queue.size() >= 3000) { - srs_freep(pkt_queue.begin()->second); - pkt_queue.erase(pkt_queue.begin()); - } -} - -SrsRtpSharedPacket* SrsRtpPacketQueue::find(const uint16_t& sequence) -{ - if (pkt_queue.empty()) { - return NULL; - } - - SrsRtpSharedPacket* pkt = NULL; - map::iterator iter = pkt_queue.find(sequence); - if (iter != pkt_queue.end()) { - pkt = iter->second->copy(); - } - - return pkt; -} -#endif - SrsOriginHub::SrsOriginHub() { source = NULL; @@ -905,10 +828,7 @@ SrsOriginHub::SrsOriginHub() dash = new SrsDash(); dvr = new SrsDvr(); encoder = new SrsEncoder(); -#ifdef SRS_AUTO_RTC - rtc = new SrsRtc(); -#endif -#ifdef SRS_AUTO_HDS +#ifdef SRS_HDS hds = new SrsHds(); #endif ng_exec = new SrsNgExec(); @@ -936,7 +856,7 @@ SrsOriginHub::~SrsOriginHub() srs_freep(dash); srs_freep(dvr); srs_freep(encoder); -#ifdef SRS_AUTO_HDS +#ifdef SRS_HDS srs_freep(hds); #endif } @@ -951,12 +871,6 @@ srs_error_t SrsOriginHub::initialize(SrsSource* s, SrsRequest* r) if ((err = format->initialize()) != srs_success) { return srs_error_wrap(err, "format initialize"); } - -#ifdef SRS_AUTO_RTC - if ((err = rtc->initialize(this, req)) != srs_success) { - return srs_error_wrap(err, "rtc initialize"); - } -#endif if ((err = hls->initialize(this, req)) != srs_success) { return srs_error_wrap(err, "hls initialize"); @@ -1029,7 +943,8 @@ srs_error_t SrsOriginHub::on_audio(SrsSharedPtrMessage* shared_audio) srs_error_t err = srs_success; SrsSharedPtrMessage* msg = shared_audio; - + + // TODO: FIXME: Support parsing OPUS for RTC. if ((err = format->on_audio(msg)) != srs_success) { return srs_error_wrap(err, "format consume audio"); } @@ -1055,14 +970,6 @@ srs_error_t SrsOriginHub::on_audio(SrsSharedPtrMessage* shared_audio) flv_sample_sizes[c->sound_size], flv_sound_types[c->sound_type], srs_flv_srates[c->sound_rate]); } - -#ifdef SRS_AUTO_RTC - if ((err = rtc->on_audio(msg, format)) != srs_success) { - srs_warn("rtc: ignore audio error %s", srs_error_desc(err).c_str()); - srs_error_reset(err); - rtc->on_unpublish(); - } -#endif if ((err = hls->on_audio(msg, format)) != srs_success) { // apply the error strategy for hls. @@ -1095,7 +1002,7 @@ srs_error_t SrsOriginHub::on_audio(SrsSharedPtrMessage* shared_audio) dvr->on_unpublish(); } -#ifdef SRS_AUTO_HDS +#ifdef SRS_HDS if ((err = hds->on_audio(msg)) != srs_success) { srs_warn("hds: ignore audio error %s", srs_error_desc(err).c_str()); srs_error_reset(err); @@ -1156,21 +1063,6 @@ srs_error_t SrsOriginHub::on_video(SrsSharedPtrMessage* shared_video, bool is_se if (format->vcodec && !format->vcodec->is_avc_codec_ok()) { return err; } - -#ifdef SRS_AUTO_RTC - // Parse RTMP message to RTP packets, in FU-A if too large. - if ((err = rtc->on_video(msg, format)) != srs_success) { - // TODO: We should support more strategies. - srs_warn("rtc: ignore video error %s", srs_error_desc(err).c_str()); - srs_error_reset(err); - rtc->on_unpublish(); - } - - // TODO: FIXME: Refactor to move to rtp? - // Save the RTP packets for find_rtp_packet() to rtx or restore it. - // TODO: FIXME: Remove dead code. - //source->rtp_queue->push(msg->rtp_packets); -#endif if ((err = hls->on_video(msg, format)) != srs_success) { // TODO: We should support more strategies. @@ -1204,7 +1096,7 @@ srs_error_t SrsOriginHub::on_video(SrsSharedPtrMessage* shared_video, bool is_se dvr->on_unpublish(); } -#ifdef SRS_AUTO_HDS +#ifdef SRS_HDS if ((err = hds->on_video(msg)) != srs_success) { srs_warn("hds: ignore video error %s", srs_error_desc(err).c_str()); srs_error_reset(err); @@ -1240,12 +1132,6 @@ srs_error_t SrsOriginHub::on_publish() return srs_error_wrap(err, "encoder publish"); } -#ifdef SRS_AUTO_RTC - if ((err = rtc->on_publish()) != srs_success) { - return srs_error_wrap(err, "rtc publish"); - } -#endif - if ((err = hls->on_publish()) != srs_success) { return srs_error_wrap(err, "hls publish"); } @@ -1259,7 +1145,7 @@ srs_error_t SrsOriginHub::on_publish() } // TODO: FIXME: use initialize to set req. -#ifdef SRS_AUTO_HDS +#ifdef SRS_HDS if ((err = hds->on_publish(req)) != srs_success) { return srs_error_wrap(err, "hds publish"); } @@ -1283,14 +1169,11 @@ void SrsOriginHub::on_unpublish() destroy_forwarders(); encoder->on_unpublish(); -#ifdef SRS_AUTO_RTC - rtc->on_unpublish(); -#endif hls->on_unpublish(); dash->on_unpublish(); dvr->on_unpublish(); -#ifdef SRS_AUTO_HDS +#ifdef SRS_HDS hds->on_unpublish(); #endif @@ -1479,7 +1362,7 @@ srs_error_t SrsOriginHub::on_reload_vhost_hds(string vhost) // TODO: FIXME: maybe should ignore when publish already stopped? -#ifdef SRS_AUTO_HDS +#ifdef SRS_HDS hds->on_unpublish(); // Don't start HDS when source is not active. @@ -1824,6 +1707,7 @@ srs_error_t SrsSourceManager::fetch_or_create(SrsRequest* r, ISrsSourceHandler* // Use lock to protect coroutine switch. // @bug https://github.com/ossrs/srs/issues/1230 + // TODO: FIXME: Use smaller lock. SrsLocker(lock); SrsSource* source = NULL; @@ -1838,17 +1722,41 @@ srs_error_t SrsSourceManager::fetch_or_create(SrsRequest* r, ISrsSourceHandler* // should always not exists for create a source. srs_assert (pool.find(stream_url) == pool.end()); +#ifdef SRS_RTC + bool rtc_server_enabled = _srs_config->get_rtc_server_enabled(); + bool rtc_enabled = _srs_config->get_rtc_enabled(r->vhost); + + // Get the RTC source and bridger. + SrsRtcStream* rtc = NULL; + if (rtc_server_enabled && rtc_enabled) { + if ((err = _srs_rtc_sources->fetch_or_create(r, &rtc)) != srs_success) { + err = srs_error_wrap(err, "init rtc %s", r->get_stream_url().c_str()); + goto failed; + } + } +#endif srs_trace("new source, stream_url=%s", stream_url.c_str()); - + source = new SrsSource(); if ((err = source->initialize(r, h)) != srs_success) { - return srs_error_wrap(err, "init source %s", r->get_stream_url().c_str()); + err = srs_error_wrap(err, "init source %s", r->get_stream_url().c_str()); + goto failed; } + +#ifdef SRS_RTC + // If rtc enabled, bridge RTMP source to RTC, + // all RTMP packets will be forwarded to RTC source. + if (source && rtc) { + source->bridge_to(rtc->bridger()); + } +#endif pool[stream_url] = source; - *pps = source; - + return err; + +failed: + srs_freep(source); return err; } @@ -1883,7 +1791,7 @@ void SrsSourceManager::dispose() srs_error_t SrsSourceManager::cycle() { - int cid = _srs_context->get_id(); + SrsContextId cid = _srs_context->get_id(); srs_error_t err = do_cycle(); _srs_context->set_id(cid); @@ -1900,7 +1808,7 @@ srs_error_t SrsSourceManager::do_cycle() // Do cycle source to cleanup components, such as hls dispose. if ((err = source->cycle()) != srs_success) { - return srs_error_wrap(err, "source=%d/%d cycle", source->source_id(), source->pre_source_id()); + return srs_error_wrap(err, "source=%s/%s cycle", source->source_id().c_str(), source->pre_source_id().c_str()); } // TODO: FIXME: support source cleanup. @@ -1941,19 +1849,26 @@ void SrsSourceManager::destroy() pool.clear(); } +ISrsSourceBridger::ISrsSourceBridger() +{ +} + +ISrsSourceBridger::~ISrsSourceBridger() +{ +} + SrsSource::SrsSource() { req = NULL; jitter_algorithm = SrsRtmpJitterAlgorithmOFF; mix_correct = false; mix_queue = new SrsMixQueue(); -#ifdef SRS_AUTO_RTC - rtp_queue = new SrsRtpPacketQueue(); -#endif _can_publish = true; - _pre_source_id = _source_id = -1; die_at = 0; + + handler = NULL; + bridger = NULL; play_edge = new SrsPlayEdge(); publish_edge = new SrsPublishEdge(); @@ -1979,9 +1894,6 @@ SrsSource::~SrsSource() srs_freep(hub); srs_freep(meta); srs_freep(mix_queue); -#ifdef SRS_AUTO_RTC - srs_freep(rtp_queue); -#endif srs_freep(play_edge); srs_freep(publish_edge); @@ -2063,6 +1975,11 @@ srs_error_t SrsSource::initialize(SrsRequest* r, ISrsSourceHandler* h) return err; } +void SrsSource::bridge_to(ISrsSourceBridger* v) +{ + bridger = v; +} + srs_error_t SrsSource::on_reload_vhost_play(string vhost) { srs_error_t err = srs_success; @@ -2146,20 +2063,17 @@ srs_error_t SrsSource::on_reload_vhost_play(string vhost) return err; } -srs_error_t SrsSource::on_source_id_changed(int id) +srs_error_t SrsSource::on_source_id_changed(SrsContextId id) { srs_error_t err = srs_success; - if (_source_id == id) { + if (!_source_id.compare(id)) { return err; } - - if (_pre_source_id == -1) { + + if (_pre_source_id.empty()) { _pre_source_id = id; - } else if (_pre_source_id != _source_id) { - _pre_source_id = _source_id; } - _source_id = id; // notice all consumer @@ -2172,12 +2086,12 @@ srs_error_t SrsSource::on_source_id_changed(int id) return err; } -int SrsSource::source_id() +SrsContextId SrsSource::source_id() { return _source_id; } -int SrsSource::pre_source_id() +SrsContextId SrsSource::pre_source_id() { return _pre_source_id; } @@ -2313,6 +2227,11 @@ srs_error_t SrsSource::on_audio_imp(SrsSharedPtrMessage* msg) return srs_error_wrap(err, "consume audio"); } + // For bridger to consume the message. + if (bridger && (err = bridger->on_audio(msg)) != srs_success) { + return srs_error_wrap(err, "bridger consume audio"); + } + // copy to all consumer if (!drop_for_reduce) { for (int i = 0; i < (int)consumers.size(); i++) { @@ -2341,7 +2260,7 @@ srs_error_t SrsSource::on_audio_imp(SrsSharedPtrMessage* msg) if ((err = gop_cache->cache(msg)) != srs_success) { return srs_error_wrap(err, "gop cache consume audio"); } - + // if atc, update the sequence header to abs time. if (atc) { if (meta->ash()) { @@ -2438,6 +2357,11 @@ srs_error_t SrsSource::on_video_imp(SrsSharedPtrMessage* msg) return srs_error_wrap(err, "hub consume video"); } + // For bridger to consume the message. + if (bridger && (err = bridger->on_video(msg)) != srs_success) { + return srs_error_wrap(err, "bridger consume video"); + } + // copy to all consumer if (!drop_for_reduce) { for (int i = 0; i < (int)consumers.size(); i++) { @@ -2597,6 +2521,11 @@ srs_error_t SrsSource::on_publish() if ((err = handler->on_publish(this, req)) != srs_success) { return srs_error_wrap(err, "handle publish"); } + + if (bridger && (err = bridger->on_publish()) != srs_success) { + return srs_error_wrap(err, "bridger publish"); + } + SrsStatistic* stat = SrsStatistic::instance(); stat->on_stream_publish(req, _source_id); @@ -2626,13 +2555,21 @@ void SrsSource::on_unpublish() srs_trace("cleanup when unpublish"); _can_publish = true; - _source_id = -1; - + if (!_source_id.empty()) { + _pre_source_id = _source_id; + } + _source_id = SrsContextId(); + // notify the handler. srs_assert(handler); SrsStatistic* stat = SrsStatistic::instance(); stat->on_stream_close(req); + handler->on_unpublish(this, req); + + if (bridger) { + bridger->on_unpublish(); + } // no consumer, stream is die. if (consumers.empty()) { @@ -2640,12 +2577,27 @@ void SrsSource::on_unpublish() } } -srs_error_t SrsSource::create_consumer(SrsConnection* conn, SrsConsumer*& consumer, bool ds, bool dm, bool dg) +srs_error_t SrsSource::create_consumer(SrsConsumer*& consumer) { srs_error_t err = srs_success; - consumer = new SrsConsumer(this, conn); + consumer = new SrsConsumer(this); consumers.push_back(consumer); + + // for edge, when play edge stream, check the state + if (_srs_config->get_vhost_is_edge(req->vhost)) { + // notice edge to start for the first client. + if ((err = play_edge->on_client_play()) != srs_success) { + return srs_error_wrap(err, "play edge"); + } + } + + return err; +} + +srs_error_t SrsSource::consumer_dumps(SrsConsumer* consumer, bool ds, bool dm, bool dg) +{ + srs_error_t err = srs_success; srs_utime_t queue_size = _srs_config->get_queue_length(req->vhost); consumer->set_queue_size(queue_size); @@ -2682,15 +2634,7 @@ srs_error_t SrsSource::create_consumer(SrsConnection* conn, SrsConsumer*& consum } else { srs_trace("create consumer, active=%d, ignore gop cache, jitter=%d", hub->active(), jitter_algorithm); } - - // for edge, when play edge stream, check the state - if (_srs_config->get_vhost_is_edge(req->vhost)) { - // notice edge to start for the first client. - if ((err = play_edge->on_client_play()) != srs_success) { - return srs_error_wrap(err, "play edge"); - } - } - + return err; } @@ -2739,14 +2683,3 @@ string SrsSource::get_curr_origin() return play_edge->get_curr_origin(); } -#ifdef SRS_AUTO_RTC -SrsRtpSharedPacket* SrsSource::find_rtp_packet(const uint16_t& seq) -{ - return rtp_queue->find(seq); -} - -SrsMetaCache* SrsSource::cached_meta() -{ - return meta; -} -#endif diff --git a/trunk/src/app/srs_app_source.hpp b/trunk/src/app/srs_app_source.hpp index fdfe9c789..4a58eb954 100644 --- a/trunk/src/app/srs_app_source.hpp +++ b/trunk/src/app/srs_app_source.hpp @@ -51,7 +51,6 @@ class SrsRtmpServer; class SrsEdgeProxyContext; class SrsMessageArray; class SrsNgExec; -class SrsConnection; class SrsMessageHeader; class SrsHls; class SrsRtc; @@ -59,10 +58,9 @@ class SrsDvr; class SrsDash; class SrsEncoder; class SrsBuffer; -#ifdef SRS_AUTO_HDS +#ifdef SRS_HDS class SrsHds; #endif -class SrsRtpSharedPacket; // The time jitter algorithm: // 1. full, to ensure stream start at zero, and ensure stream monotonically increasing. @@ -74,7 +72,7 @@ enum SrsRtmpJitterAlgorithm SrsRtmpJitterAlgorithmZERO, SrsRtmpJitterAlgorithmOFF }; -int _srs_time_jitter_string2int(std::string time_jitter); +int srs_time_jitter_string2int(std::string time_jitter); // Time jitter detect and correct, to ensure the rtmp stream is monotonically. class SrsRtmpJitter @@ -151,13 +149,12 @@ public: // Enqueue the message, the timestamp always monotonically. // @param msg, the msg to enqueue, user never free it whatever the return code. // @param is_overflow, whether overflow and shrinked. NULL to ignore. - // @remark If pass_timestamp, we never shrink and never care about the timestamp or duration. - virtual srs_error_t enqueue(SrsSharedPtrMessage* msg, bool* is_overflow = NULL, bool pass_timestamp = false); + virtual srs_error_t enqueue(SrsSharedPtrMessage* msg, bool* is_overflow = NULL); // Get packets in consumer queue. // @pmsgs SrsSharedPtrMessage*[], used to store the msgs, user must alloc it. // @count the count in array, output param. // @max_count the max count to dequeue, must be positive. - virtual srs_error_t dump_packets(int max_count, SrsSharedPtrMessage** pmsgs, int& count, bool pass_timestamp = false); + virtual srs_error_t dump_packets(int max_count, SrsSharedPtrMessage** pmsgs, int& count); // Dumps packets to consumer, use specified args. // @remark the atc/tba/tbv/ag are same to SrsConsumer.enqueue(). virtual srs_error_t dump_packets(SrsConsumer* consumer, bool atc, SrsRtmpJitterAlgorithm ag); @@ -185,14 +182,12 @@ public: }; // The consumer for SrsSource, that is a play client. -class SrsConsumer : public ISrsWakable +class SrsConsumer : virtual public ISrsWakable { private: SrsRtmpJitter* jitter; SrsSource* source; SrsMessageQueue* queue; - // The owner connection for debug, maybe NULL. - SrsConnection* conn; bool paused; // when source id changed, notice all consumers bool should_update_source_id; @@ -204,17 +199,10 @@ private: int mw_min_msgs; srs_utime_t mw_duration; #endif -private: - // For RTC, we never use jitter to correct timestamp. - // But we should not change the atc or time_jitter for source or RTMP. - // @remark In this mode, we also never check the queue by timstamp, but only by count. - bool pass_timestamp; public: - SrsConsumer(SrsSource* s, SrsConnection* c); + SrsConsumer(SrsSource* s); virtual ~SrsConsumer(); public: - // Use pass timestamp mode. - void enable_pass_timestamp() { pass_timestamp = true; } // Set the size of queue. virtual void set_queue_size(srs_utime_t queue_size); // when source id changed, notice client to print. @@ -333,32 +321,6 @@ public: virtual SrsSharedPtrMessage* pop(); }; -#ifdef SRS_AUTO_RTC -// To find the RTP packet for RTX or restore. -// TODO: FIXME: Should queue RTP packets in connection level. -class SrsRtpPacketQueue -{ -private: - struct SeqComp - { - bool operator()(const uint16_t& l, const uint16_t& r) const - { - return ((int16_t)(r - l)) > 0; - } - }; -private: - std::map pkt_queue; -public: - SrsRtpPacketQueue(); - virtual ~SrsRtpPacketQueue(); -public: - void clear(); - void push(std::vector& pkts); - void insert(const uint16_t& sequence, SrsRtpSharedPacket* pkt); - SrsRtpSharedPacket* find(const uint16_t& sequence); -}; -#endif - // The hub for origin is a collection of utilities for origin only, // For example, DVR, HLS, Forward and Transcode are only available for origin, // they are meanless for edge server. @@ -371,10 +333,6 @@ private: private: // The format, codec information. SrsRtmpFormat* format; -#ifdef SRS_AUTO_RTC - // rtc handler - SrsRtc* rtc; -#endif // hls handler. SrsHls* hls; // The DASH encoder. @@ -383,7 +341,7 @@ private: SrsDvr* dvr; // transcoding handler. SrsEncoder* encoder; -#ifdef SRS_AUTO_HDS +#ifdef SRS_HDS // adobe hds(http dynamic streaming). SrsHds *hds; #endif @@ -517,7 +475,7 @@ public: private: virtual srs_error_t do_cycle(); public: - // when system exit, destroy the sources, + // when system exit, destroy th`e sources, // For gmc to analysis mem leaks. virtual void destroy(); }; @@ -525,6 +483,19 @@ public: // Global singleton instance. extern SrsSourceManager* _srs_sources; +// For two sources to bridge with each other. +class ISrsSourceBridger +{ +public: + ISrsSourceBridger(); + virtual ~ISrsSourceBridger(); +public: + virtual srs_error_t on_publish() = 0; + virtual srs_error_t on_audio(SrsSharedPtrMessage* audio) = 0; + virtual srs_error_t on_video(SrsSharedPtrMessage* video) = 0; + virtual void on_unpublish() = 0; +}; + // live streaming source. class SrsSource : public ISrsReloadHandler { @@ -534,9 +505,9 @@ private: // For edge, it's the edge ingest id. // when source id changed, for example, the edge reconnect, // invoke the on_source_id_changed() to let all clients know. - int _source_id; + SrsContextId _source_id; // previous source id. - int _pre_source_id; + SrsContextId _pre_source_id; // deep copy of client request. SrsRequest* req; // To delivery stream to clients. @@ -547,10 +518,6 @@ private: bool mix_correct; // The mix queue to implements the mix correct algorithm. SrsMixQueue* mix_queue; -#ifdef SRS_AUTO_RTC - // rtp packet queue - SrsRtpPacketQueue* rtp_queue; -#endif // For play, whether enabled atc. // The atc(use absolute time and donot adjust time), // directly use msg time and donot adjust if atc is true, @@ -562,6 +529,8 @@ private: int64_t last_packet_time; // The event handler. ISrsSourceHandler* handler; + // The source bridger for other source. + ISrsSourceBridger* bridger; // The edge control service SrsPlayEdge* play_edge; SrsPublishEdge* publish_edge; @@ -588,15 +557,17 @@ public: public: // Initialize the hls with handlers. virtual srs_error_t initialize(SrsRequest* r, ISrsSourceHandler* h); + // Bridge to other source, forward packets to it. + void bridge_to(ISrsSourceBridger* v); // Interface ISrsReloadHandler public: virtual srs_error_t on_reload_vhost_play(std::string vhost); public: // The source id changed. - virtual srs_error_t on_source_id_changed(int id); + virtual srs_error_t on_source_id_changed(SrsContextId id); // Get current source id. - virtual int source_id(); - virtual int pre_source_id(); + virtual SrsContextId source_id(); + virtual SrsContextId pre_source_id(); // Whether source is inactive, which means there is no publishing stream source. // @remark For edge, it's inactive util stream has been pulled from origin. virtual bool inactive(); @@ -606,10 +577,12 @@ public: virtual bool can_publish(bool is_edge); virtual srs_error_t on_meta_data(SrsCommonMessage* msg, SrsOnMetaDataPacket* metadata); public: + // TODO: FIXME: Use SrsSharedPtrMessage instead. virtual srs_error_t on_audio(SrsCommonMessage* audio); private: virtual srs_error_t on_audio_imp(SrsSharedPtrMessage* audio); public: + // TODO: FIXME: Use SrsSharedPtrMessage instead. virtual srs_error_t on_video(SrsCommonMessage* video); private: virtual srs_error_t on_video_imp(SrsSharedPtrMessage* video); @@ -621,12 +594,14 @@ public: virtual srs_error_t on_publish(); virtual void on_unpublish(); public: - // Create consumer and dumps packets in cache. + // Create consumer // @param consumer, output the create consumer. + virtual srs_error_t create_consumer(SrsConsumer*& consumer); + // Dumps packets in cache to consumer. // @param ds, whether dumps the sequence header. // @param dm, whether dumps the metadata. // @param dg, whether dumps the gop cache. - virtual srs_error_t create_consumer(SrsConnection* conn, SrsConsumer*& consumer, bool ds = true, bool dm = true, bool dg = true); + virtual srs_error_t consumer_dumps(SrsConsumer* consumer, bool ds = true, bool dm = true, bool dg = true); virtual void on_consumer_destroy(SrsConsumer* consumer); virtual void set_cache(bool enabled); virtual SrsRtmpJitterAlgorithm jitter(); @@ -639,13 +614,6 @@ public: virtual void on_edge_proxy_unpublish(); public: virtual std::string get_curr_origin(); -public: -#ifdef SRS_AUTO_RTC - // Find rtp packet by sequence - SrsRtpSharedPacket* find_rtp_packet(const uint16_t& seq); - // Get the cached meta, as such the sps/pps. - SrsMetaCache* cached_meta(); -#endif }; #endif diff --git a/trunk/src/app/srs_app_st.cpp b/trunk/src/app/srs_app_st.cpp index 831a0613a..b11c50fd1 100755 --- a/trunk/src/app/srs_app_st.cpp +++ b/trunk/src/app/srs_app_st.cpp @@ -40,6 +40,14 @@ ISrsCoroutineHandler::~ISrsCoroutineHandler() { } +ISrsStartable::ISrsStartable() +{ +} + +ISrsStartable::~ISrsStartable() +{ +} + SrsCoroutine::SrsCoroutine() { } @@ -74,21 +82,37 @@ srs_error_t SrsDummyCoroutine::pull() return srs_error_new(ERROR_THREAD_DUMMY, "dummy pull"); } -int SrsDummyCoroutine::cid() +const SrsContextId& SrsDummyCoroutine::cid() { - return 0; + return _srs_context->get_id(); } _ST_THREAD_CREATE_PFN _pfn_st_thread_create = (_ST_THREAD_CREATE_PFN)st_thread_create; -SrsSTCoroutine::SrsSTCoroutine(string n, ISrsCoroutineHandler* h, int cid) +SrsSTCoroutine::SrsSTCoroutine(string n, ISrsCoroutineHandler* h) { + // TODO: FIXME: Reduce duplicated code. name = n; handler = h; - context = cid; trd = NULL; trd_err = srs_success; started = interrupted = disposed = cycle_done = false; + + // 0 use default, default is 64K. + stack_size = 0; +} + +SrsSTCoroutine::SrsSTCoroutine(string n, ISrsCoroutineHandler* h, SrsContextId cid) +{ + name = n; + handler = h; + cid_ = cid; + trd = NULL; + trd_err = srs_success; + started = interrupted = disposed = cycle_done = false; + + // 0 use default, default is 64K. + stack_size = 0; } SrsSTCoroutine::~SrsSTCoroutine() @@ -98,6 +122,11 @@ SrsSTCoroutine::~SrsSTCoroutine() srs_freep(trd_err); } +void SrsSTCoroutine::set_stack_size(int v) +{ + stack_size = v; +} + srs_error_t SrsSTCoroutine::start() { srs_error_t err = srs_success; @@ -115,8 +144,8 @@ srs_error_t SrsSTCoroutine::start() return err; } - - if ((trd = (srs_thread_t)_pfn_st_thread_create(pfn, this, 1, 0)) == NULL) { + + if ((trd = (srs_thread_t)_pfn_st_thread_create(pfn, this, 1, stack_size)) == NULL) { err = srs_error_new(ERROR_ST_CREATE_CYCLE_THREAD, "create failed"); srs_freep(trd_err); @@ -180,19 +209,18 @@ srs_error_t SrsSTCoroutine::pull() return srs_error_copy(trd_err); } -int SrsSTCoroutine::cid() +const SrsContextId& SrsSTCoroutine::cid() { - return context; + return cid_; } srs_error_t SrsSTCoroutine::cycle() { if (_srs_context) { - if (context) { - _srs_context->set_id(context); - } else { - context = _srs_context->generate_id(); + if (cid_.empty()) { + cid_ = _srs_context->generate_id(); } + _srs_context->set_id(cid_); } srs_error_t err = handler->cycle(); diff --git a/trunk/src/app/srs_app_st.hpp b/trunk/src/app/srs_app_st.hpp index 956e00b42..42313ed7a 100644 --- a/trunk/src/app/srs_app_st.hpp +++ b/trunk/src/app/srs_app_st.hpp @@ -28,6 +28,7 @@ #include +#include #include #include @@ -67,20 +68,29 @@ public: virtual srs_error_t cycle() = 0; }; +// Start the object, generally a croutine. +class ISrsStartable +{ +public: + ISrsStartable(); + virtual ~ISrsStartable(); +public: + virtual srs_error_t start() = 0; +}; + // The corotine object. -class SrsCoroutine +class SrsCoroutine : public ISrsStartable { public: SrsCoroutine(); virtual ~SrsCoroutine(); public: - virtual srs_error_t start() = 0; virtual void stop() = 0; virtual void interrupt() = 0; // @return a copy of error, which should be freed by user. // NULL if not terminated and user should pull again. virtual srs_error_t pull() = 0; - virtual int cid() = 0; + virtual const SrsContextId& cid() = 0; }; // An empty coroutine, user can default to this object before create any real coroutine. @@ -95,7 +105,7 @@ public: virtual void stop(); virtual void interrupt(); virtual srs_error_t pull(); - virtual int cid(); + virtual const SrsContextId& cid(); }; // For utest to mock the thread create. @@ -118,10 +128,11 @@ class SrsSTCoroutine : public SrsCoroutine { private: std::string name; + int stack_size; ISrsCoroutineHandler* handler; private: srs_thread_t trd; - int context; + SrsContextId cid_; srs_error_t trd_err; private: bool started; @@ -132,8 +143,12 @@ private: public: // Create a thread with name n and handler h. // @remark User can specify a cid for thread to use, or we will allocate a new one. - SrsSTCoroutine(std::string n, ISrsCoroutineHandler* h, int cid = 0); + SrsSTCoroutine(std::string n, ISrsCoroutineHandler* h); + SrsSTCoroutine(std::string n, ISrsCoroutineHandler* h, SrsContextId cid); virtual ~SrsSTCoroutine(); +public: + // Set the stack size of coroutine, default to 0(64KB). + void set_stack_size(int v); public: // Start the thread. // @remark Should never start it when stopped or terminated. @@ -154,7 +169,7 @@ public: // @remark Return ERROR_THREAD_INTERRUPED when thread is interrupted. virtual srs_error_t pull(); // Get the context id of thread. - virtual int cid(); + virtual const SrsContextId& cid(); private: virtual srs_error_t cycle(); static void* pfn(void* arg); diff --git a/trunk/src/app/srs_app_statistic.cpp b/trunk/src/app/srs_app_statistic.cpp index 890e43746..69d4a5cb5 100644 --- a/trunk/src/app/srs_app_statistic.cpp +++ b/trunk/src/app/srs_app_statistic.cpp @@ -35,14 +35,17 @@ using namespace std; #include #include -int64_t srs_gvid = 0; - -int64_t srs_generate_id() +string srs_generate_id() { + static int64_t srs_gvid = 0; + if (srs_gvid == 0) { - srs_gvid = getpid() * 3; + srs_gvid = getpid(); } - return srs_gvid++; + + string prefix = "vid"; + string rand_id = srs_int2str(srs_get_system_time() % 1000); + return prefix + "-" + srs_int2str(srs_gvid++) + "-" + rand_id; } SrsStatisticVhost::SrsStatisticVhost() @@ -71,7 +74,7 @@ srs_error_t SrsStatisticVhost::dumps(SrsJsonObject* obj) bool hls_enabled = _srs_config->get_hls_enabled(vhost); bool enabled = _srs_config->get_vhost_enabled(vhost); - obj->set("id", SrsJsonAny::integer(id)); + obj->set("id", SrsJsonAny::str(id.c_str())); obj->set("name", SrsJsonAny::str(vhost.c_str())); obj->set("enabled", SrsJsonAny::boolean(enabled)); obj->set("clients", SrsJsonAny::integer(nb_clients)); @@ -101,8 +104,7 @@ SrsStatisticStream::SrsStatisticStream() id = srs_generate_id(); vhost = NULL; active = false; - connection_cid = -1; - + has_video = false; vcodec = SrsVideoCodecIdReserved; avc_profile = SrsAvcProfileReserved; @@ -134,9 +136,9 @@ srs_error_t SrsStatisticStream::dumps(SrsJsonObject* obj) { srs_error_t err = srs_success; - obj->set("id", SrsJsonAny::integer(id)); + obj->set("id", SrsJsonAny::str(id.c_str())); obj->set("name", SrsJsonAny::str(stream.c_str())); - obj->set("vhost", SrsJsonAny::integer(vhost->id)); + obj->set("vhost", SrsJsonAny::str(vhost->id.c_str())); obj->set("app", SrsJsonAny::str(app.c_str())); obj->set("live_ms", SrsJsonAny::integer(srsu2ms(srs_get_system_time()))); obj->set("clients", SrsJsonAny::integer(nb_clients)); @@ -154,7 +156,7 @@ srs_error_t SrsStatisticStream::dumps(SrsJsonObject* obj) obj->set("publish", publish); publish->set("active", SrsJsonAny::boolean(active)); - publish->set("cid", SrsJsonAny::integer(connection_cid)); + publish->set("cid", SrsJsonAny::str(connection_cid.c_str())); if (!has_video) { obj->set("video", SrsJsonAny::null()); @@ -184,7 +186,7 @@ srs_error_t SrsStatisticStream::dumps(SrsJsonObject* obj) return err; } -void SrsStatisticStream::publish(int cid) +void SrsStatisticStream::publish(SrsContextId cid) { connection_cid = cid; active = true; @@ -203,7 +205,6 @@ void SrsStatisticStream::close() SrsStatisticClient::SrsStatisticClient() { - id = 0; stream = NULL; conn = NULL; req = NULL; @@ -219,9 +220,9 @@ srs_error_t SrsStatisticClient::dumps(SrsJsonObject* obj) { srs_error_t err = srs_success; - obj->set("id", SrsJsonAny::integer(id)); - obj->set("vhost", SrsJsonAny::integer(stream->vhost->id)); - obj->set("stream", SrsJsonAny::integer(stream->id)); + obj->set("id", SrsJsonAny::str(id.c_str())); + obj->set("vhost", SrsJsonAny::str(stream->vhost->id.c_str())); + obj->set("stream", SrsJsonAny::str(stream->id.c_str())); obj->set("ip", SrsJsonAny::str(req->ip.c_str())); obj->set("pageUrl", SrsJsonAny::str(req->pageUrl.c_str())); obj->set("swfUrl", SrsJsonAny::str(req->swfUrl.c_str())); @@ -267,12 +268,9 @@ SrsStatistic::SrsStatistic() perf_iovs = new SrsStatisticCategory(); perf_msgs = new SrsStatisticCategory(); - perf_sendmmsg = new SrsStatisticCategory(); - perf_gso = new SrsStatisticCategory(); perf_rtp = new SrsStatisticCategory(); perf_rtc = new SrsStatisticCategory(); perf_bytes = new SrsStatisticCategory(); - perf_dropped = new SrsStatisticCategory(); } SrsStatistic::~SrsStatistic() @@ -281,21 +279,21 @@ SrsStatistic::~SrsStatistic() srs_freep(clk); if (true) { - std::map::iterator it; + std::map::iterator it; for (it = vhosts.begin(); it != vhosts.end(); it++) { SrsStatisticVhost* vhost = it->second; srs_freep(vhost); } } if (true) { - std::map::iterator it; + std::map::iterator it; for (it = streams.begin(); it != streams.end(); it++) { SrsStatisticStream* stream = it->second; srs_freep(stream); } } if (true) { - std::map::iterator it; + std::map::iterator it; for (it = clients.begin(); it != clients.end(); it++) { SrsStatisticClient* client = it->second; srs_freep(client); @@ -309,12 +307,9 @@ SrsStatistic::~SrsStatistic() srs_freep(perf_iovs); srs_freep(perf_msgs); - srs_freep(perf_sendmmsg); - srs_freep(perf_gso); srs_freep(perf_rtp); srs_freep(perf_rtc); srs_freep(perf_bytes); - srs_freep(perf_dropped); } SrsStatistic* SrsStatistic::instance() @@ -325,21 +320,21 @@ SrsStatistic* SrsStatistic::instance() return _instance; } -SrsStatisticVhost* SrsStatistic::find_vhost(int vid) +SrsStatisticVhost* SrsStatistic::find_vhost_by_id(std::string vid) { - std::map::iterator it; + std::map::iterator it; if ((it = vhosts.find(vid)) != vhosts.end()) { return it->second; } return NULL; } -SrsStatisticVhost* SrsStatistic::find_vhost(string name) +SrsStatisticVhost* SrsStatistic::find_vhost_by_name(string name) { if (rvhosts.empty()) { return NULL; } - + std::map::iterator it; if ((it = rvhosts.find(name)) != rvhosts.end()) { return it->second; @@ -347,19 +342,19 @@ SrsStatisticVhost* SrsStatistic::find_vhost(string name) return NULL; } -SrsStatisticStream* SrsStatistic::find_stream(int sid) +SrsStatisticStream* SrsStatistic::find_stream(string sid) { - std::map::iterator it; + std::map::iterator it; if ((it = streams.find(sid)) != streams.end()) { return it->second; } return NULL; } -SrsStatisticClient* SrsStatistic::find_client(int cid) +SrsStatisticClient* SrsStatistic::find_client(string client_id) { - std::map::iterator it; - if ((it = clients.find(cid)) != clients.end()) { + std::map::iterator it; + if ((it = clients.find(client_id)) != clients.end()) { return it->second; } return NULL; @@ -411,7 +406,7 @@ srs_error_t SrsStatistic::on_video_frames(SrsRequest* req, int nb_frames) return err; } -void SrsStatistic::on_stream_publish(SrsRequest* req, int cid) +void SrsStatistic::on_stream_publish(SrsRequest* req, SrsContextId cid) { SrsStatisticVhost* vhost = create_vhost(req); SrsStatisticStream* stream = create_stream(vhost, req); @@ -427,7 +422,7 @@ void SrsStatistic::on_stream_close(SrsRequest* req) // TODO: FIXME: Should fix https://github.com/ossrs/srs/issues/803 if (true) { - std::map::iterator it; + std::map::iterator it; if ((it=streams.find(stream->id)) != streams.end()) { streams.erase(it); } @@ -436,16 +431,19 @@ void SrsStatistic::on_stream_close(SrsRequest* req) // TODO: FIXME: Should fix https://github.com/ossrs/srs/issues/803 if (true) { std::map::iterator it; - if ((it=rstreams.find(stream->url)) != rstreams.end()) { + if ((it = rstreams.find(stream->url)) != rstreams.end()) { rstreams.erase(it); } } } -srs_error_t SrsStatistic::on_client(int id, SrsRequest* req, SrsConnection* conn, SrsRtmpConnType type) +srs_error_t SrsStatistic::on_client(SrsContextId cid, SrsRequest* req, ISrsExpire* conn, SrsRtmpConnType type) { srs_error_t err = srs_success; - + + // TODO: FIXME: We should use UUID for client ID. + std::string id = cid.c_str(); + SrsStatisticVhost* vhost = create_vhost(req); SrsStatisticStream* stream = create_stream(vhost, req); @@ -470,9 +468,12 @@ srs_error_t SrsStatistic::on_client(int id, SrsRequest* req, SrsConnection* conn return err; } -void SrsStatistic::on_disconnect(int id) +void SrsStatistic::on_disconnect(const SrsContextId& cid) { - std::map::iterator it; + // TODO: FIXME: We should use UUID for client ID. + std::string id = cid.c_str(); + + std::map::iterator it; if ((it = clients.find(id)) == clients.end()) { return; } @@ -488,9 +489,10 @@ void SrsStatistic::on_disconnect(int id) vhost->nb_clients--; } -void SrsStatistic::kbps_add_delta(SrsConnection* conn) +void SrsStatistic::kbps_add_delta(const SrsContextId& cid, ISrsKbpsDelta* delta) { - int id = conn->srs_id(); + // TODO: FIXME: Should not use context id as connection id. + std::string id = cid.c_str(); if (clients.find(id) == clients.end()) { return; } @@ -499,7 +501,7 @@ void SrsStatistic::kbps_add_delta(SrsConnection* conn) // resample the kbps to collect the delta. int64_t in, out; - conn->remark(&in, &out); + delta->remark(&in, &out); // add delta of connection to kbps. // for next sample() of server kbps can get the stat. @@ -512,14 +514,14 @@ SrsKbps* SrsStatistic::kbps_sample() { kbps->sample(); if (true) { - std::map::iterator it; + std::map::iterator it; for (it = vhosts.begin(); it != vhosts.end(); it++) { SrsStatisticVhost* vhost = it->second; vhost->kbps->sample(); } } if (true) { - std::map::iterator it; + std::map::iterator it; for (it = streams.begin(); it != streams.end(); it++) { SrsStatisticStream* stream = it->second; stream->kbps->sample(); @@ -529,7 +531,7 @@ SrsKbps* SrsStatistic::kbps_sample() return kbps; } -int64_t SrsStatistic::server_id() +std::string SrsStatistic::server_id() { return _server_id; } @@ -538,7 +540,7 @@ srs_error_t SrsStatistic::dumps_vhosts(SrsJsonArray* arr) { srs_error_t err = srs_success; - std::map::iterator it; + std::map::iterator it; for (it = vhosts.begin(); it != vhosts.end(); it++) { SrsStatisticVhost* vhost = it->second; @@ -557,7 +559,7 @@ srs_error_t SrsStatistic::dumps_streams(SrsJsonArray* arr) { srs_error_t err = srs_success; - std::map::iterator it; + std::map::iterator it; for (it = streams.begin(); it != streams.end(); it++) { SrsStatisticStream* stream = it->second; @@ -576,7 +578,7 @@ srs_error_t SrsStatistic::dumps_clients(SrsJsonArray* arr, int start, int count) { srs_error_t err = srs_success; - std::map::iterator it = clients.begin(); + std::map::iterator it = clients.begin(); for (int i = 0; i < start + count && it != clients.end(); it++, i++) { if (i < start) { continue; @@ -625,16 +627,6 @@ srs_error_t SrsStatistic::dumps_perf_rtp_packets(SrsJsonObject* obj) return dumps_perf(perf_rtp, obj); } -void SrsStatistic::perf_on_gso_packets(int nb_packets) -{ - perf_on_packets(perf_gso, nb_packets); -} - -srs_error_t SrsStatistic::dumps_perf_gso(SrsJsonObject* obj) -{ - return dumps_perf(perf_gso, obj); -} - void SrsStatistic::perf_on_writev_iovs(int nb_iovs) { perf_on_packets(perf_iovs, nb_iovs); @@ -645,16 +637,6 @@ srs_error_t SrsStatistic::dumps_perf_writev_iovs(SrsJsonObject* obj) return dumps_perf(perf_iovs, obj); } -void SrsStatistic::perf_on_sendmmsg_packets(int nb_packets) -{ - perf_on_packets(perf_sendmmsg, nb_packets); -} - -srs_error_t SrsStatistic::dumps_perf_sendmmsg(SrsJsonObject* obj) -{ - return dumps_perf(perf_sendmmsg, obj); -} - void SrsStatistic::perf_on_rtc_bytes(int nn_bytes, int nn_rtp_bytes, int nn_padding) { // a: AVFrame bytes. @@ -678,48 +660,19 @@ srs_error_t SrsStatistic::dumps_perf_bytes(SrsJsonObject* obj) return srs_success; } -void SrsStatistic::perf_on_dropped(int nn_msgs, int nn_rtc, int nn_dropped) -{ - // a: System AVFrames. - // b: RTC frames. - // c: Dropped frames. - perf_dropped->a += nn_msgs; - perf_dropped->b += nn_rtc; - perf_dropped->c += nn_dropped; - - perf_dropped->nn += nn_dropped; -} - -srs_error_t SrsStatistic::dumps_perf_dropped(SrsJsonObject* obj) -{ - obj->set("avframes", SrsJsonAny::integer(perf_dropped->a)); - obj->set("rtc_frames", SrsJsonAny::integer(perf_dropped->b)); - obj->set("rtc_dropeed", SrsJsonAny::integer(perf_dropped->c)); - - obj->set("nn", SrsJsonAny::integer(perf_dropped->nn)); - - return srs_success; -} - void SrsStatistic::reset_perf() { srs_freep(perf_iovs); srs_freep(perf_msgs); - srs_freep(perf_sendmmsg); - srs_freep(perf_gso); srs_freep(perf_rtp); srs_freep(perf_rtc); srs_freep(perf_bytes); - srs_freep(perf_dropped); perf_iovs = new SrsStatisticCategory(); perf_msgs = new SrsStatisticCategory(); - perf_sendmmsg = new SrsStatisticCategory(); - perf_gso = new SrsStatisticCategory(); perf_rtp = new SrsStatisticCategory(); perf_rtc = new SrsStatisticCategory(); perf_bytes = new SrsStatisticCategory(); - perf_dropped = new SrsStatisticCategory(); } void SrsStatistic::perf_on_packets(SrsStatisticCategory* p, int nb_msgs) diff --git a/trunk/src/app/srs_app_statistic.hpp b/trunk/src/app/srs_app_statistic.hpp index 81ed9e27d..c62eec78d 100644 --- a/trunk/src/app/srs_app_statistic.hpp +++ b/trunk/src/app/srs_app_statistic.hpp @@ -36,14 +36,15 @@ class SrsKbps; class SrsWallClock; class SrsRequest; -class SrsConnection; +class ISrsExpire; class SrsJsonObject; class SrsJsonArray; +class ISrsKbpsDelta; struct SrsStatisticVhost { public: - int64_t id; + std::string id; std::string vhost; int nb_streams; int nb_clients; @@ -61,13 +62,13 @@ public: struct SrsStatisticStream { public: - int64_t id; + std::string id; SrsStatisticVhost* vhost; std::string app; std::string stream; std::string url; bool active; - int connection_cid; + SrsContextId connection_cid; int nb_clients; uint64_t nb_frames; public: @@ -101,7 +102,7 @@ public: virtual srs_error_t dumps(SrsJsonObject* obj); public: // Publish the stream. - virtual void publish(int cid); + virtual void publish(SrsContextId cid); // Close the stream. virtual void close(); }; @@ -109,11 +110,11 @@ public: struct SrsStatisticClient { public: + ISrsExpire* conn; SrsStatisticStream* stream; - SrsConnection* conn; SrsRequest* req; SrsRtmpConnType type; - int id; + std::string id; srs_utime_t create; public: SrsStatisticClient(); @@ -148,44 +149,41 @@ class SrsStatistic : public ISrsProtocolPerf private: static SrsStatistic *_instance; // The id to identify the sever. - int64_t _server_id; + std::string _server_id; private: // The key: vhost id, value: vhost object. - std::map vhosts; + std::map vhosts; // The key: vhost url, value: vhost Object. // @remark a fast index for vhosts. std::map rvhosts; private: // The key: stream id, value: stream Object. - std::map streams; + std::map streams; // The key: stream url, value: stream Object. // @remark a fast index for streams. std::map rstreams; private: // The key: client id, value: stream object. - std::map clients; + std::map clients; // The server total kbps. SrsKbps* kbps; SrsWallClock* clk; // The perf stat for mw(merged write). SrsStatisticCategory* perf_iovs; SrsStatisticCategory* perf_msgs; - SrsStatisticCategory* perf_sendmmsg; - SrsStatisticCategory* perf_gso; SrsStatisticCategory* perf_rtp; SrsStatisticCategory* perf_rtc; SrsStatisticCategory* perf_bytes; - SrsStatisticCategory* perf_dropped; private: SrsStatistic(); virtual ~SrsStatistic(); public: static SrsStatistic* instance(); public: - virtual SrsStatisticVhost* find_vhost(int vid); - virtual SrsStatisticVhost* find_vhost(std::string name); - virtual SrsStatisticStream* find_stream(int sid); - virtual SrsStatisticClient* find_client(int cid); + virtual SrsStatisticVhost* find_vhost_by_id(std::string vid); + virtual SrsStatisticVhost* find_vhost_by_name(std::string name); + virtual SrsStatisticStream* find_stream(std::string sid); + virtual SrsStatisticClient* find_client(std::string client_id); public: // When got video info for stream. virtual srs_error_t on_video_info(SrsRequest* req, SrsVideoCodecId vcodec, SrsAvcProfile avc_profile, @@ -199,7 +197,7 @@ public: // When publish stream. // @param req the request object of publish connection. // @param cid the cid of publish connection. - virtual void on_stream_publish(SrsRequest* req, int cid); + virtual void on_stream_publish(SrsRequest* req, SrsContextId cid); // When close stream. virtual void on_stream_close(SrsRequest* req); public: @@ -208,23 +206,24 @@ public: // @param req, the client request object. // @param conn, the physical absract connection object. // @param type, the type of connection. - virtual srs_error_t on_client(int id, SrsRequest* req, SrsConnection* conn, SrsRtmpConnType type); + // TODO: FIXME: We should not use context id as client id. + virtual srs_error_t on_client(SrsContextId id, SrsRequest* req, ISrsExpire* conn, SrsRtmpConnType type); // Client disconnect // @remark the on_disconnect always call, while the on_client is call when // only got the request object, so the client specified by id maybe not // exists in stat. - virtual void on_disconnect(int id); + // TODO: FIXME: We should not use context id as client id. + virtual void on_disconnect(const SrsContextId& id); // Sample the kbps, add delta bytes of conn. // Use kbps_sample() to get all result of kbps stat. - // TODO: FIXME: the add delta must use ISrsKbpsDelta interface instead. - virtual void kbps_add_delta(SrsConnection* conn); + virtual void kbps_add_delta(const SrsContextId& cid, ISrsKbpsDelta* delta); // Calc the result for all kbps. // @return the server kbps. virtual SrsKbps* kbps_sample(); public: // Get the server id, used to identify the server. // For example, when restart, the server id must changed. - virtual int64_t server_id(); + virtual std::string server_id(); // Dumps the vhosts to amf0 array. virtual srs_error_t dumps_vhosts(SrsJsonArray* arr); // Dumps the streams to amf0 array. @@ -248,27 +247,14 @@ public: // For example, a RTC/opus packet maybe package to three RTP packets. virtual void perf_on_rtp_packets(int nb_packets); virtual srs_error_t dumps_perf_rtp_packets(SrsJsonObject* obj); -public: - // Stat for packets UDP GSO, nb_packets is the merged RTP packets. - // For example, three RTP/audio packets maybe GSO to one msghdr. - virtual void perf_on_gso_packets(int nb_packets); - virtual srs_error_t dumps_perf_gso(SrsJsonObject* obj); public: // Stat for TCP writev, nb_iovs is the total number of iovec. virtual void perf_on_writev_iovs(int nb_iovs); virtual srs_error_t dumps_perf_writev_iovs(SrsJsonObject* obj); -public: - // Stat for packets UDP sendmmsg, nb_packets is the vlen for sendmmsg. - virtual void perf_on_sendmmsg_packets(int nb_packets); - virtual srs_error_t dumps_perf_sendmmsg(SrsJsonObject* obj); public: // Stat for bytes, nn_bytes is the size of bytes, nb_padding is padding bytes. virtual void perf_on_rtc_bytes(int nn_bytes, int nn_rtp_bytes, int nn_padding); virtual srs_error_t dumps_perf_bytes(SrsJsonObject* obj); -public: - // Stat for rtc messages, nn_rtc is rtc messages, nn_dropped is dropped messages. - virtual void perf_on_dropped(int nn_msgs, int nn_rtc, int nn_dropped); - virtual srs_error_t dumps_perf_dropped(SrsJsonObject* obj); public: // Reset all perf stat data. virtual void reset_perf(); diff --git a/trunk/src/app/srs_app_thread.cpp b/trunk/src/app/srs_app_thread.cpp deleted file mode 100755 index 19a2b6535..000000000 --- a/trunk/src/app/srs_app_thread.cpp +++ /dev/null @@ -1,92 +0,0 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2013-2020 Winlin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#include - -#include -#include - -#include -using namespace std; - -SrsCoroutineManager::SrsCoroutineManager() -{ - cond = srs_cond_new(); - trd = new SrsSTCoroutine("manager", this); -} - -SrsCoroutineManager::~SrsCoroutineManager() -{ - srs_freep(trd); - srs_cond_destroy(cond); - - clear(); -} - -srs_error_t SrsCoroutineManager::start() -{ - srs_error_t err = srs_success; - - if ((err = trd->start()) != srs_success) { - return srs_error_wrap(err, "coroutine manager"); - } - - return err; -} - -srs_error_t SrsCoroutineManager::cycle() -{ - srs_error_t err = srs_success; - - while (true) { - if ((err = trd->pull()) != srs_success) { - return srs_error_wrap(err, "coroutine mansger"); - } - - srs_cond_wait(cond); - clear(); - } - - return err; -} - -void SrsCoroutineManager::remove(ISrsConnection* c) -{ - conns.push_back(c); - srs_cond_signal(cond); -} - -void SrsCoroutineManager::clear() -{ - // To prevent thread switch when delete connection, - // we copy all connections then free one by one. - vector copy = conns; - conns.clear(); - - vector::iterator it; - for (it = copy.begin(); it != copy.end(); ++it) { - ISrsConnection* conn = *it; - srs_freep(conn); - } -} - diff --git a/trunk/src/app/srs_app_thread.hpp b/trunk/src/app/srs_app_thread.hpp deleted file mode 100644 index 8aa384a3d..000000000 --- a/trunk/src/app/srs_app_thread.hpp +++ /dev/null @@ -1,60 +0,0 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2013-2020 Winlin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#ifndef SRS_APP_THREAD_HPP -#define SRS_APP_THREAD_HPP - -#include - -#include - -#include -#include - -// The coroutine manager use a thread to delete a connection, which will stop the service -// thread, for example, when the RTMP connection thread cycle terminated, it will notify -// the manager(the server) to remove the connection from list of server and push it to -// the manager thread to delete it, finally the thread of connection will stop. -class SrsCoroutineManager : virtual public ISrsCoroutineHandler, virtual public IConnectionManager -{ -private: - SrsCoroutine* trd; - std::vector conns; - srs_cond_t cond; -public: - SrsCoroutineManager(); - virtual ~SrsCoroutineManager(); -public: - srs_error_t start(); -// Interface ISrsCoroutineHandler -public: - virtual srs_error_t cycle(); -// Interface IConnectionManager -public: - virtual void remove(ISrsConnection* c); -private: - void clear(); -}; - -#endif - diff --git a/trunk/src/app/srs_app_utility.cpp b/trunk/src/app/srs_app_utility.cpp index 0fc17ce41..bde16dc83 100644 --- a/trunk/src/app/srs_app_utility.cpp +++ b/trunk/src/app/srs_app_utility.cpp @@ -35,7 +35,7 @@ #include #include #include -#ifdef SRS_AUTO_OSX +#ifdef SRS_OSX #include #endif using namespace std; @@ -235,7 +235,7 @@ void srs_update_system_rusage() return; } - _srs_system_rusage.sample_time = srsu2ms(srs_get_system_time()); + _srs_system_rusage.sample_time = srsu2ms(srs_update_system_time()); _srs_system_rusage.ok = true; } @@ -329,7 +329,7 @@ SrsProcSystemStat* srs_get_system_proc_stat() bool get_proc_system_stat(SrsProcSystemStat& r) { -#ifndef SRS_AUTO_OSX +#ifndef SRS_OSX FILE* f = fopen("/proc/stat", "r"); if (f == NULL) { srs_warn("open system cpu stat failed, ignore"); @@ -368,7 +368,7 @@ bool get_proc_system_stat(SrsProcSystemStat& r) bool get_proc_self_stat(SrsProcSelfStat& r) { -#ifndef SRS_AUTO_OSX +#ifndef SRS_OSX FILE* f = fopen("/proc/self/stat", "r"); if (f == NULL) { srs_warn("open self cpu stat failed, ignore"); @@ -420,7 +420,7 @@ void srs_update_proc_stat() return; } - r.sample_time = srsu2ms(srs_get_system_time()); + r.sample_time = srsu2ms(srs_update_system_time()); // calc usage in percent SrsProcSystemStat& o = _srs_system_cpu_system_stat; @@ -446,7 +446,7 @@ void srs_update_proc_stat() return; } - r.sample_time = srsu2ms(srs_get_system_time()); + r.sample_time = srsu2ms(srs_update_system_time()); // calc usage in percent SrsProcSelfStat& o = _srs_system_cpu_self_stat; @@ -491,14 +491,14 @@ SrsDiskStat* srs_get_disk_stat() bool srs_get_disk_vmstat_stat(SrsDiskStat& r) { -#ifndef SRS_AUTO_OSX +#ifndef SRS_OSX FILE* f = fopen("/proc/vmstat", "r"); if (f == NULL) { srs_warn("open vmstat failed, ignore"); return false; } - r.sample_time = srsu2ms(srs_get_system_time()); + r.sample_time = srsu2ms(srs_update_system_time()); static char buf[1024]; while (fgets(buf, sizeof(buf), f)) { @@ -521,9 +521,9 @@ bool srs_get_disk_vmstat_stat(SrsDiskStat& r) bool srs_get_disk_diskstats_stat(SrsDiskStat& r) { r.ok = true; - r.sample_time = srsu2ms(srs_get_system_time()); + r.sample_time = srsu2ms(srs_update_system_time()); -#ifndef SRS_AUTO_OSX +#ifndef SRS_OSX // if disabled, ignore all devices. SrsConfDirective* conf = _srs_config->get_stats_disk_device(); if (conf == NULL) { @@ -687,7 +687,7 @@ void srs_update_meminfo() { SrsMemInfo& r = _srs_system_meminfo; -#ifndef SRS_AUTO_OSX +#ifndef SRS_OSX FILE* f = fopen("/proc/meminfo", "r"); if (f == NULL) { srs_warn("open meminfo failed, ignore"); @@ -715,7 +715,7 @@ void srs_update_meminfo() fclose(f); #endif - r.sample_time = srsu2ms(srs_get_system_time()); + r.sample_time = srsu2ms(srs_update_system_time()); r.MemActive = r.MemTotal - r.MemFree; r.RealInUse = r.MemActive - r.Buffers - r.Cached; r.NotInUse = r.MemTotal - r.RealInUse; @@ -781,7 +781,7 @@ void srs_update_platform_info() r.srs_startup_time = srsu2ms(srs_get_system_startup_time()); -#ifndef SRS_AUTO_OSX +#ifndef SRS_OSX if (true) { FILE* f = fopen("/proc/uptime", "r"); if (f == NULL) { @@ -893,7 +893,7 @@ int srs_get_network_devices_count() void srs_update_network_devices() { -#ifndef SRS_AUTO_OSX +#ifndef SRS_OSX if (true) { FILE* f = fopen("/proc/net/dev", "r"); if (f == NULL) { @@ -924,7 +924,7 @@ void srs_update_network_devices() _nb_srs_system_network_devices = i + 1; srs_info("scan network device ifname=%s, total=%d", r.name, _nb_srs_system_network_devices); - r.sample_time = srsu2ms(srs_get_system_time()); + r.sample_time = srsu2ms(srs_update_system_time()); r.ok = true; } @@ -940,6 +940,9 @@ SrsNetworkRtmpServer::SrsNetworkRtmpServer() nb_conn_sys = nb_conn_srs = 0; nb_conn_sys_et = nb_conn_sys_tw = 0; nb_conn_sys_udp = 0; + rkbps = skbps = 0; + rkbps_30s = skbps_30s = 0; + rkbps_5m = skbps_5m = 0; } static SrsNetworkRtmpServer _srs_network_rtmp_server; @@ -978,7 +981,7 @@ void srs_update_rtmp_server(int nb_conn, SrsKbps* kbps) int nb_tcp_mem = 0; int nb_udp4 = 0; -#ifndef SRS_AUTO_OSX +#ifndef SRS_OSX if (true) { FILE* f = fopen("/proc/net/sockstat", "r"); if (f == NULL) { @@ -1021,7 +1024,7 @@ void srs_update_rtmp_server(int nb_conn, SrsKbps* kbps) int nb_tcp_estab = 0; -#ifndef SRS_AUTO_OSX +#ifndef SRS_OSX if (true) { FILE* f = fopen("/proc/net/snmp", "r"); if (f == NULL) { @@ -1067,7 +1070,7 @@ void srs_update_rtmp_server(int nb_conn, SrsKbps* kbps) r.ok = true; r.nb_conn_srs = nb_conn; - r.sample_time = srsu2ms(srs_get_system_time()); + r.sample_time = srsu2ms(srs_update_system_time()); r.rbytes = kbps->get_recv_bytes(); r.rkbps = kbps->get_recv_kbps(); @@ -1143,6 +1146,28 @@ string srs_get_peer_ip(int fd) return std::string(saddr); } +int srs_get_peer_port(int fd) +{ + // discovery client information + sockaddr_storage addr; + socklen_t addrlen = sizeof(addr); + if (getpeername(fd, (sockaddr*)&addr, &addrlen) == -1) { + return 0; + } + + int port = 0; + switch(addr.ss_family) { + case AF_INET: + port = ntohs(((sockaddr_in*)&addr)->sin_port); + break; + case AF_INET6: + port = ntohs(((sockaddr_in6*)&addr)->sin6_port); + break; + } + + return port; +} + bool srs_is_boolean(string str) { return str == "true" || str == "false"; @@ -1165,7 +1190,7 @@ void srs_api_dump_summaries(SrsJsonObject* obj) self_mem_percent = (float)(r->r.ru_maxrss / (double)m->MemTotal); } - int64_t now = srsu2ms(srs_get_system_time()); + int64_t now = srsu2ms(srs_update_system_time()); double srs_uptime = (now - p->srs_startup_time) / 100 / 10.0; int64_t n_sample_time = 0; @@ -1259,37 +1284,63 @@ void srs_api_dump_summaries(SrsJsonObject* obj) sys->set("conn_srs", SrsJsonAny::integer(nrs->nb_conn_srs)); } -string srs_string_dumps_hex(const std::string& str, const int& limit) +string srs_string_dumps_hex(const std::string& str) { - return srs_string_dumps_hex(str.c_str(), str.size(), limit); + return srs_string_dumps_hex(str.c_str(), str.size()); } -string srs_string_dumps_hex(const char* buf, const int length, const int& limit) +string srs_string_dumps_hex(const char* str, int length) { - string ret; + return srs_string_dumps_hex(str, length, INT_MAX); +} - char tmp_buf[1024*16]; - tmp_buf[0] = '\n'; - int len = 1; - - for (int i = 0; i < length && i < limit; ++i) { - int nb = snprintf(tmp_buf + len, sizeof(tmp_buf) - len - 2, "%02X ", (uint8_t)buf[i]); - if (nb <= 0) +string srs_string_dumps_hex(const char* str, int length, int limit) +{ + return srs_string_dumps_hex(str, length, limit, ' ', 128, '\n'); +} + +string srs_string_dumps_hex(const char* str, int length, int limit, char seperator, int line_limit, char newline) +{ + // 1 byte trailing '\0'. + const int LIMIT = 1024*16 + 1; + static char buf[LIMIT]; + + int len = 0; + for (int i = 0; i < length && i < limit && len < LIMIT; ++i) { + int nb = snprintf(buf + len, LIMIT - len, "%02x", (uint8_t)str[i]); + if (nb < 0 || nb >= LIMIT - len) { break; + } + len += nb; - len += nb; + // Only append seperator and newline when not last byte. + if (i < length - 1 && i < limit - 1 && len < LIMIT) { + if (seperator) { + buf[len++] = seperator; + } - if (i % 48 == 47) { - tmp_buf[len++] = '\n'; - ret.append(tmp_buf, len); - len = 0; - } - } - tmp_buf[len] = '\0'; - ret.append(tmp_buf, len); + if (newline && line_limit && i > 0 && ((i + 1) % line_limit) == 0) { + buf[len++] = newline; + } + } + } - return ret; + // Empty string. + if (len <= 0) { + return ""; + } + // If overflow, cut the trailing newline. + if (newline && len >= LIMIT - 2 && buf[len - 1] == newline) { + len--; + } + + // If overflow, cut the trailing seperator. + if (seperator && len >= LIMIT - 3 && buf[len - 1] == seperator) { + len--; + } + + return string(buf, len); } string srs_getenv(string key) diff --git a/trunk/src/app/srs_app_utility.hpp b/trunk/src/app/srs_app_utility.hpp index 8618c3798..63bfef577 100644 --- a/trunk/src/app/srs_app_utility.hpp +++ b/trunk/src/app/srs_app_utility.hpp @@ -640,6 +640,7 @@ extern std::string srs_get_local_ip(int fd); extern int srs_get_local_port(int fd); // Where peer ip is the client public ip which connected to server. extern std::string srs_get_peer_ip(int fd); +extern int srs_get_peer_port(int fd); // Whether string is boolean // is_bool("true") == true @@ -651,8 +652,11 @@ extern bool srs_is_boolean(std::string str); extern void srs_api_dump_summaries(SrsJsonObject* obj); // Dump string(str in length) to hex, it will process min(limit, length) chars. -extern std::string srs_string_dumps_hex(const std::string& str, const int& limit = INT_MAX); -extern std::string srs_string_dumps_hex(const char* str, const int length, const int& limit = INT_MAX); +// Append seperator between each elem, and newline when exceed line_limit, '\0' to ignore. +extern std::string srs_string_dumps_hex(const std::string& str); +extern std::string srs_string_dumps_hex(const char* str, int length); +extern std::string srs_string_dumps_hex(const char* str, int length, int limit); +extern std::string srs_string_dumps_hex(const char* str, int length, int limit, char seperator, int line_limit, char newline); // Get ENV variable, which may starts with $. // srs_getenv("EIP") === srs_getenv("$EIP") diff --git a/trunk/src/core/srs_core.cpp b/trunk/src/core/srs_core.cpp index 57259c0de..29b882ebe 100644 --- a/trunk/src/core/srs_core.cpp +++ b/trunk/src/core/srs_core.cpp @@ -23,4 +23,43 @@ #include +_SrsContextId::_SrsContextId() +{ +} + +_SrsContextId::_SrsContextId(const _SrsContextId& cp) +{ + v_ = cp.v_; +} + +_SrsContextId& _SrsContextId::operator=(const _SrsContextId& cp) +{ + v_ = cp.v_; + return *this; +} + +_SrsContextId::~_SrsContextId() +{ +} + +const char* _SrsContextId::c_str() const +{ + return v_.c_str(); +} + +bool _SrsContextId::empty() const +{ + return v_.empty(); +} + +int _SrsContextId::compare(const _SrsContextId& to) const +{ + return v_.compare(to.v_); +} + +_SrsContextId& _SrsContextId::set_value(const std::string& v) +{ + v_ = v; + return *this; +} diff --git a/trunk/src/core/srs_core.hpp b/trunk/src/core/srs_core.hpp index 725c1bff4..0fbe07c39 100644 --- a/trunk/src/core/srs_core.hpp +++ b/trunk/src/core/srs_core.hpp @@ -33,11 +33,6 @@ // The macros generated by configure script. #include -// Alias for debug. -#ifdef SRS_AUTO_DEBUG -#define SRS_DEBUG -#endif - // To convert macro values to string. // @see https://gcc.gnu.org/onlinedocs/cpp/Stringification.html#Stringification #define SRS_INTERNAL_STR(v) #v @@ -51,10 +46,11 @@ #define RTMP_SIG_SRS_AUTHORS "Winlin,Wenjie,Runner365,John,B.P.Y,Lixin" #define RTMP_SIG_SRS_VERSION SRS_XSTR(VERSION_MAJOR) "." SRS_XSTR(VERSION_MINOR) "." SRS_XSTR(VERSION_REVISION) #define RTMP_SIG_SRS_SERVER RTMP_SIG_SRS_KEY "/" RTMP_SIG_SRS_VERSION "(" RTMP_SIG_SRS_CODE ")" +#define RTMP_SIG_SRS_DOMAIN "ossrs.net" // The current stable release. #define VERSION_STABLE 3 -#define VERSION_STABLE_BRANCH SRS_XSTR(VERSION_STABLE)".0release" +#define VERSION_STABLE_BRANCH SRS_XSTR(VERSION_STABLE) ".0release" // For 32bit os, 2G big file limit for unistd io, // ie. read/write/lseek to use 64bits size for huge file. @@ -68,7 +64,7 @@ #endif // For RTC/FFMPEG build. -#if defined(SRS_AUTO_RTC) && !defined(__STDC_CONSTANT_MACROS) +#if defined(SRS_RTC) && !defined(__STDC_CONSTANT_MACROS) #define __STDC_CONSTANT_MACROS #endif @@ -118,4 +114,34 @@ class SrsCplxError; typedef SrsCplxError* srs_error_t; +#include +// The context ID, it default to a string object, we can also use other objects. +// @remark User can directly user string as SrsContextId, we user struct to ensure the context is an object. +#if 1 +class _SrsContextId +{ +private: + std::string v_; +public: + _SrsContextId(); + _SrsContextId(const _SrsContextId& cp); + _SrsContextId& operator=(const _SrsContextId& cp); + virtual ~_SrsContextId(); +public: + const char* c_str() const; + bool empty() const; + // Compare the two context id. @see http://www.cplusplus.com/reference/string/string/compare/ + // 0 They compare equal + // <0 Either the value of the first character that does not match is lower in the compared string, or all compared characters match but the compared string is shorter. + // >0 Either the value of the first character that does not match is greater in the compared string, or all compared characters match but the compared string is longer. + int compare(const _SrsContextId& to) const; + // Set the value of context id. + _SrsContextId& set_value(const std::string& v); +}; +typedef _SrsContextId SrsContextId; +#else +// Actually, we can directly user string as SrsContextId. +typedef std::string SrsContextId; +#endif + #endif diff --git a/trunk/src/core/srs_core_mem_watch.cpp b/trunk/src/core/srs_core_mem_watch.cpp index dbc6c9411..bc39e8523 100644 --- a/trunk/src/core/srs_core_mem_watch.cpp +++ b/trunk/src/core/srs_core_mem_watch.cpp @@ -23,7 +23,7 @@ #include -#ifdef SRS_AUTO_MEM_WATCH +#ifdef SRS_MEM_WATCH #include #include diff --git a/trunk/src/core/srs_core_mem_watch.hpp b/trunk/src/core/srs_core_mem_watch.hpp index d10bbb675..bbd154a15 100644 --- a/trunk/src/core/srs_core_mem_watch.hpp +++ b/trunk/src/core/srs_core_mem_watch.hpp @@ -26,7 +26,7 @@ #include -#ifdef SRS_AUTO_MEM_WATCH +#ifdef SRS_MEM_WATCH #warning "MemoryWatch is deprecated." diff --git a/trunk/src/core/srs_core_performance.hpp b/trunk/src/core/srs_core_performance.hpp index 39493646a..be1554851 100644 --- a/trunk/src/core/srs_core_performance.hpp +++ b/trunk/src/core/srs_core_performance.hpp @@ -125,6 +125,7 @@ * @remark this improve performance for large connectios. * @see https://github.com/ossrs/srs/issues/251 */ +// TODO: FIXME: Should always enable it. #define SRS_PERF_QUEUE_COND_WAIT #ifdef SRS_PERF_QUEUE_COND_WAIT // For RTMP, use larger wait queue. @@ -192,27 +193,5 @@ #define SRS_PERF_GLIBC_MEMORY_CHECK #undef SRS_PERF_GLIBC_MEMORY_CHECK -// For RTC, how many iovs we alloc for each mmsghdr for GSO. -// Assume that there are 3300 clients, say, 10000 msgs in queue to send, the memory is: -// 2 # We have two queue, cache and hotspot. -// * 4 # We have reuseport, each have msg cache queue. -// * (64 + 16*SRS_PERF_RTC_GSO_MAX + SRS_PERF_RTC_GSO_IOVS * 1500) # Each message size. -// * 10000 # Total messages. -// = 197MB # For SRS_PERF_RTC_GSO_IOVS = 1 -// = 311MB # For SRS_PERF_RTC_GSO_IOVS = 2 -// = 540MB # For SRS_PERF_RTC_GSO_IOVS = 4 -// = 998MB # For SRS_PERF_RTC_GSO_IOVS = 8 -#if defined(__linux__) - #define SRS_PERF_RTC_GSO_IOVS 4 -#else - #define SRS_PERF_RTC_GSO_IOVS 1 -#endif - -// For RTC, the max iovs in msghdr, the max packets sent in a msghdr. -#define SRS_PERF_RTC_GSO_MAX 64 - -// For RTC, the max count of RTP packets we process in one loop. -#define SRS_PERF_RTC_RTP_PACKETS 1024 - #endif diff --git a/trunk/src/core/srs_core_time.cpp b/trunk/src/core/srs_core_time.cpp index 86d360975..b977a3c4a 100644 --- a/trunk/src/core/srs_core_time.cpp +++ b/trunk/src/core/srs_core_time.cpp @@ -23,3 +23,12 @@ #include +srs_utime_t srs_duration(srs_utime_t start, srs_utime_t end) +{ + if (start == 0 || end == 0) { + return 0; + } + + return end - start; +} + diff --git a/trunk/src/core/srs_core_time.hpp b/trunk/src/core/srs_core_time.hpp index 1d0799d42..df33ef623 100644 --- a/trunk/src/core/srs_core_time.hpp +++ b/trunk/src/core/srs_core_time.hpp @@ -36,6 +36,9 @@ typedef int64_t srs_utime_t; #define srsu2ms(us) ((us) / SRS_UTIME_MILLISECONDS) #define srsu2msi(us) int((us) / SRS_UTIME_MILLISECONDS) +// Them time duration = end - start. return 0, if start or end is 0. +srs_utime_t srs_duration(srs_utime_t start, srs_utime_t end); + // The time unit in ms, for example 120 * SRS_UTIME_SECONDS means 120s. #define SRS_UTIME_SECONDS 1000000LL diff --git a/trunk/src/core/srs_core_version3.hpp b/trunk/src/core/srs_core_version3.hpp index d6bf63b98..a3d519d3f 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 140 +#define SRS_VERSION3_REVISION 157 #endif diff --git a/trunk/src/core/srs_core_version4.hpp b/trunk/src/core/srs_core_version4.hpp index e47dfdc68..1c994c88a 100644 --- a/trunk/src/core/srs_core_version4.hpp +++ b/trunk/src/core/srs_core_version4.hpp @@ -24,6 +24,6 @@ #ifndef SRS_CORE_VERSION4_HPP #define SRS_CORE_VERSION4_HPP -#define SRS_VERSION4_REVISION 23 +#define SRS_VERSION4_REVISION 62 #endif diff --git a/trunk/src/kernel/srs_kernel_aac.cpp b/trunk/src/kernel/srs_kernel_aac.cpp index 95fa34b7b..5a4d42499 100644 --- a/trunk/src/kernel/srs_kernel_aac.cpp +++ b/trunk/src/kernel/srs_kernel_aac.cpp @@ -46,6 +46,8 @@ SrsAacTransmuxer::SrsAacTransmuxer() writer = NULL; got_sequence_header = false; aac_object = SrsAacObjectTypeReserved; + aac_sample_rate = 0; + aac_channels = 0; } SrsAacTransmuxer::~SrsAacTransmuxer() diff --git a/trunk/src/kernel/srs_kernel_buffer.cpp b/trunk/src/kernel/srs_kernel_buffer.cpp index be94d0bcb..255357878 100644 --- a/trunk/src/kernel/srs_kernel_buffer.cpp +++ b/trunk/src/kernel/srs_kernel_buffer.cpp @@ -45,19 +45,10 @@ ISrsCodec::~ISrsCodec() { } -SrsBuffer::SrsBuffer() -{ - p = bytes = NULL; - nb_bytes = 0; - - // TODO: support both little and big endian. - srs_assert(srs_is_little_endian()); -} - -SrsBuffer::SrsBuffer(char* b, int nb_b) +SrsBuffer::SrsBuffer(char* b, int nn) { p = bytes = b; - nb_bytes = nb_b; + nb_bytes = nn; // TODO: support both little and big endian. srs_assert(srs_is_little_endian()); @@ -67,11 +58,26 @@ SrsBuffer::~SrsBuffer() { } +char* SrsBuffer::data() +{ + return bytes; +} + +char* SrsBuffer::head() +{ + return p; +} + int SrsBuffer::size() { return nb_bytes; } +void SrsBuffer::set_size(int v) +{ + nb_bytes = v; +} + int SrsBuffer::pos() { return (int)(p - bytes); @@ -122,6 +128,18 @@ int16_t SrsBuffer::read_2bytes() return value; } +int16_t SrsBuffer::read_le2bytes() +{ + srs_assert(require(2)); + + int16_t value; + char* pp = (char*)&value; + pp[0] = *p++; + pp[1] = *p++; + + return value; +} + int32_t SrsBuffer::read_3bytes() { srs_assert(require(3)); @@ -135,6 +153,19 @@ int32_t SrsBuffer::read_3bytes() return value; } +int32_t SrsBuffer::read_le3bytes() +{ + srs_assert(require(3)); + + int32_t value = 0x00; + char* pp = (char*)&value; + pp[0] = *p++; + pp[1] = *p++; + pp[2] = *p++; + + return value; +} + int32_t SrsBuffer::read_4bytes() { srs_assert(require(4)); @@ -149,6 +180,20 @@ int32_t SrsBuffer::read_4bytes() return value; } +int32_t SrsBuffer::read_le4bytes() +{ + srs_assert(require(4)); + + int32_t value; + char* pp = (char*)&value; + pp[0] = *p++; + pp[1] = *p++; + pp[2] = *p++; + pp[3] = *p++; + + return value; +} + int64_t SrsBuffer::read_8bytes() { srs_assert(require(8)); @@ -167,6 +212,24 @@ int64_t SrsBuffer::read_8bytes() return value; } +int64_t SrsBuffer::read_le8bytes() +{ + srs_assert(require(8)); + + int64_t value; + char* pp = (char*)&value; + pp[0] = *p++; + pp[1] = *p++; + pp[2] = *p++; + pp[3] = *p++; + pp[4] = *p++; + pp[5] = *p++; + pp[6] = *p++; + pp[7] = *p++; + + return value; +} + string SrsBuffer::read_string(int len) { srs_assert(require(len)); @@ -204,6 +267,15 @@ void SrsBuffer::write_2bytes(int16_t value) *p++ = pp[0]; } +void SrsBuffer::write_le2bytes(int16_t value) +{ + srs_assert(require(2)); + + char* pp = (char*)&value; + *p++ = pp[0]; + *p++ = pp[1]; +} + void SrsBuffer::write_4bytes(int32_t value) { srs_assert(require(4)); @@ -215,6 +287,17 @@ void SrsBuffer::write_4bytes(int32_t value) *p++ = pp[0]; } +void SrsBuffer::write_le4bytes(int32_t value) +{ + srs_assert(require(4)); + + char* pp = (char*)&value; + *p++ = pp[0]; + *p++ = pp[1]; + *p++ = pp[2]; + *p++ = pp[3]; +} + void SrsBuffer::write_3bytes(int32_t value) { srs_assert(require(3)); @@ -225,6 +308,16 @@ void SrsBuffer::write_3bytes(int32_t value) *p++ = pp[0]; } +void SrsBuffer::write_le3bytes(int32_t value) +{ + srs_assert(require(3)); + + char* pp = (char*)&value; + *p++ = pp[0]; + *p++ = pp[1]; + *p++ = pp[2]; +} + void SrsBuffer::write_8bytes(int64_t value) { srs_assert(require(8)); @@ -240,6 +333,21 @@ void SrsBuffer::write_8bytes(int64_t value) *p++ = pp[0]; } +void SrsBuffer::write_le8bytes(int64_t value) +{ + srs_assert(require(8)); + + char* pp = (char*)&value; + *p++ = pp[0]; + *p++ = pp[1]; + *p++ = pp[2]; + *p++ = pp[3]; + *p++ = pp[4]; + *p++ = pp[5]; + *p++ = pp[6]; + *p++ = pp[7]; +} + void SrsBuffer::write_string(string value) { srs_assert(require((int)value.length())); diff --git a/trunk/src/kernel/srs_kernel_buffer.hpp b/trunk/src/kernel/srs_kernel_buffer.hpp index 532375f8d..061ea8338 100644 --- a/trunk/src/kernel/srs_kernel_buffer.hpp +++ b/trunk/src/kernel/srs_kernel_buffer.hpp @@ -41,8 +41,7 @@ public: /** * get the number of bytes to code to. */ - // TODO: FIXME: change to uint64_t. - virtual int nb_bytes() = 0; + virtual uint64_t nb_bytes() = 0; /** * encode object to bytes in SrsBuffer. */ @@ -80,9 +79,12 @@ public: ISrsCodec(); virtual ~ISrsCodec(); public: - /** - * decode object from bytes in SrsBuffer. - */ + // Get the number of bytes to code to. + virtual uint64_t nb_bytes() = 0; + // Encode object to bytes in SrsBuffer. + virtual srs_error_t encode(SrsBuffer* buf) = 0; +public: + // Decode object from bytes in SrsBuffer. virtual srs_error_t decode(SrsBuffer* buf) = 0; }; @@ -97,118 +99,83 @@ class SrsBuffer private: // current position at bytes. char* p; - // the bytes data for stream to read or write. + // the bytes data for buffer to read or write. char* bytes; // the total number of bytes. int nb_bytes; public: - SrsBuffer(); - // Initialize buffer with data b and size nb_b. + // Create buffer with data b and size nn. // @remark User must free the data b. - SrsBuffer(char* b, int nb_b); - virtual ~SrsBuffer(); -// get the status of stream + SrsBuffer(char* b, int nn); + ~SrsBuffer(); public: - /** - * get data of stream, set by initialize. - * current bytes = data() + pos() - */ - inline char* data() { return bytes; } - inline char* head() { return p; } - /** - * the total stream size, set by initialize. - * left bytes = size() - pos(). - */ - virtual int size(); - /** - * tell the current pos. - */ - virtual int pos(); + // Get the data and head of buffer. + // current-bytes = head() = data() + pos() + char* data(); + char* head(); + // Get the total size of buffer. + // left-bytes = size() - pos() + int size(); + void set_size(int v); + // Get the current buffer position. + int pos(); // Left bytes in buffer, total size() minus the current pos(). - virtual int left(); - /** - * whether stream is empty. - * if empty, user should never read or write. - */ - virtual bool empty(); - /** - * whether required size is ok. - * @return true if stream can read/write specified required_size bytes. - * @remark assert required_size positive. - */ - virtual bool require(int required_size); - // to change stream. + int left(); + // Whether buffer is empty. + bool empty(); + // Whether buffer is able to supply required size of bytes. + // @remark User should check buffer by require then do read/write. + // @remark Assert the required_size is not negative. + bool require(int required_size); public: - /** - * to skip some size. - * @param size can be any value. positive to forward; nagetive to backward. - * @remark to skip(pos()) to reset stream. - * @remark assert initialized, the data() not NULL. - */ - virtual void skip(int size); + // Skip some size. + // @param size can be any value. positive to forward; negative to backward. + // @remark to skip(pos()) to reset buffer. + // @remark assert initialized, the data() not NULL. + void skip(int size); public: - /** - * get 1bytes char from stream. - */ - virtual int8_t read_1bytes(); - /** - * get 2bytes int from stream. - */ - virtual int16_t read_2bytes(); - /** - * get 3bytes int from stream. - */ - virtual int32_t read_3bytes(); - /** - * get 4bytes int from stream. - */ - virtual int32_t read_4bytes(); - /** - * get 8bytes int from stream. - */ - virtual int64_t read_8bytes(); - /** - * get string from stream, length specifies by param len. - */ - virtual std::string read_string(int len); - /** - * get bytes from stream, length specifies by param len. - */ - virtual void read_bytes(char* data, int size); + // Read 1bytes char from buffer. + int8_t read_1bytes(); + // Read 2bytes int from buffer. + int16_t read_2bytes(); + int16_t read_le2bytes(); + // Read 3bytes int from buffer. + int32_t read_3bytes(); + int32_t read_le3bytes(); + // Read 4bytes int from buffer. + int32_t read_4bytes(); + int32_t read_le4bytes(); + // Read 8bytes int from buffer. + int64_t read_8bytes(); + int64_t read_le8bytes(); + // Read string from buffer, length specifies by param len. + std::string read_string(int len); + // Read bytes from buffer, length specifies by param len. + void read_bytes(char* data, int size); public: - /** - * write 1bytes char to stream. - */ - virtual void write_1bytes(int8_t value); - /** - * write 2bytes int to stream. - */ - virtual void write_2bytes(int16_t value); - /** - * write 4bytes int to stream. - */ - virtual void write_4bytes(int32_t value); - /** - * write 3bytes int to stream. - */ - virtual void write_3bytes(int32_t value); - /** - * write 8bytes int to stream. - */ - virtual void write_8bytes(int64_t value); - /** - * write string to stream - */ - virtual void write_string(std::string value); - /** - * write bytes to stream - */ - virtual void write_bytes(char* data, int size); + // Write 1bytes char to buffer. + void write_1bytes(int8_t value); + // Write 2bytes int to buffer. + void write_2bytes(int16_t value); + void write_le2bytes(int16_t value); + // Write 4bytes int to buffer. + void write_4bytes(int32_t value); + void write_le4bytes(int32_t value); + // Write 3bytes int to buffer. + void write_3bytes(int32_t value); + void write_le3bytes(int32_t value); + // Write 8bytes int to buffer. + void write_8bytes(int64_t value); + void write_le8bytes(int64_t value); + // Write string to buffer + void write_string(std::string value); + // Write bytes to buffer + void write_bytes(char* data, int size); }; /** - * the bit stream, base on SrsBuffer, - * for exmaple, the h.264 avc stream is bit stream. + * the bit buffer, base on SrsBuffer, + * for exmaple, the h.264 avc buffer is bit buffer. */ class SrsBitBuffer { @@ -218,10 +185,10 @@ private: SrsBuffer* stream; public: SrsBitBuffer(SrsBuffer* b); - virtual ~SrsBitBuffer(); + ~SrsBitBuffer(); public: - virtual bool empty(); - virtual int8_t read_bit(); + bool empty(); + int8_t read_bit(); }; #endif diff --git a/trunk/src/kernel/srs_kernel_codec.cpp b/trunk/src/kernel/srs_kernel_codec.cpp index e0789c236..a937662f5 100644 --- a/trunk/src/kernel/srs_kernel_codec.cpp +++ b/trunk/src/kernel/srs_kernel_codec.cpp @@ -32,7 +32,7 @@ using namespace std; #include #include #include -#include +#include string srs_video_codec_id2str(SrsVideoCodecId codec) { @@ -368,6 +368,13 @@ SrsSample::SrsSample() bframe = false; } +SrsSample::SrsSample(char* b, int s) +{ + size = s; + bytes = b; + bframe = false; +} + SrsSample::~SrsSample() { } @@ -414,6 +421,7 @@ SrsSample* SrsSample::copy() SrsSample* p = new SrsSample(); p->bytes = bytes; p->size = size; + p->bframe = bframe; return p; } @@ -603,7 +611,7 @@ srs_error_t SrsFormat::on_audio(int64_t timestamp, char* data, int size) srs_error_t err = srs_success; if (!data || size <= 0) { - srs_trace("no audio present, ignore it."); + srs_info("no audio present, ignore it."); return err; } diff --git a/trunk/src/kernel/srs_kernel_codec.hpp b/trunk/src/kernel/srs_kernel_codec.hpp index 9c74e1387..cc2bcb711 100644 --- a/trunk/src/kernel/srs_kernel_codec.hpp +++ b/trunk/src/kernel/srs_kernel_codec.hpp @@ -538,6 +538,7 @@ public: bool bframe; public: SrsSample(); + SrsSample(char* b, int s); ~SrsSample(); public: // If we need to know whether sample is bframe, we have to parse the NALU payload. @@ -636,6 +637,7 @@ public: SrsAvcLevel avc_level; // lengthSizeMinusOne, ISO_IEC_14496-15-AVC-format-2012.pdf, page 16 int8_t NAL_unit_length; + // Note that we may resize the vector, so the under-layer bytes may change. std::vector sequenceParameterSetNALUnit; std::vector pictureParameterSetNALUnit; public: diff --git a/trunk/src/kernel/srs_kernel_consts.hpp b/trunk/src/kernel/srs_kernel_consts.hpp index addda5696..acb56b4c6 100644 --- a/trunk/src/kernel/srs_kernel_consts.hpp +++ b/trunk/src/kernel/srs_kernel_consts.hpp @@ -26,6 +26,18 @@ #include +// Default port of rtmp +#define SRS_CONSTS_RTMP_DEFAULT_PORT 1935 + +// Default http listen port. +#define SRS_DEFAULT_HTTP_PORT 80 + +// Default https listen port. +#define SRS_DEFAULT_HTTPS_PORT 443 + +// Default Redis listen port. +#define SRS_DEFAULT_REDIS_PORT 6379 + /////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////// @@ -39,8 +51,6 @@ // Default vhost of rtmp #define SRS_CONSTS_RTMP_DEFAULT_VHOST "__defaultVhost__" #define SRS_CONSTS_RTMP_DEFAULT_APP "__defaultApp__" -// Default port of rtmp -#define SRS_CONSTS_RTMP_DEFAULT_PORT 1935 // The default chunk size for system. #define SRS_CONSTS_RTMP_SRS_CHUNK_SIZE 60000 diff --git a/trunk/src/kernel/srs_kernel_error.cpp b/trunk/src/kernel/srs_kernel_error.cpp index dd5efc35a..f6d7340f8 100644 --- a/trunk/src/kernel/srs_kernel_error.cpp +++ b/trunk/src/kernel/srs_kernel_error.cpp @@ -57,7 +57,7 @@ SrsCplxError::SrsCplxError() { code = ERROR_SUCCESS; wrapped = NULL; - cid = rerrno = line = 0; + rerrno = line = 0; } SrsCplxError::~SrsCplxError() @@ -79,7 +79,7 @@ std::string SrsCplxError::description() { next = this; while (next) { - ss << "thread [" << getpid() << "][" << next->cid << "]: " + ss << "thread [" << getpid() << "][" << next->cid.c_str() << "]: " << next->func << "() [" << next->file << ":" << next->line << "]" << "[errno=" << next->rerrno << "]"; diff --git a/trunk/src/kernel/srs_kernel_error.hpp b/trunk/src/kernel/srs_kernel_error.hpp index ed41db1ba..de3040793 100644 --- a/trunk/src/kernel/srs_kernel_error.hpp +++ b/trunk/src/kernel/srs_kernel_error.hpp @@ -282,11 +282,13 @@ #define ERROR_DASH_WRITE_FAILED 3087 #define ERROR_TS_CONTEXT_NOT_READY 3088 #define ERROR_MP4_ILLEGAL_MOOF 3089 -#define ERROR_OCLUSTER_DISCOVER 3090 -#define ERROR_OCLUSTER_REDIRECT 3091 -#define ERROR_INOTIFY_CREATE 3092 -#define ERROR_INOTIFY_OPENFD 3093 -#define ERROR_INOTIFY_WATCH 3094 +#define ERROR_MP4_ILLEGAL_MDAT 3090 +#define ERROR_OCLUSTER_DISCOVER 3091 +#define ERROR_OCLUSTER_REDIRECT 3092 +#define ERROR_INOTIFY_CREATE 3093 +#define ERROR_INOTIFY_OPENFD 3094 +#define ERROR_INOTIFY_WATCH 3095 +#define ERROR_HTTP_URL_UNESCAPE 3096 /////////////////////////////////////////////////////// // HTTP/StreamCaster protocol error. @@ -324,6 +326,11 @@ #define ERROR_HTTP_302_INVALID 4038 #define ERROR_BASE64_DECODE 4039 #define ERROR_HTTP_STREAM_EOF 4040 +#define ERROR_HTTPS_NOT_SUPPORTED 4041 +#define ERROR_HTTPS_HANDSHAKE 4042 +#define ERROR_HTTPS_READ 4043 +#define ERROR_HTTPS_WRITE 4044 +#define ERROR_HTTPS_KEY_CRT 4045 /////////////////////////////////////////////////////// // RTC protocol error. @@ -347,6 +354,19 @@ #define ERROR_RTC_RTCP_CHECK 5016 #define ERROR_RTC_SOURCE_CHECK 5017 #define ERROR_RTC_SDP_EXCHANGE 5018 +#define ERROR_RTC_API_BODY 5019 +#define ERROR_RTC_SOURCE_BUSY 5020 +#define ERROR_RTC_DISABLED 5021 +#define ERROR_RTC_NO_SESSION 5022 +#define ERROR_RTC_INVALID_PARAMS 5023 +#define ERROR_RTC_DUMMY_BRIDGER 5024 +#define ERROR_RTC_STREM_STARTED 5025 +#define ERROR_RTC_TRACK_CODEC 5026 +#define ERROR_RTC_NO_PLAYER 5027 +#define ERROR_RTC_NO_PUBLISHER 5028 +#define ERROR_RTC_DUPLICATED_SSRC 5029 +#define ERROR_RTC_NO_TRACK 5030 +#define ERROR_RTC_RTCP_EMPTY_RR 5031 /////////////////////////////////////////////////////// // GB28181 API error. @@ -370,6 +390,8 @@ #define ERROR_GB28181_SIP_PTZ_FAILED 6016 #define ERROR_GB28181_SIP_NOT_INVITE 6017 #define ERROR_GB28181_SIP_PTZ_CMD_INVALID 6018 +#define ERROR_GB28181_H264_FRAMESIZE 6019 +#define ERROR_GB28181_H264_FRAME_FULL 6020 /////////////////////////////////////////////////////// // HTTP API error. @@ -406,7 +428,7 @@ private: std::string file; int line; - int cid; + SrsContextId cid; int rerrno; std::string desc; diff --git a/trunk/src/kernel/srs_kernel_file.cpp b/trunk/src/kernel/srs_kernel_file.cpp index 95e1139fa..e884113b2 100644 --- a/trunk/src/kernel/srs_kernel_file.cpp +++ b/trunk/src/kernel/srs_kernel_file.cpp @@ -37,11 +37,11 @@ using namespace std; #include // For utest to mock it. -_srs_open_t _srs_open_fn = ::open; -_srs_write_t _srs_write_fn = ::write; -_srs_read_t _srs_read_fn = ::read; -_srs_lseek_t _srs_lseek_fn = ::lseek; -_srs_close_t _srs_close_fn = ::close; +srs_open_t _srs_open_fn = ::open; +srs_write_t _srs_write_fn = ::write; +srs_read_t _srs_read_fn = ::read; +srs_lseek_t _srs_lseek_fn = ::lseek; +srs_close_t _srs_close_fn = ::close; SrsFileWriter::SrsFileWriter() { @@ -81,7 +81,7 @@ srs_error_t SrsFileWriter::open_append(string p) return srs_error_new(ERROR_SYSTEM_FILE_ALREADY_OPENED, "file %s already opened", path.c_str()); } - int flags = O_APPEND|O_WRONLY; + int flags = O_CREAT|O_APPEND|O_WRONLY; mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH; if ((fd = _srs_open_fn(p.c_str(), flags, mode)) < 0) { diff --git a/trunk/src/kernel/srs_kernel_file.hpp b/trunk/src/kernel/srs_kernel_file.hpp index b8ed2a15f..46ed18b48 100644 --- a/trunk/src/kernel/srs_kernel_file.hpp +++ b/trunk/src/kernel/srs_kernel_file.hpp @@ -121,11 +121,11 @@ public: }; // For utest to mock it. -typedef int (*_srs_open_t)(const char* path, int oflag, ...); -typedef ssize_t (*_srs_write_t)(int fildes, const void* buf, size_t nbyte); -typedef ssize_t (*_srs_read_t)(int fildes, void* buf, size_t nbyte); -typedef off_t (*_srs_lseek_t)(int fildes, off_t offset, int whence); -typedef int (*_srs_close_t)(int fildes); +typedef int (*srs_open_t)(const char* path, int oflag, ...); +typedef ssize_t (*srs_write_t)(int fildes, const void* buf, size_t nbyte); +typedef ssize_t (*srs_read_t)(int fildes, void* buf, size_t nbyte); +typedef off_t (*srs_lseek_t)(int fildes, off_t offset, int whence); +typedef int (*srs_close_t)(int fildes); #endif diff --git a/trunk/src/kernel/srs_kernel_flv.cpp b/trunk/src/kernel/srs_kernel_flv.cpp index eead02374..edbcb829c 100644 --- a/trunk/src/kernel/srs_kernel_flv.cpp +++ b/trunk/src/kernel/srs_kernel_flv.cpp @@ -40,9 +40,6 @@ using namespace std; #include #include #include -#ifdef SRS_AUTO_RTC -#include -#endif SrsMessageHeader::SrsMessageHeader() { @@ -164,7 +161,7 @@ SrsCommonMessage::SrsCommonMessage() SrsCommonMessage::~SrsCommonMessage() { -#ifdef SRS_AUTO_MEM_WATCH +#ifdef SRS_MEM_WATCH srs_memory_unwatch(payload); #endif srs_freepa(payload); @@ -177,7 +174,7 @@ void SrsCommonMessage::create_payload(int size) payload = new char[size]; srs_verbose("create payload for RTMP message. size=%d", size); -#ifdef SRS_AUTO_MEM_WATCH +#ifdef SRS_MEM_WATCH srs_memory_watch(payload, "RTMP.msg.payload", size); #endif } @@ -210,35 +207,14 @@ SrsSharedPtrMessage::SrsSharedPtrPayload::SrsSharedPtrPayload() payload = NULL; size = 0; shared_count = 0; - -#ifdef SRS_AUTO_RTC - samples = NULL; - nn_samples = 0; - has_idr = false; - - extra_payloads = NULL; - nn_extra_payloads = 0; - nn_max_extra_payloads = 0; -#endif } SrsSharedPtrMessage::SrsSharedPtrPayload::~SrsSharedPtrPayload() { -#ifdef SRS_AUTO_MEM_WATCH +#ifdef SRS_MEM_WATCH srs_memory_unwatch(payload); #endif srs_freepa(payload); - -#ifdef SRS_AUTO_RTC - srs_freepa(samples); - - for (int i = 0; i < nn_extra_payloads; i++) { - SrsSample* p = extra_payloads + i; - srs_freep(p->bytes); - } - srs_freepa(extra_payloads); - nn_extra_payloads = 0; -#endif } SrsSharedPtrMessage::SrsSharedPtrMessage() : timestamp(0), stream_id(0), size(0), payload(NULL) @@ -303,6 +279,18 @@ srs_error_t SrsSharedPtrMessage::create(SrsMessageHeader* pheader, char* payload return err; } +void SrsSharedPtrMessage::wrap(char* payload, int size) +{ + srs_assert(!ptr); + ptr = new SrsSharedPtrPayload(); + + ptr->payload = payload; + ptr->size = size; + + this->payload = ptr->payload; + this->size = ptr->size; +} + int SrsSharedPtrMessage::count() { srs_assert(ptr); @@ -376,30 +364,6 @@ SrsSharedPtrMessage* SrsSharedPtrMessage::copy() return copy; } -#ifdef SRS_AUTO_RTC -void SrsSharedPtrMessage::set_extra_payloads(SrsSample* payloads, int nn_payloads) -{ - srs_assert(nn_payloads); - srs_assert(!ptr->extra_payloads); - - ptr->nn_extra_payloads = nn_payloads; - - ptr->extra_payloads = new SrsSample[nn_payloads]; - memcpy((void*)ptr->extra_payloads, payloads, nn_payloads * sizeof(SrsSample)); -} - -void SrsSharedPtrMessage::set_samples(SrsSample* samples, int nn_samples) -{ - srs_assert(nn_samples); - srs_assert(!ptr->samples); - - ptr->nn_samples = nn_samples; - - ptr->samples = new SrsSample[nn_samples]; - memcpy((void*)ptr->samples, samples, nn_samples * sizeof(SrsSample)); -} -#endif - SrsFlvTransmuxer::SrsFlvTransmuxer() { writer = NULL; diff --git a/trunk/src/kernel/srs_kernel_flv.hpp b/trunk/src/kernel/srs_kernel_flv.hpp index 8cfda3557..f3be5931b 100644 --- a/trunk/src/kernel/srs_kernel_flv.hpp +++ b/trunk/src/kernel/srs_kernel_flv.hpp @@ -39,7 +39,6 @@ class ISrsWriter; class ISrsReader; class SrsFileReader; class SrsPacket; -class SrsRtpSharedPacket; class SrsSample; #define SRS_FLV_TAG_HEADER_SIZE 11 @@ -158,6 +157,7 @@ public: // 1byte. // One byte field to represent the message type. A range of type IDs // (1-7) are reserved for protocol control messages. + // For example, RTMP_MSG_AudioMessage or RTMP_MSG_VideoMessage. int8_t message_type; // 4bytes. // Four-byte field that identifies the stream of the message. These @@ -245,6 +245,7 @@ public: // 1byte. // One byte field to represent the message type. A range of type IDs // (1-7) are reserved for protocol control messages. + // For example, RTMP_MSG_AudioMessage or RTMP_MSG_VideoMessage. int8_t message_type; // Get the perfered cid(chunk stream id) which sendout over. // set at decoding, and canbe used for directly send message, @@ -302,23 +303,6 @@ private: int size; // The reference count int shared_count; -#ifdef SRS_AUTO_RTC - public: - // For RTC video, we need to know the NALU structures, - // because the RTP STAP-A or FU-A based on NALU. - SrsSample* samples; - int nn_samples; - // For RTC video, whether NALUs has IDR. - bool has_idr; - public: - // For RTC audio, we may need to transcode AAC to opus, - // so there must be an extra payloads, which is transformed from payload. - SrsSample* extra_payloads; - int nn_extra_payloads; - // The max size payload in extras. - // @remark For GSO to fast guess the best padding. - int nn_max_extra_payloads; -#endif public: SrsSharedPtrPayload(); virtual ~SrsSharedPtrPayload(); @@ -339,6 +323,9 @@ public: // @remark user should never free the payload. // @param pheader, the header to copy to the message. NULL to ignore. virtual srs_error_t create(SrsMessageHeader* pheader, char* payload, int size); + // Create shared ptr message from RAW payload. + // @remark Note that the header is set to zero. + virtual void wrap(char* payload, int size); // Get current reference count. // when this object created, count set to 0. // if copy() this object, count increase 1. @@ -360,24 +347,6 @@ public: // copy current shared ptr message, use ref-count. // @remark, assert object is created. virtual SrsSharedPtrMessage* copy(); -public: -#ifdef SRS_AUTO_RTC - // Set extra samples, for example, when we transcode an AAC audio packet to OPUS, - // we may get more than one OPUS packets, we set these OPUS packets in extra payloads. - void set_extra_payloads(SrsSample* payloads, int nn_payloads); - int nn_extra_payloads() { return ptr->nn_extra_payloads; } - SrsSample* extra_payloads() { return ptr->extra_payloads; } - // The max extra payload size. - void set_max_extra_payload(int v) { ptr->nn_max_extra_payloads = v; } - int nn_max_extra_payloads() { return ptr->nn_max_extra_payloads; } - // Whether samples has idr. - bool has_idr() { return ptr->has_idr; } - void set_has_idr(bool v) { ptr->has_idr = v; } - // Set samples, each sample points to the address of payload. - void set_samples(SrsSample* samples, int nn_samples); - int nn_samples() { return ptr->nn_samples; } - SrsSample* samples() { return ptr->samples; } -#endif }; // Transmux RTMP packets to FLV stream. diff --git a/trunk/src/kernel/srs_kernel_log.cpp b/trunk/src/kernel/srs_kernel_log.cpp index 1b2bfcfef..6cfe61068 100644 --- a/trunk/src/kernel/srs_kernel_log.cpp +++ b/trunk/src/kernel/srs_kernel_log.cpp @@ -23,8 +23,6 @@ #include -#include - ISrsLog::ISrsLog() { } @@ -33,56 +31,12 @@ ISrsLog::~ISrsLog() { } -srs_error_t ISrsLog::initialize() -{ - return srs_success; -} - -void ISrsLog::reopen() +ISrsContext::ISrsContext() { } -void ISrsLog::verbose(const char* /*tag*/, int /*context_id*/, const char* /*fmt*/, ...) +ISrsContext::~ISrsContext() { } -void ISrsLog::info(const char* /*tag*/, int /*context_id*/, const char* /*fmt*/, ...) -{ -} - -void ISrsLog::trace(const char* /*tag*/, int /*context_id*/, const char* /*fmt*/, ...) -{ -} - -void ISrsLog::warn(const char* /*tag*/, int /*context_id*/, const char* /*fmt*/, ...) -{ -} - -void ISrsLog::error(const char* /*tag*/, int /*context_id*/, const char* /*fmt*/, ...) -{ -} - -ISrsThreadContext::ISrsThreadContext() -{ -} - -ISrsThreadContext::~ISrsThreadContext() -{ -} - -int ISrsThreadContext::generate_id() -{ - return 0; -} - -int ISrsThreadContext::get_id() -{ - return 0; -} - -int ISrsThreadContext::set_id(int /*v*/) -{ - return 0; -} - diff --git a/trunk/src/kernel/srs_kernel_log.hpp b/trunk/src/kernel/srs_kernel_log.hpp index 1d7af37bb..ca1cd9cd4 100644 --- a/trunk/src/kernel/srs_kernel_log.hpp +++ b/trunk/src/kernel/srs_kernel_log.hpp @@ -30,6 +30,7 @@ #include #include +#include #include @@ -59,85 +60,76 @@ public: virtual ~ISrsLog(); public: // Initialize log utilities. - virtual srs_error_t initialize(); + virtual srs_error_t initialize() = 0; // Reopen the log file for log rotate. - virtual void reopen(); + virtual void reopen() = 0; public: // The log for verbose, very verbose information. - virtual void verbose(const char* tag, int context_id, const char* fmt, ...); + virtual void verbose(const char* tag, SrsContextId context_id, const char* fmt, ...) = 0; // The log for debug, detail information. - virtual void info(const char* tag, int context_id, const char* fmt, ...); + virtual void info(const char* tag, SrsContextId context_id, const char* fmt, ...) = 0; // The log for trace, important information. - virtual void trace(const char* tag, int context_id, const char* fmt, ...); + virtual void trace(const char* tag, SrsContextId context_id, const char* fmt, ...) = 0; // The log for warn, warn is something should take attention, but not a error. - virtual void warn(const char* tag, int context_id, const char* fmt, ...); + virtual void warn(const char* tag, SrsContextId context_id, const char* fmt, ...) = 0; // The log for error, something error occur, do something about the error, ie. close the connection, // but we will donot abort the program. - virtual void error(const char* tag, int context_id, const char* fmt, ...); + virtual void error(const char* tag, SrsContextId context_id, const char* fmt, ...) = 0; }; -// The context id manager to identify context, for instance, the green-thread. -// Usage: -// _srs_context->generate_id(); // when thread start. -// _srs_context->get_id(); // get current generated id. -// int old_id = _srs_context->set_id(1000); // set context id if need to merge thread context. -// The context for multiple clients. -class ISrsThreadContext +// The logic context, for example, a RTMP connection, or RTC Session, etc. +// We can grep the context id to identify the logic unit, for debugging. +// For example: +// SrsContextId cid = _srs_context->get_id(); // Get current context id. +// SrsContextId new_cid = _srs_context->generate_id(); // Generate a new context id. +// SrsContextId old_cid = _srs_context->set_id(new_cid); // Change the context id. +class ISrsContext { public: - ISrsThreadContext(); - virtual ~ISrsThreadContext(); + ISrsContext(); + virtual ~ISrsContext(); public: - // Generate the id for current context. - virtual int generate_id(); - // Get the generated id of current context. - virtual int get_id(); - // Set the id of current context. - // @return the previous id value; 0 if no context. - virtual int set_id(int v); + // Generate a new context id. + // @remark We do not set to current thread, user should do this. + virtual SrsContextId generate_id() = 0; + // Get the context id of current thread. + virtual const SrsContextId& get_id() = 0; + // Set the context id of current thread. + // @return the current context id. + virtual const SrsContextId& set_id(const SrsContextId& v) = 0; }; // @global User must provides a log object extern ISrsLog* _srs_log; // @global User must implements the LogContext and define a global instance. -extern ISrsThreadContext* _srs_context; +extern ISrsContext* _srs_context; // Log style. // Use __FUNCTION__ to print c method // Use __PRETTY_FUNCTION__ to print c++ class:method -#if 1 - #define srs_verbose(msg, ...) _srs_log->verbose(NULL, _srs_context->get_id(), msg, ##__VA_ARGS__) - #define srs_info(msg, ...) _srs_log->info(NULL, _srs_context->get_id(), msg, ##__VA_ARGS__) - #define srs_trace(msg, ...) _srs_log->trace(NULL, _srs_context->get_id(), msg, ##__VA_ARGS__) - #define srs_warn(msg, ...) _srs_log->warn(NULL, _srs_context->get_id(), msg, ##__VA_ARGS__) - #define srs_error(msg, ...) _srs_log->error(NULL, _srs_context->get_id(), msg, ##__VA_ARGS__) -#endif -#if 0 - #define srs_verbose(msg, ...) _srs_log->verbose(__FUNCTION__, _srs_context->get_id(), msg, ##__VA_ARGS__) - #define srs_info(msg, ...) _srs_log->info(__FUNCTION__, _srs_context->get_id(), msg, ##__VA_ARGS__) - #define srs_trace(msg, ...) _srs_log->trace(__FUNCTION__, _srs_context->get_id(), msg, ##__VA_ARGS__) - #define srs_warn(msg, ...) _srs_log->warn(__FUNCTION__, _srs_context->get_id(), msg, ##__VA_ARGS__) - #define srs_error(msg, ...) _srs_log->error(__FUNCTION__, _srs_context->get_id(), msg, ##__VA_ARGS__) -#endif -#if 0 - #define srs_verbose(msg, ...) _srs_log->verbose(__PRETTY_FUNCTION__, _srs_context->get_id(), msg, ##__VA_ARGS__) - #define srs_info(msg, ...) _srs_log->info(__PRETTY_FUNCTION__, _srs_context->get_id(), msg, ##__VA_ARGS__) - #define srs_trace(msg, ...) _srs_log->trace(__PRETTY_FUNCTION__, _srs_context->get_id(), msg, ##__VA_ARGS__) - #define srs_warn(msg, ...) _srs_log->warn(__PRETTY_FUNCTION__, _srs_context->get_id(), msg, ##__VA_ARGS__) - #define srs_error(msg, ...) _srs_log->error(__PRETTY_FUNCTION__, _srs_context->get_id(), msg, ##__VA_ARGS__) -#endif +#define srs_verbose(msg, ...) _srs_log->verbose(NULL, _srs_context->get_id(), msg, ##__VA_ARGS__) +#define srs_info(msg, ...) _srs_log->info(NULL, _srs_context->get_id(), msg, ##__VA_ARGS__) +#define srs_trace(msg, ...) _srs_log->trace(NULL, _srs_context->get_id(), msg, ##__VA_ARGS__) +#define srs_warn(msg, ...) _srs_log->warn(NULL, _srs_context->get_id(), msg, ##__VA_ARGS__) +#define srs_error(msg, ...) _srs_log->error(NULL, _srs_context->get_id(), msg, ##__VA_ARGS__) +// With tag. +#define srs_verbose2(tag, msg, ...) _srs_log->verbose(tag, _srs_context->get_id(), msg, ##__VA_ARGS__) +#define srs_info2(tag, msg, ...) _srs_log->info(tag, _srs_context->get_id(), msg, ##__VA_ARGS__) +#define srs_trace2(tag, msg, ...) _srs_log->trace(tag, _srs_context->get_id(), msg, ##__VA_ARGS__) +#define srs_warn2(tag, msg, ...) _srs_log->warn(tag, _srs_context->get_id(), msg, ##__VA_ARGS__) +#define srs_error2(tag, msg, ...) _srs_log->error(tag, _srs_context->get_id(), msg, ##__VA_ARGS__) // TODO: FIXME: Add more verbose and info logs. -#ifndef SRS_AUTO_VERBOSE +#ifndef SRS_VERBOSE #undef srs_verbose #define srs_verbose(msg, ...) (void)0 #endif -#ifndef SRS_AUTO_INFO +#ifndef SRS_INFO #undef srs_info #define srs_info(msg, ...) (void)0 #endif -#ifndef SRS_AUTO_TRACE +#ifndef SRS_TRACE #undef srs_trace #define srs_trace(msg, ...) (void)0 #endif diff --git a/trunk/src/kernel/srs_kernel_mp4.cpp b/trunk/src/kernel/srs_kernel_mp4.cpp index a41f27f56..d3d5a7731 100644 --- a/trunk/src/kernel/srs_kernel_mp4.cpp +++ b/trunk/src/kernel/srs_kernel_mp4.cpp @@ -190,17 +190,18 @@ int SrsMp4Box::sz_header() return nb_header(); } -int SrsMp4Box::update_size() +uint64_t SrsMp4Box::update_size() { uint64_t size = nb_bytes(); if (size > 0xffffffff) { largesize = size; + smallsize = SRS_MP4_USE_LARGE_SIZE; } else { smallsize = (uint32_t)size; } - return (int)size; + return size; } int SrsMp4Box::left_space(SrsBuffer* buf) @@ -299,11 +300,11 @@ srs_error_t SrsMp4Box::discovery(SrsBuffer* buf, SrsMp4Box** ppbox) *ppbox = NULL; srs_error_t err = srs_success; - + if (!buf->require(8)) { return srs_error_new(ERROR_MP4_BOX_REQUIRE_SPACE, "requires 8 only %d bytes", buf->left()); } - + // Discovery the size and type. uint64_t largesize = 0; uint32_t smallsize = (uint32_t)buf->read_4bytes(); @@ -313,15 +314,14 @@ srs_error_t SrsMp4Box::discovery(SrsBuffer* buf, SrsMp4Box** ppbox) return srs_error_new(ERROR_MP4_BOX_REQUIRE_SPACE, "requires 16 only %d bytes", buf->left()); } largesize = (uint64_t)buf->read_8bytes(); + } + + // Reset the buffer, because we only peek it. + buf->skip(-8); + if (smallsize == SRS_MP4_USE_LARGE_SIZE) { buf->skip(-8); } - buf->skip(-8); - - // Only support 31bits size. - if (largesize > 0x7fffffff) { - return srs_error_new(ERROR_MP4_BOX_OVERFLOW, "overflow 31bits, largesize=%" PRId64, largesize); - } - + SrsMp4Box* box = NULL; switch(type) { case SrsMp4BoxTypeFTYP: box = new SrsMp4FileTypeBox(); break; @@ -382,9 +382,9 @@ srs_error_t SrsMp4Box::discovery(SrsBuffer* buf, SrsMp4Box** ppbox) return err; } -int SrsMp4Box::nb_bytes() +uint64_t SrsMp4Box::nb_bytes() { - int sz = nb_header(); + uint64_t sz = nb_header(); vector::iterator it; for (it = boxes.begin(); it != boxes.end(); ++it) { @@ -488,21 +488,16 @@ srs_error_t SrsMp4Box::encode_header(SrsBuffer* buf) { srs_error_t err = srs_success; - // Only support 31bits size. - if (sz() > 0x7fffffff) { - return srs_error_new(ERROR_MP4_BOX_OVERFLOW, "box size overflow 31bits, size=%" PRId64, sz()); - } - int size = SrsMp4Box::nb_header(); if (!buf->require(size)) { return srs_error_new(ERROR_MP4_BOX_REQUIRE_SPACE, "requires %d only %d bytes", size, buf->left()); } buf->write_4bytes(smallsize); + buf->write_4bytes(type); if (smallsize == SRS_MP4_USE_LARGE_SIZE) { buf->write_8bytes(largesize); } - buf->write_4bytes(type); if (type == SrsMp4BoxTypeUUID) { buf->write_bytes(&usertype[0], 16); @@ -1071,7 +1066,7 @@ SrsMp4TrunEntry::~SrsMp4TrunEntry() { } -int SrsMp4TrunEntry::nb_bytes() +uint64_t SrsMp4TrunEntry::nb_bytes() { int size = 0; @@ -1291,7 +1286,7 @@ SrsMp4MediaDataBox::~SrsMp4MediaDataBox() { } -int SrsMp4MediaDataBox::nb_bytes() +uint64_t SrsMp4MediaDataBox::nb_bytes() { return SrsMp4Box::nb_header() + nb_data; } @@ -1315,7 +1310,7 @@ srs_error_t SrsMp4MediaDataBox::decode(SrsBuffer* buf) return srs_error_wrap(err, "decode box"); } - nb_data = (int)(sz() - nb_header()); + nb_data = sz() - (uint64_t)nb_header(); return err; } @@ -1758,6 +1753,12 @@ void SrsMp4TrackBox::set_tkhd(SrsMp4TrackHeaderBox* v) boxes.insert(boxes.begin(), v); } +void SrsMp4TrackBox::set_edts(SrsMp4EditBox* v) +{ + remove(SrsMp4BoxTypeEDTS); + boxes.insert(boxes.begin(), v); +} + SrsMp4ChunkOffsetBox* SrsMp4TrackBox::stco() { SrsMp4SampleTableBox* box = stbl(); @@ -2022,6 +2023,12 @@ SrsMp4EditBox::~SrsMp4EditBox() { } +void SrsMp4EditBox::set_elst(SrsMp4EditListBox* v) +{ + remove(SrsMp4BoxTypeELST); + boxes.insert(boxes.begin(), v); +} + SrsMp4ElstEntry::SrsMp4ElstEntry() : segment_duration(0), media_time(0), media_rate_integer(0) { media_rate_fraction = 0; @@ -2744,12 +2751,10 @@ SrsMp4DataEntryBox* SrsMp4DataReferenceBox::entry_at(int index) return entries.at(index); } -void SrsMp4DataReferenceBox::append(SrsMp4Box* v) +// Note that box must be SrsMp4DataEntryBox* +void SrsMp4DataReferenceBox::append(SrsMp4Box* box) { - SrsMp4DataEntryBox* pv = dynamic_cast(v); - if (pv) { - entries.push_back(pv); - } + entries.push_back((SrsMp4DataEntryBox*)box); } int SrsMp4DataReferenceBox::nb_header() @@ -3260,7 +3265,7 @@ int SrsMp4BaseDescriptor::left_space(SrsBuffer* buf) return srs_max(0, left); } -int SrsMp4BaseDescriptor::nb_bytes() +uint64_t SrsMp4BaseDescriptor::nb_bytes() { // 1 byte tag. int size = 1; @@ -3767,12 +3772,10 @@ SrsMp4SampleEntry* SrsMp4SampleDescriptionBox::entrie_at(int index) return entries.at(index); } -void SrsMp4SampleDescriptionBox::append(SrsMp4Box* v) +// Note that box must be SrsMp4SampleEntry* +void SrsMp4SampleDescriptionBox::append(SrsMp4Box* box) { - SrsMp4SampleEntry* pv = dynamic_cast(v); - if (pv) { - entries.push_back(pv); - } + entries.push_back((SrsMp4SampleEntry*)box); } int SrsMp4SampleDescriptionBox::nb_header() @@ -3912,7 +3915,7 @@ srs_error_t SrsMp4DecodingTime2SampleBox::on_sample(uint32_t sample_index, SrsMp index++; if (index >= entries.size()) { - return srs_error_new(ERROR_MP4_ILLEGAL_TIMESTAMP, "illegal ts, stts overflow, count=%d", entries.size()); + return srs_error_new(ERROR_MP4_ILLEGAL_TIMESTAMP, "illegal ts, stts overflow, count=%zd", entries.size()); } count += entries[index].sample_count; @@ -4038,7 +4041,7 @@ srs_error_t SrsMp4CompositionTime2SampleBox::on_sample(uint32_t sample_index, Sr index++; if (index >= entries.size()) { - return srs_error_new(ERROR_MP4_ILLEGAL_TIMESTAMP, "illegal ts, ctts overflow, count=%d", entries.size()); + return srs_error_new(ERROR_MP4_ILLEGAL_TIMESTAMP, "illegal ts, ctts overflow, count=%d", (int)entries.size()); } count += entries[index].sample_count; @@ -4581,6 +4584,8 @@ SrsMp4SegmentIndexBox::SrsMp4SegmentIndexBox() { type = SrsMp4BoxTypeSIDX; version = 0; + flags = reference_id = timescale = 0; + earliest_presentation_time = first_offset = 0; } SrsMp4SegmentIndexBox::~SrsMp4SegmentIndexBox() @@ -5215,7 +5220,7 @@ srs_error_t SrsMp4BoxReader::read(SrsSimpleStream* stream, SrsMp4Box** ppbox) while (stream->length() < (int)required) { ssize_t nread; if ((err = rsio->read(buf, SRS_MP4_BUF_SIZE, &nread)) != srs_success) { - return srs_error_wrap(err, "load failed, nread=%d, required=%d", nread, required); + return srs_error_wrap(err, "load failed, nread=%d, required=%d", (int)nread, (int)required); } srs_assert(nread > 0); @@ -5777,6 +5782,18 @@ srs_error_t SrsMp4Encoder::flush() if (nb_videos || !pavcc.empty()) { SrsMp4TrackBox* trak = new SrsMp4TrackBox(); moov->add_trak(trak); + + SrsMp4EditBox* edts = new SrsMp4EditBox(); + trak->set_edts(edts); + + SrsMp4EditListBox* elst = new SrsMp4EditListBox(); + edts->set_elst(elst); + elst->version = 0; + + SrsMp4ElstEntry entry; + entry.segment_duration = mvhd->duration_in_tbn; + entry.media_rate_integer = 1; + elst->entries.push_back(entry); SrsMp4TrackHeaderBox* tkhd = new SrsMp4TrackHeaderBox(); trak->set_tkhd(tkhd); @@ -5942,16 +5959,15 @@ srs_error_t SrsMp4Encoder::flush() // Write mdat box. if (true) { - // Update the mdat box header. - if ((err = wsio->lseek(mdat_offset, SEEK_SET, NULL)) != srs_success) { - return srs_error_wrap(err, "seek to mdat"); - } - // Write mdat box with size of data, // its payload already writen by samples, // and we will update its header(size) when flush. SrsMp4MediaDataBox* mdat = new SrsMp4MediaDataBox(); SrsAutoFree(SrsMp4MediaDataBox, mdat); + + // Update the size of mdat first, for over 2GB file. + mdat->nb_data = mdat_bytes; + mdat->update_size(); int nb_data = mdat->sz_header(); uint8_t* data = new uint8_t[nb_data]; @@ -5959,12 +5975,25 @@ srs_error_t SrsMp4Encoder::flush() SrsBuffer* buffer = new SrsBuffer((char*)data, nb_data); SrsAutoFree(SrsBuffer, buffer); - - // TODO: FIXME: Support 64bits size. - mdat->nb_data = (int)mdat_bytes; + if ((err = mdat->encode(buffer)) != srs_success) { return srs_error_wrap(err, "encode mdat"); } + + // We might adjust the offset of mdat, for large size, 2GB+ as such. + if (nb_data > 8) { + // For large size, the header of mdat MUST be 16. + if (nb_data != 16) { + return srs_error_new(ERROR_MP4_ILLEGAL_MDAT, "Invalid mdat header size %d", nb_data); + } + // Use large size, to the start of reserved free box. + mdat_offset -= 8; + } + + // Seek to the start of mdat. + if ((err = wsio->lseek(mdat_offset, SEEK_SET, NULL)) != srs_success) { + return srs_error_wrap(err, "seek to mdat"); + } // TODO: FIXME: Ensure all bytes are writen. if ((err = wsio->write(data, nb_data, NULL)) != srs_success) { @@ -6276,17 +6305,16 @@ SrsMp4M2tsSegmentEncoder::SrsMp4M2tsSegmentEncoder() writer = NULL; nb_audios = nb_videos = 0; samples = new SrsMp4SampleManager(); - buffer = new SrsBuffer(); sequence_number = 0; decode_basetime = 0; styp_bytes = 0; mdat_bytes = 0; + track_id = 0; } SrsMp4M2tsSegmentEncoder::~SrsMp4M2tsSegmentEncoder() { srs_freep(samples); - srs_freep(buffer); } srs_error_t SrsMp4M2tsSegmentEncoder::initialize(ISrsWriter* w, uint32_t sequence, srs_utime_t basetime, uint32_t tid) @@ -6427,7 +6455,7 @@ srs_error_t SrsMp4M2tsSegmentEncoder::flush(uint64_t& dts) // @remark Remember the data_offset of turn is size(moof)+header(mdat), not including styp or sidx. int moof_bytes = moof->nb_bytes(); trun->data_offset = (int32_t)(moof_bytes + mdat->sz_header()); - mdat->nb_data = (int)mdat_bytes; + mdat->nb_data = mdat_bytes; // Update the size of sidx. SrsMp4SegmentIndexEntry* entry = &sidx->entries[0]; diff --git a/trunk/src/kernel/srs_kernel_mp4.hpp b/trunk/src/kernel/srs_kernel_mp4.hpp index ea12508b6..4e856df23 100644 --- a/trunk/src/kernel/srs_kernel_mp4.hpp +++ b/trunk/src/kernel/srs_kernel_mp4.hpp @@ -71,6 +71,8 @@ class SrsMp4TrackFragmentBox; class SrsMp4TrackFragmentHeaderBox; class SrsMp4TrackFragmentDecodeTimeBox; class SrsMp4TrackFragmentRunBox; +class SrsMp4EditBox; +class SrsMp4EditListBox; // 4.2 Object Structure // ISO_IEC_14496-12-base-format-2012.pdf, page 16 @@ -203,7 +205,7 @@ public: // @remark For mdat box, we must codec its header, use this instead of sz(). virtual int sz_header(); // Update the size of box. - virtual int update_size(); + virtual uint64_t update_size(); // Get the left space of box, for decoder. virtual int left_space(SrsBuffer* buf); // Box type helper. @@ -225,7 +227,7 @@ public: static srs_error_t discovery(SrsBuffer* buf, SrsMp4Box** ppbox); // Interface ISrsCodec public: - virtual int nb_bytes(); + virtual uint64_t nb_bytes(); virtual srs_error_t encode(SrsBuffer* buf); virtual srs_error_t decode(SrsBuffer* buf); protected: @@ -493,7 +495,7 @@ public: SrsMp4TrunEntry(SrsMp4FullBox* o); virtual ~SrsMp4TrunEntry(); - virtual int nb_bytes(); + virtual uint64_t nb_bytes(); virtual srs_error_t encode(SrsBuffer* buf); virtual srs_error_t decode(SrsBuffer* buf); @@ -579,8 +581,7 @@ class SrsMp4MediaDataBox : public SrsMp4Box { public: // The contained media data, which we never directly read/write it. - // TODO: FIXME: Support 64bits size. - int nb_data; + uint64_t nb_data; public: SrsMp4MediaDataBox(); virtual ~SrsMp4MediaDataBox(); @@ -588,7 +589,7 @@ public: public: // The total size of bytes, including the sz_header() and nb_data, // which used to write the smallsize or largesize of mp4. - virtual int nb_bytes(); + virtual uint64_t nb_bytes(); // To encode the mdat box, the buf should only contains the sz_header(), // because the mdata only encode the header. virtual srs_error_t encode(SrsBuffer* buf); @@ -766,6 +767,8 @@ public: // Get the track header box. virtual SrsMp4TrackHeaderBox* tkhd(); virtual void set_tkhd(SrsMp4TrackHeaderBox* v); + // Set the EDTS box. + virtual void set_edts(SrsMp4EditBox* v); public: // Get the chunk offset box. virtual SrsMp4ChunkOffsetBox* stco(); @@ -874,6 +877,8 @@ class SrsMp4EditBox : public SrsMp4Box public: SrsMp4EditBox(); virtual ~SrsMp4EditBox(); +public: + virtual void set_elst(SrsMp4EditListBox* v); }; // 8.6.6 Edit List Box @@ -1173,7 +1178,8 @@ public: public: virtual uint32_t entry_count(); virtual SrsMp4DataEntryBox* entry_at(int index); - virtual void append(SrsMp4Box* v); + // Note that box must be SrsMp4DataEntryBox* + virtual void append(SrsMp4Box* box); protected: virtual int nb_header(); virtual srs_error_t encode_header(SrsBuffer* buf); @@ -1362,7 +1368,7 @@ public: virtual int left_space(SrsBuffer* buf); // Interface ISrsCodec public: - virtual int nb_bytes(); + virtual uint64_t nb_bytes(); virtual srs_error_t encode(SrsBuffer* buf); virtual srs_error_t decode(SrsBuffer* buf); protected: @@ -1520,7 +1526,8 @@ public: public: virtual uint32_t entry_count(); virtual SrsMp4SampleEntry* entrie_at(int index); - virtual void append(SrsMp4Box* v); + // Note that box must be SrsMp4SampleEntry* + virtual void append(SrsMp4Box* box); protected: virtual int nb_header(); virtual srs_error_t encode_header(SrsBuffer* buf); @@ -2105,7 +2112,6 @@ class SrsMp4M2tsSegmentEncoder { private: ISrsWriter* writer; - SrsBuffer* buffer; uint32_t sequence_number; srs_utime_t decode_basetime; uint32_t track_id; diff --git a/trunk/src/kernel/srs_kernel_rtc_rtcp.cpp b/trunk/src/kernel/srs_kernel_rtc_rtcp.cpp new file mode 100644 index 000000000..4e0f98eee --- /dev/null +++ b/trunk/src/kernel/srs_kernel_rtc_rtcp.cpp @@ -0,0 +1,1823 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2013-2020 LiPeng + * + * 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 +using namespace std; + +SrsRtcpCommon::SrsRtcpCommon(): ssrc_(0), data_(NULL), nb_data_(0) +{ + payload_len_ = 0; +} + +SrsRtcpCommon::~SrsRtcpCommon() +{ +} + +uint8_t SrsRtcpCommon::type() const +{ + return header_.type; +} + +uint8_t SrsRtcpCommon::get_rc() const +{ + return header_.rc; +} + +uint32_t SrsRtcpCommon::get_ssrc() +{ + return ssrc_; +} + +void SrsRtcpCommon::set_ssrc(uint32_t ssrc) +{ + ssrc_ = ssrc; +} + +char* SrsRtcpCommon::data() +{ + return data_; +} + +int SrsRtcpCommon::size() +{ + return nb_data_; +} + +srs_error_t SrsRtcpCommon::decode_header(SrsBuffer *buffer) +{ + if (!buffer->require(sizeof(SrsRtcpHeader) + 4)) { + return srs_error_new(ERROR_RTC_RTCP, "require %d", sizeof(SrsRtcpHeader) + 4); + } + + buffer->read_bytes((char*)(&header_), sizeof(SrsRtcpHeader)); + header_.length = ntohs(header_.length); + + int payload_len = header_.length * 4; + if (payload_len > buffer->left()) { + return srs_error_new(ERROR_RTC_RTCP, + "require payload len=%u, buffer left=%u", payload_len, buffer->left()); + } + ssrc_ = buffer->read_4bytes(); + + return srs_success; +} + +srs_error_t SrsRtcpCommon::encode_header(SrsBuffer *buffer) +{ + if(! buffer->require(sizeof(SrsRtcpHeader) + 4)) { + return srs_error_new(ERROR_RTC_RTCP, "require %d", sizeof(SrsRtcpHeader) + 4); + } + header_.length = htons(header_.length); + buffer->write_bytes((char*)(&header_), sizeof(SrsRtcpHeader)); + buffer->write_4bytes(ssrc_); + + return srs_success; +} + +srs_error_t SrsRtcpCommon::decode(SrsBuffer *buffer) +{ + srs_error_t err = srs_success; + data_ = buffer->head(); + nb_data_ = buffer->left(); + + if(srs_success != (err = decode_header(buffer))) { + return srs_error_wrap(err, "decode header"); + } + + payload_len_ = (header_.length + 1) * 4 - sizeof(SrsRtcpHeader) - 4; + buffer->read_bytes((char *)payload_, payload_len_); + + return err; +} + +uint64_t SrsRtcpCommon::nb_bytes() +{ + return sizeof(SrsRtcpHeader) + 4 + payload_len_; +} + +srs_error_t SrsRtcpCommon::encode(SrsBuffer *buffer) +{ + return srs_error_new(ERROR_RTC_RTCP, "not implement"); +} + +SrsRtcpApp::SrsRtcpApp() +{ + ssrc_ = 0; + header_.padding = 0; + header_.type = SrsRtcpType_app; + header_.rc = 0; + header_.version = kRtcpVersion; +} + +SrsRtcpApp::~SrsRtcpApp() +{ +} + +bool SrsRtcpApp::is_rtcp_app(uint8_t *data, int nb_data) +{ + if (!data || nb_data <12) { + return false; + } + + SrsRtcpHeader *header = (SrsRtcpHeader*)data; + if (header->version == kRtcpVersion + && header->type == SrsRtcpType_app + && ntohs(header->length) >= 2) { + return true; + } + + return false; +} + +uint8_t SrsRtcpApp::type() const +{ + return SrsRtcpType_app; +} + +uint8_t SrsRtcpApp::get_subtype() const +{ + return header_.rc; +} + +string SrsRtcpApp::get_name() const +{ + return string((char*)name_, strnlen((char*)name_, 4)); +} + +srs_error_t SrsRtcpApp::get_payload(uint8_t*& payload, int& len) +{ + len = payload_len_; + payload = payload_; + + return srs_success; +} + +srs_error_t SrsRtcpApp::set_subtype(uint8_t type) +{ + if(31 < type) { + return srs_error_new(ERROR_RTC_RTCP, "invalid type: %d", type); + } + + header_.rc = type; + + return srs_success; +} + +srs_error_t SrsRtcpApp::set_name(std::string name) +{ + if(name.length() > 4) { + return srs_error_new(ERROR_RTC_RTCP, "invalid name length %zu", name.length()); + } + + memset(name_, 0, sizeof(name_)); + memcpy(name_, name.c_str(), name.length()); + + return srs_success; +} + +srs_error_t SrsRtcpApp::set_payload(uint8_t* payload, int len) +{ + if(len > (kRtcpPacketSize - 12)) { + return srs_error_new(ERROR_RTC_RTCP, "invalid payload length %d", len); + } + + payload_len_ = (len + 3)/ 4 * 4;; + memcpy(payload_, payload, len); + if (payload_len_ > len) { + memset(&payload_[len], 0, payload_len_ - len); //padding + } + header_.length = payload_len_/4 + 3 - 1; + + return srs_success; +} + +srs_error_t SrsRtcpApp::decode(SrsBuffer *buffer) +{ + /* + @doc: https://tools.ietf.org/html/rfc3550#section-6.7 + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| subtype | PT=APP=204 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC/CSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | name (ASCII) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | application-dependent data ... + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + srs_error_t err = srs_success; + data_ = buffer->head(); + nb_data_ = buffer->left(); + + if(srs_success != (err = decode_header(buffer))) { + return srs_error_wrap(err, "decode header"); + } + + if (header_.type != SrsRtcpType_app || !buffer->require(4)) { + return srs_error_new(ERROR_RTC_RTCP, "not rtcp app"); + } + + buffer->read_bytes((char *)name_, sizeof(name_)); + + // TODO: FIXME: Should check size? + payload_len_ = (header_.length + 1) * 4 - 8 - sizeof(name_); + if (payload_len_ > 0) { + buffer->read_bytes((char *)payload_, payload_len_); + } + + return srs_success; +} + +uint64_t SrsRtcpApp::nb_bytes() +{ + return sizeof(SrsRtcpHeader) + sizeof(ssrc_) + sizeof(name_) + payload_len_; +} + +srs_error_t SrsRtcpApp::encode(SrsBuffer *buffer) +{ + /* + @doc: https://tools.ietf.org/html/rfc3550#section-6.7 + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| subtype | PT=APP=204 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC/CSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | name (ASCII) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | application-dependent data ... + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + srs_error_t err = srs_success; + + if(!buffer->require(nb_bytes())) { + return srs_error_new(ERROR_RTC_RTCP, "requires %d bytes", nb_bytes()); + } + + if(srs_success != (err = encode_header(buffer))) { + return srs_error_wrap(err, "encode header"); + } + + buffer->write_bytes((char*)name_, sizeof(name_)); + buffer->write_bytes((char*)payload_, payload_len_); + + return srs_success; +} + +SrsRtcpSR::SrsRtcpSR() +{ + header_.padding = 0; + header_.type = SrsRtcpType_sr; + header_.rc = 0; + header_.version = kRtcpVersion; + header_.length = 6; + + ssrc_ = 0; + ntp_ = 0; + rtp_ts_ = 0; + send_rtp_packets_ = 0; + send_rtp_bytes_ = 0; + send_rtp_bytes_ = 0; +} + +SrsRtcpSR::~SrsRtcpSR() +{ +} + +uint8_t SrsRtcpSR::get_rc() const +{ + return header_.rc; +} + +uint8_t SrsRtcpSR::type() const +{ + return SrsRtcpType_sr; +} + +uint64_t SrsRtcpSR::get_ntp() const +{ + return ntp_; +} + +uint32_t SrsRtcpSR::get_rtp_ts() const +{ + return rtp_ts_; +} + +uint32_t SrsRtcpSR::get_rtp_send_packets() const +{ + return send_rtp_packets_; +} + +uint32_t SrsRtcpSR::get_rtp_send_bytes() const +{ + return send_rtp_bytes_; +} + +void SrsRtcpSR::set_ntp(uint64_t ntp) +{ + ntp_ = ntp; +} + +void SrsRtcpSR::set_rtp_ts(uint32_t ts) +{ + rtp_ts_ = ts; +} + +void SrsRtcpSR::set_rtp_send_packets(uint32_t packets) +{ + send_rtp_packets_ = packets; +} + +void SrsRtcpSR::set_rtp_send_bytes(uint32_t bytes) +{ + send_rtp_bytes_ = bytes; +} + +srs_error_t SrsRtcpSR::decode(SrsBuffer *buffer) +{ + /* @doc: https://tools.ietf.org/html/rfc3550#section-6.4.1 + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +header |V=2|P| RC | PT=SR=200 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of sender | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +sender | NTP timestamp, most significant word | +info +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | NTP timestamp, least significant word | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | RTP timestamp | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | sender's packet count | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | sender's octet count | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +report | SSRC_1 (SSRC of first source) | +block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + 1 | fraction lost | cumulative number of packets lost | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | extended highest sequence number received | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | interarrival jitter | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | last SR (LSR) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | delay since last SR (DLSR) | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +report | SSRC_2 (SSRC of second source) | +block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + 2 : ... : + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + | profile-specific extensions | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + srs_error_t err = srs_success; + data_ = buffer->head(); + nb_data_ = buffer->left(); + + if(srs_success != (err = decode_header(buffer))) { + return srs_error_wrap(err, "decode header"); + } + + ntp_ = buffer->read_8bytes(); + rtp_ts_ = buffer->read_4bytes(); + send_rtp_packets_ = buffer->read_4bytes(); + send_rtp_bytes_ = buffer->read_4bytes(); + + if(header_.rc > 0) { + char buf[1500]; + buffer->read_bytes(buf, header_.rc * 24); + } + + return err; +} + +uint64_t SrsRtcpSR::nb_bytes() +{ + return (header_.length + 1) * 4; +} + +srs_error_t SrsRtcpSR::encode(SrsBuffer *buffer) +{ + srs_error_t err = srs_success; + /* @doc: https://tools.ietf.org/html/rfc3550#section-6.4.1 + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +header |V=2|P| RC | PT=SR=200 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of sender | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +sender | NTP timestamp, most significant word | +info +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | NTP timestamp, least significant word | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | RTP timestamp | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | sender's packet count | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | sender's octet count | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +report | SSRC_1 (SSRC of first source) | +block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + 1 | fraction lost | cumulative number of packets lost | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | extended highest sequence number received | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | interarrival jitter | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | last SR (LSR) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | delay since last SR (DLSR) | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +report | SSRC_2 (SSRC of second source) | +block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + 2 : ... : + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + | profile-specific extensions | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + if(!buffer->require(nb_bytes())) { + return srs_error_new(ERROR_RTC_RTCP, "requires %d bytes", nb_bytes()); + } + + if(srs_success != (err = encode_header(buffer))) { + return srs_error_wrap(err, "encode header"); + } + + buffer->write_8bytes(ntp_); + buffer->write_4bytes(rtp_ts_); + buffer->write_4bytes(send_rtp_packets_); + buffer->write_4bytes(send_rtp_bytes_); + + return err; +} + +SrsRtcpRR::SrsRtcpRR(uint32_t sender_ssrc) +{ + header_.padding = 0; + header_.type = SrsRtcpType_rr; + header_.rc = 0; + header_.version = kRtcpVersion; + header_.length = 7; + ssrc_ = sender_ssrc; + memset(&rb_, 0, sizeof(SrsRtcpRB)); +} + +SrsRtcpRR::~SrsRtcpRR() +{ +} + +uint8_t SrsRtcpRR::type() const +{ + return SrsRtcpType_rr; +} + +uint32_t SrsRtcpRR::get_rb_ssrc() const +{ + return rb_.ssrc; +} + +float SrsRtcpRR::get_lost_rate() const +{ + return rb_.fraction_lost / 256; +} + +uint32_t SrsRtcpRR::get_lost_packets() const +{ + return rb_.lost_packets; +} + +uint32_t SrsRtcpRR::get_highest_sn() const +{ + return rb_.highest_sn; +} + +uint32_t SrsRtcpRR::get_jitter() const +{ + return rb_.jitter; +} + +uint32_t SrsRtcpRR::get_lsr() const +{ + return rb_.lsr; +} + +uint32_t SrsRtcpRR::get_dlsr() const +{ + return rb_.dlsr; +} + +void SrsRtcpRR::set_rb_ssrc(uint32_t ssrc) +{ + rb_.ssrc = ssrc; +} + +void SrsRtcpRR::set_lost_rate(float rate) +{ + rb_.fraction_lost = rate * 256; +} + +void SrsRtcpRR::set_lost_packets(uint32_t count) +{ + rb_.lost_packets = count; +} + +void SrsRtcpRR::set_highest_sn(uint32_t sn) +{ + rb_.highest_sn = sn; +} + +void SrsRtcpRR::set_jitter(uint32_t jitter) +{ + rb_.jitter = jitter; +} + +void SrsRtcpRR::set_lsr(uint32_t lsr) +{ + rb_.lsr = lsr; +} + +void SrsRtcpRR::set_dlsr(uint32_t dlsr) +{ + rb_.dlsr = dlsr; +} + +void SrsRtcpRR::set_sender_ntp(uint64_t ntp) +{ + uint32_t lsr = (uint32_t)((ntp >> 16) & 0x00000000FFFFFFFF); + rb_.lsr = lsr; +} + +srs_error_t SrsRtcpRR::decode(SrsBuffer *buffer) +{ + /* + @doc: https://tools.ietf.org/html/rfc3550#section-6.4.2 + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +header |V=2|P| RC | PT=RR=201 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of packet sender | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +report | SSRC_1 (SSRC of first source) | +block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + 1 | fraction lost | cumulative number of packets lost | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | extended highest sequence number received | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | interarrival jitter | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | last SR (LSR) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | delay since last SR (DLSR) | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +report | SSRC_2 (SSRC of second source) | +block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + 2 : ... : + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + | profile-specific extensions | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + srs_error_t err = srs_success; + data_ = buffer->head(); + nb_data_ = buffer->left(); + + if(srs_success != (err = decode_header(buffer))) { + return srs_error_wrap(err, "decode header"); + } + + // @doc https://tools.ietf.org/html/rfc3550#section-6.4.2 + // An empty RR packet (RC = 0) MUST be put at the head of a compound + // RTCP packet when there is no data transmission or reception to + // report. e.g. {80 c9 00 01 00 00 00 01} + if(header_.rc == 0) { + return srs_error_new(ERROR_RTC_RTCP_EMPTY_RR, "rc=0"); + } + + // TODO: FIXME: Security check for read. + rb_.ssrc = buffer->read_4bytes(); + rb_.fraction_lost = buffer->read_1bytes(); + rb_.lost_packets = buffer->read_3bytes(); + rb_.highest_sn = buffer->read_4bytes(); + rb_.jitter = buffer->read_4bytes(); + rb_.lsr = buffer->read_4bytes(); + rb_.dlsr = buffer->read_4bytes(); + + // TODO: FIXME: Security check for read. + if(header_.rc > 1) { + char buf[1500]; + buffer->read_bytes(buf, (header_.rc -1 ) * 24); + } + + return err; +} + +uint64_t SrsRtcpRR::nb_bytes() +{ + return (header_.length + 1) * 4; +} + +srs_error_t SrsRtcpRR::encode(SrsBuffer *buffer) +{ + /* + @doc: https://tools.ietf.org/html/rfc3550#section-6.4.2 + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +header |V=2|P| RC | PT=RR=201 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of packet sender | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +report | SSRC_1 (SSRC of first source) | +block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + 1 | fraction lost | cumulative number of packets lost | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | extended highest sequence number received | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | interarrival jitter | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | last SR (LSR) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | delay since last SR (DLSR) | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +report | SSRC_2 (SSRC of second source) | +block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + 2 : ... : + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + | profile-specific extensions | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + srs_error_t err = srs_success; + + if(!buffer->require(nb_bytes())) { + return srs_error_new(ERROR_RTC_RTCP, "requires %d bytes", nb_bytes()); + } + + header_.rc = 1; + if(srs_success != (err = encode_header(buffer))) { + return srs_error_wrap(err, "encode header"); + } + + buffer->write_4bytes(rb_.ssrc); + buffer->write_1bytes(rb_.fraction_lost); + buffer->write_3bytes(rb_.lost_packets); + buffer->write_4bytes(rb_.highest_sn); + buffer->write_4bytes(rb_.jitter); + buffer->write_4bytes(rb_.lsr); + buffer->write_4bytes(rb_.dlsr); + + return err; +} + +SrsRtcpTWCC::SrsRtcpTWCCChunk::SrsRtcpTWCCChunk() + : size(0), all_same(true), has_large_delta(false) +{ +} + +SrsRtcpTWCC::SrsRtcpTWCC(uint32_t sender_ssrc) : pkt_len(0) +{ + header_.padding = 0; + header_.type = SrsRtcpType_rtpfb; + header_.rc = 15; + header_.version = kRtcpVersion; + ssrc_ = sender_ssrc; + media_ssrc_ = 0; + base_sn_ = 0; + packet_count_ = 0; + reference_time_ = 0; + fb_pkt_count_ = 0; +} + +SrsRtcpTWCC::~SrsRtcpTWCC() +{ +} + +void SrsRtcpTWCC::clear() +{ + encoded_chucks_.clear(); + pkt_deltas_.clear(); + recv_packets_.clear(); + recv_sns_.clear(); +} + +uint32_t SrsRtcpTWCC::get_media_ssrc() const +{ + return media_ssrc_; +} +uint16_t SrsRtcpTWCC::get_base_sn() const +{ + return base_sn_; +} + +uint32_t SrsRtcpTWCC::get_reference_time() const +{ + return reference_time_; +} + +uint8_t SrsRtcpTWCC::get_feedback_count() const +{ + return fb_pkt_count_; +} + +uint16_t SrsRtcpTWCC::get_packet_status_count() const +{ + return packet_count_; +} + +vector SrsRtcpTWCC::get_packet_chucks() const +{ + return encoded_chucks_; +} + +vector SrsRtcpTWCC::get_recv_deltas() const +{ + return pkt_deltas_; +} + +void SrsRtcpTWCC::set_media_ssrc(uint32_t ssrc) +{ + media_ssrc_ = ssrc; +} +void SrsRtcpTWCC::set_base_sn(uint16_t sn) +{ + base_sn_ = sn; +} + +void SrsRtcpTWCC::set_packet_status_count(uint16_t count) +{ + packet_count_ = count; +} + +void SrsRtcpTWCC::set_reference_time(uint32_t time) +{ + reference_time_ = time; +} + +void SrsRtcpTWCC::set_feedback_count(uint8_t count) +{ + fb_pkt_count_ = count; +} + +void SrsRtcpTWCC::add_packet_chuck(uint16_t chunk) +{ + encoded_chucks_.push_back(chunk); +} + +void SrsRtcpTWCC::add_recv_delta(uint16_t delta) +{ + pkt_deltas_.push_back(delta); +} + +srs_error_t SrsRtcpTWCC::recv_packet(uint16_t sn, srs_utime_t ts) +{ + map::iterator it = recv_packets_.find(sn); + if(it != recv_packets_.end()) { + return srs_error_new(ERROR_RTC_RTCP, "TWCC dup seq: %d", sn); + } + + recv_packets_[sn] = ts; + recv_sns_.insert(sn); + + return srs_success; +} + +bool SrsRtcpTWCC::need_feedback() +{ + return recv_packets_.size() > 0; +} + +srs_error_t SrsRtcpTWCC::decode(SrsBuffer *buffer) +{ + /* + @doc: https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#section-3.1 + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| FMT=15 | PT=205 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of packet sender | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of media source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | base sequence number | packet status count | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | reference time | fb pkt. count | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | packet chunk | packet chunk | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + . . + . . + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | packet chunk | recv delta | recv delta | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + . . + . . + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | recv delta | recv delta | zero padding | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + srs_error_t err = srs_success; + data_ = buffer->head(); + nb_data_ = buffer->left(); + + if(srs_success != (err = decode_header(buffer))) { + return srs_error_wrap(err, "decode header"); + } + + payload_len_ = (header_.length + 1) * 4 - sizeof(SrsRtcpHeader) - 4; + buffer->read_bytes((char *)payload_, payload_len_); + + return err; +} + +uint64_t SrsRtcpTWCC::nb_bytes() +{ + return kRtcpPacketSize; +} + +srs_utime_t SrsRtcpTWCC::calculate_delta_us(srs_utime_t ts, srs_utime_t last) +{ + int64_t divisor = kTwccFbReferenceTimeDivisor; + int64_t delta_us = (ts - last) % divisor; + + if (delta_us > (divisor >> 1)) + delta_us -= divisor; + + delta_us += (delta_us < 0) ? (-kTwccFbDeltaUnit / 2) : (kTwccFbDeltaUnit / 2); + delta_us /= kTwccFbDeltaUnit; + + return delta_us; +} + +bool SrsRtcpTWCC::can_add_to_chunk(SrsRtcpTWCC::SrsRtcpTWCCChunk& chunk, int delta_size) +{ + srs_verbose("can_add %d chunk->size %u delta_sizes %d %d %d %d %d %d %d %d %d %d %d %d %d %d all_same %d has_large_delta %d", + delta_size, chunk.size, chunk.delta_sizes[0], chunk.delta_sizes[1], chunk.delta_sizes[2], chunk.delta_sizes[3], + chunk.delta_sizes[4], chunk.delta_sizes[5], chunk.delta_sizes[6], chunk.delta_sizes[7], chunk.delta_sizes[8], + chunk.delta_sizes[9], chunk.delta_sizes[10], chunk.delta_sizes[11], chunk.delta_sizes[12], chunk.delta_sizes[13], + (int)chunk.all_same, (int)chunk.has_large_delta); + + if (chunk.size < kTwccFbTwoBitElements) { + return true; + } + + if (chunk.size < kTwccFbOneBitElements && !chunk.has_large_delta && delta_size != kTwccFbLargeRecvDeltaBytes) { + return true; + } + + if (chunk.size < kTwccFbMaxRunLength && chunk.all_same && chunk.delta_sizes[0] == delta_size) { + srs_verbose("< %d && all_same && delta_size[0] %d == %d", kTwccFbMaxRunLength, chunk.delta_sizes[0], delta_size); + return true; + } + + return false; +} + +void SrsRtcpTWCC::add_to_chunk(SrsRtcpTWCC::SrsRtcpTWCCChunk& chunk, int delta_size) +{ + if (chunk.size < kTwccFbMaxBitElements) { + chunk.delta_sizes[chunk.size] = delta_size; + } + + chunk.size += 1; + chunk.all_same = chunk.all_same && delta_size == chunk.delta_sizes[0]; + chunk.has_large_delta = chunk.has_large_delta || delta_size >= kTwccFbLargeRecvDeltaBytes; +} + +srs_error_t SrsRtcpTWCC::encode_chunk_run_length(SrsRtcpTWCC::SrsRtcpTWCCChunk& chunk) +{ + if (!chunk.all_same || chunk.size > kTwccFbMaxRunLength) { + return srs_error_new(ERROR_RTC_RTCP, "invalid run all_same:%d, size:%d", chunk.all_same, chunk.size); + } + + uint16_t encoded_chunk = (chunk.delta_sizes[0] << 13) | chunk.size; + + encoded_chucks_.push_back(encoded_chunk); + pkt_len += sizeof(encoded_chunk); + + return srs_success; +} + +srs_error_t SrsRtcpTWCC::encode_chunk_one_bit(SrsRtcpTWCC::SrsRtcpTWCCChunk& chunk) +{ + int i = 0; + if (chunk.has_large_delta) { + return srs_error_new(ERROR_RTC_RTCP, "invalid large delta"); + } + + uint16_t encoded_chunk = 0x8000; + for (i = 0; i < chunk.size; ++i) { + encoded_chunk |= (chunk.delta_sizes[i] << (kTwccFbOneBitElements - 1 - i)); + } + + encoded_chucks_.push_back(encoded_chunk); + pkt_len += sizeof(encoded_chunk); + + // 1 0 symbol_list + return srs_success; +} + +srs_error_t SrsRtcpTWCC::encode_chunk_two_bit(SrsRtcpTWCC::SrsRtcpTWCCChunk& chunk, size_t size, bool shift) +{ + unsigned int i = 0; + uint8_t delta_size = 0; + + uint16_t encoded_chunk = 0xc000; + // 1 1 symbol_list + for (i = 0; i < size; ++i) { + encoded_chunk |= (chunk.delta_sizes[i] << (2 * (kTwccFbTwoBitElements - 1 - i))); + } + encoded_chucks_.push_back(encoded_chunk); + pkt_len += sizeof(encoded_chunk); + + if (shift) { + chunk.size -= size; + chunk.all_same = true; + chunk.has_large_delta = false; + for (i = 0; i < chunk.size; ++i) { + delta_size = chunk.delta_sizes[i + size]; + chunk.delta_sizes[i] = delta_size; + chunk.all_same = (chunk.all_same && delta_size == chunk.delta_sizes[0]); + chunk.has_large_delta = chunk.has_large_delta || delta_size == kTwccFbLargeRecvDeltaBytes; + } + } + + return srs_success; +} + +void SrsRtcpTWCC::reset_chunk(SrsRtcpTWCC::SrsRtcpTWCCChunk& chunk) +{ + chunk.size = 0; + + chunk.all_same = true; + chunk.has_large_delta = false; +} + +srs_error_t SrsRtcpTWCC::encode_chunk(SrsRtcpTWCC::SrsRtcpTWCCChunk& chunk) +{ + srs_error_t err = srs_success; + + if (can_add_to_chunk(chunk, 0) && can_add_to_chunk(chunk, 1) && can_add_to_chunk(chunk, 2)) { + return srs_error_new(ERROR_RTC_RTCP, "TWCC chunk"); + } + + if (chunk.all_same) { + if ((err = encode_chunk_run_length(chunk)) != srs_success) { + return srs_error_wrap(err, "encode run"); + } + reset_chunk(chunk); + return err; + } + + if (chunk.size == kTwccFbOneBitElements) { + if ((err = encode_chunk_one_bit(chunk)) != srs_success) { + return srs_error_wrap(err, "encode chunk"); + } + reset_chunk(chunk); + return err; + } + + if ((err = encode_chunk_two_bit(chunk, kTwccFbTwoBitElements, true)) != srs_success) { + return srs_error_wrap(err, "encode chunk"); + } + + return err; +} + +srs_error_t SrsRtcpTWCC::encode_remaining_chunk(SrsRtcpTWCC::SrsRtcpTWCCChunk& chunk) +{ + if (chunk.all_same) { + return encode_chunk_run_length(chunk); + } else if (chunk.size <= kTwccFbTwoBitElements) { + // FIXME, TRUE or FALSE + return encode_chunk_two_bit(chunk, chunk.size, false); + } + return encode_chunk_one_bit(chunk); +} + +srs_error_t SrsRtcpTWCC::process_pkt_chunk(SrsRtcpTWCC::SrsRtcpTWCCChunk& chunk, int delta_size) +{ + srs_error_t err = srs_success; + + size_t needed_chunk_size = chunk.size == 0 ? kTwccFbChunkBytes : 0; + + size_t might_occupied = pkt_len + needed_chunk_size + delta_size; + if (might_occupied > kRtcpPacketSize) { + return srs_error_new(ERROR_RTC_RTCP, "might_occupied %zu", might_occupied); + } + + if (can_add_to_chunk(chunk, delta_size)) { + //pkt_len += needed_chunk_size; + add_to_chunk(chunk, delta_size); + return err; + } + if ((err = encode_chunk(chunk)) != srs_success) { + return srs_error_wrap(err, "encode chunk, delta_size %u", delta_size); + } + add_to_chunk(chunk, delta_size); + return err; +} + +srs_error_t SrsRtcpTWCC::encode(SrsBuffer *buffer) +{ + srs_error_t err = srs_success; + + err = do_encode(buffer); + + clear(); + + return err; +} + +srs_error_t SrsRtcpTWCC::do_encode(SrsBuffer *buffer) +{ + /* + @doc: https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#section-3.1 + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| FMT=15 | PT=205 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of packet sender | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of media source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | base sequence number | packet status count | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | reference time | fb pkt. count | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | packet chunk | packet chunk | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + . . + . . + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | packet chunk | recv delta | recv delta | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + . . + . . + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | recv delta | recv delta | zero padding | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + srs_error_t err = srs_success; + + if(!buffer->require(nb_bytes())) { + return srs_error_new(ERROR_RTC_RTCP, "requires %d bytes", nb_bytes()); + } + + pkt_len = kTwccFbPktHeaderSize; + set::iterator it_sn = recv_sns_.begin(); + base_sn_ = *it_sn; + map::iterator it_ts = recv_packets_.find(base_sn_); + srs_utime_t ts = it_ts->second; + reference_time_ = (ts % kTwccFbReferenceTimeDivisor) / kTwccFbTimeMultiplier; + srs_utime_t last_ts = (srs_utime_t)(reference_time_) * kTwccFbTimeMultiplier; + uint16_t last_sn = base_sn_; + packet_count_ = recv_packets_.size(); + + // encode chunk + SrsRtcpTWCC::SrsRtcpTWCCChunk chunk; + for(; it_sn != recv_sns_.end(); ++it_sn) { + uint16_t current_sn = *it_sn; + // calculate delta + it_ts = recv_packets_.find(current_sn); + srs_utime_t delta_us = calculate_delta_us(it_ts->second, last_ts); + int16_t delta = delta_us; + if(delta != delta_us) { + return srs_error_new(ERROR_RTC_RTCP, "twcc: delta:%" PRId64 ", exceeds the 16bits", delta_us); + } + + if(current_sn > (last_sn + 1)) { + // lost packet + for(uint16_t lost_sn = last_sn + 1; lost_sn < current_sn; ++lost_sn) { + process_pkt_chunk(chunk, 0); + packet_count_++; + } + } + + // FIXME 24-bit base receive delta not supported + int recv_delta_size = (delta >= 0 && delta <= 0xff) ? 1 : 2; + if ((err = process_pkt_chunk(chunk, recv_delta_size)) != srs_success) { + return srs_error_wrap(err, "delta_size %d, failed to append_recv_delta", recv_delta_size); + } + + pkt_deltas_.push_back(delta); + last_ts += delta * kTwccFbDeltaUnit; + pkt_len += recv_delta_size; + last_sn = current_sn; + } + + if(0 < chunk.size) { + if((err = encode_remaining_chunk(chunk)) != srs_success) { + return srs_error_wrap(err, "encode chunk"); + } + } + + // encode rtcp twcc packet + if((pkt_len % 4) == 0) { + header_.length = pkt_len / 4; + } else { + header_.length = (pkt_len + 4 - (pkt_len%4)) / 4; + } + header_.length -= 1; + + if(srs_success != (err = encode_header(buffer))) { + return srs_error_wrap(err, "encode header"); + } + buffer->write_4bytes(media_ssrc_); + buffer->write_2bytes(base_sn_); + buffer->write_2bytes(packet_count_); + buffer->write_3bytes(reference_time_); + buffer->write_1bytes(fb_pkt_count_); + + for(vector::iterator it = encoded_chucks_.begin(); it != encoded_chucks_.end(); ++it) { + buffer->write_2bytes(*it); + } + for(vector::iterator it = pkt_deltas_.begin(); it != pkt_deltas_.end(); ++it) { + if(0 <= *it && 0xFF >= *it) { + // small delta + uint8_t delta = *it; + buffer->write_1bytes(delta); + } else { + // large or negative delta + buffer->write_2bytes(*it); + } + } + while((pkt_len % 4) != 0) { + buffer->write_1bytes(0); + pkt_len++; + } + + return err; +} + +SrsRtcpNack::SrsRtcpNack(uint32_t sender_ssrc) +{ + header_.padding = 0; + header_.type = SrsRtcpType_rtpfb; + header_.rc = 1; + header_.version = kRtcpVersion; + ssrc_ = sender_ssrc; + media_ssrc_ = 0; +} + +SrsRtcpNack::~SrsRtcpNack() +{ +} + +uint32_t SrsRtcpNack::get_media_ssrc() const +{ + return media_ssrc_; +} + +vector SrsRtcpNack::get_lost_sns() const +{ + vector sn; + for(set::iterator it = lost_sns_.begin(); it != lost_sns_.end(); ++it) { + sn.push_back(*it); + } + return sn; +} + +void SrsRtcpNack::set_media_ssrc(uint32_t ssrc) +{ + media_ssrc_ = ssrc; +} + +void SrsRtcpNack::add_lost_sn(uint16_t sn) +{ + lost_sns_.insert(sn); +} + +srs_error_t SrsRtcpNack::decode(SrsBuffer *buffer) +{ + /* + @doc: https://tools.ietf.org/html/rfc4585#section-6.1 + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| FMT | PT | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of packet sender | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of media source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : Feedback Control Information (FCI) : + : : + + Generic NACK + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | PID | BLP | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + srs_error_t err = srs_success; + data_ = buffer->head(); + nb_data_ = buffer->left(); + + if(srs_success != (err = decode_header(buffer))) { + return srs_error_wrap(err, "decode header"); + } + + media_ssrc_ = buffer->read_4bytes(); + char bitmask[20]; + for(int i = 0; i < (header_.length - 2); i++) { + uint16_t pid = buffer->read_2bytes(); + uint16_t blp = buffer->read_2bytes(); + lost_sns_.insert(pid); + memset(bitmask, 0, 20); + for(int j=0; j<16; j++) { + bitmask[j] = (blp & ( 1 << j )) >> j ? '1' : '0'; + if((blp & ( 1 << j )) >> j) + lost_sns_.insert(pid+j+1); + } + bitmask[16] = '\n'; + srs_info("[%d] %d / %s", i, pid, bitmask); + } + + return err; +} +uint64_t SrsRtcpNack::nb_bytes() +{ + return kRtcpPacketSize; +} + +srs_error_t SrsRtcpNack::encode(SrsBuffer *buffer) +{ + /* + @doc: https://tools.ietf.org/html/rfc4585#section-6.1 + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| FMT | PT | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of packet sender | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of media source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : Feedback Control Information (FCI) : + : : + + Generic NACK + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | PID | BLP | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + srs_error_t err = srs_success; + if(!buffer->require(nb_bytes())) { + return srs_error_new(ERROR_RTC_RTCP, "requires %d bytes", nb_bytes()); + } + + vector chunks; + do { + SrsPidBlp chunk; + chunk.in_use = false; + uint16_t pid = 0; + for(set::iterator it = lost_sns_.begin(); it != lost_sns_.end(); ++it) { + uint16_t sn = *it; + if(!chunk.in_use) { + chunk.pid = sn; + chunk.blp = 0; + chunk.in_use = true; + pid = sn; + continue; + } + if((sn - pid) < 1) { + srs_info("skip seq %d", sn); + } else if( (sn - pid) > 16) { + // add new chunk + chunks.push_back(chunk); + chunk.in_use = false; + } else { + chunk.blp |= 1 << (sn-pid-1); + } + } + if(chunk.in_use) { + chunks.push_back(chunk); + } + + header_.length = 2 + chunks.size(); + if(srs_success != (err = encode_header(buffer))) { + err = srs_error_wrap(err, "encode header"); + break; + } + + buffer->write_4bytes(media_ssrc_); + for(vector::iterator it_chunk = chunks.begin(); it_chunk != chunks.end(); it_chunk++) { + buffer->write_2bytes(it_chunk->pid); + buffer->write_2bytes(it_chunk->blp); + } + } while(0); + + return err; +} + +SrsRtcpPsfbCommon::SrsRtcpPsfbCommon() +{ + header_.padding = 0; + header_.type = SrsRtcpType_psfb; + header_.rc = 1; + header_.version = kRtcpVersion; + //ssrc_ = sender_ssrc; +} + +SrsRtcpPsfbCommon::~SrsRtcpPsfbCommon() +{ + +} + +uint32_t SrsRtcpPsfbCommon::get_media_ssrc() const +{ + return media_ssrc_; +} + +void SrsRtcpPsfbCommon::set_media_ssrc(uint32_t ssrc) +{ + media_ssrc_ = ssrc; +} + +srs_error_t SrsRtcpPsfbCommon::decode(SrsBuffer *buffer) +{ + /* + @doc: https://tools.ietf.org/html/rfc4585#section-6.1 + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| FMT | PT | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of packet sender | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of media source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : Feedback Control Information (FCI) : + : : + */ + + srs_error_t err = srs_success; + data_ = buffer->head(); + nb_data_ = buffer->left(); + + if(srs_success != (err = decode_header(buffer))) { + return srs_error_wrap(err, "decode header"); + } + + media_ssrc_ = buffer->read_4bytes(); + int len = (header_.length + 1) * 4 - 12; + buffer->skip(len); + return err; +} + +uint64_t SrsRtcpPsfbCommon::nb_bytes() +{ + return kRtcpPacketSize; +} + +srs_error_t SrsRtcpPsfbCommon::encode(SrsBuffer *buffer) +{ + return srs_error_new(ERROR_RTC_RTCP, "not support"); +} + +SrsRtcpPli::SrsRtcpPli(uint32_t sender_ssrc/*= 0*/) +{ + header_.padding = 0; + header_.type = SrsRtcpType_psfb; + header_.rc = kPLI; + header_.version = kRtcpVersion; + ssrc_ = sender_ssrc; +} + +SrsRtcpPli::~SrsRtcpPli() +{ +} + +srs_error_t SrsRtcpPli::decode(SrsBuffer *buffer) +{ + /* + @doc: https://tools.ietf.org/html/rfc4585#section-6.1 + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| FMT | PT | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of packet sender | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of media source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : Feedback Control Information (FCI) : + : : + */ + + srs_error_t err = srs_success; + data_ = buffer->head(); + nb_data_ = buffer->left(); + + if(srs_success != (err = decode_header(buffer))) { + return srs_error_wrap(err, "decode header"); + } + + media_ssrc_ = buffer->read_4bytes(); + return err; +} + +uint64_t SrsRtcpPli::nb_bytes() +{ + return 12; +} + +srs_error_t SrsRtcpPli::encode(SrsBuffer *buffer) +{ + /* + @doc: https://tools.ietf.org/html/rfc4585#section-6.1 + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| FMT | PT | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of packet sender | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of media source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : Feedback Control Information (FCI) : + : : + */ + srs_error_t err = srs_success; + if(!buffer->require(nb_bytes())) { + return srs_error_new(ERROR_RTC_RTCP, "requires %d bytes", nb_bytes()); + } + + header_.length = 2; + if(srs_success != (err = encode_header(buffer))) { + return srs_error_wrap(err, "encode header"); + } + + buffer->write_4bytes(media_ssrc_); + + return err; +} + +SrsRtcpSli::SrsRtcpSli(uint32_t sender_ssrc/*= 0*/) +{ + first_ = 0; + number_ = 0; + picture_ = 0; + + header_.padding = 0; + header_.type = SrsRtcpType_psfb; + header_.rc = kSLI; + header_.version = kRtcpVersion; + ssrc_ = sender_ssrc; +} + +SrsRtcpSli::~SrsRtcpSli() +{ +} + + +srs_error_t SrsRtcpSli::decode(SrsBuffer *buffer) +{ + /* + @doc: https://tools.ietf.org/html/rfc4585#section-6.1 + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| FMT | PT | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of packet sender | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of media source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : Feedback Control Information (FCI) : + : : + + + @doc: https://tools.ietf.org/html/rfc4585#section-6.3.2 + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | First | Number | PictureID | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + + srs_error_t err = srs_success; + data_ = buffer->head(); + nb_data_ = buffer->left(); + + if(srs_success != (err = decode_header(buffer))) { + return srs_error_wrap(err, "decode header"); + } + + media_ssrc_ = buffer->read_4bytes(); + int len = (header_.length + 1) * 4 - 12; + buffer->skip(len); + return err; +} + +uint64_t SrsRtcpSli::nb_bytes() +{ + return kRtcpPacketSize; +} + +srs_error_t SrsRtcpSli::encode(SrsBuffer *buffer) +{ + srs_error_t err = srs_success; + + return err; +} + +SrsRtcpRpsi::SrsRtcpRpsi(uint32_t sender_ssrc/* = 0*/) +{ + pb_ = 0; + payload_type_ = 0; + native_rpsi_ = NULL; + nb_native_rpsi_ = 0; + + header_.padding = 0; + header_.type = SrsRtcpType_psfb; + header_.rc = kRPSI; + header_.version = kRtcpVersion; + ssrc_ = sender_ssrc; +} + +SrsRtcpRpsi::~SrsRtcpRpsi() +{ +} + +srs_error_t SrsRtcpRpsi::decode(SrsBuffer *buffer) +{ +/* + @doc: https://tools.ietf.org/html/rfc4585#section-6.1 + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| FMT | PT | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of packet sender | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of media source | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : Feedback Control Information (FCI) : + : : + + + @doc: https://tools.ietf.org/html/rfc4585#section-6.3.3 + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | PB |0| Payload Type| Native RPSI bit string | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | defined per codec ... | Padding (0) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + + srs_error_t err = srs_success; + data_ = buffer->head(); + nb_data_ = buffer->left(); + + if(srs_success != (err = decode_header(buffer))) { + return srs_error_wrap(err, "decode header"); + } + + media_ssrc_ = buffer->read_4bytes(); + int len = (header_.length + 1) * 4 - 12; + buffer->skip(len); + return err; +} + +uint64_t SrsRtcpRpsi::nb_bytes() +{ + return kRtcpPacketSize; +} + +srs_error_t SrsRtcpRpsi::encode(SrsBuffer *buffer) +{ + srs_error_t err = srs_success; + + return err; +} + +SrsRtcpXr::SrsRtcpXr(uint32_t ssrc/*= 0*/) +{ + header_.padding = 0; + header_.type = SrsRtcpType_xr; + header_.rc = 0; + header_.version = kRtcpVersion; + ssrc_ = ssrc; +} + +SrsRtcpXr::~SrsRtcpXr() +{ +} + +srs_error_t SrsRtcpXr::decode(SrsBuffer *buffer) +{ +/* + @doc: https://tools.ietf.org/html/rfc3611#section-2 + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P|reserved | PT=XR=207 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : report blocks : + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + + srs_error_t err = srs_success; + data_ = buffer->head(); + nb_data_ = buffer->left(); + + if(srs_success != (err = decode_header(buffer))) { + return srs_error_wrap(err, "decode header"); + } + + int len = (header_.length + 1) * 4 - 8; + buffer->skip(len); + return err; +} + +uint64_t SrsRtcpXr::nb_bytes() +{ + return kRtcpPacketSize; +} + +srs_error_t SrsRtcpXr::encode(SrsBuffer *buffer) +{ + return srs_error_new(ERROR_RTC_RTCP, "not support"); +} + +SrsRtcpCompound::SrsRtcpCompound(): nb_bytes_(0), data_(NULL), nb_data_(0) +{ +} + +SrsRtcpCompound::~SrsRtcpCompound() +{ + clear(); +} + +SrsRtcpCommon* SrsRtcpCompound::get_next_rtcp() +{ + if(rtcps_.empty()) { + return NULL; + } + SrsRtcpCommon *rtcp = rtcps_.back(); + rtcps_.pop_back(); + return rtcp; +} + +srs_error_t SrsRtcpCompound::add_rtcp(SrsRtcpCommon *rtcp) +{ + int new_len = rtcp->nb_bytes(); + if((new_len + nb_bytes_) > kRtcpPacketSize) { + return srs_error_new(ERROR_RTC_RTCP, "overflow, new rtcp: %d, current: %d", new_len, nb_bytes_); + } + nb_bytes_ += new_len; + rtcps_.push_back(rtcp); + + return srs_success; +} + +srs_error_t SrsRtcpCompound::decode(SrsBuffer *buffer) +{ + srs_error_t err = srs_success; + data_ = buffer->data(); + nb_data_ = buffer->size(); + + while (!buffer->empty()) { + SrsRtcpCommon* rtcp = NULL; + SrsRtcpHeader* header = (SrsRtcpHeader*)(buffer->head()); + if (header->type == SrsRtcpType_sr) { + rtcp = new SrsRtcpSR(); + } else if (header->type == SrsRtcpType_rr) { + rtcp = new SrsRtcpRR(); + } else if (header->type == SrsRtcpType_rtpfb) { + if(1 == header->rc) { + //nack + rtcp = new SrsRtcpNack(); + } else if (15 == header->rc) { + //twcc + rtcp = new SrsRtcpTWCC(); + } + } else if(header->type == SrsRtcpType_psfb) { + if(1 == header->rc) { + // pli + rtcp = new SrsRtcpPli(); + } else if(2 == header->rc) { + //sli + rtcp = new SrsRtcpSli(); + } else if(3 == header->rc) { + //rpsi + rtcp = new SrsRtcpRpsi(); + } else { + // common psfb + rtcp = new SrsRtcpPsfbCommon(); + } + } else if(header->type == SrsRtcpType_xr) { + rtcp = new SrsRtcpXr(); + } else { + rtcp = new SrsRtcpCommon(); + } + + if(srs_success != (err = rtcp->decode(buffer))) { + srs_freep(rtcp); + + // @doc https://tools.ietf.org/html/rfc3550#section-6.4.2 + // An empty RR packet (RC = 0) MUST be put at the head of a compound + // RTCP packet when there is no data transmission or reception to + // report. e.g. {80 c9 00 01 00 00 00 01} + if (ERROR_RTC_RTCP_EMPTY_RR == srs_error_code(err)) { + srs_freep(err); + continue; + } + + return srs_error_wrap(err, "decode rtcp type=%u rc=%u", header->type, header->rc); + } + + rtcps_.push_back(rtcp); + } + + return err; +} + +uint64_t SrsRtcpCompound::nb_bytes() +{ + return kRtcpPacketSize; +} + +srs_error_t SrsRtcpCompound::encode(SrsBuffer *buffer) +{ + srs_error_t err = srs_success; + if(!buffer->require(nb_bytes_)) { + return srs_error_new(ERROR_RTC_RTCP, "requires %d bytes", nb_bytes_); + } + + vector::iterator it; + for(it = rtcps_.begin(); it != rtcps_.end(); ++it) { + SrsRtcpCommon *rtcp = *it; + if((err = rtcp->encode(buffer)) != srs_success) { + return srs_error_wrap(err, "encode compound type:%d", rtcp->type()); + } + } + + clear(); + return err; +} + +void SrsRtcpCompound::clear() +{ + vector::iterator it; + for(it = rtcps_.begin(); it != rtcps_.end(); ++it) { + SrsRtcpCommon *rtcp = *it; + delete rtcp; + rtcp = NULL; + } + rtcps_.clear(); + nb_bytes_ = 0; +} + +char* SrsRtcpCompound::data() +{ + return data_; +} + +int SrsRtcpCompound::size() +{ + return nb_data_; +} + diff --git a/trunk/src/kernel/srs_kernel_rtc_rtcp.hpp b/trunk/src/kernel/srs_kernel_rtc_rtcp.hpp new file mode 100644 index 000000000..56c30912a --- /dev/null +++ b/trunk/src/kernel/srs_kernel_rtc_rtcp.hpp @@ -0,0 +1,465 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2013-2020 LiPeng + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef SRS_KERNEL_RTC_RTCP_HPP +#define SRS_KERNEL_RTC_RTCP_HPP + +#include + +#include +#include +#include + +#include +#include + +const int kRtcpPacketSize = 1500; +const uint8_t kRtcpVersion = 0x2; + +// RTCP Packet Types, @see http://www.networksorcery.com/enp/protocol/rtcp.htm +enum SrsRtcpType +{ + SrsRtcpType_fir = 192, + SrsRtcpType_sr = 200, + SrsRtcpType_rr = 201, + SrsRtcpType_sdes = 202, + SrsRtcpType_bye = 203, + SrsRtcpType_app = 204, + SrsRtcpType_rtpfb = 205, + SrsRtcpType_psfb = 206, + SrsRtcpType_xr = 207, +}; + +// @see: https://tools.ietf.org/html/rfc4585#section-6.3 +const uint8_t kPLI = 1; +const uint8_t kSLI = 2; +const uint8_t kRPSI = 3; +const uint8_t kAFB = 15; + +// RTCP Header, @see http://tools.ietf.org/html/rfc3550#section-6.1 +// @remark The header must be 4 bytes, which align with the max field size 2B. +struct SrsRtcpHeader +{ + uint16_t rc:5; + uint16_t padding:1; + uint16_t version:2; + uint16_t type:8; + + uint16_t length:16; + + SrsRtcpHeader() { + rc = 0; + padding = 0; + version = 0; + type = 0; + length = 0; + } +}; + +class SrsRtcpCommon: public ISrsCodec +{ +protected: + SrsRtcpHeader header_; + uint32_t ssrc_; + uint8_t payload_[kRtcpPacketSize]; + int payload_len_; + + char* data_; + int nb_data_; +protected: + srs_error_t decode_header(SrsBuffer *buffer); + srs_error_t encode_header(SrsBuffer *buffer); +public: + SrsRtcpCommon(); + virtual ~SrsRtcpCommon(); + virtual uint8_t type() const; + virtual uint8_t get_rc() const; + + uint32_t get_ssrc(); + void set_ssrc(uint32_t ssrc); + + char* data(); + int size(); +// interface ISrsCodec +public: + virtual srs_error_t decode(SrsBuffer *buffer); + virtual uint64_t nb_bytes(); + virtual srs_error_t encode(SrsBuffer *buffer); +}; + +class SrsRtcpApp : public SrsRtcpCommon +{ +private: + uint8_t name_[4]; +public: + SrsRtcpApp(); + virtual ~SrsRtcpApp(); + + static bool is_rtcp_app(uint8_t *data, int nb_data); + + virtual uint8_t type() const; + + uint8_t get_subtype() const; + std::string get_name() const; + srs_error_t get_payload(uint8_t*& payload, int& len); + + srs_error_t set_subtype(uint8_t type); + srs_error_t set_name(std::string name); + srs_error_t set_payload(uint8_t* payload, int len); +// interface ISrsCodec +public: + virtual srs_error_t decode(SrsBuffer *buffer); + virtual uint64_t nb_bytes(); + virtual srs_error_t encode(SrsBuffer *buffer); +}; + +struct SrsRtcpRB +{ + uint32_t ssrc; + uint8_t fraction_lost; + uint32_t lost_packets; + uint32_t highest_sn; + uint32_t jitter; + uint32_t lsr; + uint32_t dlsr; + + SrsRtcpRB() { + ssrc = 0; + fraction_lost = 0; + lost_packets = 0; + highest_sn = 0; + jitter = 0; + lsr = 0; + dlsr = 0; + } +}; + +class SrsRtcpSR : public SrsRtcpCommon +{ +private: + uint64_t ntp_; + uint32_t rtp_ts_; + uint32_t send_rtp_packets_; + uint32_t send_rtp_bytes_; + +public: + SrsRtcpSR(); + virtual ~SrsRtcpSR(); + + uint8_t get_rc() const; + // overload SrsRtcpCommon + virtual uint8_t type() const; + uint64_t get_ntp() const; + uint32_t get_rtp_ts() const; + uint32_t get_rtp_send_packets() const; + uint32_t get_rtp_send_bytes() const; + + void set_ntp(uint64_t ntp); + void set_rtp_ts(uint32_t ts); + void set_rtp_send_packets(uint32_t packets); + void set_rtp_send_bytes(uint32_t bytes); +// interface ISrsCodec +public: + virtual srs_error_t decode(SrsBuffer *buffer); + virtual uint64_t nb_bytes(); + virtual srs_error_t encode(SrsBuffer *buffer); +}; + +class SrsRtcpRR : public SrsRtcpCommon +{ +private: + SrsRtcpRB rb_; +public: + SrsRtcpRR(uint32_t sender_ssrc = 0); + virtual ~SrsRtcpRR(); + + // overload SrsRtcpCommon + virtual uint8_t type() const; + + uint32_t get_rb_ssrc() const; + float get_lost_rate() const; + uint32_t get_lost_packets() const; + uint32_t get_highest_sn() const; + uint32_t get_jitter() const; + uint32_t get_lsr() const; + uint32_t get_dlsr() const; + + void set_rb_ssrc(uint32_t ssrc); + void set_lost_rate(float rate); + void set_lost_packets(uint32_t count); + void set_highest_sn(uint32_t sn); + void set_jitter(uint32_t jitter); + void set_lsr(uint32_t lsr); + void set_dlsr(uint32_t dlsr); + void set_sender_ntp(uint64_t ntp); +// interface ISrsCodec +public: + virtual srs_error_t decode(SrsBuffer *buffer); + virtual uint64_t nb_bytes(); + virtual srs_error_t encode(SrsBuffer *buffer); + +}; + +// The Message format of TWCC, @see https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#section-3.1 +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |V=2|P| FMT=15 | PT=205 | length | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of packet sender | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | SSRC of media source | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | base sequence number | packet status count | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | reference time | fb pkt. count | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | packet chunk | packet chunk | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// . . +// . . +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | packet chunk | recv delta | recv delta | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// . . +// . . +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | recv delta | recv delta | zero padding | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +#define kTwccFbPktHeaderSize (4 + 8 + 8) +#define kTwccFbChunkBytes (2) +#define kTwccFbPktFormat (15) +#define kTwccFbPayloadType (205) +#define kTwccFbMaxPktStatusCount (0xffff) +#define kTwccFbDeltaUnit (250) // multiple of 250us +#define kTwccFbTimeMultiplier (kTwccFbDeltaUnit * (1 << 8)) // multiplicand multiplier/* 250us -> 64ms (1 << 8) */ +#define kTwccFbReferenceTimeDivisor ((1ll<<24) * kTwccFbTimeMultiplier) // dividend divisor + +#define kTwccFbMaxRunLength 0x1fff +#define kTwccFbOneBitElements 14 +#define kTwccFbTwoBitElements 7 +#define kTwccFbLargeRecvDeltaBytes 2 +#define kTwccFbMaxBitElements kTwccFbOneBitElements + +class SrsRtcpTWCC : public SrsRtcpCommon +{ +private: + uint32_t media_ssrc_; + uint16_t base_sn_; + uint16_t packet_count_; + int32_t reference_time_; + uint8_t fb_pkt_count_; + std::vector encoded_chucks_; + std::vector pkt_deltas_; + + std::map recv_packets_; + std::set recv_sns_; + + struct SrsRtcpTWCCChunk { + uint8_t delta_sizes[kTwccFbMaxBitElements]; + uint16_t size; + bool all_same; + bool has_large_delta; + SrsRtcpTWCCChunk(); + }; + + int pkt_len; +private: + void clear(); + srs_utime_t calculate_delta_us(srs_utime_t ts, srs_utime_t last); + srs_error_t process_pkt_chunk(SrsRtcpTWCCChunk& chunk, int delta_size); + bool can_add_to_chunk(SrsRtcpTWCCChunk& chunk, int delta_size); + void add_to_chunk(SrsRtcpTWCCChunk& chunk, int delta_size); + srs_error_t encode_chunk(SrsRtcpTWCCChunk& chunk); + srs_error_t encode_chunk_run_length(SrsRtcpTWCCChunk& chunk); + srs_error_t encode_chunk_one_bit(SrsRtcpTWCCChunk& chunk); + srs_error_t encode_chunk_two_bit(SrsRtcpTWCCChunk& chunk, size_t size, bool shift); + void reset_chunk(SrsRtcpTWCCChunk& chunk); + srs_error_t encode_remaining_chunk(SrsRtcpTWCCChunk& chunk); +public: + SrsRtcpTWCC(uint32_t sender_ssrc = 0); + virtual ~SrsRtcpTWCC(); + + uint32_t get_media_ssrc() const; + uint16_t get_base_sn() const; + uint16_t get_packet_status_count() const; + uint32_t get_reference_time() const; + uint8_t get_feedback_count() const; + std::vector get_packet_chucks() const; + std::vector get_recv_deltas() const; + + void set_media_ssrc(uint32_t ssrc); + void set_base_sn(uint16_t sn); + void set_packet_status_count(uint16_t count); + void set_reference_time(uint32_t time); + void set_feedback_count(uint8_t count); + void add_packet_chuck(uint16_t chuck); + void add_recv_delta(uint16_t delta); + + srs_error_t recv_packet(uint16_t sn, srs_utime_t ts); + bool need_feedback(); + +// interface ISrsCodec +public: + virtual srs_error_t decode(SrsBuffer *buffer); + virtual uint64_t nb_bytes(); + virtual srs_error_t encode(SrsBuffer *buffer); +private: + srs_error_t do_encode(SrsBuffer *buffer); +}; + +class SrsRtcpNack : public SrsRtcpCommon +{ +private: + struct SrsPidBlp { + uint16_t pid; + uint16_t blp; + bool in_use; + }; + + uint32_t media_ssrc_; + std::set lost_sns_; +public: + SrsRtcpNack(uint32_t sender_ssrc = 0); + virtual ~SrsRtcpNack(); + + uint32_t get_media_ssrc() const; + std::vector get_lost_sns() const; + + void set_media_ssrc(uint32_t ssrc); + void add_lost_sn(uint16_t sn); +// interface ISrsCodec +public: + virtual srs_error_t decode(SrsBuffer *buffer); + virtual uint64_t nb_bytes(); + virtual srs_error_t encode(SrsBuffer *buffer); +}; + +class SrsRtcpPsfbCommon : public SrsRtcpCommon +{ +protected: + uint32_t media_ssrc_; +public: + SrsRtcpPsfbCommon(); + virtual ~SrsRtcpPsfbCommon(); + + uint32_t get_media_ssrc() const; + void set_media_ssrc(uint32_t ssrc); + +// interface ISrsCodec +public: + virtual srs_error_t decode(SrsBuffer *buffer); + virtual uint64_t nb_bytes(); + virtual srs_error_t encode(SrsBuffer *buffer); +}; + +class SrsRtcpPli : public SrsRtcpPsfbCommon +{ +public: + SrsRtcpPli(uint32_t sender_ssrc = 0); + virtual ~SrsRtcpPli(); + +// interface ISrsCodec +public: + virtual srs_error_t decode(SrsBuffer *buffer); + virtual uint64_t nb_bytes(); + virtual srs_error_t encode(SrsBuffer *buffer); +}; + +class SrsRtcpSli : public SrsRtcpPsfbCommon +{ +private: + uint16_t first_; + uint16_t number_; + uint8_t picture_; +public: + SrsRtcpSli(uint32_t sender_ssrc = 0); + virtual ~SrsRtcpSli(); + + // interface ISrsCodec +public: + virtual srs_error_t decode(SrsBuffer *buffer); + virtual uint64_t nb_bytes(); + virtual srs_error_t encode(SrsBuffer *buffer); +}; + +class SrsRtcpRpsi : public SrsRtcpPsfbCommon +{ +private: + uint8_t pb_; + uint8_t payload_type_; + char* native_rpsi_; + int nb_native_rpsi_; + +public: + SrsRtcpRpsi(uint32_t sender_ssrc = 0); + virtual ~SrsRtcpRpsi(); + + // interface ISrsCodec +public: + virtual srs_error_t decode(SrsBuffer *buffer); + virtual uint64_t nb_bytes(); + virtual srs_error_t encode(SrsBuffer *buffer); +}; + +class SrsRtcpXr : public SrsRtcpCommon +{ +public: + SrsRtcpXr (uint32_t ssrc = 0); + virtual ~SrsRtcpXr(); + + // interface ISrsCodec +public: + virtual srs_error_t decode(SrsBuffer *buffer); + virtual uint64_t nb_bytes(); + virtual srs_error_t encode(SrsBuffer *buffer); +}; + +class SrsRtcpCompound : public ISrsCodec +{ +private: + std::vector rtcps_; + int nb_bytes_; + char* data_; + int nb_data_; +public: + SrsRtcpCompound(); + virtual ~SrsRtcpCompound(); + + // TODO: FIXME: Should rename it to pop(), because it's not a GET method. + SrsRtcpCommon* get_next_rtcp(); + srs_error_t add_rtcp(SrsRtcpCommon *rtcp); + void clear(); + + char* data(); + int size(); + +// interface ISrsCodec +public: + virtual srs_error_t decode(SrsBuffer *buffer); + virtual uint64_t nb_bytes(); + virtual srs_error_t encode(SrsBuffer *buffer); +}; + +#endif + diff --git a/trunk/src/kernel/srs_kernel_rtc_rtp.cpp b/trunk/src/kernel/srs_kernel_rtc_rtp.cpp new file mode 100644 index 000000000..9396d867c --- /dev/null +++ b/trunk/src/kernel/srs_kernel_rtc_rtp.cpp @@ -0,0 +1,1246 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2013-2020 Winlin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include + +#include +#include +using namespace std; + +#include +#include +#include +#include +#include + +// If value is newer than pre_value,return true; otherwise false +bool srs_seq_is_newer(uint16_t value, uint16_t pre_value) +{ + return srs_rtp_seq_distance(pre_value, value) > 0; +} + +bool srs_seq_is_rollback(uint16_t value, uint16_t pre_value) +{ + if(srs_seq_is_newer(value, pre_value)) { + return pre_value > value; + } + return false; +} + +// If value is newer then pre_value, return positive, otherwise negative. +int32_t srs_seq_distance(uint16_t value, uint16_t pre_value) +{ + return srs_rtp_seq_distance(pre_value, value); +} + +SrsRtpExtensionTypes::SrsRtpExtensionTypes() +{ + memset(ids_, kRtpExtensionNone, sizeof(ids_)); +} + +SrsRtpExtensionTypes::~SrsRtpExtensionTypes() +{ +} + +bool SrsRtpExtensionTypes::register_by_uri(int id, std::string uri) +{ + for (int i = 0; i < (int)(sizeof(kExtensions)/sizeof(kExtensions[0])); ++i) { + if (kExtensions[i].uri == uri) { + return register_id(id, kExtensions[i].type, kExtensions[i].uri); + } + } + return false; +} + +bool SrsRtpExtensionTypes::register_id(int id, SrsRtpExtensionType type, std::string uri) +{ + if (id < 1 || id > 255) { + return false; + } + + ids_[type] = static_cast(id); + return true; +} + +SrsRtpExtensionType SrsRtpExtensionTypes::get_type(int id) const +{ + for (int type = kRtpExtensionNone + 1; type < kRtpExtensionNumberOfExtensions; ++type) { + if (ids_[type] == id) { + return static_cast(type); + } + } + return kInvalidType; +} + + + +SrsRtpExtensionTwcc::SrsRtpExtensionTwcc(): has_twcc_(false), id_(0), sn_(0) +{ +} + +SrsRtpExtensionTwcc::~SrsRtpExtensionTwcc() +{ +} + +bool SrsRtpExtensionTwcc::has_twcc_ext() +{ + return has_twcc_; +} + +srs_error_t SrsRtpExtensionTwcc::decode(SrsBuffer* buf) +{ + srs_error_t err = srs_success; + + // 0 1 2 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | ID | L=1 |transport wide sequence number | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + if (!buf->require(1)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 1); + } + uint8_t v = buf->read_1bytes(); + + id_ = (v & 0xF0) >> 4; + uint8_t len = (v & 0x0F); + if(!id_ || len != 1) { + return srs_error_new(ERROR_RTC_RTP, "invalid twcc id=%d, len=%d", id_, len); + } + + if (!buf->require(2)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 2); + } + sn_ = buf->read_2bytes(); + + has_twcc_ = true; + return err; +} + +uint64_t SrsRtpExtensionTwcc::nb_bytes() +{ + return 3; +} + +srs_error_t SrsRtpExtensionTwcc::encode(SrsBuffer* buf) +{ + srs_error_t err = srs_success; + + if(!buf->require(3)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 3); + } + + uint8_t id_len = (id_ & 0x0F)<< 4| 0x01; + buf->write_1bytes(id_len); + buf->write_2bytes(sn_); + + return err; +} + + +uint8_t SrsRtpExtensionTwcc::get_id() +{ + return id_; +} + +void SrsRtpExtensionTwcc::set_id(uint8_t id) +{ + id_ = id; + has_twcc_ = true; +} + +uint16_t SrsRtpExtensionTwcc::get_sn() +{ + return sn_; +} + +void SrsRtpExtensionTwcc::set_sn(uint16_t sn) +{ + sn_ = sn; + has_twcc_ = true; +} + +SrsRtpExtensions::SrsRtpExtensions() : has_ext_(false) +{ +} + +SrsRtpExtensions::~SrsRtpExtensions() +{ +} + +srs_error_t SrsRtpExtensions::decode(SrsBuffer* buf) +{ + srs_error_t err = srs_success; + + /* @see https://tools.ietf.org/html/rfc3550#section-5.3.1 + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | defined by profile | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | header extension | + | .... | + */ + if (!buf->require(4)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires 4 bytes"); + } + uint16_t profile_id = buf->read_2bytes(); + uint16_t extension_length = buf->read_2bytes(); + + // @see: https://tools.ietf.org/html/rfc5285#section-4.2 + if (profile_id == 0xBEDE) { + SrsBuffer xbuf(buf->head(), extension_length * 4); + buf->skip(extension_length * 4); + return decode_0xbede(&xbuf); + } else if (profile_id == 0x1000) { + buf->skip(extension_length * 4); + } else { + return srs_error_new(ERROR_RTC_RTP_MUXER, "fail to parse extension"); + } + return err; +} + +srs_error_t SrsRtpExtensions::decode_0xbede(SrsBuffer* buf) +{ + srs_error_t err = srs_success; + + while (!buf->empty()) { + // The first byte maybe padding or id+len. + if (!buf->require(1)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 1); + } + uint8_t v = *((uint8_t*)buf->head()); + + // Padding, ignore + if(v == 0) { + buf->skip(1); + continue; + } + + // 0 + // 0 1 2 3 4 5 6 7 + // +-+-+-+-+-+-+-+-+ + // | ID | len | + // +-+-+-+-+-+-+-+-+ + // Note that 'len' is the header extension element length, which is the + // number of bytes - 1. + uint8_t id = (v & 0xF0) >> 4; + uint8_t len = (v & 0x0F); + + SrsRtpExtensionType xtype = types_.get_type(id); + if (xtype == kRtpExtensionTransportSequenceNumber) { + if ((err = twcc_.decode(buf)) != srs_success) { + return srs_error_wrap(err, "decode twcc extension"); + } + has_ext_ = true; + } else { + buf->skip(1 + (len + 1)); + } + } + + return err; +} + +uint64_t SrsRtpExtensions::nb_bytes() +{ + int size = 4 + (twcc_.has_twcc_ext() ? twcc_.nb_bytes() : 0); + // add padding + size += (size % 4 == 0) ? 0 : (4 - size % 4); + return size; +} + +srs_error_t SrsRtpExtensions::encode(SrsBuffer* buf) +{ + srs_error_t err = srs_success; + + buf->write_2bytes(0xBEDE); + + // Write length. + int len = 0; + + if (twcc_.has_twcc_ext()) { + len += twcc_.nb_bytes(); + } + + int padding_count = (len % 4 == 0) ? 0 : (4 - len % 4); + len += padding_count; + + buf->write_2bytes(len / 4); + + // Write extensions. + if (twcc_.has_twcc_ext()) { + if ((err = twcc_.encode(buf)) != srs_success) { + return srs_error_wrap(err, "encode twcc extension"); + } + } + + // add padding + while(padding_count > 0) { + buf->write_1bytes(0); + padding_count--; + } + + return err; +} + +bool SrsRtpExtensions::exists() +{ + return has_ext_; +} + +void SrsRtpExtensions::set_types_(const SrsRtpExtensionTypes* types) +{ + if(types) { + types_ = *types; + } +} + +srs_error_t SrsRtpExtensions::get_twcc_sequence_number(uint16_t& twcc_sn) +{ + if (twcc_.has_twcc_ext()) { + twcc_sn = twcc_.get_sn(); + return srs_success; + } + return srs_error_new(ERROR_RTC_RTP_MUXER, "not find twcc sequence number"); +} + +srs_error_t SrsRtpExtensions::set_twcc_sequence_number(uint8_t id, uint16_t sn) +{ + has_ext_ = true; + twcc_.set_id(id); + twcc_.set_sn(sn); + return srs_success; +} + +SrsRtpHeader::SrsRtpHeader() +{ + padding_length = 0; + cc = 0; + marker = false; + payload_type = 0; + sequence = 0; + timestamp = 0; + ssrc = 0; + ignore_padding_ = false; +} + +SrsRtpHeader::~SrsRtpHeader() +{ +} + +srs_error_t SrsRtpHeader::decode(SrsBuffer* buf) +{ + srs_error_t err = srs_success; + + if (!buf->require(kRtpHeaderFixedSize)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d+ bytes", kRtpHeaderFixedSize); + } + + /* @see https://tools.ietf.org/html/rfc1889#section-5.1 + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P|X| CC |M| PT | sequence number | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | timestamp | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | synchronization source (SSRC) identifier | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + | contributing source (CSRC) identifiers | + | .... | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + + uint8_t first = buf->read_1bytes(); + bool padding = (first & 0x20); + bool extension = (first & 0x10); + cc = (first & 0x0F); + + uint8_t second = buf->read_1bytes(); + marker = (second & 0x80); + payload_type = (second & 0x7F); + + sequence = buf->read_2bytes(); + timestamp = buf->read_4bytes(); + ssrc = buf->read_4bytes(); + + int ext_bytes = nb_bytes() - kRtpHeaderFixedSize; + if (!buf->require(ext_bytes)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d+ bytes", ext_bytes); + } + + for (uint8_t i = 0; i < cc; ++i) { + csrc[i] = buf->read_4bytes(); + } + + if (extension) { + if ((err = parse_extensions(buf)) != srs_success) { + return srs_error_wrap(err, "fail to parse extension"); + } + } + + if (padding && !ignore_padding_ && !buf->empty()) { + padding_length = *(reinterpret_cast(buf->data() + buf->size() - 1)); + if (!buf->require(padding_length)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "padding requires %d bytes", padding_length); + } + } + + return err; +} + +srs_error_t SrsRtpHeader::parse_extensions(SrsBuffer* buf) { + srs_error_t err = srs_success; + + if(srs_success != (err = extensions_.decode(buf))) { + return srs_error_wrap(err, "decode rtp extension"); + } + + return err; +} + +srs_error_t SrsRtpHeader::encode(SrsBuffer* buf) +{ + srs_error_t err = srs_success; + + // Encode the RTP fix header, 12bytes. + // @see https://tools.ietf.org/html/rfc1889#section-5.1 + // The version, padding, extension and cc, total 1 byte. + uint8_t v = 0x80 | cc; + if (padding_length > 0) { + v |= 0x20; + } + if (extensions_.exists()) { + v |= 0x10; + } + buf->write_1bytes(v); + + // The marker and payload type, total 1 byte. + v = payload_type; + if (marker) { + v |= kRtpMarker; + } + buf->write_1bytes(v); + + // The sequence number, 2 bytes. + buf->write_2bytes(sequence); + + // The timestamp, 4 bytes. + buf->write_4bytes(timestamp); + + // The SSRC, 4 bytes. + buf->write_4bytes(ssrc); + + // The CSRC list: 0 to 15 items, each is 4 bytes. + for (size_t i = 0; i < cc; ++i) { + buf->write_4bytes(csrc[i]); + } + + if (extensions_.exists()) { + if(srs_success != (err = extensions_.encode(buf))) { + return srs_error_wrap(err, "encode rtp extension"); + } + } + + return err; +} +void SrsRtpHeader::set_extensions(const SrsRtpExtensionTypes* extmap) +{ + if (extmap) { + extensions_.set_types_(extmap); + } +} + +void SrsRtpHeader::ignore_padding(bool v) +{ + ignore_padding_ = v; +} + +srs_error_t SrsRtpHeader::get_twcc_sequence_number(uint16_t& twcc_sn) +{ + if (extensions_.exists()) { + return extensions_.get_twcc_sequence_number(twcc_sn); + } + return srs_error_new(ERROR_RTC_RTP_MUXER, "no rtp extension"); +} + +srs_error_t SrsRtpHeader::set_twcc_sequence_number(uint8_t id, uint16_t sn) +{ + return extensions_.set_twcc_sequence_number(id, sn); +} + +uint64_t SrsRtpHeader::nb_bytes() +{ + return kRtpHeaderFixedSize + cc * 4 + (extensions_.exists() ? extensions_.nb_bytes() : 0); +} + +void SrsRtpHeader::set_marker(bool v) +{ + marker = v; +} + +bool SrsRtpHeader::get_marker() const +{ + return marker; +} + +void SrsRtpHeader::set_payload_type(uint8_t v) +{ + payload_type = v; +} + +uint8_t SrsRtpHeader::get_payload_type() const +{ + return payload_type; +} + +void SrsRtpHeader::set_sequence(uint16_t v) +{ + sequence = v; +} + +uint16_t SrsRtpHeader::get_sequence() const +{ + return sequence; +} + +void SrsRtpHeader::set_timestamp(uint32_t v) +{ + timestamp = v; +} + +uint32_t SrsRtpHeader::get_timestamp() const +{ + return timestamp; +} + +void SrsRtpHeader::set_ssrc(uint32_t v) +{ + ssrc = v; +} + +uint32_t SrsRtpHeader::get_ssrc() const +{ + return ssrc; +} + +void SrsRtpHeader::set_padding(uint8_t v) +{ + padding_length = v; +} + +uint8_t SrsRtpHeader::get_padding() const +{ + return padding_length; +} + +ISrsRtpPayloader::ISrsRtpPayloader() +{ +} + +ISrsRtpPayloader::~ISrsRtpPayloader() +{ +} + +ISrsRtpPacketDecodeHandler::ISrsRtpPacketDecodeHandler() +{ +} + +ISrsRtpPacketDecodeHandler::~ISrsRtpPacketDecodeHandler() +{ +} + +SrsRtpPacket2::SrsRtpPacket2() +{ + payload = NULL; + decode_handler = NULL; + + nalu_type = SrsAvcNaluTypeReserved; + shared_msg = NULL; + frame_type = SrsFrameTypeReserved; + cached_payload_size = 0; +} + +SrsRtpPacket2::~SrsRtpPacket2() +{ + srs_freep(payload); + srs_freep(shared_msg); +} + +void SrsRtpPacket2::set_padding(int size) +{ + header.set_padding(size); + if (cached_payload_size) { + cached_payload_size += size - header.get_padding(); + } +} + +void SrsRtpPacket2::add_padding(int size) +{ + header.set_padding(header.get_padding() + size); + if (cached_payload_size) { + cached_payload_size += size; + } +} + +void SrsRtpPacket2::set_decode_handler(ISrsRtpPacketDecodeHandler* h) +{ + decode_handler = h; +} + +bool SrsRtpPacket2::is_audio() +{ + return frame_type == SrsFrameTypeAudio; +} + +SrsRtpPacket2* SrsRtpPacket2::copy() +{ + SrsRtpPacket2* cp = new SrsRtpPacket2(); + + cp->header = header; + cp->payload = payload? payload->copy():NULL; + + cp->nalu_type = nalu_type; + cp->shared_msg = shared_msg? shared_msg->copy():NULL; + cp->frame_type = frame_type; + + cp->cached_payload_size = cached_payload_size; + cp->decode_handler = decode_handler; + + return cp; +} + +void SrsRtpPacket2::set_extension_types(const SrsRtpExtensionTypes* v) +{ + return header.set_extensions(v); +} + +uint64_t SrsRtpPacket2::nb_bytes() +{ + if (!cached_payload_size) { + int nn_payload = (payload? payload->nb_bytes():0); + cached_payload_size = header.nb_bytes() + nn_payload + header.get_padding(); + } + return cached_payload_size; +} + +srs_error_t SrsRtpPacket2::encode(SrsBuffer* buf) +{ + srs_error_t err = srs_success; + + if ((err = header.encode(buf)) != srs_success) { + return srs_error_wrap(err, "rtp header"); + } + + if (payload && (err = payload->encode(buf)) != srs_success) { + return srs_error_wrap(err, "rtp payload"); + } + + if (header.get_padding() > 0) { + uint8_t padding = header.get_padding(); + if (!buf->require(padding)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", padding); + } + memset(buf->data() + buf->pos(), padding, padding); + buf->skip(padding); + } + + return err; +} + +srs_error_t SrsRtpPacket2::decode(SrsBuffer* buf) +{ + srs_error_t err = srs_success; + + if ((err = header.decode(buf)) != srs_success) { + return srs_error_wrap(err, "rtp header"); + } + + // We must skip the padding bytes before parsing payload. + uint8_t padding = header.get_padding(); + if (!buf->require(padding)) { + return srs_error_wrap(err, "requires padding %d bytes", padding); + } + buf->set_size(buf->size() - padding); + + // Try to parse the NALU type for video decoder. + if (!buf->empty()) { + nalu_type = SrsAvcNaluType((uint8_t)(buf->head()[0] & kNalTypeMask)); + } + + // If user set the decode handler, call it to set the payload. + if (decode_handler) { + decode_handler->on_before_decode_payload(this, buf, &payload); + } + + // By default, we always use the RAW payload. + if (!payload) { + payload = new SrsRtpRawPayload(); + } + + if ((err = payload->decode(buf)) != srs_success) { + return srs_error_wrap(err, "rtp payload"); + } + + return err; +} + +SrsRtpRawPayload::SrsRtpRawPayload() +{ + payload = NULL; + nn_payload = 0; +} + +SrsRtpRawPayload::~SrsRtpRawPayload() +{ +} + +uint64_t SrsRtpRawPayload::nb_bytes() +{ + return nn_payload; +} + +srs_error_t SrsRtpRawPayload::encode(SrsBuffer* buf) +{ + if (nn_payload <= 0) { + return srs_success; + } + + if (!buf->require(nn_payload)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", nn_payload); + } + + buf->write_bytes(payload, nn_payload); + + return srs_success; +} + +srs_error_t SrsRtpRawPayload::decode(SrsBuffer* buf) +{ + if (buf->empty()) { + return srs_success; + } + + payload = buf->head(); + nn_payload = buf->left(); + + return srs_success; +} + +ISrsRtpPayloader* SrsRtpRawPayload::copy() +{ + SrsRtpRawPayload* cp = new SrsRtpRawPayload(); + + cp->payload = payload; + cp->nn_payload = nn_payload; + + return cp; +} + +SrsRtpRawNALUs::SrsRtpRawNALUs() +{ + cursor = 0; + nn_bytes = 0; +} + +SrsRtpRawNALUs::~SrsRtpRawNALUs() +{ + int nn_nalus = (int)nalus.size(); + for (int i = 0; i < nn_nalus; i++) { + SrsSample* p = nalus[i]; + srs_freep(p); + } +} + +void SrsRtpRawNALUs::push_back(SrsSample* sample) +{ + if (sample->size <= 0) { + return; + } + + if (!nalus.empty()) { + SrsSample* p = new SrsSample(); + p->bytes = (char*)"\0\0\1"; + p->size = 3; + nn_bytes += 3; + nalus.push_back(p); + } + + nn_bytes += sample->size; + nalus.push_back(sample); +} + +uint8_t SrsRtpRawNALUs::skip_first_byte() +{ + srs_assert (cursor >= 0 && nn_bytes > 0 && cursor < nn_bytes); + cursor++; + return uint8_t(nalus[0]->bytes[0]); +} + +srs_error_t SrsRtpRawNALUs::read_samples(vector& samples, int packet_size) +{ + if (cursor + packet_size < 0 || cursor + packet_size > nn_bytes) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "cursor=%d, max=%d, size=%d", cursor, nn_bytes, packet_size); + } + + int pos = cursor; + cursor += packet_size; + int left = packet_size; + + int nn_nalus = (int)nalus.size(); + for (int i = 0; left > 0 && i < nn_nalus; i++) { + SrsSample* p = nalus[i]; + + // Ignore previous consumed samples. + if (pos && pos - p->size >= 0) { + pos -= p->size; + continue; + } + + // Now, we are working at the sample. + int nn = srs_min(left, p->size - pos); + srs_assert(nn > 0); + + SrsSample* sample = new SrsSample(); + samples.push_back(sample); + + sample->bytes = p->bytes + pos; + sample->size = nn; + + left -= nn; + pos = 0; + } + + return srs_success; +} + +uint64_t SrsRtpRawNALUs::nb_bytes() +{ + int size = 0; + + int nn_nalus = (int)nalus.size(); + for (int i = 0; i < nn_nalus; i++) { + SrsSample* p = nalus[i]; + size += p->size; + } + + return size; +} + +srs_error_t SrsRtpRawNALUs::encode(SrsBuffer* buf) +{ + int nn_nalus = (int)nalus.size(); + for (int i = 0; i < nn_nalus; i++) { + SrsSample* p = nalus[i]; + + if (!buf->require(p->size)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", p->size); + } + + buf->write_bytes(p->bytes, p->size); + } + + return srs_success; +} + +srs_error_t SrsRtpRawNALUs::decode(SrsBuffer* buf) +{ + if (buf->empty()) { + return srs_success; + } + + SrsSample* sample = new SrsSample(); + sample->bytes = buf->head(); + sample->size = buf->left(); + buf->skip(sample->size); + + nalus.push_back(sample); + + return srs_success; +} + +ISrsRtpPayloader* SrsRtpRawNALUs::copy() +{ + SrsRtpRawNALUs* cp = new SrsRtpRawNALUs(); + + cp->nn_bytes = nn_bytes; + cp->cursor = cursor; + + int nn_nalus = (int)nalus.size(); + for (int i = 0; i < nn_nalus; i++) { + SrsSample* p = nalus[i]; + cp->nalus.push_back(p->copy()); + } + + return cp; +} + +SrsRtpSTAPPayload::SrsRtpSTAPPayload() +{ + nri = (SrsAvcNaluType)0; +} + +SrsRtpSTAPPayload::~SrsRtpSTAPPayload() +{ + int nn_nalus = (int)nalus.size(); + for (int i = 0; i < nn_nalus; i++) { + SrsSample* p = nalus[i]; + srs_freep(p); + } +} + +SrsSample* SrsRtpSTAPPayload::get_sps() +{ + int nn_nalus = (int)nalus.size(); + for (int i = 0; i < nn_nalus; i++) { + SrsSample* p = nalus[i]; + if (!p || !p->size) { + continue; + } + + SrsAvcNaluType nalu_type = (SrsAvcNaluType)(p->bytes[0] & kNalTypeMask); + if (nalu_type == SrsAvcNaluTypeSPS) { + return p; + } + } + + return NULL; +} + +SrsSample* SrsRtpSTAPPayload::get_pps() +{ + int nn_nalus = (int)nalus.size(); + for (int i = 0; i < nn_nalus; i++) { + SrsSample* p = nalus[i]; + if (!p || !p->size) { + continue; + } + + SrsAvcNaluType nalu_type = (SrsAvcNaluType)(p->bytes[0] & kNalTypeMask); + if (nalu_type == SrsAvcNaluTypePPS) { + return p; + } + } + + return NULL; +} + +uint64_t SrsRtpSTAPPayload::nb_bytes() +{ + int size = 1; + + int nn_nalus = (int)nalus.size(); + for (int i = 0; i < nn_nalus; i++) { + SrsSample* p = nalus[i]; + size += 2 + p->size; + } + + return size; +} + +srs_error_t SrsRtpSTAPPayload::encode(SrsBuffer* buf) +{ + if (!buf->require(1)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 1); + } + + // STAP header, RTP payload format for aggregation packets + // @see https://tools.ietf.org/html/rfc6184#section-5.7 + uint8_t v = kStapA; + v |= (nri & (~kNalTypeMask)); + buf->write_1bytes(v); + + // NALUs. + int nn_nalus = (int)nalus.size(); + for (int i = 0; i < nn_nalus; i++) { + SrsSample* p = nalus[i]; + + if (!buf->require(2 + p->size)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 2 + p->size); + } + + buf->write_2bytes(p->size); + buf->write_bytes(p->bytes, p->size); + } + + return srs_success; +} + +srs_error_t SrsRtpSTAPPayload::decode(SrsBuffer* buf) +{ + if (!buf->require(1)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 1); + } + + // STAP header, RTP payload format for aggregation packets + // @see https://tools.ietf.org/html/rfc6184#section-5.7 + uint8_t v = buf->read_1bytes(); + + // forbidden_zero_bit shoul be zero. + // @see https://tools.ietf.org/html/rfc6184#section-5.3 + uint8_t f = (v & 0x80); + if (f == 0x80) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "forbidden_zero_bit should be zero"); + } + + nri = SrsAvcNaluType(v & (~kNalTypeMask)); + + // NALUs. + while (!buf->empty()) { + if (!buf->require(2)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 2); + } + + int size = buf->read_2bytes(); + if (!buf->require(size)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", size); + } + + SrsSample* sample = new SrsSample(); + sample->bytes = buf->head(); + sample->size = size; + buf->skip(size); + + nalus.push_back(sample); + } + + return srs_success; +} + +ISrsRtpPayloader* SrsRtpSTAPPayload::copy() +{ + SrsRtpSTAPPayload* cp = new SrsRtpSTAPPayload(); + + cp->nri = nri; + + int nn_nalus = (int)nalus.size(); + for (int i = 0; i < nn_nalus; i++) { + SrsSample* p = nalus[i]; + cp->nalus.push_back(p->copy()); + } + + return cp; +} + +SrsRtpFUAPayload::SrsRtpFUAPayload() +{ + start = end = false; + nri = nalu_type = (SrsAvcNaluType)0; +} + +SrsRtpFUAPayload::~SrsRtpFUAPayload() +{ + int nn_nalus = (int)nalus.size(); + for (int i = 0; i < nn_nalus; i++) { + SrsSample* p = nalus[i]; + srs_freep(p); + } +} + +uint64_t SrsRtpFUAPayload::nb_bytes() +{ + int size = 2; + + int nn_nalus = (int)nalus.size(); + for (int i = 0; i < nn_nalus; i++) { + SrsSample* p = nalus[i]; + size += p->size; + } + + return size; +} + +srs_error_t SrsRtpFUAPayload::encode(SrsBuffer* buf) +{ + if (!buf->require(2)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 1); + } + + // FU indicator, @see https://tools.ietf.org/html/rfc6184#section-5.8 + uint8_t fu_indicate = kFuA; + fu_indicate |= (nri & (~kNalTypeMask)); + buf->write_1bytes(fu_indicate); + + // FU header, @see https://tools.ietf.org/html/rfc6184#section-5.8 + uint8_t fu_header = nalu_type; + if (start) { + fu_header |= kStart; + } + if (end) { + fu_header |= kEnd; + } + buf->write_1bytes(fu_header); + + // FU payload, @see https://tools.ietf.org/html/rfc6184#section-5.8 + int nn_nalus = (int)nalus.size(); + for (int i = 0; i < nn_nalus; i++) { + SrsSample* p = nalus[i]; + + if (!buf->require(p->size)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", p->size); + } + + buf->write_bytes(p->bytes, p->size); + } + + return srs_success; +} + +srs_error_t SrsRtpFUAPayload::decode(SrsBuffer* buf) +{ + if (!buf->require(2)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 2); + } + + // FU indicator, @see https://tools.ietf.org/html/rfc6184#section-5.8 + uint8_t v = buf->read_1bytes(); + nri = SrsAvcNaluType(v & (~kNalTypeMask)); + + // FU header, @see https://tools.ietf.org/html/rfc6184#section-5.8 + v = buf->read_1bytes(); + start = v & kStart; + end = v & kEnd; + nalu_type = SrsAvcNaluType(v & kNalTypeMask); + + if (!buf->require(1)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 1); + } + + SrsSample* sample = new SrsSample(); + sample->bytes = buf->head(); + sample->size = buf->left(); + buf->skip(sample->size); + + nalus.push_back(sample); + + return srs_success; +} + +ISrsRtpPayloader* SrsRtpFUAPayload::copy() +{ + SrsRtpFUAPayload* cp = new SrsRtpFUAPayload(); + + cp->nri = nri; + cp->start = start; + cp->end = end; + cp->nalu_type = nalu_type; + + int nn_nalus = (int)nalus.size(); + for (int i = 0; i < nn_nalus; i++) { + SrsSample* p = nalus[i]; + cp->nalus.push_back(p->copy()); + } + + return cp; +} + +SrsRtpFUAPayload2::SrsRtpFUAPayload2() +{ + start = end = false; + nri = nalu_type = (SrsAvcNaluType)0; + + payload = NULL; + size = 0; +} + +SrsRtpFUAPayload2::~SrsRtpFUAPayload2() +{ +} + +uint64_t SrsRtpFUAPayload2::nb_bytes() +{ + return 2 + size; +} + +srs_error_t SrsRtpFUAPayload2::encode(SrsBuffer* buf) +{ + if (!buf->require(2 + size)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 1); + } + + // Fast encoding. + char* p = buf->head(); + + // FU indicator, @see https://tools.ietf.org/html/rfc6184#section-5.8 + uint8_t fu_indicate = kFuA; + fu_indicate |= (nri & (~kNalTypeMask)); + *p++ = fu_indicate; + + // FU header, @see https://tools.ietf.org/html/rfc6184#section-5.8 + uint8_t fu_header = nalu_type; + if (start) { + fu_header |= kStart; + } + if (end) { + fu_header |= kEnd; + } + *p++ = fu_header; + + // FU payload, @see https://tools.ietf.org/html/rfc6184#section-5.8 + memcpy(p, payload, size); + + // Consume bytes. + buf->skip(2 + size); + + return srs_success; +} + +srs_error_t SrsRtpFUAPayload2::decode(SrsBuffer* buf) +{ + if (!buf->require(2)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 2); + } + + // FU indicator, @see https://tools.ietf.org/html/rfc6184#section-5.8 + uint8_t v = buf->read_1bytes(); + nri = SrsAvcNaluType(v & (~kNalTypeMask)); + + // FU header, @see https://tools.ietf.org/html/rfc6184#section-5.8 + v = buf->read_1bytes(); + start = v & kStart; + end = v & kEnd; + nalu_type = SrsAvcNaluType(v & kNalTypeMask); + + if (!buf->require(1)) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 1); + } + + payload = buf->head(); + size = buf->left(); + buf->skip(size); + + return srs_success; +} + +ISrsRtpPayloader* SrsRtpFUAPayload2::copy() +{ + SrsRtpFUAPayload2* cp = new SrsRtpFUAPayload2(); + + cp->nri = nri; + cp->start = start; + cp->end = end; + cp->nalu_type = nalu_type; + cp->payload = payload; + cp->size = size; + + return cp; +} diff --git a/trunk/src/kernel/srs_kernel_rtc_rtp.hpp b/trunk/src/kernel/srs_kernel_rtc_rtp.hpp new file mode 100644 index 000000000..76a39fbc5 --- /dev/null +++ b/trunk/src/kernel/srs_kernel_rtc_rtp.hpp @@ -0,0 +1,387 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2013-2020 Winlin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef SRS_KERNEL_RTC_RTP_HPP +#define SRS_KERNEL_RTC_RTP_HPP + +#include + +#include +#include + +#include + +class SrsRtpPacket2; + +// The RTP packet max size, should never exceed this size. +const int kRtpPacketSize = 1500; + +const int kRtpHeaderFixedSize = 12; +const uint8_t kRtpMarker = 0x80; + +// H.264 nalu header type mask. +const uint8_t kNalTypeMask = 0x1F; + +// @see: https://tools.ietf.org/html/rfc6184#section-5.2 +const uint8_t kStapA = 24; + +// @see: https://tools.ietf.org/html/rfc6184#section-5.2 +const uint8_t kFuA = 28; + +// @see: https://tools.ietf.org/html/rfc6184#section-5.8 +const uint8_t kStart = 0x80; // Fu-header start bit +const uint8_t kEnd = 0x40; // Fu-header end bit + + +class SrsBuffer; +class SrsRtpRawPayload; +class SrsRtpFUAPayload2; +class SrsSharedPtrMessage; + +// The "distance" between two uint16 number, for example: +// distance(prev_value=3, value=5) === (int16_t)(uint16_t)((uint16_t)3-(uint16_t)5) === -2 +// distance(prev_value=3, value=65534) === (int16_t)(uint16_t)((uint16_t)3-(uint16_t)65534) === 5 +// distance(prev_value=65532, value=65534) === (int16_t)(uint16_t)((uint16_t)65532-(uint16_t)65534) === -2 +// For RTP sequence, it's only uint16 and may flip back, so 3 maybe 3+0xffff. +// @remark Note that srs_rtp_seq_distance(0, 32768)>0 is TRUE by https://mp.weixin.qq.com/s/JZTInmlB9FUWXBQw_7NYqg +// but for WebRTC jitter buffer it's FALSE and we follow it. +// @remark For srs_rtp_seq_distance(32768, 0)>0, it's FALSE definitely. +inline int16_t srs_rtp_seq_distance(const uint16_t& prev_value, const uint16_t& value) +{ + return (int16_t)(value - prev_value); +} + +// For map to compare the sequence of RTP. +struct SrsSeqCompareLess { + bool operator()(const uint16_t& pre_value, const uint16_t& value) const { + return srs_rtp_seq_distance(pre_value, value) > 0; + } +}; + +bool srs_seq_is_newer(uint16_t value, uint16_t pre_value); +bool srs_seq_is_rollback(uint16_t value, uint16_t pre_value); +int32_t srs_seq_distance(uint16_t value, uint16_t pre_value); + +enum SrsRtpExtensionType +{ + kRtpExtensionNone, + kRtpExtensionTransportSequenceNumber, + kRtpExtensionNumberOfExtensions // Must be the last entity in the enum. +}; + +struct SrsExtensionInfo +{ + SrsRtpExtensionType type; + std::string uri; +}; + +const SrsExtensionInfo kExtensions[] = { + {kRtpExtensionTransportSequenceNumber, std::string("http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01")} +}; + +class SrsRtpExtensionTypes +{ +public: + static const SrsRtpExtensionType kInvalidType = kRtpExtensionNone; + static const int kInvalidId = 0; +public: + bool register_by_uri(int id, std::string uri); + SrsRtpExtensionType get_type(int id) const; +public: + SrsRtpExtensionTypes(); + virtual ~SrsRtpExtensionTypes(); +private: + bool register_id(int id, SrsRtpExtensionType type, std::string uri); +private: + uint8_t ids_[kRtpExtensionNumberOfExtensions]; +}; + +class SrsRtpExtensionTwcc : public ISrsCodec +{ + bool has_twcc_; + uint8_t id_; + uint16_t sn_; +public: + SrsRtpExtensionTwcc(); + virtual ~SrsRtpExtensionTwcc(); + + bool has_twcc_ext(); + uint8_t get_id(); + void set_id(uint8_t id); + uint16_t get_sn(); + void set_sn(uint16_t sn); + +public: + // ISrsCodec + virtual srs_error_t decode(SrsBuffer* buf); + virtual srs_error_t encode(SrsBuffer* buf); + virtual uint64_t nb_bytes(); +}; + +class SrsRtpExtensions : public ISrsCodec +{ +private: + bool has_ext_; + SrsRtpExtensionTypes types_; + SrsRtpExtensionTwcc twcc_; +public: + SrsRtpExtensions(); + virtual ~SrsRtpExtensions(); + + bool exists(); + void set_types_(const SrsRtpExtensionTypes* types); + srs_error_t get_twcc_sequence_number(uint16_t& twcc_sn); + srs_error_t set_twcc_sequence_number(uint8_t id, uint16_t sn); + +// ISrsCodec +public: + virtual srs_error_t decode(SrsBuffer* buf); +private: + srs_error_t decode_0xbede(SrsBuffer* buf); +public: + virtual srs_error_t encode(SrsBuffer* buf); + virtual uint64_t nb_bytes(); +}; + +class SrsRtpHeader : public ISrsCodec +{ +private: + uint8_t padding_length; + uint8_t cc; + bool marker; + uint8_t payload_type; + uint16_t sequence; + uint32_t timestamp; + uint32_t ssrc; + uint32_t csrc[15]; + SrsRtpExtensions extensions_; + bool ignore_padding_; +public: + SrsRtpHeader(); + virtual ~SrsRtpHeader(); +public: + virtual srs_error_t decode(SrsBuffer* buf); +private: + srs_error_t parse_extensions(SrsBuffer* buf); +public: + virtual srs_error_t encode(SrsBuffer* buf); + virtual uint64_t nb_bytes(); +public: + void set_marker(bool v); + bool get_marker() const; + void set_payload_type(uint8_t v); + uint8_t get_payload_type() const; + void set_sequence(uint16_t v); + uint16_t get_sequence() const; + void set_timestamp(uint32_t v); + uint32_t get_timestamp() const; + void set_ssrc(uint32_t v); + uint32_t get_ssrc() const; + void set_padding(uint8_t v); + uint8_t get_padding() const; + void set_extensions(const SrsRtpExtensionTypes* extmap); + void ignore_padding(bool v); + srs_error_t get_twcc_sequence_number(uint16_t& twcc_sn); + srs_error_t set_twcc_sequence_number(uint8_t id, uint16_t sn); +}; + +class ISrsRtpPayloader : public ISrsCodec +{ +public: + ISrsRtpPayloader(); + virtual ~ISrsRtpPayloader(); +public: + virtual ISrsRtpPayloader* copy() = 0; +}; + +class ISrsRtpPacketDecodeHandler +{ +public: + ISrsRtpPacketDecodeHandler(); + virtual ~ISrsRtpPacketDecodeHandler(); +public: + // We don't know the actual payload, so we depends on external handler. + virtual void on_before_decode_payload(SrsRtpPacket2* pkt, SrsBuffer* buf, ISrsRtpPayloader** ppayload) = 0; +}; + +class SrsRtpPacket2 +{ +// RTP packet fields. +public: + SrsRtpHeader header; + ISrsRtpPayloader* payload; +// Helper fields. +public: + // The first byte as nalu type, for video decoder only. + SrsAvcNaluType nalu_type; + // The original shared message, all RTP packets can refer to its data. + SrsSharedPtrMessage* shared_msg; + // The frame type, for RTMP bridger or SFU source. + SrsFrameType frame_type; +// Fast cache for performance. +private: + // The cached payload size for packet. + int cached_payload_size; + // The helper handler for decoder, use RAW payload if NULL. + ISrsRtpPacketDecodeHandler* decode_handler; +public: + SrsRtpPacket2(); + virtual ~SrsRtpPacket2(); +public: + // Set the padding of RTP packet. + void set_padding(int size); + // Increase the padding of RTP packet. + void add_padding(int size); + // Set the decode handler. + void set_decode_handler(ISrsRtpPacketDecodeHandler* h); + // Whether the packet is Audio packet. + bool is_audio(); + // Copy the RTP packet. + SrsRtpPacket2* copy(); + // Set RTP header extensions for encoding or decoding header extension + void set_extension_types(const SrsRtpExtensionTypes* v); +// interface ISrsEncoder +public: + virtual uint64_t nb_bytes(); + virtual srs_error_t encode(SrsBuffer* buf); + virtual srs_error_t decode(SrsBuffer* buf); +}; + +// Single payload data. +class SrsRtpRawPayload : public ISrsRtpPayloader +{ +public: + // The RAW payload, directly point to the shared memory. + // @remark We only refer to the memory, user must free its bytes. + char* payload; + int nn_payload; +public: + SrsRtpRawPayload(); + virtual ~SrsRtpRawPayload(); +// interface ISrsRtpPayloader +public: + virtual uint64_t nb_bytes(); + virtual srs_error_t encode(SrsBuffer* buf); + virtual srs_error_t decode(SrsBuffer* buf); + virtual ISrsRtpPayloader* copy(); +}; + +// Multiple NALUs, automatically insert 001 between NALUs. +class SrsRtpRawNALUs : public ISrsRtpPayloader +{ +private: + // We will manage the samples, but the sample itself point to the shared memory. + std::vector nalus; + int nn_bytes; + int cursor; +public: + SrsRtpRawNALUs(); + virtual ~SrsRtpRawNALUs(); +public: + void push_back(SrsSample* sample); +public: + uint8_t skip_first_byte(); + // We will manage the returned samples, if user want to manage it, please copy it. + srs_error_t read_samples(std::vector& samples, int packet_size); +// interface ISrsRtpPayloader +public: + virtual uint64_t nb_bytes(); + virtual srs_error_t encode(SrsBuffer* buf); + virtual srs_error_t decode(SrsBuffer* buf); + virtual ISrsRtpPayloader* copy(); +}; + +// STAP-A, for multiple NALUs. +class SrsRtpSTAPPayload : public ISrsRtpPayloader +{ +public: + // The NRI in NALU type. + SrsAvcNaluType nri; + // The NALU samples, we will manage the samples. + // @remark We only refer to the memory, user must free its bytes. + std::vector nalus; +public: + SrsRtpSTAPPayload(); + virtual ~SrsRtpSTAPPayload(); +public: + SrsSample* get_sps(); + SrsSample* get_pps(); +// interface ISrsRtpPayloader +public: + virtual uint64_t nb_bytes(); + virtual srs_error_t encode(SrsBuffer* buf); + virtual srs_error_t decode(SrsBuffer* buf); + virtual ISrsRtpPayloader* copy(); +}; + +// FU-A, for one NALU with multiple fragments. +// With more than one payload. +class SrsRtpFUAPayload : public ISrsRtpPayloader +{ +public: + // The NRI in NALU type. + SrsAvcNaluType nri; + // The FUA header. + bool start; + bool end; + SrsAvcNaluType nalu_type; + // The NALU samples, we manage the samples. + // @remark We only refer to the memory, user must free its bytes. + std::vector nalus; +public: + SrsRtpFUAPayload(); + virtual ~SrsRtpFUAPayload(); +// interface ISrsRtpPayloader +public: + virtual uint64_t nb_bytes(); + virtual srs_error_t encode(SrsBuffer* buf); + virtual srs_error_t decode(SrsBuffer* buf); + virtual ISrsRtpPayloader* copy(); +}; + +// FU-A, for one NALU with multiple fragments. +// With only one payload. +class SrsRtpFUAPayload2 : public ISrsRtpPayloader +{ +public: + // The NRI in NALU type. + SrsAvcNaluType nri; + // The FUA header. + bool start; + bool end; + SrsAvcNaluType nalu_type; + // The payload and size, + char* payload; + int size; +public: + SrsRtpFUAPayload2(); + virtual ~SrsRtpFUAPayload2(); +// interface ISrsRtpPayloader +public: + virtual uint64_t nb_bytes(); + virtual srs_error_t encode(SrsBuffer* buf); + virtual srs_error_t decode(SrsBuffer* buf); + virtual ISrsRtpPayloader* copy(); +}; + +#endif diff --git a/trunk/src/kernel/srs_kernel_rtp.cpp b/trunk/src/kernel/srs_kernel_rtp.cpp deleted file mode 100644 index 5c23cbd34..000000000 --- a/trunk/src/kernel/srs_kernel_rtp.cpp +++ /dev/null @@ -1,682 +0,0 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2013-2020 Winlin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#include - -#include -#include -using namespace std; - -#include -#include -#include -#include - -// @see: https://tools.ietf.org/html/rfc6184#section-5.2 -const uint8_t kStapA = 24; - -// @see: https://tools.ietf.org/html/rfc6184#section-5.2 -const uint8_t kFuA = 28; - -// @see: https://tools.ietf.org/html/rfc6184#section-5.8 -const uint8_t kStart = 0x80; // Fu-header start bit -const uint8_t kEnd = 0x40; // Fu-header end bit - -SrsRtpHeader::SrsRtpHeader() -{ - padding = false; - extension = false; - cc = 0; - marker = false; - payload_type = 0; - sequence = 0; - timestamp = 0; - ssrc = 0; - extension_length = 0; -} - -void SrsRtpHeader::reset() -{ - // We only reset the optional fields, the required field such as ssrc - // will always be set by user. - padding = false; - extension = false; - cc = 0; - marker = false; - extension_length = 0; -} - -SrsRtpHeader::~SrsRtpHeader() -{ -} - -srs_error_t SrsRtpHeader::decode(SrsBuffer* stream) -{ - srs_error_t err = srs_success; - - // TODO: FIXME: Implements it. - - return err; -} - -srs_error_t SrsRtpHeader::encode(SrsBuffer* stream) -{ - srs_error_t err = srs_success; - - // Encode the RTP fix header, 12bytes. - // @see https://tools.ietf.org/html/rfc1889#section-5.1 - char* op = stream->head(); - char* p = op; - - // The version, padding, extension and cc, total 1 byte. - uint8_t v = 0x80 | cc; - if (padding) { - v |= 0x20; - } - if (extension) { - v |= 0x10; - } - *p++ = v; - - // The marker and payload type, total 1 byte. - v = payload_type; - if (marker) { - v |= kRtpMarker; - } - *p++ = v; - - // The sequence number, 2 bytes. - char* pp = (char*)&sequence; - *p++ = pp[1]; - *p++ = pp[0]; - - // The timestamp, 4 bytes. - pp = (char*)×tamp; - *p++ = pp[3]; - *p++ = pp[2]; - *p++ = pp[1]; - *p++ = pp[0]; - - // The SSRC, 4 bytes. - pp = (char*)&ssrc; - *p++ = pp[3]; - *p++ = pp[2]; - *p++ = pp[1]; - *p++ = pp[0]; - - // The CSRC list: 0 to 15 items, each is 4 bytes. - for (size_t i = 0; i < cc; ++i) { - pp = (char*)&csrc[i]; - *p++ = pp[3]; - *p++ = pp[2]; - *p++ = pp[1]; - *p++ = pp[0]; - } - - // TODO: Write exteinsion field. - if (extension) { - } - - // Consume the data. - stream->skip(p - op); - - return err; -} - -size_t SrsRtpHeader::header_size() -{ - return kRtpHeaderFixedSize + cc * 4 + (extension ? (extension_length + 1) * 4 : 0); -} - -SrsRtpPacket2::SrsRtpPacket2() -{ - payload = NULL; - padding = 0; - - cache_raw = new SrsRtpRawPayload(); - cache_fua = new SrsRtpFUAPayload2(); - cache_payload = 0; -} - -SrsRtpPacket2::~SrsRtpPacket2() -{ - // We may use the cache as payload. - if (payload == cache_raw || payload == cache_fua) { - payload = NULL; - } - - srs_freep(payload); - srs_freep(cache_raw); - srs_freep(cache_fua); -} - -void SrsRtpPacket2::set_padding(int size) -{ - rtp_header.set_padding(size > 0); - if (cache_payload) { - cache_payload += size - padding; - } - padding = size; -} - -void SrsRtpPacket2::add_padding(int size) -{ - rtp_header.set_padding(padding + size > 0); - if (cache_payload) { - cache_payload += size; - } - padding += size; -} - -void SrsRtpPacket2::reset() -{ - rtp_header.reset(); - padding = 0; - cache_payload = 0; - - // We may use the cache as payload. - if (payload == cache_raw || payload == cache_fua) { - payload = NULL; - } - srs_freep(payload); -} - -SrsRtpRawPayload* SrsRtpPacket2::reuse_raw() -{ - payload = cache_raw; - return cache_raw; -} - -SrsRtpFUAPayload2* SrsRtpPacket2::reuse_fua() -{ - payload = cache_fua; - return cache_fua; -} - -int SrsRtpPacket2::nb_bytes() -{ - if (!cache_payload) { - cache_payload = rtp_header.header_size() + (payload? payload->nb_bytes():0) + padding; - } - return cache_payload; -} - -srs_error_t SrsRtpPacket2::encode(SrsBuffer* buf) -{ - srs_error_t err = srs_success; - - if ((err = rtp_header.encode(buf)) != srs_success) { - return srs_error_wrap(err, "rtp header"); - } - - if (payload && (err = payload->encode(buf)) != srs_success) { - return srs_error_wrap(err, "encode payload"); - } - - if (padding > 0) { - if (!buf->require(padding)) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", padding); - } - memset(buf->data() + buf->pos(), padding, padding); - buf->skip(padding); - } - - return err; -} - -SrsRtpRawPayload::SrsRtpRawPayload() -{ - payload = NULL; - nn_payload = 0; -} - -SrsRtpRawPayload::~SrsRtpRawPayload() -{ -} - -int SrsRtpRawPayload::nb_bytes() -{ - return nn_payload; -} - -srs_error_t SrsRtpRawPayload::encode(SrsBuffer* buf) -{ - if (nn_payload <= 0) { - return srs_success; - } - - if (!buf->require(nn_payload)) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", nn_payload); - } - - buf->write_bytes(payload, nn_payload); - - return srs_success; -} - -SrsRtpRawNALUs::SrsRtpRawNALUs() -{ - cursor = 0; - nn_bytes = 0; -} - -SrsRtpRawNALUs::~SrsRtpRawNALUs() -{ - if (true) { - int nn_nalus = (int)nalus.size(); - for (int i = 0; i < nn_nalus; i++) { - SrsSample* p = nalus[i]; - srs_freep(p); - } - } -} - -void SrsRtpRawNALUs::push_back(SrsSample* sample) -{ - if (sample->size <= 0) { - return; - } - - if (!nalus.empty()) { - SrsSample* p = new SrsSample(); - p->bytes = (char*)"\0\0\1"; - p->size = 3; - nn_bytes += 3; - nalus.push_back(p); - } - - nn_bytes += sample->size; - nalus.push_back(sample); -} - -uint8_t SrsRtpRawNALUs::skip_first_byte() -{ - srs_assert (cursor >= 0 && nn_bytes > 0 && cursor < nn_bytes); - cursor++; - return uint8_t(nalus[0]->bytes[0]); -} - -srs_error_t SrsRtpRawNALUs::read_samples(vector& samples, int packet_size) -{ - if (cursor + packet_size < 0 || cursor + packet_size > nn_bytes) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "cursor=%d, max=%d, size=%d", cursor, nn_bytes, packet_size); - } - - int pos = cursor; - cursor += packet_size; - int left = packet_size; - - int nn_nalus = (int)nalus.size(); - for (int i = 0; left > 0 && i < nn_nalus; i++) { - SrsSample* p = nalus[i]; - - // Ignore previous consumed samples. - if (pos && pos - p->size >= 0) { - pos -= p->size; - continue; - } - - // Now, we are working at the sample. - int nn = srs_min(left, p->size - pos); - srs_assert(nn > 0); - - SrsSample* sample = new SrsSample(); - samples.push_back(sample); - - sample->bytes = p->bytes + pos; - sample->size = nn; - - left -= nn; - pos = 0; - } - - return srs_success; -} - -int SrsRtpRawNALUs::nb_bytes() -{ - int size = 0; - - int nn_nalus = (int)nalus.size(); - for (int i = 0; i < nn_nalus; i++) { - SrsSample* p = nalus[i]; - size += p->size; - } - - return size; -} - -srs_error_t SrsRtpRawNALUs::encode(SrsBuffer* buf) -{ - int nn_nalus = (int)nalus.size(); - for (int i = 0; i < nn_nalus; i++) { - SrsSample* p = nalus[i]; - - if (!buf->require(p->size)) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", p->size); - } - - buf->write_bytes(p->bytes, p->size); - } - - return srs_success; -} - -SrsRtpSTAPPayload::SrsRtpSTAPPayload() -{ - nri = (SrsAvcNaluType)0; -} - -SrsRtpSTAPPayload::~SrsRtpSTAPPayload() -{ - int nn_nalus = (int)nalus.size(); - for (int i = 0; i < nn_nalus; i++) { - SrsSample* p = nalus[i]; - srs_freep(p); - } -} - -int SrsRtpSTAPPayload::nb_bytes() -{ - int size = 1; - - int nn_nalus = (int)nalus.size(); - for (int i = 0; i < nn_nalus; i++) { - SrsSample* p = nalus[i]; - size += 2 + p->size; - } - - return size; -} - -srs_error_t SrsRtpSTAPPayload::encode(SrsBuffer* buf) -{ - if (!buf->require(1)) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 1); - } - - // STAP header, RTP payload format for aggregation packets - // @see https://tools.ietf.org/html/rfc6184#section-5.7 - uint8_t v = kStapA; - v |= (nri & (~kNalTypeMask)); - buf->write_1bytes(v); - - // NALUs. - int nn_nalus = (int)nalus.size(); - for (int i = 0; i < nn_nalus; i++) { - SrsSample* p = nalus[i]; - - if (!buf->require(2 + p->size)) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 2 + p->size); - } - - buf->write_2bytes(p->size); - buf->write_bytes(p->bytes, p->size); - } - - return srs_success; -} - -SrsRtpFUAPayload::SrsRtpFUAPayload() -{ - start = end = false; - nri = nalu_type = (SrsAvcNaluType)0; -} - -SrsRtpFUAPayload::~SrsRtpFUAPayload() -{ - int nn_nalus = (int)nalus.size(); - for (int i = 0; i < nn_nalus; i++) { - SrsSample* p = nalus[i]; - srs_freep(p); - } -} - -int SrsRtpFUAPayload::nb_bytes() -{ - int size = 2; - - int nn_nalus = (int)nalus.size(); - for (int i = 0; i < nn_nalus; i++) { - SrsSample* p = nalus[i]; - size += p->size; - } - - return size; -} - -srs_error_t SrsRtpFUAPayload::encode(SrsBuffer* buf) -{ - if (!buf->require(2)) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 1); - } - - // FU indicator, @see https://tools.ietf.org/html/rfc6184#section-5.8 - uint8_t fu_indicate = kFuA; - fu_indicate |= (nri & (~kNalTypeMask)); - buf->write_1bytes(fu_indicate); - - // FU header, @see https://tools.ietf.org/html/rfc6184#section-5.8 - uint8_t fu_header = nalu_type; - if (start) { - fu_header |= kStart; - } - if (end) { - fu_header |= kEnd; - } - buf->write_1bytes(fu_header); - - // FU payload, @see https://tools.ietf.org/html/rfc6184#section-5.8 - int nn_nalus = (int)nalus.size(); - for (int i = 0; i < nn_nalus; i++) { - SrsSample* p = nalus[i]; - - if (!buf->require(p->size)) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", p->size); - } - - buf->write_bytes(p->bytes, p->size); - } - - return srs_success; -} - -SrsRtpFUAPayload2::SrsRtpFUAPayload2() -{ - start = end = false; - nri = nalu_type = (SrsAvcNaluType)0; - - payload = NULL; - size = 0; -} - -SrsRtpFUAPayload2::~SrsRtpFUAPayload2() -{ -} - -int SrsRtpFUAPayload2::nb_bytes() -{ - return 2 + size; -} - -srs_error_t SrsRtpFUAPayload2::encode(SrsBuffer* buf) -{ - if (!buf->require(2 + size)) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "requires %d bytes", 1); - } - - // Fast encoding. - char* p = buf->head(); - - // FU indicator, @see https://tools.ietf.org/html/rfc6184#section-5.8 - uint8_t fu_indicate = kFuA; - fu_indicate |= (nri & (~kNalTypeMask)); - *p++ = fu_indicate; - - // FU header, @see https://tools.ietf.org/html/rfc6184#section-5.8 - uint8_t fu_header = nalu_type; - if (start) { - fu_header |= kStart; - } - if (end) { - fu_header |= kEnd; - } - *p++ = fu_header; - - // FU payload, @see https://tools.ietf.org/html/rfc6184#section-5.8 - memcpy(p, payload, size); - - // Consume bytes. - buf->skip(2 + size); - - return srs_success; -} - -SrsRtpSharedPacket::SrsRtpSharedPacketPayload::SrsRtpSharedPacketPayload() -{ - payload = NULL; - size = 0; - shared_count = 0; -} - -SrsRtpSharedPacket::SrsRtpSharedPacketPayload::~SrsRtpSharedPacketPayload() -{ - srs_freepa(payload); -} - -SrsRtpSharedPacket::SrsRtpSharedPacket() -{ - payload_ptr = NULL; - - payload = NULL; - size = 0; -} - -SrsRtpSharedPacket::~SrsRtpSharedPacket() -{ - if (payload_ptr) { - if (payload_ptr->shared_count == 0) { - srs_freep(payload_ptr); - } else { - --payload_ptr->shared_count; - } - } -} - -srs_error_t SrsRtpSharedPacket::create(int64_t timestamp, uint16_t sequence, uint32_t ssrc, uint16_t payload_type, char* p, int s) -{ - srs_error_t err = srs_success; - - if (s < 0) { - return srs_error_new(ERROR_RTP_PACKET_CREATE, "create packet size=%d", s); - } - - srs_assert(!payload_ptr); - - rtp_header.set_timestamp(timestamp); - rtp_header.set_sequence(sequence); - rtp_header.set_ssrc(ssrc); - rtp_header.set_payload_type(payload_type); - - // TODO: rtp header padding. - size_t buffer_size = rtp_header.header_size() + s; - - char* buffer = new char[buffer_size]; - SrsBuffer stream(buffer, buffer_size); - if ((err = rtp_header.encode(&stream)) != srs_success) { - srs_freepa(buffer); - return srs_error_wrap(err, "rtp header encode"); - } - - stream.write_bytes(p, s); - payload_ptr = new SrsRtpSharedPacketPayload(); - payload_ptr->payload = buffer; - payload_ptr->size = buffer_size; - - this->payload = payload_ptr->payload; - this->size = payload_ptr->size; - - return err; -} - -SrsRtpSharedPacket* SrsRtpSharedPacket::copy() -{ - SrsRtpSharedPacket* copy = new SrsRtpSharedPacket(); - - copy->payload_ptr = payload_ptr; - payload_ptr->shared_count++; - - copy->rtp_header = rtp_header; - - copy->payload = payload; - copy->size = size; - - return copy; -} - -srs_error_t SrsRtpSharedPacket::modify_rtp_header_marker(bool marker) -{ - srs_error_t err = srs_success; - if (payload_ptr == NULL || payload_ptr->payload == NULL || payload_ptr->size < kRtpHeaderFixedSize) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "rtp payload incorrect"); - } - - rtp_header.set_marker(marker); - if (marker) { - payload_ptr->payload[1] |= kRtpMarker; - } else { - payload_ptr->payload[1] &= (~kRtpMarker); - } - - return err; -} - -srs_error_t SrsRtpSharedPacket::modify_rtp_header_ssrc(uint32_t ssrc) -{ - srs_error_t err = srs_success; - - if (payload_ptr == NULL || payload_ptr->payload == NULL || payload_ptr->size < kRtpHeaderFixedSize) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "rtp payload incorrect"); - } - - rtp_header.set_ssrc(ssrc); - - SrsBuffer stream(payload_ptr->payload + 8, 4); - stream.write_4bytes(ssrc); - - return err; -} - -srs_error_t SrsRtpSharedPacket::modify_rtp_header_payload_type(uint8_t payload_type) -{ - srs_error_t err = srs_success; - - if (payload_ptr == NULL || payload_ptr->payload == NULL || payload_ptr->size < kRtpHeaderFixedSize) { - return srs_error_new(ERROR_RTC_RTP_MUXER, "rtp payload incorrect"); - } - - rtp_header.set_payload_type(payload_type); - payload_ptr->payload[1] = (payload_ptr->payload[1] & 0x80) | payload_type; - - return err; -} diff --git a/trunk/src/kernel/srs_kernel_rtp.hpp b/trunk/src/kernel/srs_kernel_rtp.hpp deleted file mode 100644 index 349c388a5..000000000 --- a/trunk/src/kernel/srs_kernel_rtp.hpp +++ /dev/null @@ -1,248 +0,0 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2013-2020 Winlin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#ifndef SRS_KERNEL_RTP_HPP -#define SRS_KERNEL_RTP_HPP - -#include - -#include -#include - -#include - -const int kRtpHeaderFixedSize = 12; -const uint8_t kRtpMarker = 0x80; - -// H.264 nalu header type mask. -const uint8_t kNalTypeMask = 0x1F; - -class SrsBuffer; -class SrsRtpRawPayload; -class SrsRtpFUAPayload2; - -class SrsRtpHeader -{ -private: - bool padding; - bool extension; - uint8_t cc; - bool marker; - uint8_t payload_type; - uint16_t sequence; - int32_t timestamp; - uint32_t ssrc; - uint32_t csrc[15]; - uint16_t extension_length; - // TODO:extension field. -public: - SrsRtpHeader(); - virtual ~SrsRtpHeader(); - void reset(); -public: - srs_error_t decode(SrsBuffer* stream); - srs_error_t encode(SrsBuffer* stream); -public: - size_t header_size(); -public: - inline void set_marker(bool v) { marker = v; } - bool get_marker() const { return marker; } - inline void set_payload_type(uint8_t v) { payload_type = v; } - uint8_t get_payload_type() const { return payload_type; } - inline void set_sequence(uint16_t v) { sequence = v; } - uint16_t get_sequence() const { return sequence; } - inline void set_timestamp(int64_t v) { timestamp = (uint32_t)v; } - int64_t get_timestamp() const { return timestamp; } - inline void set_ssrc(uint32_t v) { ssrc = v; } - uint32_t get_ssrc() const { return ssrc; } - inline void set_padding(bool v) { padding = v; } -}; - -class SrsRtpPacket2 -{ -public: - SrsRtpHeader rtp_header; - ISrsEncoder* payload; - int padding; -private: - SrsRtpRawPayload* cache_raw; - SrsRtpFUAPayload2* cache_fua; - int cache_payload; -public: - SrsRtpPacket2(); - virtual ~SrsRtpPacket2(); -public: - // Set the padding of RTP packet. - void set_padding(int size); - // Increase the padding of RTP packet. - void add_padding(int size); - // Reset RTP packet. - void reset(); - // Reuse the cached raw message as payload. - SrsRtpRawPayload* reuse_raw(); - // Reuse the cached fua message as payload. - SrsRtpFUAPayload2* reuse_fua(); -// interface ISrsEncoder -public: - virtual int nb_bytes(); - virtual srs_error_t encode(SrsBuffer* buf); -}; - -// Single payload data. -class SrsRtpRawPayload : public ISrsEncoder -{ -public: - // The RAW payload, directly point to the shared memory. - // @remark We only refer to the memory, user must free its bytes. - char* payload; - int nn_payload; -public: - SrsRtpRawPayload(); - virtual ~SrsRtpRawPayload(); -// interface ISrsEncoder -public: - virtual int nb_bytes(); - virtual srs_error_t encode(SrsBuffer* buf); -}; - -// Multiple NALUs, automatically insert 001 between NALUs. -class SrsRtpRawNALUs : public ISrsEncoder -{ -private: - // We will manage the samples, but the sample itself point to the shared memory. - std::vector nalus; - int nn_bytes; - int cursor; -public: - SrsRtpRawNALUs(); - virtual ~SrsRtpRawNALUs(); -public: - void push_back(SrsSample* sample); -public: - uint8_t skip_first_byte(); - // We will manage the returned samples, if user want to manage it, please copy it. - srs_error_t read_samples(std::vector& samples, int packet_size); -// interface ISrsEncoder -public: - virtual int nb_bytes(); - virtual srs_error_t encode(SrsBuffer* buf); -}; - -// STAP-A, for multiple NALUs. -class SrsRtpSTAPPayload : public ISrsEncoder -{ -public: - // The NRI in NALU type. - SrsAvcNaluType nri; - // The NALU samples, we will manage the samples. - // @remark We only refer to the memory, user must free its bytes. - std::vector nalus; -public: - SrsRtpSTAPPayload(); - virtual ~SrsRtpSTAPPayload(); -// interface ISrsEncoder -public: - virtual int nb_bytes(); - virtual srs_error_t encode(SrsBuffer* buf); -}; - -// FU-A, for one NALU with multiple fragments. -// With more than one payload. -class SrsRtpFUAPayload : public ISrsEncoder -{ -public: - // The NRI in NALU type. - SrsAvcNaluType nri; - // The FUA header. - bool start; - bool end; - SrsAvcNaluType nalu_type; - // The NALU samples, we manage the samples. - // @remark We only refer to the memory, user must free its bytes. - std::vector nalus; -public: - SrsRtpFUAPayload(); - virtual ~SrsRtpFUAPayload(); -// interface ISrsEncoder -public: - virtual int nb_bytes(); - virtual srs_error_t encode(SrsBuffer* buf); -}; - -// FU-A, for one NALU with multiple fragments. -// With only one payload. -class SrsRtpFUAPayload2 : public ISrsEncoder -{ -public: - // The NRI in NALU type. - SrsAvcNaluType nri; - // The FUA header. - bool start; - bool end; - SrsAvcNaluType nalu_type; - // The payload and size, - char* payload; - int size; -public: - SrsRtpFUAPayload2(); - virtual ~SrsRtpFUAPayload2(); -// interface ISrsEncoder -public: - virtual int nb_bytes(); - virtual srs_error_t encode(SrsBuffer* buf); -}; - -class SrsRtpSharedPacket -{ -private: - class SrsRtpSharedPacketPayload - { - public: - // Rtp packet buffer, include rtp header and payload. - char* payload; - int size; - int shared_count; - public: - SrsRtpSharedPacketPayload(); - virtual ~SrsRtpSharedPacketPayload(); - }; -private: - SrsRtpSharedPacketPayload* payload_ptr; -public: - SrsRtpHeader rtp_header; - char* payload; - int size; -public: - SrsRtpSharedPacket(); - virtual ~SrsRtpSharedPacket(); -public: - srs_error_t create(int64_t timestamp, uint16_t sequence, uint32_t ssrc, uint16_t payload_type, char* payload, int size); - SrsRtpSharedPacket* copy(); -// Interface to modify rtp header -public: - srs_error_t modify_rtp_header_marker(bool marker); - srs_error_t modify_rtp_header_ssrc(uint32_t ssrc); - srs_error_t modify_rtp_header_payload_type(uint8_t payload_type); -}; - -#endif diff --git a/trunk/src/kernel/srs_kernel_ts.cpp b/trunk/src/kernel/srs_kernel_ts.cpp index 27d40315c..d0e0d44a0 100644 --- a/trunk/src/kernel/srs_kernel_ts.cpp +++ b/trunk/src/kernel/srs_kernel_ts.cpp @@ -1275,6 +1275,41 @@ SrsTsPayloadPES::SrsTsPayloadPES(SrsTsPacket* p) : SrsTsPayload(p) nb_paddings = 0; const2bits = 0x02; const1_value0 = 0x07; + + packet_start_code_prefix = 0; + stream_id = 0; + PES_packet_length = 0; + PES_scrambling_control = 0; + PES_priority = 0; + data_alignment_indicator = 0; + copyright = 0; + original_or_copy = 0; + PTS_DTS_flags = 0; + ESCR_flag = 0; + ES_rate_flag = 0; + DSM_trick_mode_flag = 0; + additional_copy_info_flag = 0; + PES_CRC_flag = 0; + PES_extension_flag = 0; + PES_header_data_length = 0; + pts = dts = 0; + ESCR_base = 0; + ESCR_extension = 0; + ES_rate = 0; + trick_mode_control = 0; + trick_mode_value = 0; + additional_copy_info = 0; + previous_PES_packet_CRC = 0; + PES_private_data_flag = 0; + pack_header_field_flag = 0; + program_packet_sequence_counter_flag = 0; + P_STD_buffer_flag = 0; + PES_extension_flag_2 = 0; + program_packet_sequence_counter = 0; + MPEG1_MPEG2_identifier = 0; + original_stuff_length = 0; + P_STD_buffer_scale = 0; + P_STD_buffer_size = 0; } SrsTsPayloadPES::~SrsTsPayloadPES() @@ -1977,6 +2012,9 @@ SrsTsPayloadPSI::SrsTsPayloadPSI(SrsTsPacket* p) : SrsTsPayload(p) const0_value = 0; const1_value = 3; CRC_32 = 0; + section_length = 0; + section_syntax_indicator = 0; + table_id = SrsTsPsiIdPas; } SrsTsPayloadPSI::~SrsTsPayloadPSI() @@ -2188,7 +2226,12 @@ srs_error_t SrsTsPayloadPATProgram::encode(SrsBuffer* stream) SrsTsPayloadPAT::SrsTsPayloadPAT(SrsTsPacket* p) : SrsTsPayloadPSI(p) { + transport_stream_id = 0; const3_value = 3; + version_number = 0; + current_next_indicator = 0; + section_number = 0; + last_section_number = 0; } SrsTsPayloadPAT::~SrsTsPayloadPAT() @@ -2387,6 +2430,12 @@ SrsTsPayloadPMT::SrsTsPayloadPMT(SrsTsPacket* p) : SrsTsPayloadPSI(p) const1_value0 = 3; const1_value1 = 7; const1_value2 = 0x0f; + PCR_PID = 0; + last_section_number = 0; + program_number = 0; + version_number = 0; + current_next_indicator = 0; + section_number = 0; } SrsTsPayloadPMT::~SrsTsPayloadPMT() diff --git a/trunk/src/kernel/srs_kernel_utility.cpp b/trunk/src/kernel/srs_kernel_utility.cpp index 1d91b72bd..2cd9d88f1 100644 --- a/trunk/src/kernel/srs_kernel_utility.cpp +++ b/trunk/src/kernel/srs_kernel_utility.cpp @@ -124,8 +124,8 @@ srs_utime_t srs_get_system_startup_time() } // For utest to mock it. -#ifndef SRS_AUTO_OSX -_srs_gettimeofday_t _srs_gettimeofday = ::gettimeofday; +#ifndef SRS_OSX +srs_gettimeofday_t _srs_gettimeofday = (srs_gettimeofday_t)::gettimeofday; #endif srs_utime_t srs_update_system_time() @@ -480,59 +480,54 @@ int srs_string_count(string str, string flag) return nn; } -vector srs_string_split(string str, string flag) + +vector srs_string_split(string s, string seperator) { - vector arr; - - if (flag.empty()) { - arr.push_back(str); - return arr; + vector result; + if(seperator.empty()){ + result.push_back(s); + return result; } - size_t pos; - string s = str; - - while ((pos = s.find(flag)) != string::npos) { - if (pos != 0) { - arr.push_back(s.substr(0, pos)); - } - s = s.substr(pos + flag.length()); + size_t posBegin = 0; + size_t posSeperator = s.find(seperator); + while (posSeperator != string::npos) { + result.push_back(s.substr(posBegin, posSeperator - posBegin)); + posBegin = posSeperator + seperator.length(); // next byte of seperator + posSeperator = s.find(seperator, posBegin); } - - if (!s.empty()) { - arr.push_back(s); - } - - return arr; + // push the last element + result.push_back(s.substr(posBegin)); + return result; } -string srs_string_min_match(string str, vector flags) +string srs_string_min_match(string str, vector seperators) { string match; - if (flags.empty()) { + if (seperators.empty()) { return str; } size_t min_pos = string::npos; - for (vector::iterator it = flags.begin(); it != flags.end(); ++it) { - string flag = *it; + for (vector::iterator it = seperators.begin(); it != seperators.end(); ++it) { + string seperator = *it; - size_t pos = str.find(flag); + size_t pos = str.find(seperator); if (pos == string::npos) { continue; } if (min_pos == string::npos || pos < min_pos) { min_pos = pos; - match = flag; + match = seperator; } } return match; } -vector srs_string_split(string str, vector flags) +vector srs_string_split(string str, vector seperators) { vector arr; @@ -540,19 +535,17 @@ vector srs_string_split(string str, vector flags) string s = str; while (true) { - string flag = srs_string_min_match(s, flags); - if (flag.empty()) { + string seperator = srs_string_min_match(s, seperators); + if (seperator.empty()) { break; } - if ((pos = s.find(flag)) == string::npos) { + if ((pos = s.find(seperator)) == string::npos) { break; } - - if (pos != 0) { - arr.push_back(s.substr(0, pos)); - } - s = s.substr(pos + flag.length()); + + arr.push_back(s.substr(0, pos)); + s = s.substr(pos + seperator.length()); } if (!s.empty()) { @@ -926,17 +919,18 @@ uint32_t srs_crc32_mpegts(const void* buf, int size) return __crc32_table_driven(__crc32_MPEG_table, buf, size, 0x00, reflect_in, xor_in, reflect_out, xor_out); } +// We use the standard encoding: +// var StdEncoding = NewEncoding(encodeStd) +// StdEncoding is the standard base64 encoding, as defined in RFC 4648. +namespace { + char padding = '='; + string encoder = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +} // @see golang encoding/base64/base64.go srs_error_t srs_av_base64_decode(string cipher, string& plaintext) { srs_error_t err = srs_success; - // We use the standard encoding: - // var StdEncoding = NewEncoding(encodeStd) - // StdEncoding is the standard base64 encoding, as defined in RFC 4648. - char padding = '='; - string encoder = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - uint8_t decodeMap[256]; memset(decodeMap, 0xff, sizeof(decodeMap)); @@ -1036,6 +1030,64 @@ srs_error_t srs_av_base64_decode(string cipher, string& plaintext) return err; } +// @see golang encoding/base64/base64.go +srs_error_t srs_av_base64_encode(std::string plaintext, std::string& cipher) +{ + srs_error_t err = srs_success; + uint8_t decodeMap[256]; + memset(decodeMap, 0xff, sizeof(decodeMap)); + + for (int i = 0; i < (int)encoder.length(); i++) { + decodeMap[(uint8_t)encoder.at(i)] = uint8_t(i); + } + cipher.clear(); + + uint32_t val = 0; + int di = 0; + int si = 0; + int n = (plaintext.length() / 3) * 3; + uint8_t* p = (uint8_t*)plaintext.c_str(); + while(si < n) { + // Convert 3x 8bit source bytes into 4 bytes + val = (uint32_t(p[si + 0]) << 16) | (uint32_t(p[si + 1])<< 8) | uint32_t(p[si + 2]); + + cipher += encoder[val>>18&0x3f]; + cipher += encoder[val>>12&0x3f]; + cipher += encoder[val>>6&0x3f]; + cipher += encoder[val&0x3f]; + + si += 3; + di += 4; + } + + int remain = plaintext.length() - si; + if(0 == remain) { + return err; + } + + val = uint32_t(p[si + 0]) << 16; + if( 2 == remain) { + val |= uint32_t(p[si + 1]) << 8; + } + + cipher += encoder[val>>18&0x3f]; + cipher += encoder[val>>12&0x3f]; + + switch (remain) { + case 2: + cipher += encoder[val>>6&0x3f]; + cipher += padding; + break; + case 1: + cipher += padding; + cipher += padding; + break; + } + + + return err; +} + #define SPACE_CHARS " \t\r\n" int av_toupper(int c) @@ -1078,6 +1130,22 @@ char* srs_data_to_hex(char* des, const u_int8_t* src, int len) return des; } +char* srs_data_to_hex_lowercase(char* des, const u_int8_t* src, int len) +{ + if(src == NULL || len == 0 || des == NULL){ + return NULL; + } + + const char *hex_table = "0123456789abcdef"; + + for (int i=0; i> 4]; + des[i * 2 + 1] = hex_table[src[i] & 0x0F]; + } + + return des; +} + int srs_hex_to_data(uint8_t* data, const char* p, int size) { if (size <= 0 || (size%2) == 1) { diff --git a/trunk/src/kernel/srs_kernel_utility.hpp b/trunk/src/kernel/srs_kernel_utility.hpp index c63937430..5a388d129 100644 --- a/trunk/src/kernel/srs_kernel_utility.hpp +++ b/trunk/src/kernel/srs_kernel_utility.hpp @@ -102,9 +102,9 @@ extern bool srs_string_contains(std::string str, std::string flag0, std::string extern int srs_string_count(std::string str, std::string flag); // Find the min match in str for flags. extern std::string srs_string_min_match(std::string str, std::vector flags); -// Split the string by flag to array. -extern std::vector srs_string_split(std::string str, std::string flag); -extern std::vector srs_string_split(std::string str, std::vector flags); +// Split the string by seperator to array. +extern std::vector srs_string_split(std::string s, std::string seperator); +extern std::vector srs_string_split(std::string s, std::vector seperators); // Compare the memory in bytes. // @return true if completely equal; otherwise, false. @@ -141,16 +141,23 @@ extern uint32_t srs_crc32_ieee(const void* buf, int size, uint32_t previous = 0) // Decode a base64-encoded string. extern srs_error_t srs_av_base64_decode(std::string cipher, std::string& plaintext); +// Encode a plaintext to base64-encoded string. +extern srs_error_t srs_av_base64_encode(std::string plaintext, std::string& cipher); // Calculate the output size needed to base64-encode x bytes to a null-terminated string. #define SRS_AV_BASE64_SIZE(x) (((x)+2) / 3 * 4 + 1) -// Convert hex string to data, for example, p=config='139056E5A0' -// The output data in hex {0x13, 0x90, 0x56, 0xe5, 0xa0} as such. +// Covert hex string to uint8 data, for example: +// srs_hex_to_data(data, string("139056E5A0")) +// which outputs the data in hex {0x13, 0x90, 0x56, 0xe5, 0xa0}. extern int srs_hex_to_data(uint8_t* data, const char* p, int size); -// Convert data string to hex. -extern char *srs_data_to_hex(char *des, const uint8_t *src, int len); +// Convert data string to hex, for example: +// srs_data_to_hex(des, {0xf3, 0x3f}, 2) +// which outputs the des is string("F33F"). +extern char* srs_data_to_hex(char* des, const uint8_t* src, int len); +// Output in lowercase, such as string("f33f"). +extern char* srs_data_to_hex_lowercase(char* des, const uint8_t* src, int len); // Generate the c0 chunk header for msg. // @param cache, the cache to write header. @@ -166,10 +173,10 @@ extern int srs_chunk_header_c3(int perfer_cid, uint32_t timestamp, char* cache, // For utest to mock it. #include -#ifdef SRS_AUTO_OSX +#ifdef SRS_OSX #define _srs_gettimeofday gettimeofday #else - typedef int (*_srs_gettimeofday_t) (struct timeval* tv, struct timezone* tz); + typedef int (*srs_gettimeofday_t) (struct timeval* tv, struct timezone* tz); #endif #endif diff --git a/trunk/src/libs/srs_lib_bandwidth.cpp b/trunk/src/libs/srs_lib_bandwidth.cpp deleted file mode 100644 index 611c2c6b8..000000000 --- a/trunk/src/libs/srs_lib_bandwidth.cpp +++ /dev/null @@ -1,416 +0,0 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2013-2020 Winlin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#include - -// for srs-librtmp, @see https://github.com/ossrs/srs/issues/213 -#ifndef _WIN32 -#include -#endif - -#include -using namespace std; - -#include -#include -#include -#include -#include -#include - -/** - * recv bandwidth helper. - */ -typedef bool (*_CheckPacketType)(SrsBandwidthPacket* pkt); -bool _bandwidth_is_start_play(SrsBandwidthPacket* pkt) -{ - return pkt->is_start_play(); -} -bool _bandwidth_is_stop_play(SrsBandwidthPacket* pkt) -{ - return pkt->is_stop_play(); -} -bool _bandwidth_is_start_publish(SrsBandwidthPacket* pkt) -{ - return pkt->is_start_publish(); -} -bool _bandwidth_is_stop_publish(SrsBandwidthPacket* pkt) -{ - return pkt->is_stop_publish(); -} -bool _bandwidth_is_finish(SrsBandwidthPacket* pkt) -{ - return pkt->is_finish(); -} -int _srs_expect_bandwidth_packet(SrsRtmpClient* rtmp, _CheckPacketType pfn) -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - while (true) { - SrsCommonMessage* msg = NULL; - SrsBandwidthPacket* pkt = NULL; - if ((err = rtmp->expect_message(&msg, &pkt)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - SrsAutoFree(SrsCommonMessage, msg); - SrsAutoFree(SrsBandwidthPacket, pkt); - srs_info("get final message success."); - - if (pfn(pkt)) { - return ret; - } - } - - return ret; -} -int _srs_expect_bandwidth_packet2(SrsRtmpClient* rtmp, _CheckPacketType pfn, SrsBandwidthPacket** ppkt) -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - while (true) { - SrsCommonMessage* msg = NULL; - SrsBandwidthPacket* pkt = NULL; - if ((err = rtmp->expect_message(&msg, &pkt)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - SrsAutoFree(SrsCommonMessage, msg); - srs_info("get final message success."); - - if (pfn(pkt)) { - *ppkt = pkt; - return ret; - } - - srs_freep(pkt); - } - - return ret; -} - -SrsBandwidthClient::SrsBandwidthClient() -{ - _rtmp = NULL; -} - -SrsBandwidthClient::~SrsBandwidthClient() -{ -} - -int SrsBandwidthClient::initialize(SrsRtmpClient* rtmp) -{ - _rtmp = rtmp; - - return ERROR_SUCCESS; -} - -int SrsBandwidthClient::bandwidth_check( - int64_t* start_time, int64_t* end_time, - int* play_kbps, int* publish_kbps, - int* play_bytes, int* publish_bytes, - int* play_duration, int* publish_duration -) { - int ret = ERROR_SUCCESS; - - *start_time = srsu2ms(srs_update_system_time()); - - // play - if ((ret = play_start()) != ERROR_SUCCESS) { - return ret; - } - if ((ret = play_checking()) != ERROR_SUCCESS) { - return ret; - } - if ((ret = play_stop()) != ERROR_SUCCESS) { - return ret; - } - - // publish - int duration_ms = 0; - int actual_play_kbps = 0; - if ((ret = publish_start(duration_ms, actual_play_kbps)) != ERROR_SUCCESS) { - return ret; - } - if ((ret = publish_checking(duration_ms, actual_play_kbps)) != ERROR_SUCCESS) { - return ret; - } - if ((ret = publish_stop()) != ERROR_SUCCESS) { - return ret; - } - - SrsBandwidthPacket* pkt = NULL; - if ((ret = do_final(&pkt)) != ERROR_SUCCESS) { - return ret; - } - SrsAutoFree(SrsBandwidthPacket, pkt); - - // get data - if (true ) { - SrsAmf0Any* prop = NULL; - if ((prop = pkt->data->ensure_property_number("play_kbps")) != NULL) { - *play_kbps = (int)prop->to_number(); - } - if ((prop = pkt->data->ensure_property_number("publish_kbps")) != NULL) { - *publish_kbps = (int)prop->to_number(); - } - if ((prop = pkt->data->ensure_property_number("play_bytes")) != NULL) { - *play_bytes = (int)prop->to_number(); - } - if ((prop = pkt->data->ensure_property_number("publish_bytes")) != NULL) { - *publish_bytes = (int)prop->to_number(); - } - if ((prop = pkt->data->ensure_property_number("play_time")) != NULL) { - *play_duration = (int)prop->to_number(); - } - if ((prop = pkt->data->ensure_property_number("publish_time")) != NULL) { - *publish_duration = (int)prop->to_number(); - } - } - - *end_time = srsu2ms(srs_update_system_time()); - - return ret; -} - -int SrsBandwidthClient::play_start() -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - if ((ret = _srs_expect_bandwidth_packet(_rtmp, _bandwidth_is_start_play)) != ERROR_SUCCESS) { - return ret; - } - srs_info("BW check recv play begin request."); - - if (true) { - // send start play response to server. - SrsBandwidthPacket* pkt = SrsBandwidthPacket::create_starting_play(); - - if ((err = _rtmp->send_and_free_packet(pkt, 0)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - srs_error("send bandwidth check start play message failed. ret=%d", ret); - return ret; - } - } - srs_info("BW check play begin."); - - return ret; -} - -int SrsBandwidthClient::play_checking() -{ - int ret = ERROR_SUCCESS; - return ret; -} - -int SrsBandwidthClient::play_stop() -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - if ((ret = _srs_expect_bandwidth_packet(_rtmp, _bandwidth_is_stop_play)) != ERROR_SUCCESS) { - return ret; - } - srs_info("BW check recv play stop request."); - - if (true) { - // send stop play response to server. - SrsBandwidthPacket* pkt = SrsBandwidthPacket::create_stopped_play(); - - if ((err = _rtmp->send_and_free_packet(pkt, 0)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - srs_error("send bandwidth check stop play message failed. ret=%d", ret); - return ret; - } - } - srs_info("BW check play stop."); - - return ret; -} - -int SrsBandwidthClient::publish_start(int& duration_ms, int& play_kbps) -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - if (true) { - SrsBandwidthPacket* pkt = NULL; - if ((ret = _srs_expect_bandwidth_packet2(_rtmp, _bandwidth_is_start_publish, &pkt)) != ERROR_SUCCESS) { - return ret; - } - SrsAutoFree(SrsBandwidthPacket, pkt); - - SrsAmf0Any* prop = NULL; - if ((prop = pkt->data->ensure_property_number("duration_ms")) != NULL) { - duration_ms = (int)prop->to_number(); - } - if ((prop = pkt->data->ensure_property_number("limit_kbps")) != NULL) { - play_kbps = (int)prop->to_number(); - } - } - srs_info("BW check recv publish begin request."); - - if (true) { - // send start publish response to server. - SrsBandwidthPacket* pkt = SrsBandwidthPacket::create_starting_publish(); - - if ((err = _rtmp->send_and_free_packet(pkt, 0)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - srs_error("send bandwidth check start publish message failed. ret=%d", ret); - return ret; - } - } - srs_info("BW check publish begin."); - - return ret; -} - -int SrsBandwidthClient::publish_checking(int duration_ms, int play_kbps) -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - if (duration_ms <= 0) { - ret = ERROR_RTMP_BWTC_DATA; - srs_error("server must specifies the duration, ret=%d", ret); - return ret; - } - - if (play_kbps <= 0) { - ret = ERROR_RTMP_BWTC_DATA; - srs_error("server must specifies the play kbp, ret=%d", ret); - return ret; - } - - int data_count = 1; - int64_t starttime = srsu2ms(srs_update_system_time()); - while (int64_t(srsu2ms(srs_get_system_time()) - starttime) < duration_ms) { - // TODO: FIXME: use shared ptr message. - SrsBandwidthPacket* pkt = SrsBandwidthPacket::create_publishing(); - - // TODO: FIXME: magic number - for (int i = 0; i < data_count; ++i) { - std::stringstream seq; - seq << i; - std::string play_data = "SRS band check data from server's publishing......"; - pkt->data->set(seq.str(), SrsAmf0Any::str(play_data.c_str())); - } - data_count += 2; - - if ((err = _rtmp->send_and_free_packet(pkt, 0)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - srs_error("send bandwidth check publish messages failed. ret=%d", ret); - return ret; - } - - // use the play kbps to control the publish - int elaps = (int)(srsu2ms(srs_update_system_time()) - starttime); - if (elaps > 0) { - int current_kbps = (int)(_rtmp->get_send_bytes() * 8 / elaps); - while (current_kbps > play_kbps) { - srs_update_system_time(); - elaps = (int)(srsu2ms(srs_get_system_time()) - starttime); - current_kbps = (int)(_rtmp->get_send_bytes() * 8 / elaps); - usleep(100 * 1000); // TODO: FIXME: magic number. - } - } - } - srs_info("BW check send publish bytes over."); - - return ret; -} - -int SrsBandwidthClient::publish_stop() -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - if (true) { - // send start publish response to server. - SrsBandwidthPacket* pkt = SrsBandwidthPacket::create_stop_publish(); - - if ((err = _rtmp->send_and_free_packet(pkt, 0)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - srs_error("send bandwidth check stop publish message failed. ret=%d", ret); - return ret; - } - } - srs_info("BW client stop publish request."); - - if ((ret = _srs_expect_bandwidth_packet(_rtmp, _bandwidth_is_stop_publish)) != ERROR_SUCCESS) { - return ret; - } - srs_info("BW check recv publish stop request."); - - if (true) { - // send start publish response to server. - SrsBandwidthPacket* pkt = SrsBandwidthPacket::create_stopped_publish(); - - if ((err = _rtmp->send_and_free_packet(pkt, 0)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - srs_error("send bandwidth check stop publish message failed. ret=%d", ret); - return ret; - } - } - srs_info("BW check publish stop."); - - return ret; -} - -int SrsBandwidthClient::do_final(SrsBandwidthPacket** ppkt) -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - if ((ret = _srs_expect_bandwidth_packet2(_rtmp, _bandwidth_is_finish, ppkt)) != ERROR_SUCCESS) { - return ret; - } - srs_info("BW check recv finish/report request."); - - if (true) { - // send final response to server. - SrsBandwidthPacket* pkt = SrsBandwidthPacket::create_final(); - - if ((err = _rtmp->send_and_free_packet(pkt, 0)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - srs_error("send bandwidth check final message failed. ret=%d", ret); - return ret; - } - } - srs_info("BW check final."); - - return ret; -} - diff --git a/trunk/src/libs/srs_lib_bandwidth.hpp b/trunk/src/libs/srs_lib_bandwidth.hpp deleted file mode 100644 index 2bcf57956..000000000 --- a/trunk/src/libs/srs_lib_bandwidth.hpp +++ /dev/null @@ -1,85 +0,0 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2013-2020 Winlin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#ifndef SRS_LIB_BANDWIDTH_HPP -#define SRS_LIB_BANDWIDTH_HPP - -#include - -class SrsRtmpClient; -class SrsBandwidthPacket; - -/** - * bandwith client library for srs-librtmp. - */ -class SrsBandwidthClient -{ -private: - SrsRtmpClient* _rtmp; -public: - SrsBandwidthClient(); - virtual ~SrsBandwidthClient(); -public: - /** - * initialize the bandwidth check client. - */ - virtual int initialize(SrsRtmpClient* rtmp); - /** - * do bandwidth check. - * - * bandwidth info: - * @param start_time, output the start time, in ms. - * @param end_time, output the end time, in ms. - * @param play_kbps, output the play/download kbps. - * @param publish_kbps, output the publish/upload kbps. - * @param play_bytes, output the play/download bytes. - * @param publish_bytes, output the publish/upload bytes. - * @param play_duration, output the play/download test duration, in ms. - * @param publish_duration, output the publish/upload test duration, in ms. - */ - virtual int bandwidth_check( - int64_t* start_time, int64_t* end_time, - int* play_kbps, int* publish_kbps, - int* play_bytes, int* publish_bytes, - int* play_duration, int* publish_duration); -private: - /** - * play check/test, downloading bandwidth kbps. - */ - virtual int play_start(); - virtual int play_checking(); - virtual int play_stop(); - /** - * publish check/test, publishing bandwidth kbps. - */ - virtual int publish_start(int& duration_ms, int& play_kbps); - virtual int publish_checking(int duration_ms, int play_kbps); - virtual int publish_stop(); - /** - * report and final packet - */ - virtual int do_final(SrsBandwidthPacket** ppkt); -}; - -#endif - diff --git a/trunk/src/libs/srs_lib_simple_socket.cpp b/trunk/src/libs/srs_lib_simple_socket.cpp deleted file mode 100644 index 59adf2278..000000000 --- a/trunk/src/libs/srs_lib_simple_socket.cpp +++ /dev/null @@ -1,486 +0,0 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2013-2020 Winlin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#include - -#include - -#include - -// for srs-librtmp, @see https://github.com/ossrs/srs/issues/213 -#ifndef _WIN32 -#define SOCKET_ETIME EWOULDBLOCK -#define SOCKET_ECONNRESET ECONNRESET - -#define SOCKET_ERRNO() errno -#define SOCKET_RESET(fd) fd = -1; (void)0 -#define SOCKET_CLOSE(fd) \ - if (fd > 0) {\ - ::close(fd); \ - fd = -1; \ - } \ - (void)0 -#define SOCKET_VALID(x) (x > 0) -#define SOCKET_SETUP() (void)0 -#define SOCKET_CLEANUP() (void)0 -#else -#define SOCKET_ETIME WSAETIMEDOUT -#define SOCKET_ECONNRESET WSAECONNRESET -#define SOCKET_ERRNO() WSAGetLastError() -#define SOCKET_RESET(x) x=INVALID_SOCKET -#define SOCKET_CLOSE(x) if(x!=INVALID_SOCKET){::closesocket(x);x=INVALID_SOCKET;} -#define SOCKET_VALID(x) (x!=INVALID_SOCKET) -#define SOCKET_BUFF(x) ((char*)x) -#define SOCKET_SETUP() socket_setup() -#define SOCKET_CLEANUP() socket_cleanup() -#endif - -// for srs-librtmp, @see https://github.com/ossrs/srs/issues/213 -#ifndef _WIN32 -#include -#include -#include -#include -#include -#endif - -#include -#include -#include -#include - -#include -#include -#include - -// when io not hijacked, use simple socket, the block sync stream. -#ifndef SRS_HIJACK_IO -struct SrsBlockSyncSocket -{ - int family; - SOCKET fd; - SOCKET fdv4; - SOCKET fdv6; - // Bytes transmit. - int64_t rbytes; - int64_t sbytes; - // The send/recv timeout in ms. - int64_t rtm; - int64_t stm; - - SrsBlockSyncSocket() { - family = AF_UNSPEC; - stm = rtm = SRS_UTIME_NO_TIMEOUT; - rbytes = sbytes = 0; - - SOCKET_RESET(fd); - SOCKET_RESET(fdv4); - SOCKET_RESET(fdv6); - SOCKET_SETUP(); - } - - virtual ~SrsBlockSyncSocket() { - if (SOCKET_VALID(fd)) { - SOCKET_CLOSE(fd); - } - if (SOCKET_VALID(fdv4)) { - SOCKET_CLOSE(fdv4); - } - if (SOCKET_VALID(fdv6)) { - SOCKET_CLOSE(fdv6); - } - SOCKET_CLEANUP(); - } -}; -srs_hijack_io_t srs_hijack_io_create() -{ - SrsBlockSyncSocket* skt = new SrsBlockSyncSocket(); - return skt; -} -void srs_hijack_io_destroy(srs_hijack_io_t ctx) -{ - SrsBlockSyncSocket* skt = (SrsBlockSyncSocket*)ctx; - srs_freep(skt); -} -int srs_hijack_io_create_socket(srs_hijack_io_t ctx, srs_rtmp_t owner) -{ - SrsBlockSyncSocket* skt = (SrsBlockSyncSocket*)ctx; - - skt->family = AF_UNSPEC; - skt->fdv4 = ::socket(AF_INET, SOCK_STREAM, 0); - skt->fdv6 = ::socket(AF_INET6, SOCK_STREAM, 0); - if (!SOCKET_VALID(skt->fdv4) && !SOCKET_VALID(skt->fdv4)) { - return ERROR_SOCKET_CREATE; - } - - // No TCP cache. - int v = 1; - setsockopt(skt->fdv4, IPPROTO_TCP, TCP_NODELAY, &v, sizeof(v)); - setsockopt(skt->fdv6, IPPROTO_TCP, TCP_NODELAY, &v, sizeof(v)); - - return ERROR_SUCCESS; -} -int srs_hijack_io_connect(srs_hijack_io_t ctx, const char* server_ip, int port) -{ - SrsBlockSyncSocket* skt = (SrsBlockSyncSocket*)ctx; - - char sport[8]; - snprintf(sport, sizeof(sport), "%d", port); - - addrinfo hints; - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - - addrinfo* r = NULL; - SrsAutoFree(addrinfo, r); - if(getaddrinfo(server_ip, sport, (const addrinfo*)&hints, &r)) { - return ERROR_SOCKET_CONNECT; - } - - skt->family = r->ai_family; - if (r->ai_family == AF_INET6) { - skt->fd = skt->fdv6; - SOCKET_RESET(skt->fdv6); - } else { - skt->fd = skt->fdv4; - SOCKET_RESET(skt->fdv4); - } - - if(::connect(skt->fd, r->ai_addr, r->ai_addrlen) < 0){ - return ERROR_SOCKET_CONNECT; - } - - return ERROR_SUCCESS; -} -int srs_hijack_io_read(srs_hijack_io_t ctx, void* buf, size_t size, ssize_t* nread) -{ - SrsBlockSyncSocket* skt = (SrsBlockSyncSocket*)ctx; - - int ret = ERROR_SUCCESS; - - ssize_t nb_read = ::recv(skt->fd, (char*)buf, size, 0); - - if (nread) { - *nread = nb_read; - } - - // On success a non-negative integer indicating the number of bytes actually read is returned - // (a value of 0 means the network connection is closed or end of file is reached). - if (nb_read <= 0) { - if (nb_read < 0 && SOCKET_ERRNO() == SOCKET_ETIME) { - return ERROR_SOCKET_TIMEOUT; - } - - if (nb_read == 0) { - errno = SOCKET_ECONNRESET; - } - - return ERROR_SOCKET_READ; - } - - skt->rbytes += nb_read; - - return ret; -} -int srs_hijack_io_set_recv_timeout(srs_hijack_io_t ctx, int64_t tm) -{ - SrsBlockSyncSocket* skt = (SrsBlockSyncSocket*)ctx; - -#ifdef _WIN32 - DWORD tv = (DWORD)(tm); - - // To convert tv to const char* to make VS2015 happy. - if (setsockopt(skt->fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) { - return SOCKET_ERRNO(); - } -#else - // The default for this option is zero, - // which indicates that a receive operation shall not time out. - int32_t sec = 0; - int32_t usec = 0; - - if (tm != SRS_UTIME_NO_TIMEOUT) { - sec = (int32_t)(tm / 1000); - usec = (int32_t)((tm % 1000)*1000); - } - - struct timeval tv = { sec , usec }; - if (setsockopt(skt->fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) { - return SOCKET_ERRNO(); - } -#endif - - skt->rtm = tm; - - return ERROR_SUCCESS; -} -int64_t srs_hijack_io_get_recv_timeout(srs_hijack_io_t ctx) -{ - SrsBlockSyncSocket* skt = (SrsBlockSyncSocket*)ctx; - return skt->rtm; -} -int64_t srs_hijack_io_get_recv_bytes(srs_hijack_io_t ctx) -{ - SrsBlockSyncSocket* skt = (SrsBlockSyncSocket*)ctx; - return skt->rbytes; -} -int srs_hijack_io_set_send_timeout(srs_hijack_io_t ctx, int64_t tm) -{ - SrsBlockSyncSocket* skt = (SrsBlockSyncSocket*)ctx; - -#ifdef _WIN32 - DWORD tv = (DWORD)(tm); - - // To convert tv to const char* to make VS2015 happy. - if (setsockopt(skt->fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) == -1) { - return SOCKET_ERRNO(); - } -#else - // The default for this option is zero, - // which indicates that a receive operation shall not time out. - int32_t sec = 0; - int32_t usec = 0; - - if (tm != SRS_UTIME_NO_TIMEOUT) { - sec = (int32_t)(tm / 1000); - usec = (int32_t)((tm % 1000)*1000); - } - - struct timeval tv = { sec , usec }; - if (setsockopt(skt->fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) == -1) { - return SOCKET_ERRNO(); - } -#endif - - skt->stm = tm; - - return ERROR_SUCCESS; -} -int64_t srs_hijack_io_get_send_timeout(srs_hijack_io_t ctx) -{ - SrsBlockSyncSocket* skt = (SrsBlockSyncSocket*)ctx; - return skt->stm; -} -int64_t srs_hijack_io_get_send_bytes(srs_hijack_io_t ctx) -{ - SrsBlockSyncSocket* skt = (SrsBlockSyncSocket*)ctx; - return skt->sbytes; -} -int srs_hijack_io_writev(srs_hijack_io_t ctx, const iovec *iov, int iov_size, ssize_t* nwrite) -{ - SrsBlockSyncSocket* skt = (SrsBlockSyncSocket*)ctx; - - int ret = ERROR_SUCCESS; - - ssize_t nb_write = ::writev(skt->fd, iov, iov_size); - - if (nwrite) { - *nwrite = nb_write; - } - - // On success, the readv() function returns the number of bytes read; - // the writev() function returns the number of bytes written. On error, -1 is - // returned, and errno is set appropriately. - if (nb_write <= 0) { - // @see https://github.com/ossrs/srs/issues/200 - if (nb_write < 0 && SOCKET_ERRNO() == SOCKET_ETIME) { - return ERROR_SOCKET_TIMEOUT; - } - - return ERROR_SOCKET_WRITE; - } - - skt->sbytes += nb_write; - - return ret; -} -int srs_hijack_io_is_never_timeout(srs_hijack_io_t ctx, int64_t tm) -{ - return tm == SRS_UTIME_NO_TIMEOUT; -} -int srs_hijack_io_read_fully(srs_hijack_io_t ctx, void* buf, size_t size, ssize_t* nread) -{ - SrsBlockSyncSocket* skt = (SrsBlockSyncSocket*)ctx; - - int ret = ERROR_SUCCESS; - - size_t left = size; - ssize_t nb_read = 0; - - while (left > 0) { - char* this_buf = (char*)buf + nb_read; - ssize_t this_nread; - - if ((ret = srs_hijack_io_read(ctx, this_buf, left, &this_nread)) != ERROR_SUCCESS) { - return ret; - } - - nb_read += this_nread; - left -= (size_t)this_nread; - } - - if (nread) { - *nread = nb_read; - } - skt->rbytes += nb_read; - - return ret; -} -int srs_hijack_io_write(srs_hijack_io_t ctx, void* buf, size_t size, ssize_t* nwrite) -{ - SrsBlockSyncSocket* skt = (SrsBlockSyncSocket*)ctx; - - int ret = ERROR_SUCCESS; - - ssize_t nb_write = ::send(skt->fd, (char*)buf, size, 0); - - if (nwrite) { - *nwrite = nb_write; - } - - if (nb_write <= 0) { - // @see https://github.com/ossrs/srs/issues/200 - if (nb_write < 0 && SOCKET_ERRNO() == SOCKET_ETIME) { - return ERROR_SOCKET_TIMEOUT; - } - - return ERROR_SOCKET_WRITE; - } - - skt->sbytes += nb_write; - - return ret; -} -#endif - -SimpleSocketStream::SimpleSocketStream() -{ - io = srs_hijack_io_create(); -} - -SimpleSocketStream::~SimpleSocketStream() -{ - if (io) { - srs_hijack_io_destroy(io); - io = NULL; - } -} - -srs_hijack_io_t SimpleSocketStream::hijack_io() -{ - return io; -} - -int SimpleSocketStream::create_socket(srs_rtmp_t owner) -{ - srs_assert(io); - return srs_hijack_io_create_socket(io, owner); -} - -int SimpleSocketStream::connect(const char* server_ip, int port) -{ - srs_assert(io); - return srs_hijack_io_connect(io, server_ip, port); -} - -// Interface ISrsReader -srs_error_t SimpleSocketStream::read(void* buf, size_t size, ssize_t* nread) -{ - srs_assert(io); - int ret = srs_hijack_io_read(io, buf, size, nread); - if (ret != ERROR_SUCCESS) { - return srs_error_new(ret, "read"); - } - return srs_success; -} - -// Interface ISrsProtocolReader -void SimpleSocketStream::set_recv_timeout(srs_utime_t tm) -{ - srs_assert(io); - srs_hijack_io_set_recv_timeout(io, srsu2ms(tm)); -} - -srs_utime_t SimpleSocketStream::get_recv_timeout() -{ - srs_assert(io); - return srs_hijack_io_get_recv_timeout(io) * SRS_UTIME_MILLISECONDS; -} - -int64_t SimpleSocketStream::get_recv_bytes() -{ - srs_assert(io); - return srs_hijack_io_get_recv_bytes(io); -} - -// Interface ISrsProtocolWriter -void SimpleSocketStream::set_send_timeout(srs_utime_t tm) -{ - srs_assert(io); - srs_hijack_io_set_send_timeout(io, srsu2ms(tm)); -} - -srs_utime_t SimpleSocketStream::get_send_timeout() -{ - srs_assert(io); - return srs_hijack_io_get_send_timeout(io) * SRS_UTIME_MILLISECONDS; -} - -int64_t SimpleSocketStream::get_send_bytes() -{ - srs_assert(io); - return srs_hijack_io_get_send_bytes(io); -} - -srs_error_t SimpleSocketStream::writev(const iovec *iov, int iov_size, ssize_t* nwrite) -{ - srs_assert(io); - int ret = srs_hijack_io_writev(io, iov, iov_size, nwrite); - if (ret != ERROR_SUCCESS) { - return srs_error_new(ret, "read"); - } - return srs_success; -} - -srs_error_t SimpleSocketStream::read_fully(void* buf, size_t size, ssize_t* nread) -{ - srs_assert(io); - int ret = srs_hijack_io_read_fully(io, buf, size, nread); - if (ret != ERROR_SUCCESS) { - return srs_error_new(ret, "read"); - } - return srs_success; -} - -srs_error_t SimpleSocketStream::write(void* buf, size_t size, ssize_t* nwrite) -{ - srs_assert(io); - int ret = srs_hijack_io_write(io, buf, size, nwrite); - if (ret != ERROR_SUCCESS) { - return srs_error_new(ret, "read"); - } - return srs_success; -} - - diff --git a/trunk/src/libs/srs_lib_simple_socket.hpp b/trunk/src/libs/srs_lib_simple_socket.hpp deleted file mode 100644 index 021aeeea5..000000000 --- a/trunk/src/libs/srs_lib_simple_socket.hpp +++ /dev/null @@ -1,73 +0,0 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2013-2020 Winlin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#ifndef SRS_LIB_SIMPLE_SOCKET_HPP -#define SRS_LIB_SIMPLE_SOCKET_HPP - -#include - -#include -#include - -// for srs-librtmp, @see https://github.com/ossrs/srs/issues/213 -#ifndef _WIN32 -#define SOCKET int -#endif - -/** - * simple socket stream, - * use tcp socket, sync block mode, for client like srs-librtmp. - */ -class SimpleSocketStream : public ISrsProtocolReadWriter -{ -private: - srs_hijack_io_t io; -public: - SimpleSocketStream(); - virtual ~SimpleSocketStream(); -public: - virtual srs_hijack_io_t hijack_io(); - virtual int create_socket(srs_rtmp_t owner); - virtual int connect(const char* server, int port); -// Interface ISrsReader -public: - virtual srs_error_t read(void* buf, size_t size, ssize_t* nread); -// Interface ISrsProtocolReader -public: - virtual void set_recv_timeout(srs_utime_t tm); - virtual srs_utime_t get_recv_timeout(); - virtual int64_t get_recv_bytes(); -// Interface ISrsProtocolWriter -public: - virtual void set_send_timeout(srs_utime_t tm); - virtual srs_utime_t get_send_timeout(); - virtual int64_t get_send_bytes(); - virtual srs_error_t writev(const iovec *iov, int iov_size, ssize_t* nwrite); -// Interface ISrsProtocolReadWriter -public: - virtual srs_error_t read_fully(void* buf, size_t size, ssize_t* nread); - virtual srs_error_t write(void* buf, size_t size, ssize_t* nwrite); -}; - -#endif - diff --git a/trunk/src/libs/srs_librtmp.cpp b/trunk/src/libs/srs_librtmp.cpp deleted file mode 100644 index a2f9728e0..000000000 --- a/trunk/src/libs/srs_librtmp.cpp +++ /dev/null @@ -1,2924 +0,0 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2013-2020 Winlin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#include - -#include -#include - -// for srs-librtmp, @see https://github.com/ossrs/srs/issues/213 -#ifndef _WIN32 -#include -#include -#endif - -#include -#include -using namespace std; - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// kernel module. -ISrsLog* _srs_log = new ISrsLog(); -ISrsThreadContext* _srs_context = new ISrsThreadContext(); - -// The default socket timeout in ms. -#define SRS_SOCKET_DEFAULT_TMMS (30 * 1000) - -/** - * export runtime context. - */ -struct Context -{ - // The original RTMP url. - std::string url; - - // Parse from url. - std::string tcUrl; - std::string host; - std::string vhost; - std::string app; - std::string stream; - std::string param; - - // Parse ip:port from host. - std::string ip; - int port; - - // The URL schema, about vhost/app/stream?param - srs_url_schema schema; - // The server information, response by connect app. - SrsServerInfo si; - - // The extra request object for connect to server, NULL to ignore. - SrsRequest* req; - - // the message received cache, - // for example, when got aggregate message, - // the context will parse to videos/audios, - // and return one by one. - std::vector msgs; - - SrsRtmpClient* rtmp; - SimpleSocketStream* skt; - int stream_id; - - // the remux raw codec. - SrsRawH264Stream avc_raw; - SrsRawAacStream aac_raw; - - // about SPS, @see: 7.3.2.1.1, ISO_IEC_14496-10-AVC-2012.pdf, page 62 - std::string h264_sps; - std::string h264_pps; - // whether the sps and pps sent, - // @see https://github.com/ossrs/srs/issues/203 - bool h264_sps_pps_sent; - // only send the ssp and pps when both changed. - // @see https://github.com/ossrs/srs/issues/204 - bool h264_sps_changed; - bool h264_pps_changed; - // the aac sequence header. - std::string aac_specific_config; - - // user set timeout, in ms. - int64_t stimeout; - int64_t rtimeout; - - // The RTMP handler level buffer, can used to format packet. - char buffer[1024]; - - Context() : port(0) { - rtmp = NULL; - skt = NULL; - req = NULL; - stream_id = 0; - h264_sps_pps_sent = false; - h264_sps_changed = false; - h264_pps_changed = false; - rtimeout = stimeout = SRS_UTIME_NO_TIMEOUT; - schema = srs_url_schema_normal; - } - virtual ~Context() { - srs_freep(req); - srs_freep(rtmp); - srs_freep(skt); - - std::vector::iterator it; - for (it = msgs.begin(); it != msgs.end(); ++it) { - SrsCommonMessage* msg = *it; - srs_freep(msg); - } - msgs.clear(); - } -}; - -// for srs-librtmp, @see https://github.com/ossrs/srs/issues/213 -#ifdef _WIN32 -int gettimeofday(struct timeval* tv, struct timezone* tz) -{ - time_t clock; - struct tm tm; - SYSTEMTIME win_time; - - GetLocalTime(&win_time); - - tm.tm_year = win_time.wYear - 1900; - tm.tm_mon = win_time.wMonth - 1; - tm.tm_mday = win_time.wDay; - tm.tm_hour = win_time.wHour; - tm.tm_min = win_time.wMinute; - tm.tm_sec = win_time.wSecond; - tm.tm_isdst = -1; - - clock = mktime(&tm); - - tv->tv_sec = (long)clock; - tv->tv_usec = win_time.wMilliseconds * 1000; - - return 0; -} - -int socket_setup() -{ - WORD wVersionRequested; - WSADATA wsaData; - int err; - - /* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */ - wVersionRequested = MAKEWORD(2, 2); - - err = WSAStartup(wVersionRequested, &wsaData); - if (err != 0) { - /* Tell the user that we could not find a usable */ - /* Winsock DLL. */ - //printf("WSAStartup failed with error: %d\n", err); - return -1; - } - return 0; -} - -int socket_cleanup() -{ - WSACleanup(); - return 0; -} - -pid_t getpid(void) -{ - return (pid_t)GetCurrentProcessId(); -} - -int usleep(useconds_t usec) -{ - Sleep((DWORD)(usec / 1000)); - return 0; -} - -ssize_t writev(int fd, const struct iovec *iov, int iovcnt) -{ - ssize_t nwrite = 0; - for (int i = 0; i < iovcnt; i++) { - const struct iovec* current = iov + i; - - int nsent = ::send(fd, (char*)current->iov_base, current->iov_len, 0); - if (nsent < 0) { - return nsent; - } - - nwrite += nsent; - if (nsent == 0) { - return nwrite; - } - } - return nwrite; -} - -//////////////////////// strlcpy.c (modified) ////////////////////////// - -/* $OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 millert Exp $ */ - -/*- - * Copyright (c) 1998 Todd C. Miller - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -//#include // **** -//#include // **** -// __FBSDID("$FreeBSD: stable/9/sys/libkern/strlcpy.c 243811 2012-12-03 18:08:44Z delphij $"); // **** - -// #include // **** -// #include // **** - -/* - * Copy src to string dst of size siz. At most siz-1 characters - * will be copied. Always NUL terminates (unless siz == 0). - * Returns strlen(src); if retval >= siz, truncation occurred. - */ - -//#define __restrict // **** - -std::size_t strlcpy(char * __restrict dst, const char * __restrict src, size_t siz) -{ - char *d = dst; - const char *s = src; - size_t n = siz; - - /* Copy as many bytes as will fit */ - if (n != 0) { - while (--n != 0) { - if ((*d++ = *s++) == '\0') - break; - } - } - - /* Not enough room in dst, add NUL and traverse rest of src */ - if (n == 0) { - if (siz != 0) - *d = '\0'; /* NUL-terminate dst */ - while (*s++) - ; - } - - return(s - src - 1); /* count does not include NUL */ -} - -// http://www.cplusplus.com/forum/general/141779///////////////////////// inet_ntop.c (modified) ////////////////////////// -/* - * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") - * Copyright (c) 1996-1999 by Internet Software Consortium. - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT - * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -// #if defined(LIBC_SCCS) && !defined(lint) // **** -//static const char rcsid[] = "$Id: inet_ntop.c,v 1.3.18.2 2005/11/03 23:02:22 marka Exp $"; -// #endif /* LIBC_SCCS and not lint */ // **** -// #include // **** -// __FBSDID("$FreeBSD: stable/9/sys/libkern/inet_ntop.c 213103 2010-09-24 15:01:45Z attilio $"); // **** - -//#define _WIN32_WINNT _WIN32_WINNT_WIN8 // **** -//#include // **** -#pragma comment(lib, "Ws2_32.lib") // **** -//#include // **** - -// #include // **** -// #include // **** -// #include // **** - -// #include // **** - -/*% - * WARNING: Don't even consider trying to compile this on a system where - * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX. - */ - -static char *inet_ntop4(const u_char *src, char *dst, socklen_t size); -static char *inet_ntop6(const u_char *src, char *dst, socklen_t size); - -/* char * - * inet_ntop(af, src, dst, size) - * convert a network format address to presentation format. - * return: - * pointer to presentation format address (`dst'), or NULL (see errno). - * author: - * Paul Vixie, 1996. - */ -const char* inet_ntop(int af, const void *src, char *dst, socklen_t size) -{ - switch (af) { - case AF_INET: - return (inet_ntop4((unsigned char*)src, (char*)dst, size)); - case AF_INET6: - return (char*)(inet_ntop6((unsigned char*)src, (char*)dst, size)); - default: - return (NULL); - } - /* NOTREACHED */ -} - -/* const char * - * inet_ntop4(src, dst, size) - * format an IPv4 address - * return: - * `dst' (as a const) - * notes: - * (1) uses no statics - * (2) takes a u_char* not an in_addr as input - * author: - * Paul Vixie, 1996. - */ -static char * inet_ntop4(const u_char *src, char *dst, socklen_t size) -{ - static const char fmt[128] = "%u.%u.%u.%u"; - char tmp[sizeof "255.255.255.255"]; - int l; - - l = snprintf(tmp, sizeof(tmp), fmt, src[0], src[1], src[2], src[3]); // **** - if (l <= 0 || (socklen_t) l >= size) { - return (NULL); - } - strlcpy(dst, tmp, size); - return (dst); -} - -/* const char * - * inet_ntop6(src, dst, size) - * convert IPv6 binary address into presentation (printable) format - * author: - * Paul Vixie, 1996. - */ -static char * inet_ntop6(const u_char *src, char *dst, socklen_t size) -{ - /* - * Note that int32_t and int16_t need only be "at least" large enough - * to contain a value of the specified size. On some systems, like - * Crays, there is no such thing as an integer variable with 16 bits. - * Keep this in mind if you think this function should have been coded - * to use pointer overlays. All the world's not a VAX. - */ - char tmp[sizeof "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255"], *tp; - struct { int base, len; } best, cur; -#define NS_IN6ADDRSZ 16 -#define NS_INT16SZ 2 - u_int words[NS_IN6ADDRSZ / NS_INT16SZ]; - int i; - - /* - * Preprocess: - * Copy the input (bytewise) array into a wordwise array. - * Find the longest run of 0x00's in src[] for :: shorthanding. - */ - memset(words, '\0', sizeof words); - for (i = 0; i < NS_IN6ADDRSZ; i++) - words[i / 2] |= (src[i] << ((1 - (i % 2)) << 3)); - best.base = -1; - best.len = 0; - cur.base = -1; - cur.len = 0; - for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) { - if (words[i] == 0) { - if (cur.base == -1) - cur.base = i, cur.len = 1; - else - cur.len++; - } else { - if (cur.base != -1) { - if (best.base == -1 || cur.len > best.len) - best = cur; - cur.base = -1; - } - } - } - if (cur.base != -1) { - if (best.base == -1 || cur.len > best.len) - best = cur; - } - if (best.base != -1 && best.len < 2) - best.base = -1; - - /* - * Format the result. - */ - tp = tmp; - for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) { - /* Are we inside the best run of 0x00's? */ - if (best.base != -1 && i >= best.base && - i < (best.base + best.len)) { - if (i == best.base) - *tp++ = ':'; - continue; - } - /* Are we following an initial run of 0x00s or any real hex? */ - if (i != 0) - *tp++ = ':'; - /* Is this address an encapsulated IPv4? */ - if (i == 6 && best.base == 0 && (best.len == 6 || - (best.len == 7 && words[7] != 0x0001) || - (best.len == 5 && words[5] == 0xffff))) { - if (!inet_ntop4(src+12, tp, sizeof tmp - (tp - tmp))) - return (NULL); - tp += strlen(tp); - break; - } - tp += std::sprintf(tp, "%x", words[i]); // **** - } - /* Was it a trailing run of 0x00's? */ - if (best.base != -1 && (best.base + best.len) == - (NS_IN6ADDRSZ / NS_INT16SZ)) - *tp++ = ':'; - *tp++ = '\0'; - - /* - * Check for overflow, copy, and we're done. - */ - if ((socklen_t)(tp - tmp) > size) { - return (NULL); - } - strcpy(dst, tmp); - return (dst); -} -#endif - -int srs_librtmp_context_parse_uri(Context* context) -{ - int ret = ERROR_SUCCESS; - - std::string schema; - - srs_parse_rtmp_url(context->url, context->tcUrl, context->stream); - - // when connect, we only need to parse the tcUrl - srs_discovery_tc_url(context->tcUrl, - schema, context->host, context->vhost, context->app, context->stream, context->port, - context->param); - - return ret; -} - -int srs_librtmp_context_resolve_host(Context* context) -{ - int ret = ERROR_SUCCESS; - - // connect to server:port - int family = AF_UNSPEC; - context->ip = srs_dns_resolve(context->host, family); - if (context->ip.empty()) { - return ERROR_SYSTEM_DNS_RESOLVE; - } - - return ret; -} - -int srs_librtmp_context_connect(Context* context) -{ - int ret = ERROR_SUCCESS; - - srs_assert(context->skt); - - std::string ip = context->ip; - if ((ret = context->skt->connect(ip.c_str(), context->port)) != ERROR_SUCCESS) { - return ret; - } - - return ret; -} - -#ifdef __cplusplus -extern "C"{ -#endif - -int srs_version_major() -{ - return VERSION_MAJOR; -} - -int srs_version_minor() -{ - return VERSION_MINOR; -} - -int srs_version_revision() -{ - return VERSION_REVISION; -} - -srs_rtmp_t srs_rtmp_create(const char* url) -{ - int ret = ERROR_SUCCESS; - - Context* context = new Context(); - context->url = url; - - // create socket - srs_freep(context->skt); - context->skt = new SimpleSocketStream(); - - if ((ret = context->skt->create_socket(context)) != ERROR_SUCCESS) { - srs_human_error("Create socket failed, ret=%d", ret); - - // free the context and return NULL - srs_freep(context); - return NULL; - } - - return context; -} - -int srs_rtmp_set_timeout(srs_rtmp_t rtmp, int recv_timeout_ms, int send_timeout_ms) -{ - int ret = ERROR_SUCCESS; - - if (!rtmp) { - return ret; - } - - Context* context = (Context*)rtmp; - - context->stimeout = send_timeout_ms; - context->rtimeout = recv_timeout_ms; - - context->skt->set_recv_timeout(context->rtimeout * SRS_UTIME_MILLISECONDS); - context->skt->set_send_timeout(context->stimeout * SRS_UTIME_MILLISECONDS); - - return ret; -} - -void srs_rtmp_destroy(srs_rtmp_t rtmp) -{ - if (!rtmp) { - return; - } - - Context* context = (Context*)rtmp; - - srs_freep(context); -} - -int srs_rtmp_handshake(srs_rtmp_t rtmp) -{ - int ret = ERROR_SUCCESS; - - if ((ret = srs_rtmp_dns_resolve(rtmp)) != ERROR_SUCCESS) { - return ret; - } - - if ((ret = srs_rtmp_connect_server(rtmp)) != ERROR_SUCCESS) { - return ret; - } - - if ((ret = srs_rtmp_do_simple_handshake(rtmp)) != ERROR_SUCCESS) { - return ret; - } - - return ret; -} - -int srs_rtmp_dns_resolve(srs_rtmp_t rtmp) -{ - int ret = ERROR_SUCCESS; - - srs_assert(rtmp != NULL); - Context* context = (Context*)rtmp; - - // parse uri - if ((ret = srs_librtmp_context_parse_uri(context)) != ERROR_SUCCESS) { - return ret; - } - // resolve host - if ((ret = srs_librtmp_context_resolve_host(context)) != ERROR_SUCCESS) { - return ret; - } - - return ret; -} - -int srs_rtmp_connect_server(srs_rtmp_t rtmp) -{ - int ret = ERROR_SUCCESS; - - srs_assert(rtmp != NULL); - Context* context = (Context*)rtmp; - - // set timeout if user not set. - if (context->stimeout == SRS_UTIME_NO_TIMEOUT) { - context->stimeout = SRS_SOCKET_DEFAULT_TMMS; - context->skt->set_send_timeout(context->stimeout * SRS_UTIME_MILLISECONDS); - } - if (context->rtimeout == SRS_UTIME_NO_TIMEOUT) { - context->rtimeout = SRS_SOCKET_DEFAULT_TMMS; - context->skt->set_recv_timeout(context->rtimeout * SRS_UTIME_MILLISECONDS); - } - - if ((ret = srs_librtmp_context_connect(context)) != ERROR_SUCCESS) { - return ret; - } - - return ret; -} - -int srs_rtmp_do_complex_handshake(srs_rtmp_t rtmp) -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - srs_assert(rtmp != NULL); - Context* context = (Context*)rtmp; - - srs_assert(context->skt != NULL); - - // simple handshake - srs_freep(context->rtmp); - context->rtmp = new SrsRtmpClient(context->skt); - - if ((err = context->rtmp->complex_handshake()) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - - return ret; -} - -int srs_rtmp_do_simple_handshake(srs_rtmp_t rtmp) -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - srs_assert(rtmp != NULL); - Context* context = (Context*)rtmp; - - srs_assert(context->skt != NULL); - - // simple handshake - srs_freep(context->rtmp); - context->rtmp = new SrsRtmpClient(context->skt); - - if ((err = context->rtmp->simple_handshake()) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - - return ret; -} - -int srs_rtmp_set_connect_args(srs_rtmp_t rtmp, const char* tcUrl, const char* swfUrl, const char* pageUrl, srs_amf0_t args) -{ - int ret = ERROR_SUCCESS; - - srs_assert(rtmp != NULL); - Context* context = (Context*)rtmp; - - srs_freep(context->req); - context->req = new SrsRequest(); - - if (args) { - context->req->args = (SrsAmf0Object*)args; - } - if (tcUrl) { - context->req->tcUrl = tcUrl; - } - if (swfUrl) { - context->req->swfUrl = swfUrl; - } - if (pageUrl) { - context->req->pageUrl = pageUrl; - } - - return ret; -} - -int srs_rtmp_set_schema(srs_rtmp_t rtmp, enum srs_url_schema schema) -{ - int ret = ERROR_SUCCESS; - - srs_assert(rtmp != NULL); - Context* context = (Context*)rtmp; - - context->schema = schema; - - return ret; -} - -int srs_rtmp_connect_app(srs_rtmp_t rtmp) -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - srs_assert(rtmp != NULL); - Context* context = (Context*)rtmp; - - string tcUrl; - switch(context->schema) { - // For SRS3, only use one format url. - case srs_url_schema_normal: - case srs_url_schema_via: - case srs_url_schema_vis: - case srs_url_schema_vis2: - tcUrl = srs_generate_tc_url(context->ip, context->vhost, context->app, context->port); - default: - break; - } - - Context* c = context; - if ((err = context->rtmp->connect_app(c->app, tcUrl, c->req, true, &c->si)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - - return ret; -} - -int srs_rtmp_get_server_id(srs_rtmp_t rtmp, char** ip, int* pid, int* cid) -{ - int ret = ERROR_SUCCESS; - - Context* context = (Context*)rtmp; - *pid = context->si.pid; - *cid = context->si.cid; - *ip = context->si.ip.empty()? NULL:(char*)context->si.ip.c_str(); - - return ret; -} - -int srs_rtmp_get_server_sig(srs_rtmp_t rtmp, char** sig) -{ - int ret = ERROR_SUCCESS; - - Context* context = (Context*)rtmp; - *sig = context->si.sig.empty()? NULL:(char*)context->si.sig.c_str(); - - return ret; -} - -int srs_rtmp_get_server_version(srs_rtmp_t rtmp, int* major, int* minor, int* revision, int* build) -{ - int ret = ERROR_SUCCESS; - - Context* context = (Context*)rtmp; - *major = context->si.major; - *minor = context->si.minor; - *revision = context->si.revision; - *build = context->si.build; - - return ret; -} - -int srs_rtmp_play_stream(srs_rtmp_t rtmp) -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - srs_assert(rtmp != NULL); - Context* context = (Context*)rtmp; - - if ((err = context->rtmp->create_stream(context->stream_id)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - - // Pass params in stream, @see https://github.com/ossrs/srs/issues/1031#issuecomment-409745733 - string stream = srs_generate_stream_with_query(context->host, context->vhost, context->stream, context->param); - - if ((err = context->rtmp->play(stream, context->stream_id, SRS_CONSTS_RTMP_PROTOCOL_CHUNK_SIZE)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - - return ret; -} - -int srs_rtmp_publish_stream(srs_rtmp_t rtmp) -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - srs_assert(rtmp != NULL); - Context* context = (Context*)rtmp; - - // Pass params in stream, @see https://github.com/ossrs/srs/issues/1031#issuecomment-409745733 - string stream = srs_generate_stream_with_query(context->host, context->vhost, context->stream, context->param); - - if ((err = context->rtmp->fmle_publish(stream, context->stream_id)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - - return ret; -} - -int srs_rtmp_bandwidth_check(srs_rtmp_t rtmp, - int64_t* start_time, int64_t* end_time, - int* play_kbps, int* publish_kbps, - int* play_bytes, int* publish_bytes, - int* play_duration, int* publish_duration -) { - *start_time = 0; - *end_time = 0; - *play_kbps = 0; - *publish_kbps = 0; - *play_bytes = 0; - *publish_bytes = 0; - *play_duration = 0; - *publish_duration = 0; - - int ret = ERROR_SUCCESS; - - srs_assert(rtmp != NULL); - Context* context = (Context*)rtmp; - - SrsBandwidthClient client; - - if ((ret = client.initialize(context->rtmp)) != ERROR_SUCCESS) { - return ret; - } - - if ((ret = client.bandwidth_check( - start_time, end_time, play_kbps, publish_kbps, - play_bytes, publish_bytes, play_duration, publish_duration)) != ERROR_SUCCESS - ) { - return ret; - } - - return ret; -} - - -int srs_rtmp_on_aggregate(Context* context, SrsCommonMessage* msg) -{ - int ret = ERROR_SUCCESS; - - SrsBuffer* stream = new SrsBuffer(msg->payload, msg->size); - SrsAutoFree(SrsBuffer, stream); - - // the aggregate message always use abs time. - int delta = -1; - - while (!stream->empty()) { - if (!stream->require(1)) { - ret = ERROR_RTMP_AGGREGATE; - srs_error("invalid aggregate message type. ret=%d", ret); - return ret; - } - int8_t type = stream->read_1bytes(); - - if (!stream->require(3)) { - ret = ERROR_RTMP_AGGREGATE; - srs_error("invalid aggregate message size. ret=%d", ret); - return ret; - } - int32_t data_size = stream->read_3bytes(); - - if (data_size < 0) { - ret = ERROR_RTMP_AGGREGATE; - srs_error("invalid aggregate message size(negative). ret=%d", ret); - return ret; - } - - if (!stream->require(3)) { - ret = ERROR_RTMP_AGGREGATE; - srs_error("invalid aggregate message time. ret=%d", ret); - return ret; - } - int32_t timestamp = stream->read_3bytes(); - - if (!stream->require(1)) { - ret = ERROR_RTMP_AGGREGATE; - srs_error("invalid aggregate message time(high). ret=%d", ret); - return ret; - } - int32_t time_h = stream->read_1bytes(); - - timestamp |= time_h<<24; - timestamp &= 0x7FFFFFFF; - - // adjust abs timestamp in aggregate msg. - if (delta < 0) { - delta = (int)msg->header.timestamp - (int)timestamp; - } - timestamp += delta; - - if (!stream->require(3)) { - ret = ERROR_RTMP_AGGREGATE; - srs_error("invalid aggregate message stream_id. ret=%d", ret); - return ret; - } - int32_t stream_id = stream->read_3bytes(); - - if (data_size > 0 && !stream->require(data_size)) { - ret = ERROR_RTMP_AGGREGATE; - srs_error("invalid aggregate message data. ret=%d", ret); - return ret; - } - - // to common message. - SrsCommonMessage o; - - o.header.message_type = type; - o.header.payload_length = data_size; - o.header.timestamp_delta = timestamp; - o.header.timestamp = timestamp; - o.header.stream_id = stream_id; - o.header.perfer_cid = msg->header.perfer_cid; - - if (data_size > 0) { - o.size = data_size; - o.payload = new char[o.size]; - stream->read_bytes(o.payload, o.size); - } - - if (!stream->require(4)) { - ret = ERROR_RTMP_AGGREGATE; - srs_error("invalid aggregate message previous tag size. ret=%d", ret); - return ret; - } - stream->read_4bytes(); - - // process parsed message - SrsCommonMessage* parsed_msg = new SrsCommonMessage(); - parsed_msg->header = o.header; - parsed_msg->payload = o.payload; - parsed_msg->size = o.size; - o.payload = NULL; - context->msgs.push_back(parsed_msg); - } - - return ret; -} - -int srs_rtmp_go_packet(Context* context, SrsCommonMessage* msg, - char* type, uint32_t* timestamp, char** data, int* size, - bool* got_msg -) { - int ret = ERROR_SUCCESS; - - // generally we got a message. - *got_msg = true; - - if (msg->header.is_audio()) { - *type = SRS_RTMP_TYPE_AUDIO; - *timestamp = (uint32_t)msg->header.timestamp; - *data = (char*)msg->payload; - *size = (int)msg->size; - // detach bytes from packet. - msg->payload = NULL; - } else if (msg->header.is_video()) { - *type = SRS_RTMP_TYPE_VIDEO; - *timestamp = (uint32_t)msg->header.timestamp; - *data = (char*)msg->payload; - *size = (int)msg->size; - // detach bytes from packet. - msg->payload = NULL; - } else if (msg->header.is_amf0_data() || msg->header.is_amf3_data()) { - *type = SRS_RTMP_TYPE_SCRIPT; - *data = (char*)msg->payload; - *size = (int)msg->size; - // detach bytes from packet. - msg->payload = NULL; - } else if (msg->header.is_aggregate()) { - if ((ret = srs_rtmp_on_aggregate(context, msg)) != ERROR_SUCCESS) { - return ret; - } - *got_msg = false; - } else { - *type = msg->header.message_type; - *data = (char*)msg->payload; - *size = (int)msg->size; - // detach bytes from packet. - msg->payload = NULL; - } - - return ret; -} - -int srs_rtmp_read_packet(srs_rtmp_t rtmp, char* type, uint32_t* timestamp, char** data, int* size) -{ - *type = 0; - *timestamp = 0; - *data = NULL; - *size = 0; - - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - srs_assert(rtmp != NULL); - Context* context = (Context*)rtmp; - - for (;;) { - SrsCommonMessage* msg = NULL; - - // read from cache first. - if (!context->msgs.empty()) { - std::vector::iterator it = context->msgs.begin(); - msg = *it; - context->msgs.erase(it); - } - - // read from protocol sdk. - if (!msg && (err = context->rtmp->recv_message(&msg)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - - // no msg, try again. - if (!msg) { - continue; - } - - SrsAutoFree(SrsCommonMessage, msg); - - // process the got packet, if nothing, try again. - bool got_msg; - if ((ret = srs_rtmp_go_packet(context, msg, type, timestamp, data, size, &got_msg)) != ERROR_SUCCESS) { - return ret; - } - - // got expected message. - if (got_msg) { - break; - } - } - - return ret; -} - -int srs_rtmp_write_packet(srs_rtmp_t rtmp, char type, uint32_t timestamp, char* data, int size) -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - srs_assert(rtmp != NULL); - Context* context = (Context*)rtmp; - - SrsSharedPtrMessage* msg = NULL; - - if ((err = srs_rtmp_create_msg(type, timestamp, data, size, context->stream_id, &msg)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - - srs_assert(msg); - - // send out encoded msg. - if ((err = context->rtmp->send_and_free_message(msg, context->stream_id)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - - return ret; -} - -void srs_rtmp_free_packet(char* data) -{ - srs_freepa(data); -} - -srs_bool srs_rtmp_is_onMetaData(char type, char* data, int size) -{ - srs_error_t err = srs_success; - - if (type != SRS_RTMP_TYPE_SCRIPT) { - return false; - } - - SrsBuffer stream(data, size); - - std::string name; - if ((err = srs_amf0_read_string(&stream, name)) != srs_success) { - srs_freep(err); - return false; - } - - if (name == SRS_CONSTS_RTMP_ON_METADATA) { - return true; - } - - if (name == SRS_CONSTS_RTMP_SET_DATAFRAME) { - return true; - } - - return false; -} - -/** - * directly write a audio frame. - */ -int srs_write_audio_raw_frame(Context* context, char* frame, int frame_size, SrsRawAacStreamCodec* codec, uint32_t timestamp) -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - char* data = NULL; - int size = 0; - if ((err = context->aac_raw.mux_aac2flv(frame, frame_size, codec, timestamp, &data, &size)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - - return srs_rtmp_write_packet(context, SRS_RTMP_TYPE_AUDIO, timestamp, data, size); -} - -/** - * write aac frame in adts. - */ -int srs_write_aac_adts_frame(Context* context, SrsRawAacStreamCodec* codec, char* frame, int frame_size, uint32_t timestamp) -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - // send out aac sequence header if not sent. - if (context->aac_specific_config.empty()) { - std::string sh; - if ((err = context->aac_raw.mux_sequence_header(codec, sh)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - context->aac_specific_config = sh; - - codec->aac_packet_type = 0; - - if ((ret = srs_write_audio_raw_frame(context, (char*)sh.data(), (int)sh.length(), codec, timestamp)) != ERROR_SUCCESS) { - return ret; - } - } - - codec->aac_packet_type = 1; - return srs_write_audio_raw_frame(context, frame, frame_size, codec, timestamp); -} - -/** - * write aac frames in adts. - */ -int srs_write_aac_adts_frames(Context* context, char sound_format, char sound_rate, - char sound_size, char sound_type, char* frames, int frames_size, uint32_t timestamp -) { - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - SrsBuffer* stream = new SrsBuffer(frames, frames_size); - SrsAutoFree(SrsBuffer, stream); - - while (!stream->empty()) { - char* frame = NULL; - int frame_size = 0; - SrsRawAacStreamCodec codec; - if ((err = context->aac_raw.adts_demux(stream, &frame, &frame_size, codec)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - - // override by user specified. - codec.sound_format = sound_format; - codec.sound_rate = sound_rate; - codec.sound_size = sound_size; - codec.sound_type = sound_type; - - if ((ret = srs_write_aac_adts_frame(context, &codec, frame, frame_size, timestamp)) != ERROR_SUCCESS) { - return ret; - } - } - - return ret; -} - -/** - * write audio raw frame to SRS. - */ -int srs_audio_write_raw_frame(srs_rtmp_t rtmp, char sound_format, char sound_rate, - char sound_size, char sound_type, char* frame, int frame_size, uint32_t timestamp -) { - int ret = ERROR_SUCCESS; - - Context* context = (Context*)rtmp; - srs_assert(context); - - if (sound_format == SrsAudioCodecIdAAC) { - // for aac, the frame must be ADTS format. - if (!srs_aac_is_adts(frame, frame_size)) { - return ERROR_AAC_REQUIRED_ADTS; - } - - // for aac, demux the ADTS to RTMP format. - return srs_write_aac_adts_frames(context, sound_format, sound_rate, sound_size, sound_type, frame, frame_size, timestamp); - } else { - // use codec info for aac. - SrsRawAacStreamCodec codec; - codec.sound_format = sound_format; - codec.sound_rate = sound_rate; - codec.sound_size = sound_size; - codec.sound_type = sound_type; - codec.aac_packet_type = 0; - - // for other data, directly write frame. - return srs_write_audio_raw_frame(context, frame, frame_size, &codec, timestamp); - } - - return ret; -} - -/** - * whether aac raw data is in adts format, - * which bytes sequence matches '1111 1111 1111'B, that is 0xFFF. - */ -srs_bool srs_aac_is_adts(char* aac_raw_data, int ac_raw_size) -{ - SrsBuffer stream(aac_raw_data, ac_raw_size); - return srs_aac_startswith_adts(&stream); -} - -/** - * parse the adts header to get the frame size. - */ -int srs_aac_adts_frame_size(char* aac_raw_data, int ac_raw_size) -{ - int size = -1; - - if (!srs_aac_is_adts(aac_raw_data, ac_raw_size)) { - return size; - } - - // adts always 7bytes. - if (ac_raw_size <= 7) { - return size; - } - - // last 2bits - int16_t ch3 = aac_raw_data[3]; - // whole 8bits - int16_t ch4 = aac_raw_data[4]; - // first 3bits - int16_t ch5 = aac_raw_data[5]; - - size = ((ch3 << 11) & 0x1800) | ((ch4 << 3) & 0x07f8) | ((ch5 >> 5) & 0x0007); - - return size; -} - -/** - * write h264 IPB-frame. - */ -int srs_write_h264_ipb_frame(Context* context, char* frame, int frame_size, uint32_t dts, uint32_t pts) -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - // when sps or pps not sent, ignore the packet. - // @see https://github.com/ossrs/srs/issues/203 - if (!context->h264_sps_pps_sent) { - return ERROR_H264_DROP_BEFORE_SPS_PPS; - } - - // 5bits, 7.3.1 NAL unit syntax, - // ISO_IEC_14496-10-AVC-2003.pdf, page 44. - // 5: I Frame, 1: P/B Frame - // @remark we already group sps/pps to sequence header frame; - // for I/P NALU, we send them in isolate frame, each NALU in a frame; - // for other NALU, for example, AUD/SEI, we just ignore them, because - // AUD used in annexb to split frame, while SEI generally we can ignore it. - // TODO: maybe we should group all NALUs split by AUD to a frame. - SrsAvcNaluType nut = (SrsAvcNaluType)(frame[0] & 0x1f); - if (nut != SrsAvcNaluTypeIDR && nut != SrsAvcNaluTypeNonIDR) { - return ret; - } - - // for IDR frame, the frame is keyframe. - SrsVideoAvcFrameType frame_type = SrsVideoAvcFrameTypeInterFrame; - if (nut == SrsAvcNaluTypeIDR) { - frame_type = SrsVideoAvcFrameTypeKeyFrame; - } - - std::string ibp; - if ((err = context->avc_raw.mux_ipb_frame(frame, frame_size, ibp)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - - int8_t avc_packet_type = SrsVideoAvcFrameTraitNALU; - char* flv = NULL; - int nb_flv = 0; - if ((err = context->avc_raw.mux_avc2flv(ibp, frame_type, avc_packet_type, dts, pts, &flv, &nb_flv)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - - // the timestamp in rtmp message header is dts. - uint32_t timestamp = dts; - return srs_rtmp_write_packet(context, SRS_RTMP_TYPE_VIDEO, timestamp, flv, nb_flv); -} - -/** - * write the h264 sps/pps in context over RTMP. - */ -int srs_write_h264_sps_pps(Context* context, uint32_t dts, uint32_t pts) -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - // send when sps or pps changed. - if (!context->h264_sps_changed && !context->h264_pps_changed) { - return ret; - } - - // h264 raw to h264 packet. - std::string sh; - if ((err = context->avc_raw.mux_sequence_header(context->h264_sps, context->h264_pps, dts, pts, sh)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - - // 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 = context->avc_raw.mux_avc2flv(sh, frame_type, avc_packet_type, dts, pts, &flv, &nb_flv)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - - // reset sps and pps. - context->h264_sps_changed = false; - context->h264_pps_changed = false; - context->h264_sps_pps_sent = true; - - // the timestamp in rtmp message header is dts. - uint32_t timestamp = dts; - return srs_rtmp_write_packet(context, SRS_RTMP_TYPE_VIDEO, timestamp, flv, nb_flv); -} - -/** - * write h264 raw frame, maybe sps/pps/IPB-frame. - */ -int srs_write_h264_raw_frame(Context* context, char* frame, int frame_size, uint32_t dts, uint32_t pts) -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - // empty frame. - if (frame_size <= 0) { - return ret; - } - - // for sps - if (context->avc_raw.is_sps(frame, frame_size)) { - std::string sps; - if ((err = context->avc_raw.sps_demux(frame, frame_size, sps)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - - if (context->h264_sps == sps) { - return ERROR_H264_DUPLICATED_SPS; - } - context->h264_sps_changed = true; - context->h264_sps = sps; - - return ret; - } - - // for pps - if (context->avc_raw.is_pps(frame, frame_size)) { - std::string pps; - if ((err = context->avc_raw.pps_demux(frame, frame_size, pps)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - - if (context->h264_pps == pps) { - return ERROR_H264_DUPLICATED_PPS; - } - context->h264_pps_changed = true; - context->h264_pps = pps; - - return ret; - } - - // ignore others. - // 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, 9: AUD - SrsAvcNaluType nut = (SrsAvcNaluType)(frame[0] & 0x1f); - if (nut != SrsAvcNaluTypeSPS && nut != SrsAvcNaluTypePPS - && nut != SrsAvcNaluTypeIDR && nut != SrsAvcNaluTypeNonIDR - && nut != SrsAvcNaluTypeAccessUnitDelimiter - ) { - return ret; - } - - // send pps+sps before ipb frames when sps/pps changed. - if ((ret = srs_write_h264_sps_pps(context, dts, pts)) != ERROR_SUCCESS) { - return ret; - } - - // ibp frame. - return srs_write_h264_ipb_frame(context, frame, frame_size, dts, pts); -} - -/** - * write h264 multiple frames, in annexb format. - */ -int srs_h264_write_raw_frames(srs_rtmp_t rtmp, char* frames, int frames_size, uint32_t dts, uint32_t pts) -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - srs_assert(frames != NULL); - srs_assert(frames_size > 0); - - srs_assert(rtmp != NULL); - Context* context = (Context*)rtmp; - - SrsBuffer* stream = new SrsBuffer(frames, frames_size); - SrsAutoFree(SrsBuffer, stream); - - // use the last error - // @see https://github.com/ossrs/srs/issues/203 - // @see https://github.com/ossrs/srs/issues/204 - int error_code_return = ret; - - // send each frame. - while (!stream->empty()) { - char* frame = NULL; - int frame_size = 0; - if ((err = context->avc_raw.annexb_demux(stream, &frame, &frame_size)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - - // ignore invalid frame, - // atleast 1bytes for SPS to decode the type - if (frame_size <= 0) { - continue; - } - - // it may be return error, but we must process all packets. - if ((ret = srs_write_h264_raw_frame(context, frame, frame_size, dts, pts)) != ERROR_SUCCESS) { - error_code_return = ret; - - // ignore known error, process all packets. - if (srs_h264_is_dvbsp_error(ret) - || srs_h264_is_duplicated_sps_error(ret) - || srs_h264_is_duplicated_pps_error(ret) - ) { - continue; - } - - return ret; - } - } - - return error_code_return; -} - -srs_bool srs_h264_is_dvbsp_error(int error_code) -{ - return error_code == ERROR_H264_DROP_BEFORE_SPS_PPS; -} - -srs_bool srs_h264_is_duplicated_sps_error(int error_code) -{ - return error_code == ERROR_H264_DUPLICATED_SPS; -} - -srs_bool srs_h264_is_duplicated_pps_error(int error_code) -{ - return error_code == ERROR_H264_DUPLICATED_PPS; -} - -srs_bool srs_h264_startswith_annexb(char* h264_raw_data, int h264_raw_size, int* pnb_start_code) -{ - SrsBuffer stream(h264_raw_data, h264_raw_size); - return srs_avc_startswith_annexb(&stream, pnb_start_code); -} - -struct Mp4Context -{ - SrsFileReader reader; - SrsMp4Decoder dec; -}; - -srs_mp4_t srs_mp4_open_read(const char* file) -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - Mp4Context* mp4 = new Mp4Context(); - - if ((err = mp4->reader.open(file)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - srs_human_error("Open MP4 file failed, ret=%d", ret); - - srs_freep(mp4); - return NULL; - } - - return mp4; -} - -void srs_mp4_close(srs_mp4_t mp4) -{ - Mp4Context* context = (Mp4Context*)mp4; - srs_freep(context); -} - -int srs_mp4_init_demuxer(srs_mp4_t mp4) -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - Mp4Context* context = (Mp4Context*)mp4; - - if ((err = context->dec.initialize(&context->reader)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - - return ret; -} - -int srs_mp4_read_sample(srs_mp4_t mp4, srs_mp4_sample_t* s) -{ - s->sample = NULL; - - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - Mp4Context* context = (Mp4Context*)mp4; - SrsMp4Decoder* dec = &context->dec; - - SrsMp4HandlerType ht = SrsMp4HandlerTypeForbidden; - if ((err = dec->read_sample(&ht, &s->frame_type, &s->frame_trait, &s->dts, &s->pts, &s->sample, &s->nb_sample)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - - if (ht == SrsMp4HandlerTypeForbidden) { - return ERROR_MP4_ILLEGAL_HANDLER; - } - - if (ht == SrsMp4HandlerTypeSOUN) { - s->codec = (uint16_t)dec->acodec; - s->sample_rate = dec->sample_rate; - s->channels = dec->channels; - s->sound_bits = dec->sound_bits; - } else { - s->codec = (uint16_t)dec->vcodec; - } - s->handler_type = (uint32_t)ht; - - return ret; -} - -void srs_mp4_free_sample(srs_mp4_sample_t* s) -{ - srs_freepa(s->sample); -} - -int32_t srs_mp4_sizeof(srs_mp4_t mp4, srs_mp4_sample_t* s) -{ - if (s->handler_type == SrsMp4HandlerTypeSOUN) { - if (s->codec == (uint16_t)SrsAudioCodecIdAAC) { - return s->nb_sample + 2; - } - return s->nb_sample + 1; - } - - if (s->codec == (uint16_t)SrsVideoCodecIdAVC) { - return s->nb_sample + 5; - } - return s->nb_sample + 1; -} - -int srs_mp4_to_flv_tag(srs_mp4_t mp4, srs_mp4_sample_t* s, char* type, uint32_t* time, char* data, int32_t size) -{ - int ret = ERROR_SUCCESS; - - *time = s->dts; - - SrsBuffer p(data, size); - if (s->handler_type == SrsMp4HandlerTypeSOUN) { - *type = SRS_RTMP_TYPE_AUDIO; - - // E.4.2.1 AUDIODATA, flv_v10_1.pdf, page 3 - p.write_1bytes(uint8_t(s->codec << 4) | uint8_t(s->sample_rate << 2) | uint8_t(s->sound_bits << 1) | s->channels); - if (s->codec == SrsAudioCodecIdAAC) { - p.write_1bytes(uint8_t(s->frame_trait == (uint16_t)SrsAudioAacFrameTraitSequenceHeader? 0:1)); - } - - p.write_bytes((char*)s->sample, s->nb_sample); - return ret; - } - - // E.4.3.1 VIDEODATA, flv_v10_1.pdf, page 5 - p.write_1bytes(uint8_t(s->frame_type<<4) | uint8_t(s->codec)); - if (s->codec == SrsVideoCodecIdAVC || s->codec == SrsVideoCodecIdHEVC || s->codec == SrsVideoCodecIdAV1) { - *type = SRS_RTMP_TYPE_VIDEO; - - p.write_1bytes(uint8_t(s->frame_trait == (uint16_t)SrsVideoAvcFrameTraitSequenceHeader? 0:1)); - // cts = pts - dts, where dts = flvheader->timestamp. - uint32_t cts = s->pts - s->dts; - p.write_3bytes(cts); - } - p.write_bytes((char*)s->sample, s->nb_sample); - - return ret; -} - -srs_bool srs_mp4_is_eof(int error_code) -{ - return error_code == ERROR_SYSTEM_FILE_EOF; -} - -struct FlvContext -{ - SrsFileReader reader; - SrsFileWriter writer; - SrsFlvTransmuxer enc; - SrsFlvDecoder dec; -}; - -srs_flv_t srs_flv_open_read(const char* file) -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - FlvContext* flv = new FlvContext(); - - if ((err = flv->reader.open(file)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - srs_human_error("Open FLV file failed, ret=%d", ret); - - srs_freep(flv); - return NULL; - } - - if ((err = flv->dec.initialize(&flv->reader)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - srs_human_error("Initialize FLV demuxer failed, ret=%d", ret); - - srs_freep(flv); - return NULL; - } - - return flv; -} - -srs_flv_t srs_flv_open_write(const char* file) -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - FlvContext* flv = new FlvContext(); - - if ((err = flv->writer.open(file)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - srs_human_error("Open FLV file failed, ret=%d", ret); - - srs_freep(flv); - return NULL; - } - - if ((err = flv->enc.initialize(&flv->writer)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - srs_human_error("Initilize FLV muxer failed, ret=%d", ret); - - srs_freep(flv); - return NULL; - } - - return flv; -} - -void srs_flv_close(srs_flv_t flv) -{ - FlvContext* context = (FlvContext*)flv; - srs_freep(context); -} - -int srs_flv_read_header(srs_flv_t flv, char header[9]) -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - FlvContext* context = (FlvContext*)flv; - - if (!context->reader.is_open()) { - return ERROR_SYSTEM_IO_INVALID; - } - - if ((err = context->dec.read_header(header)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - - char ts[4]; // tag size - if ((err = context->dec.read_previous_tag_size(ts)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - - return ret; -} - -int srs_flv_read_tag_header(srs_flv_t flv, char* ptype, int32_t* pdata_size, uint32_t* ptime) -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - FlvContext* context = (FlvContext*)flv; - - if (!context->reader.is_open()) { - return ERROR_SYSTEM_IO_INVALID; - } - - if ((err = context->dec.read_tag_header(ptype, pdata_size, ptime)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - - return ret; -} - -int srs_flv_read_tag_data(srs_flv_t flv, char* data, int32_t size) -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - FlvContext* context = (FlvContext*)flv; - - if (!context->reader.is_open()) { - return ERROR_SYSTEM_IO_INVALID; - } - - if ((err = context->dec.read_tag_data(data, size)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - - char ts[4]; // tag size - if ((err = context->dec.read_previous_tag_size(ts)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - - return ret; -} - -int srs_flv_write_header(srs_flv_t flv, char header[9]) -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - FlvContext* context = (FlvContext*)flv; - - if (!context->writer.is_open()) { - return ERROR_SYSTEM_IO_INVALID; - } - - if ((err = context->enc.write_header(header)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - - return ret; -} - -int srs_flv_write_tag(srs_flv_t flv, char type, int32_t time, char* data, int size) -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - FlvContext* context = (FlvContext*)flv; - - if (!context->writer.is_open()) { - return ERROR_SYSTEM_IO_INVALID; - } - - if (type == SRS_RTMP_TYPE_AUDIO) { - if ((err = context->enc.write_audio(time, data, size)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - } else if (type == SRS_RTMP_TYPE_VIDEO) { - if ((err = context->enc.write_video(time, data, size)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - } else { - if ((err = context->enc.write_metadata(type, data, size)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - } - - return ret; -} - -int srs_flv_size_tag(int data_size) -{ - return SrsFlvTransmuxer::size_tag(data_size); -} - -int64_t srs_flv_tellg(srs_flv_t flv) -{ - FlvContext* context = (FlvContext*)flv; - return context->reader.tellg(); -} - -void srs_flv_lseek(srs_flv_t flv, int64_t offset) -{ - FlvContext* context = (FlvContext*)flv; - int64_t r0 = context->reader.seek2(offset); - srs_assert(r0 != -1); -} - -srs_bool srs_flv_is_eof(int error_code) -{ - return error_code == ERROR_SYSTEM_FILE_EOF; -} - -srs_bool srs_flv_is_sequence_header(char* data, int32_t size) -{ - return SrsFlvVideo::sh(data, (int)size); -} - -srs_bool srs_flv_is_keyframe(char* data, int32_t size) -{ - return SrsFlvVideo::keyframe(data, (int)size); -} - -srs_amf0_t srs_amf0_parse(char* data, int size, int* nparsed) -{ - srs_error_t err = srs_success; - - srs_amf0_t amf0 = NULL; - - SrsBuffer stream(data, size); - - SrsAmf0Any* any = NULL; - if ((err = SrsAmf0Any::discovery(&stream, &any)) != srs_success) { - srs_freep(err); - return amf0; - } - - stream.skip(-1 * stream.pos()); - if ((err = any->read(&stream)) != srs_success) { - srs_freep(err); - srs_freep(any); - return amf0; - } - - if (nparsed) { - *nparsed = stream.pos(); - } - amf0 = (srs_amf0_t)any; - - return amf0; -} - -srs_amf0_t srs_amf0_create_string(const char* value) -{ - return SrsAmf0Any::str(value); -} - -srs_amf0_t srs_amf0_create_number(srs_amf0_number value) -{ - return SrsAmf0Any::number(value); -} - -srs_amf0_t srs_amf0_create_ecma_array() -{ - return SrsAmf0Any::ecma_array(); -} - -srs_amf0_t srs_amf0_create_strict_array() -{ - return SrsAmf0Any::strict_array(); -} - -srs_amf0_t srs_amf0_create_object() -{ - return SrsAmf0Any::object(); -} - -srs_amf0_t srs_amf0_ecma_array_to_object(srs_amf0_t ecma_arr) -{ - srs_assert(srs_amf0_is_ecma_array(ecma_arr)); - - SrsAmf0EcmaArray* arr = (SrsAmf0EcmaArray*)ecma_arr; - SrsAmf0Object* obj = SrsAmf0Any::object(); - - for (int i = 0; i < arr->count(); i++) { - std::string key = arr->key_at(i); - SrsAmf0Any* value = arr->value_at(i); - obj->set(key, value->copy()); - } - - return obj; -} - -void srs_amf0_free(srs_amf0_t amf0) -{ - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - srs_freep(any); -} - -int srs_amf0_size(srs_amf0_t amf0) -{ - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - return any->total_size(); -} - -int srs_amf0_serialize(srs_amf0_t amf0, char* data, int size) -{ - int ret = ERROR_SUCCESS; - srs_error_t err = srs_success; - - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - - SrsBuffer stream(data, size); - - if ((err = any->write(&stream)) != srs_success) { - ret = srs_error_code(err); - srs_freep(err); - return ret; - } - - return ret; -} - -srs_bool srs_amf0_is_string(srs_amf0_t amf0) -{ - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - return any->is_string(); -} - -srs_bool srs_amf0_is_boolean(srs_amf0_t amf0) -{ - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - return any->is_boolean(); -} - -srs_bool srs_amf0_is_number(srs_amf0_t amf0) -{ - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - return any->is_number(); -} - -srs_bool srs_amf0_is_null(srs_amf0_t amf0) -{ - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - return any->is_null(); -} - -srs_bool srs_amf0_is_object(srs_amf0_t amf0) -{ - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - return any->is_object(); -} - -srs_bool srs_amf0_is_ecma_array(srs_amf0_t amf0) -{ - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - return any->is_ecma_array(); -} - -srs_bool srs_amf0_is_strict_array(srs_amf0_t amf0) -{ - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - return any->is_strict_array(); -} - -const char* srs_amf0_to_string(srs_amf0_t amf0) -{ - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - return any->to_str_raw(); -} - -srs_bool srs_amf0_to_boolean(srs_amf0_t amf0) -{ - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - return any->to_boolean(); -} - -srs_amf0_number srs_amf0_to_number(srs_amf0_t amf0) -{ - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - return any->to_number(); -} - -void srs_amf0_set_number(srs_amf0_t amf0, srs_amf0_number value) -{ - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - any->set_number(value); -} - -int srs_amf0_object_property_count(srs_amf0_t amf0) -{ - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - srs_assert(any->is_object()); - - SrsAmf0Object* obj = (SrsAmf0Object*)amf0; - return obj->count(); -} - -const char* srs_amf0_object_property_name_at(srs_amf0_t amf0, int index) -{ - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - srs_assert(any->is_object()); - - SrsAmf0Object* obj = (SrsAmf0Object*)amf0; - return obj->key_raw_at(index); -} - -srs_amf0_t srs_amf0_object_property_value_at(srs_amf0_t amf0, int index) -{ - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - srs_assert(any->is_object()); - - SrsAmf0Object* obj = (SrsAmf0Object*)amf0; - return (srs_amf0_t)obj->value_at(index); -} - -srs_amf0_t srs_amf0_object_property(srs_amf0_t amf0, const char* name) -{ - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - srs_assert(any->is_object()); - - SrsAmf0Object* obj = (SrsAmf0Object*)amf0; - return (srs_amf0_t)obj->get_property(name); -} - -void srs_amf0_object_property_set(srs_amf0_t amf0, const char* name, srs_amf0_t value) -{ - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - srs_assert(any->is_object()); - - SrsAmf0Object* obj = (SrsAmf0Object*)amf0; - any = (SrsAmf0Any*)value; - obj->set(name, any); -} - -void srs_amf0_object_clear(srs_amf0_t amf0) -{ - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - srs_assert(any->is_object()); - - SrsAmf0Object* obj = (SrsAmf0Object*)amf0; - obj->clear(); -} - -int srs_amf0_ecma_array_property_count(srs_amf0_t amf0) -{ - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - srs_assert(any->is_ecma_array()); - - SrsAmf0EcmaArray * obj = (SrsAmf0EcmaArray*)amf0; - return obj->count(); -} - -const char* srs_amf0_ecma_array_property_name_at(srs_amf0_t amf0, int index) -{ - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - srs_assert(any->is_ecma_array()); - - SrsAmf0EcmaArray* obj = (SrsAmf0EcmaArray*)amf0; - return obj->key_raw_at(index); -} - -srs_amf0_t srs_amf0_ecma_array_property_value_at(srs_amf0_t amf0, int index) -{ - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - srs_assert(any->is_ecma_array()); - - SrsAmf0EcmaArray* obj = (SrsAmf0EcmaArray*)amf0; - return (srs_amf0_t)obj->value_at(index); -} - -srs_amf0_t srs_amf0_ecma_array_property(srs_amf0_t amf0, const char* name) -{ - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - srs_assert(any->is_ecma_array()); - - SrsAmf0EcmaArray* obj = (SrsAmf0EcmaArray*)amf0; - return (srs_amf0_t)obj->get_property(name); -} - -void srs_amf0_ecma_array_property_set(srs_amf0_t amf0, const char* name, srs_amf0_t value) -{ - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - srs_assert(any->is_ecma_array()); - - SrsAmf0EcmaArray* obj = (SrsAmf0EcmaArray*)amf0; - any = (SrsAmf0Any*)value; - obj->set(name, any); -} - -int srs_amf0_strict_array_property_count(srs_amf0_t amf0) -{ - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - srs_assert(any->is_strict_array()); - - SrsAmf0StrictArray * obj = (SrsAmf0StrictArray*)amf0; - return obj->count(); -} - -srs_amf0_t srs_amf0_strict_array_property_at(srs_amf0_t amf0, int index) -{ - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - srs_assert(any->is_strict_array()); - - SrsAmf0StrictArray* obj = (SrsAmf0StrictArray*)amf0; - return (srs_amf0_t)obj->at(index); -} - -void srs_amf0_strict_array_append(srs_amf0_t amf0, srs_amf0_t value) -{ - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - srs_assert(any->is_strict_array()); - - SrsAmf0StrictArray* obj = (SrsAmf0StrictArray*)amf0; - any = (SrsAmf0Any*)value; - obj->append(any); -} - -int64_t srs_utils_time_ms() -{ - return srs_update_system_time(); -} - -int64_t srs_utils_send_bytes(srs_rtmp_t rtmp) -{ - srs_assert(rtmp != NULL); - Context* context = (Context*)rtmp; - if (!context->rtmp) { - return 0; - } - return context->rtmp->get_send_bytes(); -} - -int64_t srs_utils_recv_bytes(srs_rtmp_t rtmp) -{ - srs_assert(rtmp != NULL); - Context* context = (Context*)rtmp; - if (!context->rtmp) { - return 0; - } - return context->rtmp->get_recv_bytes(); -} - -int srs_utils_parse_timestamp( - uint32_t time, char type, char* data, int size, - uint32_t* ppts - ) { - int ret = ERROR_SUCCESS; - - if (type != SRS_RTMP_TYPE_VIDEO) { - *ppts = time; - return ret; - } - - if (!SrsFlvVideo::h264(data, size)) { - return ERROR_FLV_INVALID_VIDEO_TAG; - } - - if (SrsFlvVideo::sh(data, size)) { - *ppts = time; - return ret; - } - - // 1bytes, frame type and codec id. - // 1bytes, avc packet type. - // 3bytes, cts, composition time, - // pts = dts + cts, or - // cts = pts - dts. - if (size < 5) { - return ERROR_FLV_INVALID_VIDEO_TAG; - } - - uint32_t cts = 0; - char* p = data + 2; - char* pp = (char*)&cts; - pp[2] = *p++; - pp[1] = *p++; - pp[0] = *p++; - - *ppts = time + cts; - - return ret; -} - -srs_bool srs_utils_flv_tag_is_ok(char type) -{ - return type == SRS_RTMP_TYPE_AUDIO || type == SRS_RTMP_TYPE_VIDEO || type == SRS_RTMP_TYPE_SCRIPT; -} - -srs_bool srs_utils_flv_tag_is_audio(char type) -{ - return type == SRS_RTMP_TYPE_AUDIO; -} - -srs_bool srs_utils_flv_tag_is_video(char type) -{ - return type == SRS_RTMP_TYPE_VIDEO; -} - -srs_bool srs_utils_flv_tag_is_av(char type) -{ - return type == SRS_RTMP_TYPE_AUDIO || type == SRS_RTMP_TYPE_VIDEO; -} - -char srs_utils_flv_video_codec_id(char* data, int size) -{ - if (size < 1) { - return 0; - } - - char codec_id = data[0]; - codec_id = codec_id & 0x0F; - - return codec_id; -} - -char srs_utils_flv_video_avc_packet_type(char* data, int size) -{ - if (size < 2) { - return -1; - } - - if (!SrsFlvVideo::h264(data, size)) { - return -1; - } - - uint8_t avc_packet_type = data[1]; - - if (avc_packet_type > 2) { - return -1; - } - - return avc_packet_type; -} - -char srs_utils_flv_video_frame_type(char* data, int size) -{ - if (size < 1) { - return -1; - } - - if (!SrsFlvVideo::h264(data, size)) { - return -1; - } - - uint8_t frame_type = data[0]; - frame_type = (frame_type >> 4) & 0x0f; - if (frame_type < 1 || frame_type > 5) { - return -1; - } - - return frame_type; -} - -char srs_utils_flv_audio_sound_format(char* data, int size) -{ - if (size < 1) { - return -1; - } - - uint8_t sound_format = data[0]; - sound_format = (sound_format >> 4) & 0x0f; - if (sound_format > 15 || sound_format == 12) { - return -1; - } - - return sound_format; -} - -char srs_utils_flv_audio_sound_rate(char* data, int size) -{ - if (size < 3) { - return -1; - } - - uint8_t sound_rate = data[0]; - sound_rate = (sound_rate >> 2) & 0x03; - - // For Opus, the first UINT8 is sampling rate. - uint8_t sound_format = (data[0] >> 4) & 0x0f; - if (sound_format != SrsAudioCodecIdOpus) { - return sound_rate; - } - - // The FrameTrait for AAC or Opus. - uint8_t frame_trait = data[1]; - if ((frame_trait&SrsAudioOpusFrameTraitSamplingRate) == SrsAudioOpusFrameTraitSamplingRate) { - sound_rate = data[2]; - } - - return sound_rate; -} - -char srs_utils_flv_audio_sound_size(char* data, int size) -{ - if (size < 1) { - return -1; - } - - uint8_t sound_size = data[0]; - sound_size = (sound_size >> 1) & 0x01; - - return sound_size; -} - -char srs_utils_flv_audio_sound_type(char* data, int size) -{ - if (size < 1) { - return -1; - } - - uint8_t sound_type = data[0]; - sound_type = sound_type & 0x01; - - return sound_type; -} - -char srs_utils_flv_audio_aac_packet_type(char* data, int size) -{ - if (size < 2) { - return -1; - } - - uint8_t sound_format = srs_utils_flv_audio_sound_format(data, size); - if (sound_format != SrsAudioCodecIdAAC && sound_format != SrsAudioCodecIdOpus) { - return -1; - } - - uint8_t frame_trait = data[1]; - return frame_trait; -} - -char* srs_human_amf0_print(srs_amf0_t amf0, char** pdata, int* psize) -{ - if (!amf0) { - return NULL; - } - - SrsAmf0Any* any = (SrsAmf0Any*)amf0; - - return any->human_print(pdata, psize); -} - -const char* srs_human_flv_tag_type2string(char type) -{ - static const char* audio = "Audio"; - static const char* video = "Video"; - static const char* data = "Data"; - static const char* unknown = "Unknown"; - - switch (type) { - case SRS_RTMP_TYPE_AUDIO: return audio; - case SRS_RTMP_TYPE_VIDEO: return video; - case SRS_RTMP_TYPE_SCRIPT: return data; - default: return unknown; - } - - return unknown; -} - -const char* srs_human_flv_video_codec_id2string(char codec_id) -{ - static const char* h263 = "H.263"; - static const char* screen = "Screen"; - static const char* vp6 = "VP6"; - static const char* vp6_alpha = "VP6Alpha"; - static const char* screen2 = "Screen2"; - static const char* h264 = "H.264"; - static const char* unknown = "Unknown"; - - switch (codec_id) { - case 2: return h263; - case 3: return screen; - case 4: return vp6; - case 5: return vp6_alpha; - case 6: return screen2; - case 7: return h264; - default: return unknown; - } - - return unknown; -} - -const char* srs_human_flv_video_avc_packet_type2string(char avc_packet_type) -{ - static const char* sps_pps = "SH"; - static const char* nalu = "Nalu"; - static const char* sps_pps_end = "SpsPpsEnd"; - static const char* unknown = "Unknown"; - - switch (avc_packet_type) { - case 0: return sps_pps; - case 1: return nalu; - case 2: return sps_pps_end; - default: return unknown; - } - - return unknown; -} - -const char* srs_human_flv_video_frame_type2string(char frame_type) -{ - static const char* keyframe = "I"; - static const char* interframe = "P/B"; - static const char* disposable_interframe = "DI"; - static const char* generated_keyframe = "GI"; - static const char* video_infoframe = "VI"; - static const char* unknown = "Unknown"; - - switch (frame_type) { - case 1: return keyframe; - case 2: return interframe; - case 3: return disposable_interframe; - case 4: return generated_keyframe; - case 5: return video_infoframe; - default: return unknown; - } - - return unknown; -} - -const char* srs_human_flv_audio_sound_format2string(char sound_format) -{ - static const char* linear_pcm = "LinearPCM"; - static const char* ad_pcm = "ADPCM"; - static const char* mp3 = "MP3"; - static const char* linear_pcm_le = "LinearPCMLe"; - static const char* nellymoser_16khz = "NellymoserKHz16"; - static const char* nellymoser_8khz = "NellymoserKHz8"; - static const char* nellymoser = "Nellymoser"; - static const char* g711_a_pcm = "G711APCM"; - static const char* g711_mu_pcm = "G711MuPCM"; - static const char* reserved = "Reserved"; - static const char* aac = "AAC"; - static const char* speex = "Speex"; - static const char* mp3_8khz = "MP3KHz8"; - static const char* opus = "Opus"; - static const char* device_specific = "DeviceSpecific"; - static const char* unknown = "Unknown"; - - switch (sound_format) { - case 0: return linear_pcm; - case 1: return ad_pcm; - case 2: return mp3; - case 3: return linear_pcm_le; - case 4: return nellymoser_16khz; - case 5: return nellymoser_8khz; - case 6: return nellymoser; - case 7: return g711_a_pcm; - case 8: return g711_mu_pcm; - case 9: return reserved; - case 10: return aac; - case 11: return speex; - case 13: return opus; - case 14: return mp3_8khz; - case 15: return device_specific; - default: return unknown; - } - - return unknown; -} - -const char* srs_human_flv_audio_sound_rate2string(char sound_rate) -{ - static const char* khz_5_5 = "5.5KHz"; - static const char* khz_11 = "11KHz"; - static const char* khz_22 = "22KHz"; - static const char* khz_44 = "44KHz"; - static const char* unknown = "Unknown"; - - // For Opus, support 8, 12, 16, 24, 48KHz - // We will write a UINT8 sampling rate after FLV audio tag header. - // @doc https://tools.ietf.org/html/rfc6716#section-2 - static const char* NB8kHz = "NB8kHz"; - static const char* MB12kHz = "MB12kHz"; - static const char* WB16kHz = "WB16kHz"; - static const char* SWB24kHz = "SWB24kHz"; - static const char* FB48kHz = "FB48kHz"; - - switch (sound_rate) { - case 0: return khz_5_5; - case 1: return khz_11; - case 2: return khz_22; - case 3: return khz_44; - // For Opus, support 8, 12, 16, 24, 48KHz - case 8: return NB8kHz; - case 12: return MB12kHz; - case 16: return WB16kHz; - case 24: return SWB24kHz; - case 48: return FB48kHz; - default: return unknown; - } - - return unknown; -} - -const char* srs_human_flv_audio_sound_size2string(char sound_size) -{ - static const char* bit_8 = "8bit"; - static const char* bit_16 = "16bit"; - static const char* unknown = "Unknown"; - - switch (sound_size) { - case 0: return bit_8; - case 1: return bit_16; - default: return unknown; - } - - return unknown; -} - -const char* srs_human_flv_audio_sound_type2string(char sound_type) -{ - static const char* mono = "Mono"; - static const char* stereo = "Stereo"; - static const char* unknown = "Unknown"; - - switch (sound_type) { - case 0: return mono; - case 1: return stereo; - default: return unknown; - } - - return unknown; -} - -const char* srs_human_flv_audio_aac_packet_type2string(char aac_packet_type) -{ - static const char* sps_pps = "SH"; - static const char* raw = "Raw"; - static const char* unknown = "Unknown"; - - switch (aac_packet_type) { - case 0: return sps_pps; - case 1: return raw; - - // See enum SrsAudioAacFrameTrait - // For Opus, the frame trait, may has more than one traits. - case 2: return "RAW"; - case 4: return "SR"; - case 8: return "AL"; - case 6: return "RAW|SR"; - case 10: return "RAW|AL"; - case 14: return "RAW|SR|AL"; - - default: return unknown; - } - - return unknown; -} - -int srs_human_format_rtmp_packet(char* buffer, int nb_buffer, char type, uint32_t timestamp, char* data, int size) -{ - int ret = ERROR_SUCCESS; - - // Initialize to empty NULL terminated string. - buffer[0] = 0; - - char sbytes[40]; - if (true) { - int nb = srs_min(8, size); - int p = 0; - for (int i = 0; i < nb; i++) { - p += snprintf(sbytes+p, 40-p, "0x%02x ", (uint8_t)data[i]); - } - } - - uint32_t pts; - if ((ret = srs_utils_parse_timestamp(timestamp, type, data, size, &pts)) != ERROR_SUCCESS) { - snprintf(buffer, nb_buffer, "Rtmp packet type=%s, dts=%d, size=%d, DecodeError, (%s), ret=%d", - srs_human_flv_tag_type2string(type), timestamp, size, sbytes, ret); - return ret; - } - - if (type == SRS_RTMP_TYPE_VIDEO) { - snprintf(buffer, nb_buffer, "Video packet type=%s, dts=%d, pts=%d, size=%d, %s(%s,%s), (%s)", - srs_human_flv_tag_type2string(type), timestamp, pts, size, - srs_human_flv_video_codec_id2string(srs_utils_flv_video_codec_id(data, size)), - srs_human_flv_video_avc_packet_type2string(srs_utils_flv_video_avc_packet_type(data, size)), - srs_human_flv_video_frame_type2string(srs_utils_flv_video_frame_type(data, size)), - sbytes); - } else if (type == SRS_RTMP_TYPE_AUDIO) { - snprintf(buffer, nb_buffer, "Audio packet type=%s, dts=%d, pts=%d, size=%d, %s(%s,%s,%s,%s), (%s)", - srs_human_flv_tag_type2string(type), timestamp, pts, size, - srs_human_flv_audio_sound_format2string(srs_utils_flv_audio_sound_format(data, size)), - srs_human_flv_audio_sound_rate2string(srs_utils_flv_audio_sound_rate(data, size)), - srs_human_flv_audio_sound_size2string(srs_utils_flv_audio_sound_size(data, size)), - srs_human_flv_audio_sound_type2string(srs_utils_flv_audio_sound_type(data, size)), - srs_human_flv_audio_aac_packet_type2string(srs_utils_flv_audio_aac_packet_type(data, size)), - sbytes); - } else if (type == SRS_RTMP_TYPE_SCRIPT) { - int nb = snprintf(buffer, nb_buffer, "Data packet type=%s, time=%d, size=%d, (%s)", - srs_human_flv_tag_type2string(type), timestamp, size, sbytes); - int nparsed = 0; - while (nparsed < size) { - int nb_parsed_this = 0; - srs_amf0_t amf0 = srs_amf0_parse(data + nparsed, size - nparsed, &nb_parsed_this); - if (amf0 == NULL) { - break; - } - - nparsed += nb_parsed_this; - - char* amf0_str = NULL; - nb += snprintf(buffer + nb, nb_buffer - nb, "\n%s", srs_human_amf0_print(amf0, &amf0_str, NULL)) - 1; - srs_freepa(amf0_str); - } - buffer[nb] = 0; - } else { - snprintf(buffer, nb_buffer, "Rtmp packet type=%#x, dts=%d, pts=%d, size=%d, (%s)", - type, timestamp, pts, size, sbytes); - } - - return ret; -} - -int srs_human_format_rtmp_packet2(char* buffer, int nb_buffer, char type, uint32_t timestamp, char* data, int size, uint32_t pre_timestamp, int64_t pre_now, int64_t starttime, int64_t nb_packets) -{ - int ret = ERROR_SUCCESS; - - // Initialize to empty NULL terminated string. - buffer[0] = 0; - - // packets interval in milliseconds. - double pi = 0; - if (pre_now > starttime && nb_packets > 0) { - pi = (pre_now - starttime) / (double)nb_packets; - } - - // global fps(video and audio mixed fps). - double gfps = 0; - if (pi > 0) { - gfps = 1000 / pi; - } - - int diff = 0; - if (pre_timestamp > 0) { - diff = (int)timestamp - (int)pre_timestamp; - } - - int ndiff = 0; - if (pre_now > 0) { - ndiff = (int)(srs_utils_time_ms() - pre_now); - } - - char sbytes[40]; - if (true) { - int nb = srs_min(8, size); - int p = 0; - for (int i = 0; i < nb; i++) { - p += snprintf(sbytes+p, 40-p, "0x%02x ", (uint8_t)data[i]); - } - } - - uint32_t pts; - if ((ret = srs_utils_parse_timestamp(timestamp, type, data, size, &pts)) != ERROR_SUCCESS) { - snprintf(buffer, nb_buffer, "Rtmp packet id=%" PRId64 "/%.1f/%.1f, type=%s, dts=%d, ndiff=%d, diff=%d, size=%d, DecodeError, (%s), ret=%d", - nb_packets, pi, gfps, srs_human_flv_tag_type2string(type), timestamp, ndiff, diff, size, sbytes, ret); - return ret; - } - - if (type == SRS_RTMP_TYPE_VIDEO) { - snprintf(buffer, nb_buffer, "Video packet id=%" PRId64 "/%.1f/%.1f, type=%s, dts=%d, pts=%d, ndiff=%d, diff=%d, size=%d, %s(%s,%s), (%s)", - nb_packets, pi, gfps, srs_human_flv_tag_type2string(type), timestamp, pts, ndiff, diff, size, - srs_human_flv_video_codec_id2string(srs_utils_flv_video_codec_id(data, size)), - srs_human_flv_video_avc_packet_type2string(srs_utils_flv_video_avc_packet_type(data, size)), - srs_human_flv_video_frame_type2string(srs_utils_flv_video_frame_type(data, size)), - sbytes); - } else if (type == SRS_RTMP_TYPE_AUDIO) { - snprintf(buffer, nb_buffer, "Audio packet id=%" PRId64 "/%.1f/%.1f, type=%s, dts=%d, pts=%d, ndiff=%d, diff=%d, size=%d, %s(%s,%s,%s,%s), (%s)", - nb_packets, pi, gfps, srs_human_flv_tag_type2string(type), timestamp, pts, ndiff, diff, size, - srs_human_flv_audio_sound_format2string(srs_utils_flv_audio_sound_format(data, size)), - srs_human_flv_audio_sound_rate2string(srs_utils_flv_audio_sound_rate(data, size)), - srs_human_flv_audio_sound_size2string(srs_utils_flv_audio_sound_size(data, size)), - srs_human_flv_audio_sound_type2string(srs_utils_flv_audio_sound_type(data, size)), - srs_human_flv_audio_aac_packet_type2string(srs_utils_flv_audio_aac_packet_type(data, size)), - sbytes); - } else if (type == SRS_RTMP_TYPE_SCRIPT) { - int nb = snprintf(buffer, nb_buffer, "Data packet id=%" PRId64 "/%.1f/%.1f, type=%s, time=%d, ndiff=%d, diff=%d, size=%d, (%s)", - nb_packets, pi, gfps, srs_human_flv_tag_type2string(type), timestamp, ndiff, diff, size, sbytes); - int nparsed = 0; - while (nparsed < size) { - int nb_parsed_this = 0; - srs_amf0_t amf0 = srs_amf0_parse(data + nparsed, size - nparsed, &nb_parsed_this); - if (amf0 == NULL) { - break; - } - - nparsed += nb_parsed_this; - - char* amf0_str = NULL; - nb += snprintf(buffer + nb, nb_buffer - nb, "\n%s", srs_human_amf0_print(amf0, &amf0_str, NULL)) - 1; - srs_freepa(amf0_str); - } - buffer[nb] = 0; - } else { - snprintf(buffer, nb_buffer, "Rtmp packet id=%" PRId64 "/%.1f/%.1f, type=%#x, dts=%d, pts=%d, ndiff=%d, diff=%d, size=%d, (%s)", - nb_packets, pi, gfps, type, timestamp, pts, ndiff, diff, size, sbytes); - } - - return ret; -} - -const char* srs_human_format_time() -{ - struct timeval tv; - static char buf[24]; - - memset(buf, 0, sizeof(buf)); - - // clock time - if (gettimeofday(&tv, NULL) == -1) { - return buf; - } - - // to calendar time - struct tm* tm; - if ((tm = localtime((const time_t*)&tv.tv_sec)) == NULL) { - return buf; - } - - snprintf(buf, sizeof(buf), - "%d-%02d-%02d %02d:%02d:%02d.%03d", - 1900 + tm->tm_year, 1 + tm->tm_mon, tm->tm_mday, - tm->tm_hour, tm->tm_min, tm->tm_sec, - (int)(tv.tv_usec / 1000)); - - // for srs-librtmp, @see https://github.com/ossrs/srs/issues/213 - buf[sizeof(buf) - 1] = 0; - - return buf; -} - - -#ifdef SRS_HIJACK_IO -srs_hijack_io_t srs_hijack_io_get(srs_rtmp_t rtmp) -{ - if (!rtmp) { - return NULL; - } - - Context* context = (Context*)rtmp; - if (!context->skt) { - return NULL; - } - - return context->skt->hijack_io(); -} -#endif - -srs_rtmp_t srs_rtmp_create2(const char* url) -{ - Context* context = new Context(); - - // use url as tcUrl. - context->url = url; - // auto append stream. - context->url += "/livestream"; - - // create socket - srs_freep(context->skt); - context->skt = new SimpleSocketStream(); - - int ret = ERROR_SUCCESS; - if ((ret = context->skt->create_socket(context)) != ERROR_SUCCESS) { - srs_human_error("Create socket failed, ret=%d", ret); - - // free the context and return NULL - srs_freep(context); - return NULL; - } - - return context; -} - -int srs_rtmp_connect_app2(srs_rtmp_t rtmp, char srs_server_ip[128],char srs_server[128], char srs_primary[128], char srs_authors[128], char srs_version[32], int* srs_id, int* srs_pid) -{ - srs_server_ip[0] = 0; - srs_server[0] = 0; - srs_primary[0] = 0; - srs_authors[0] = 0; - srs_version[0] = 0; - *srs_id = 0; - *srs_pid = 0; - - int ret = ERROR_SUCCESS; - - if ((ret = srs_rtmp_connect_app(rtmp)) != ERROR_SUCCESS) { - return ret; - } - - srs_assert(rtmp != NULL); - Context* context = (Context*)rtmp; - SrsServerInfo* si = &context->si; - - snprintf(srs_server_ip, 128, "%s", si->ip.c_str()); - snprintf(srs_server, 128, "%s", si->sig.c_str()); - snprintf(srs_version, 32, "%d.%d.%d.%d", si->major, si->minor, si->revision, si->build); - - return ret; -} - -int srs_human_print_rtmp_packet(char type, uint32_t timestamp, char* data, int size) -{ - return srs_human_print_rtmp_packet3(type, timestamp, data, size, 0, 0); -} - -int srs_human_print_rtmp_packet2(char type, uint32_t timestamp, char* data, int size, uint32_t pre_timestamp) -{ - return srs_human_print_rtmp_packet3(type, timestamp, data, size, pre_timestamp, 0); -} - -int srs_human_print_rtmp_packet3(char type, uint32_t timestamp, char* data, int size, uint32_t pre_timestamp, int64_t pre_now) -{ - return srs_human_print_rtmp_packet4(type, timestamp, data, size, pre_timestamp, pre_now, 0, 0); -} - -int srs_human_print_rtmp_packet4(char type, uint32_t timestamp, char* data, int size, uint32_t pre_timestamp, int64_t pre_now, - int64_t starttime, int64_t nb_packets -) { - char buffer[1024]; - int ret = srs_human_format_rtmp_packet2(buffer, sizeof(buffer), type, timestamp, data, size, pre_timestamp, pre_now, starttime, nb_packets); - srs_human_trace("%s", buffer); - return ret; -} - -#ifdef __cplusplus -} -#endif - diff --git a/trunk/src/libs/srs_librtmp.hpp b/trunk/src/libs/srs_librtmp.hpp deleted file mode 100644 index 9f2044647..000000000 --- a/trunk/src/libs/srs_librtmp.hpp +++ /dev/null @@ -1,1256 +0,0 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2013-2020 Winlin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#ifndef SRS_LIB_RTMP_HPP -#define SRS_LIB_RTMP_HPP - -/** - * srs-librtmp is a librtmp like library, - * used to play/publish rtmp stream from/to rtmp server. - * socket: use sync and block socket to connect/recv/send data with server. - * depends: no need other libraries; depends on ssl if use srs_complex_handshake. - * thread-safe: no - */ - -// @see http://blog.csdn.net/win_lin/article/details/7912693 -#ifndef __STDC_FORMAT_MACROS - #define __STDC_FORMAT_MACROS -#endif - -/************************************************************* - ************************************************************** - * Windows SRS-LIBRTMP pre-declare - ************************************************************** - *************************************************************/ -// for srs-librtmp, @see https://github.com/ossrs/srs/issues/213 -#ifdef _WIN32 -// To disable some security warnings. -#define _CRT_SECURE_NO_WARNINGS -// include windows first. -#include -// the type used by this header for windows. -#if defined(_MSC_VER) - #include -#else - typedef char int8_t; - typedef short int16_t; - typedef int int32_t; - typedef long long int64_t; -#endif -typedef unsigned long long u_int64_t; -typedef unsigned int u_int32_t; -typedef u_int32_t uint32_t; -typedef unsigned char u_int8_t; -typedef unsigned short u_int16_t; -typedef int64_t ssize_t; -struct iovec { - void *iov_base; /* Starting address */ - size_t iov_len; /* Number of bytes to transfer */ -}; - -// for pid. -typedef int pid_t; -pid_t getpid(void); -#endif - -#include -#include - -#ifdef __cplusplus -extern "C"{ -#endif - -/** - * The schema of RTMP url, the following are legal urls: - * srs_url_schema_normal: rtmp://vhost:port/app/stream - * srs_url_schema_via : rtmp://ip:port/vhost/app/stream - * srs_url_schema_vis : rtmp://ip:port/app/stream?vhost=xxx - * srs_url_schema_vis2 : rtmp://ip:port/app/stream?domain=xxx - */ -enum srs_url_schema -{ - // Forbidden. - srs_url_schema_forbidden = 0, - // Normal RTMP URL, the vhost put in host field, using DNS to resolve the server ip. - // For example, rtmp://vhost:port/app/stream - srs_url_schema_normal, - // VIA(vhost in app), the vhost put in app field. - // For example, rtmp://ip:port/vhost/app/stream - srs_url_schema_via, - // VIS(vhost in stream), the vhost put in query string, keyword use vhost=xxx. - // For example, rtmp://ip:port/app/stream?vhost=xxx - srs_url_schema_vis, - // VIS, keyword use domain=xxx. - // For example, rtmp://ip:port/app/stream?domain=xxx - srs_url_schema_vis2 -}; - -// typedefs -typedef int srs_bool; - -/************************************************************* - ************************************************************** - * srs-librtmp version - ************************************************************** - *************************************************************/ -extern int srs_version_major(); -extern int srs_version_minor(); -extern int srs_version_revision(); - -/************************************************************* - ************************************************************** - * RTMP protocol context - ************************************************************** - *************************************************************/ -// the RTMP handler. -typedef void* srs_rtmp_t; -typedef void* srs_amf0_t; - -/** - * Create a RTMP handler. - * @param url The RTMP url, for example, rtmp://localhost/live/livestream - * @remark default timeout to 30s if not set by srs_rtmp_set_timeout. - * @remark default schema to srs_url_schema_normal, use srs_rtmp_set_schema to change it. - * - * @return a rtmp handler, or NULL if error occured. - */ -extern srs_rtmp_t srs_rtmp_create(const char* url); -/** - * set socket timeout - * @param recv_timeout_ms the timeout for receiving messages in ms. - * @param send_timeout_ms the timeout for sending message in ms. - * @remark user can set timeout once srs_rtmp_create/srs_rtmp_create2, - * or before srs_rtmp_handshake or srs_rtmp_dns_resolve to connect to server. - * @remark default timeout to 30s if not set by srs_rtmp_set_timeout. - * - * @return 0, success; otherswise, failed. - */ -extern int srs_rtmp_set_timeout(srs_rtmp_t rtmp, int recv_timeout_ms, int send_timeout_ms); -/** - * close and destroy the rtmp stack. - * @remark, user should never use the rtmp again. - */ -extern void srs_rtmp_destroy(srs_rtmp_t rtmp); - -/************************************************************* - ************************************************************** - * RTMP protocol stack - ************************************************************** - *************************************************************/ -/** - * connect and handshake with server - * category: publish/play - * previous: rtmp-create - * next: connect-app - * - * @return 0, success; otherswise, failed. - */ -/** - * simple handshake specifies in rtmp 1.0, - * not depends on ssl. - */ -/** - * srs_rtmp_handshake equals to invoke: - * srs_rtmp_dns_resolve() - * srs_rtmp_connect_server() - * srs_rtmp_do_simple_handshake() - * user can use these functions if needed. - */ -extern int srs_rtmp_handshake(srs_rtmp_t rtmp); -// parse uri, create socket, resolve host -extern int srs_rtmp_dns_resolve(srs_rtmp_t rtmp); -// connect socket to server -extern int srs_rtmp_connect_server(srs_rtmp_t rtmp); -// do simple handshake over socket. -extern int srs_rtmp_do_simple_handshake(srs_rtmp_t rtmp); -// do complex handshake over socket. -extern int srs_rtmp_do_complex_handshake(srs_rtmp_t rtmp); - -/** - * set the args of connect packet for rtmp. - * @param args, the extra amf0 object args. - * @remark, all params can be NULL to ignore. - * @remark, user should never free the args for we directly use it. - */ -extern int srs_rtmp_set_connect_args(srs_rtmp_t rtmp, const char* tcUrl, const char* swfUrl, const char* pageUrl, srs_amf0_t args); - -/** - * Set the schema of URL when connect to tcUrl by srs_rtmp_connect_app. - * @param schema, The schema of URL, @see srs_url_schema. - * @return 0, success; otherswise, failed. - */ -extern int srs_rtmp_set_schema(srs_rtmp_t rtmp, enum srs_url_schema schema); - -/** - * Connect to RTMP tcUrl(Vhost/App), similar to flash AS3 NetConnection.connect(tcUrl). - * @remark When connected to server, user can retrieve informations from RTMP handler, - * for example, use srs_rtmp_get_server_id to get server ip/pid/cid. - * @return 0, success; otherswise, failed. - */ -extern int srs_rtmp_connect_app(srs_rtmp_t rtmp); - -/** - * Retrieve server ip from RTMP handler. - * @param ip A NULL terminated string specifies the server ip. - * @param pid An int specifies the PID of server. -1 is no PID information. - * @param cid An int specifies the CID of connection. -1 is no CID information. - * @remark For SRS, ip/pid/cid is the UUID of a client. For other server, these values maybe unknown. - * @remark When connected to server by srs_rtmp_connect_app, the information is ready to be retrieved. - * @return 0, success; otherswise, failed. - */ -extern int srs_rtmp_get_server_id(srs_rtmp_t rtmp, char** ip, int* pid, int* cid); - -/** - * Retrieve server signature from RTMP handler. - * @param sig A NULL terminated string specifies the server signature. - * @remark When connected to server by srs_rtmp_connect_app, the information is ready to be retrieved. - * @return 0, success; otherswise, failed. - */ -extern int srs_rtmp_get_server_sig(srs_rtmp_t rtmp, char** sig); - -/** - * Retrieve server version from RTMP handler, which in major.minor.revision.build format. - * @remark When connected to server by srs_rtmp_connect_app, the information is ready to be retrieved. - * @return 0, success; otherswise, failed. - */ -extern int srs_rtmp_get_server_version(srs_rtmp_t rtmp, int* major, int* minor, int* revision, int* build); - -/** - * play a live/vod stream. - * category: play - * previous: connect-app - * next: destroy - * @return 0, success; otherwise, failed. - */ -extern int srs_rtmp_play_stream(srs_rtmp_t rtmp); - -/** - * publish a live stream. - * category: publish - * previous: connect-app - * next: destroy - * @return 0, success; otherwise, failed. - */ -extern int srs_rtmp_publish_stream(srs_rtmp_t rtmp); - -/** - * do bandwidth check with srs server. - * - * bandwidth info: - * @param start_time, output the start time, in ms. - * @param end_time, output the end time, in ms. - * @param play_kbps, output the play/download kbps. - * @param publish_kbps, output the publish/upload kbps. - * @param play_bytes, output the play/download bytes. - * @param publish_bytes, output the publish/upload bytes. - * @param play_duration, output the play/download test duration, in ms. - * @param publish_duration, output the publish/upload test duration, in ms. - * - * @return 0, success; otherswise, failed. - */ -extern int srs_rtmp_bandwidth_check(srs_rtmp_t rtmp, - int64_t* start_time, int64_t* end_time, - int* play_kbps, int* publish_kbps, - int* play_bytes, int* publish_bytes, - int* play_duration, int* publish_duration); - -/** - * E.4.1 FLV Tag, page 75 - */ -// 8 = audio -#define SRS_RTMP_TYPE_AUDIO 8 -// 9 = video -#define SRS_RTMP_TYPE_VIDEO 9 -// 18 = script data -#define SRS_RTMP_TYPE_SCRIPT 18 -/** - * read a audio/video/script-data packet from rtmp stream. - * @param type, output the packet type, macros: - * SRS_RTMP_TYPE_AUDIO, FlvTagAudio - * SRS_RTMP_TYPE_VIDEO, FlvTagVideo - * SRS_RTMP_TYPE_SCRIPT, FlvTagScript - * otherswise, invalid type. - * @param timestamp, in ms, overflow in 50days - * @param data, the packet data, according to type: - * FlvTagAudio, @see "E.4.2.1 AUDIODATA" - * FlvTagVideo, @see "E.4.3.1 VIDEODATA" - * FlvTagScript, @see "E.4.4.1 SCRIPTDATA" - * User can free the packet by srs_rtmp_free_packet. - * @param size, size of packet. - * @return the error code. 0 for success; otherwise, error. - * - * @remark: for read, user must free the data. - * @remark: for write, user should never free the data, even if error. - * @example /trunk/research/librtmp/srs_play.c - * @example /trunk/research/librtmp/srs_publish.c - * - * @return 0, success; otherswise, failed. - */ -extern int srs_rtmp_read_packet(srs_rtmp_t rtmp, char* type, uint32_t* timestamp, char** data, int* size); -// @param data User should never free it anymore. -extern int srs_rtmp_write_packet(srs_rtmp_t rtmp, char type, uint32_t timestamp, char* data, int size); - -/** - * Free the packet allocated by srs_rtmp_read_packet. - */ -extern void srs_rtmp_free_packet(char* data); - -/** - * whether type is script data and the data is onMetaData. - */ -extern srs_bool srs_rtmp_is_onMetaData(char type, char* data, int size); - -/************************************************************* - ************************************************************** - * audio raw codec - ************************************************************** - *************************************************************/ -/** - * write an audio raw frame to srs. - * not similar to h.264 video, the audio never aggregated, always - * encoded one frame by one, so this api is used to write a frame. - * - * @param sound_format Format of SoundData. The following values are defined: - * 0 = Linear PCM, platform endian - * 1 = ADPCM - * 2 = MP3 - * 3 = Linear PCM, little endian - * 4 = Nellymoser 16 kHz mono - * 5 = Nellymoser 8 kHz mono - * 6 = Nellymoser - * 7 = G.711 A-law logarithmic PCM - * 8 = G.711 mu-law logarithmic PCM - * 9 = reserved - * 10 = AAC - * 11 = Speex - * 14 = MP3 8 kHz - * 15 = Device-specific sound - * Formats 7, 8, 14, and 15 are reserved. - * AAC is supported in Flash Player 9,0,115,0 and higher. - * Speex is supported in Flash Player 10 and higher. - * @param sound_rate Sampling rate. The following values are defined: - * 0 = 5.5 kHz - * 1 = 11 kHz - * 2 = 22 kHz - * 3 = 44 kHz - * @param sound_size Size of each audio sample. This parameter only pertains to - * uncompressed formats. Compressed formats always decode - * to 16 bits internally. - * 0 = 8-bit samples - * 1 = 16-bit samples - * @param sound_type Mono or stereo sound - * 0 = Mono sound - * 1 = Stereo sound - * @param timestamp The timestamp of audio. - * - * @example /trunk/research/librtmp/srs_aac_raw_publish.c - * @example /trunk/research/librtmp/srs_audio_raw_publish.c - * - * @remark for aac, the frame must be in ADTS format. - * @see ISO_IEC_14496-3-AAC-2001.pdf, page 75, 1.A.2.2 ADTS - * @remark for aac, only support profile 1-4, AAC main/LC/SSR/LTP, - * @see ISO_IEC_14496-3-AAC-2001.pdf, page 23, 1.5.1.1 Audio object type - * - * @see https://github.com/ossrs/srs/issues/212 - * @see E.4.2.1 AUDIODATA of video_file_format_spec_v10_1.pdf - * - * @return 0, success; otherswise, failed. - */ -extern int srs_audio_write_raw_frame(srs_rtmp_t rtmp, - char sound_format, char sound_rate, char sound_size, char sound_type, - char* frame, int frame_size, uint32_t timestamp); - -/** - * whether aac raw data is in adts format, - * which bytes sequence matches '1111 1111 1111'B, that is 0xFFF. - * @param aac_raw_data the input aac raw data, a encoded aac frame data. - * @param ac_raw_size the size of aac raw data. - * - * @reamrk used to check whether current frame is in adts format. - * @see ISO_IEC_14496-3-AAC-2001.pdf, page 75, 1.A.2.2 ADTS - * @example /trunk/research/librtmp/srs_aac_raw_publish.c - * - * @return 0 false; otherwise, true. - */ -extern srs_bool srs_aac_is_adts(char* aac_raw_data, int ac_raw_size); - -/** - * parse the adts header to get the frame size, - * which bytes sequence matches '1111 1111 1111'B, that is 0xFFF. - * @param aac_raw_data the input aac raw data, a encoded aac frame data. - * @param ac_raw_size the size of aac raw data. - * - * @return failed when <=0 failed; otherwise, ok. - */ -extern int srs_aac_adts_frame_size(char* aac_raw_data, int ac_raw_size); - -/************************************************************* - ************************************************************** - * h264 raw codec - ************************************************************** - *************************************************************/ -/** - * write h.264 raw frame over RTMP to rtmp server. - * @param frames the input h264 raw data, encoded h.264 I/P/B frames data. - * frames can be one or more than one frame, - * each frame prefixed h.264 annexb header, by N[00] 00 00 01, where N>=0, - * for instance, frame = header(00 00 00 01) + payload(67 42 80 29 95 A0 14 01 6E 40) - * about annexb, @see ISO_IEC_14496-10-AVC-2003.pdf, page 211. - * @param frames_size the size of h264 raw data. - * assert frames_size > 0, at least has 1 bytes header. - * @param dts the dts of h.264 raw data. - * @param pts the pts of h.264 raw data. - * - * @remark, user should free the frames. - * @remark, the tbn of dts/pts is 1/1000 for RTMP, that is, in ms. - * @remark, cts = pts - dts - * @remark, use srs_h264_startswith_annexb to check whether frame is annexb format. - * @example /trunk/research/librtmp/srs_h264_raw_publish.c - * @see https://github.com/ossrs/srs/issues/66 - * - * @return 0, success; otherswise, failed. - * for dvbsp error, @see srs_h264_is_dvbsp_error(). - * for duplictated sps error, @see srs_h264_is_duplicated_sps_error(). - * for duplictated pps error, @see srs_h264_is_duplicated_pps_error(). - */ -/** - For the example file: - http://winlinvip.github.io/srs.release/3rdparty/720p.h264.raw - The data sequence is: - // SPS - 000000016742802995A014016E40 - // PPS - 0000000168CE3880 - // IFrame - 0000000165B8041014C038008B0D0D3A071..... - // PFrame - 0000000141E02041F8CDDC562BBDEFAD2F..... - User can send the SPS+PPS, then each frame: - // SPS+PPS - srs_h264_write_raw_frames('000000016742802995A014016E400000000168CE3880', size, dts, pts) - // IFrame - srs_h264_write_raw_frames('0000000165B8041014C038008B0D0D3A071......', size, dts, pts) - // PFrame - srs_h264_write_raw_frames('0000000141E02041F8CDDC562BBDEFAD2F......', size, dts, pts) - User also can send one by one: - // SPS - srs_h264_write_raw_frames('000000016742802995A014016E4', size, dts, pts) - // PPS - srs_h264_write_raw_frames('00000000168CE3880', size, dts, pts) - // IFrame - srs_h264_write_raw_frames('0000000165B8041014C038008B0D0D3A071......', size, dts, pts) - // PFrame - srs_h264_write_raw_frames('0000000141E02041F8CDDC562BBDEFAD2F......', size, dts, pts) - */ -extern int srs_h264_write_raw_frames(srs_rtmp_t rtmp, char* frames, int frames_size, uint32_t dts, uint32_t pts); -/** - * whether error_code is dvbsp(drop video before sps/pps/sequence-header) error. - * - * @see https://github.com/ossrs/srs/issues/203 - * @example /trunk/research/librtmp/srs_h264_raw_publish.c - * @remark why drop video? - * some encoder, for example, ipcamera, will send sps/pps before each IFrame, - * so, when error and reconnect the rtmp, the first video is not sps/pps(sequence header), - * this will cause SRS server to disable HLS. - */ -extern srs_bool srs_h264_is_dvbsp_error(int error_code); -/** - * whether error_code is duplicated sps error. - * - * @see https://github.com/ossrs/srs/issues/204 - * @example /trunk/research/librtmp/srs_h264_raw_publish.c - */ -extern srs_bool srs_h264_is_duplicated_sps_error(int error_code); -/** - * whether error_code is duplicated pps error. - * - * @see https://github.com/ossrs/srs/issues/204 - * @example /trunk/research/librtmp/srs_h264_raw_publish.c - */ -extern srs_bool srs_h264_is_duplicated_pps_error(int error_code); -/** - * whether h264 raw data starts with the annexb, - * which bytes sequence matches N[00] 00 00 01, where N>=0. - * @param h264_raw_data the input h264 raw data, a encoded h.264 I/P/B frame data. - * @paam h264_raw_size the size of h264 raw data. - * @param pnb_start_code output the size of start code, must >=3. - * NULL to ignore. - * - * @reamrk used to check whether current frame is in annexb format. - * @example /trunk/research/librtmp/srs_h264_raw_publish.c - * - * @return 0 false; otherwise, true. - */ -extern srs_bool srs_h264_startswith_annexb(char* h264_raw_data, int h264_raw_size, int* pnb_start_code); - -/************************************************************* - ************************************************************* - * MP4 muxer and demuxer. - * @example /trunk/research/librtmp/srs_ingest_mp4.c - ************************************************************* - *************************************************************/ -typedef void* srs_mp4_t; -// The sample struct of mp4. -typedef struct { - // The handler type, it's SrsMp4HandlerType. - uint32_t handler_type; - - // The dts in milliseconds. - uint32_t dts; - // The codec id. - // video: SrsVideoCodecId. - // audio: SrsAudioCodecId. - uint16_t codec; - // The frame trait, some characteristic: - // video: SrsVideoAvcFrameTrait. - // audio: SrsAudioAacFrameTrait. - uint16_t frame_trait; - - // The video pts in milliseconds. Ignore for audio. - uint32_t pts; - // The video frame type, it's SrsVideoAvcFrameType. - uint16_t frame_type; - - // The audio sample rate, it's SrsAudioSampleRate. - uint8_t sample_rate; - // The audio sound bits, it's SrsAudioSampleBits. - uint8_t sound_bits; - // The audio sound type, it's SrsAudioChannels. - uint8_t channels; - - // The size of sample payload in bytes. - uint32_t nb_sample; - // The output sample data, user must free it by srs_mp4_free_sample. - uint8_t* sample; -} srs_mp4_sample_t; -/** - * Open mp4 file for muxer(write) or demuxer(read). - * @return A MP4 demuxer, NULL if failed. - */ -extern srs_mp4_t srs_mp4_open_read(const char* file); -/** - * Close the MP4 demuxer. - */ -extern void srs_mp4_close(srs_mp4_t mp4); -/** - * Initialize mp4 demuxer in non-seek mode. - * @remark Only support non-seek mode, that is fmp4 or moov before mdata. - * For the live streaming, we must feed stream frame by frame. - */ -extern int srs_mp4_init_demuxer(srs_mp4_t mp4); -/** - * Read a sample form mp4. - * @remark User can use srs_mp4_sample_to_flv_tag to convert mp4 sampel to flv tag. - * Use the srs_mp4_to_flv_tag_size to calc the flv tag data size to alloc. - */ -extern int srs_mp4_read_sample(srs_mp4_t mp4, srs_mp4_sample_t* sample); -/** - * Free the allocated mp4 sample. - */ -extern void srs_mp4_free_sample(srs_mp4_sample_t* sample); -/** - * Calc the size of flv tag, for the mp4 sample to convert to. - */ -extern int32_t srs_mp4_sizeof(srs_mp4_t mp4, srs_mp4_sample_t* sample); -/** - * Covert mp4 sample to flv tag. - */ -extern int srs_mp4_to_flv_tag(srs_mp4_t mp4, srs_mp4_sample_t* sample, char* type, uint32_t* time, char* data, int32_t size); -/* error code */ -/* whether the error code indicates EOF */ -extern srs_bool srs_mp4_is_eof(int error_code); - -/************************************************************* - ************************************************************** - * flv codec - * @example /trunk/research/librtmp/srs_flv_injecter.c - * @example /trunk/research/librtmp/srs_flv_parser.c - * @example /trunk/research/librtmp/srs_ingest_flv.c - * @example /trunk/research/librtmp/srs_ingest_rtmp.c - ************************************************************** - *************************************************************/ -typedef void* srs_flv_t; -/** - * Open FLV file in demux mode. - * @return A FLV demuxer, NULL if failed. - */ -extern srs_flv_t srs_flv_open_read(const char* file); -/** - * Open FlV file in mux mode. - * @return A FLV muxer, NULL if failed. - */ -extern srs_flv_t srs_flv_open_write(const char* file); -/** - * Close the FLV demuxer or muxer. - */ -extern void srs_flv_close(srs_flv_t flv); -/** - * read the flv header. 9bytes header. - * @param header, @see E.2 The FLV header, flv_v10_1.pdf in SRS doc. - * 3bytes, signature, "FLV", - * 1bytes, version, 0x01, - * 1bytes, flags, UB[5] 0, UB[1] audio present, UB[1] 0, UB[1] video present. - * 4bytes, dataoffset, 0x09, The length of this header in bytes - * - * @return 0, success; otherswise, failed. - * @remark, drop the 4bytes zero previous tag size. - */ -extern int srs_flv_read_header(srs_flv_t flv, char header[9]); -/** - * read the flv tag header, 1bytes tag, 3bytes data_size, - * 4bytes time, 3bytes stream id. - * @param ptype, output the type of tag, macros: - * SRS_RTMP_TYPE_AUDIO, FlvTagAudio - * SRS_RTMP_TYPE_VIDEO, FlvTagVideo - * SRS_RTMP_TYPE_SCRIPT, FlvTagScript - * @param pdata_size, output the size of tag data. - * @param ptime, output the time of tag, the dts in ms. - * - * @return 0, success; otherswise, failed. - * @remark, user must ensure the next is a tag, srs never check it. - */ -extern int srs_flv_read_tag_header(srs_flv_t flv, char* ptype, int32_t* pdata_size, uint32_t* ptime); -/** - * read the tag data. drop the 4bytes previous tag size - * @param data, the data to read, user alloc and free it. - * @param size, the size of data to read, get by srs_flv_read_tag_header(). - * @remark, srs will ignore and drop the 4bytes previous tag size. - */ -extern int srs_flv_read_tag_data(srs_flv_t flv, char* data, int32_t size); -/** - * write the flv header. 9bytes header. - * @param header, @see E.2 The FLV header, flv_v10_1.pdf in SRS doc. - * 3bytes, signature, "FLV", - * 1bytes, version, 0x01, - * 1bytes, flags, UB[5] 0, UB[1] audio present, UB[1] 0, UB[1] video present. - * 4bytes, dataoffset, 0x09, The length of this header in bytes - * - * @return 0, success; otherswise, failed. - * @remark, auto write the 4bytes zero previous tag size. - */ -extern int srs_flv_write_header(srs_flv_t flv, char header[9]); -/** - * write the flv tag to file. - * - * @return 0, success; otherswise, failed. - * @remark, auto write the 4bytes zero previous tag size. - */ -/* write flv tag to file, auto write the 4bytes previous tag size */ -extern int srs_flv_write_tag(srs_flv_t flv, char type, int32_t time, char* data, int size); -/** - * get the tag size, for flv injecter to adjust offset, - * size = tag_header(11B) + data_size + previous_tag(4B) - * @return the size of tag. - */ -extern int srs_flv_size_tag(int data_size); -/* file stream */ -/* file stream tellg to get offset */ -extern int64_t srs_flv_tellg(srs_flv_t flv); -/* seek file stream, offset is form the start of file */ -extern void srs_flv_lseek(srs_flv_t flv, int64_t offset); -/* error code */ -/* whether the error code indicates EOF */ -extern srs_bool srs_flv_is_eof(int error_code); -/* media codec */ -/** - * whether the video body is sequence header - * @param data, the data of tag, read by srs_flv_read_tag_data(). - * @param size, the size of tag, read by srs_flv_read_tag_data(). - */ -extern srs_bool srs_flv_is_sequence_header(char* data, int32_t size); -/** - * whether the video body is keyframe - * @param data, the data of tag, read by srs_flv_read_tag_data(). - * @param size, the size of tag, read by srs_flv_read_tag_data(). - */ -extern srs_bool srs_flv_is_keyframe(char* data, int32_t size); - -/************************************************************* - ************************************************************** - * amf0 codec - * @example /trunk/research/librtmp/srs_ingest_flv.c - * @example /trunk/research/librtmp/srs_ingest_rtmp.c - ************************************************************** - *************************************************************/ -/* the output handler. */ -typedef double srs_amf0_number; -/** - * parse amf0 from data. - * @param nparsed, the parsed size, NULL to ignore. - * @return the parsed amf0 object. NULL for error. - * @remark user must free the parsed or created object by srs_amf0_free. - */ -extern srs_amf0_t srs_amf0_parse(char* data, int size, int* nparsed); -extern srs_amf0_t srs_amf0_create_string(const char* value); -extern srs_amf0_t srs_amf0_create_number(srs_amf0_number value); -extern srs_amf0_t srs_amf0_create_ecma_array(); -extern srs_amf0_t srs_amf0_create_strict_array(); -extern srs_amf0_t srs_amf0_create_object(); -extern srs_amf0_t srs_amf0_ecma_array_to_object(srs_amf0_t ecma_arr); -extern void srs_amf0_free(srs_amf0_t amf0); -/* size and to bytes */ -extern int srs_amf0_size(srs_amf0_t amf0); -extern int srs_amf0_serialize(srs_amf0_t amf0, char* data, int size); -/* type detecter */ -extern srs_bool srs_amf0_is_string(srs_amf0_t amf0); -extern srs_bool srs_amf0_is_boolean(srs_amf0_t amf0); -extern srs_bool srs_amf0_is_number(srs_amf0_t amf0); -extern srs_bool srs_amf0_is_null(srs_amf0_t amf0); -extern srs_bool srs_amf0_is_object(srs_amf0_t amf0); -extern srs_bool srs_amf0_is_ecma_array(srs_amf0_t amf0); -extern srs_bool srs_amf0_is_strict_array(srs_amf0_t amf0); -/* value converter */ -extern const char* srs_amf0_to_string(srs_amf0_t amf0); -extern srs_bool srs_amf0_to_boolean(srs_amf0_t amf0); -extern srs_amf0_number srs_amf0_to_number(srs_amf0_t amf0); -/* value setter */ -extern void srs_amf0_set_number(srs_amf0_t amf0, srs_amf0_number value); -/* object value converter */ -extern int srs_amf0_object_property_count(srs_amf0_t amf0); -extern const char* srs_amf0_object_property_name_at(srs_amf0_t amf0, int index); -extern srs_amf0_t srs_amf0_object_property_value_at(srs_amf0_t amf0, int index); -extern srs_amf0_t srs_amf0_object_property(srs_amf0_t amf0, const char* name); -extern void srs_amf0_object_property_set(srs_amf0_t amf0, const char* name, srs_amf0_t value); -extern void srs_amf0_object_clear(srs_amf0_t amf0); -/* ecma array value converter */ -extern int srs_amf0_ecma_array_property_count(srs_amf0_t amf0); -extern const char* srs_amf0_ecma_array_property_name_at(srs_amf0_t amf0, int index); -extern srs_amf0_t srs_amf0_ecma_array_property_value_at(srs_amf0_t amf0, int index); -extern srs_amf0_t srs_amf0_ecma_array_property(srs_amf0_t amf0, const char* name); -extern void srs_amf0_ecma_array_property_set(srs_amf0_t amf0, const char* name, srs_amf0_t value); -/* strict array value converter */ -extern int srs_amf0_strict_array_property_count(srs_amf0_t amf0); -extern srs_amf0_t srs_amf0_strict_array_property_at(srs_amf0_t amf0, int index); -extern void srs_amf0_strict_array_append(srs_amf0_t amf0, srs_amf0_t value); - -/************************************************************* - ************************************************************** - * utilities - ************************************************************** - *************************************************************/ -/** - * get the current system time in ms. - * use gettimeofday() to get system time. - */ -extern int64_t srs_utils_time_ms(); - -/** - * get the send bytes. - */ -extern int64_t srs_utils_send_bytes(srs_rtmp_t rtmp); - -/** - * get the recv bytes. - */ -extern int64_t srs_utils_recv_bytes(srs_rtmp_t rtmp); - -/** - * parse the dts and pts by time in header and data in tag, - * or to parse the RTMP packet by srs_rtmp_read_packet(). - * - * @param time, the timestamp of tag, read by srs_flv_read_tag_header(). - * @param type, the type of tag, read by srs_flv_read_tag_header(). - * @param data, the data of tag, read by srs_flv_read_tag_data(). - * @param size, the size of tag, read by srs_flv_read_tag_header(). - * @param ppts, output the pts in ms, - * - * @return 0, success; otherswise, failed. - * @remark, the dts always equals to @param time. - * @remark, the pts=dts for audio or data. - * @remark, video only support h.264. - */ -extern int srs_utils_parse_timestamp(uint32_t time, char type, char* data, int size, uint32_t* ppts); - -/** - * whether the flv tag specified by param type is ok. - * @return true when tag is video/audio/script-data; otherwise, false. - */ -extern srs_bool srs_utils_flv_tag_is_ok(char type); -extern srs_bool srs_utils_flv_tag_is_audio(char type); -extern srs_bool srs_utils_flv_tag_is_video(char type); -extern srs_bool srs_utils_flv_tag_is_av(char type); - -/** - * get the CodecID of video tag. - * Codec Identifier. The following values are defined: - * 2 = Sorenson H.263 - * 3 = Screen video - * 4 = On2 VP6 - * 5 = On2 VP6 with alpha channel - * 6 = Screen video version 2 - * 7 = AVC - * @return the code id. 0 for error. - */ -extern char srs_utils_flv_video_codec_id(char* data, int size); - -/** - * get the AVCPacketType of video tag. - * The following values are defined: - * 0 = AVC sequence header - * 1 = AVC NALU - * 2 = AVC end of sequence (lower level NALU sequence ender is - * not required or supported) - * @return the avc packet type. -1(0xff) for error. - */ -extern char srs_utils_flv_video_avc_packet_type(char* data, int size); - -/** - * get the FrameType of video tag. - * Type of video frame. The following values are defined: - * 1 = key frame (for AVC, a seekable frame) - * 2 = inter frame (for AVC, a non-seekable frame) - * 3 = disposable inter frame (H.263 only) - * 4 = generated key frame (reserved for server use only) - * 5 = video info/command frame - * @return the frame type. 0 for error. - */ -extern char srs_utils_flv_video_frame_type(char* data, int size); - -/** - * get the SoundFormat of audio tag. - * Format of SoundData. The following values are defined: - * 0 = Linear PCM, platform endian - * 1 = ADPCM - * 2 = MP3 - * 3 = Linear PCM, little endian - * 4 = Nellymoser 16 kHz mono - * 5 = Nellymoser 8 kHz mono - * 6 = Nellymoser - * 7 = G.711 A-law logarithmic PCM - * 8 = G.711 mu-law logarithmic PCM - * 9 = reserved - * 10 = AAC - * 11 = Speex - * 14 = MP3 8 kHz - * 15 = Device-specific sound - * Formats 7, 8, 14, and 15 are reserved. - * AAC is supported in Flash Player 9,0,115,0 and higher. - * Speex is supported in Flash Player 10 and higher. - * @return the sound format. -1(0xff) for error. - */ -extern char srs_utils_flv_audio_sound_format(char* data, int size); - -/** - * get the SoundRate of audio tag. - * Sampling rate. The following values are defined: - * 0 = 5.5 kHz - * 1 = 11 kHz - * 2 = 22 kHz - * 3 = 44 kHz - * @return the sound rate. -1(0xff) for error. - */ -extern char srs_utils_flv_audio_sound_rate(char* data, int size); - -/** - * get the SoundSize of audio tag. - * Size of each audio sample. This parameter only pertains to - * uncompressed formats. Compressed formats always decode - * to 16 bits internally. - * 0 = 8-bit samples - * 1 = 16-bit samples - * @return the sound size. -1(0xff) for error. - */ -extern char srs_utils_flv_audio_sound_size(char* data, int size); - -/** - * get the SoundType of audio tag. - * Mono or stereo sound - * 0 = Mono sound - * 1 = Stereo sound - * @return the sound type. -1(0xff) for error. - */ -extern char srs_utils_flv_audio_sound_type(char* data, int size); - -/** - * get the AACPacketType of audio tag. - * The following values are defined: - * 0 = AAC sequence header - * 1 = AAC raw - * @return the aac packet type. -1(0xff) for error. - */ -extern char srs_utils_flv_audio_aac_packet_type(char* data, int size); - -/************************************************************* - ************************************************************** - * human readable print. - ************************************************************** - *************************************************************/ -/** - * human readable print - * @param pdata, output the heap data, NULL to ignore. - * user must use srs_amf0_free_bytes to free it. - * @return return the *pdata for print. NULL to ignore. - */ -extern char* srs_human_amf0_print(srs_amf0_t amf0, char** pdata, int* psize); -/** - * convert the flv tag type to string. - * SRS_RTMP_TYPE_AUDIO to "Audio" - * SRS_RTMP_TYPE_VIDEO to "Video" - * SRS_RTMP_TYPE_SCRIPT to "Data" - * otherwise, "Unknown" - * @remark user never free the return char*, - * it's static shared const string. - */ -extern const char* srs_human_flv_tag_type2string(char type); - -/** - * get the codec id string. - * H.263 = Sorenson H.263 - * Screen = Screen video - * VP6 = On2 VP6 - * VP6Alpha = On2 VP6 with alpha channel - * Screen2 = Screen video version 2 - * H.264 = AVC - * otherwise, "Unknown" - * @remark user never free the return char*, - * it's static shared const string. - */ -extern const char* srs_human_flv_video_codec_id2string(char codec_id); - -/** - * get the avc packet type string. - * SH = AVC sequence header - * Nalu = AVC NALU - * SpsPpsEnd = AVC end of sequence - * otherwise, "Unknown" - * @remark user never free the return char*, - * it's static shared const string. - */ -extern const char* srs_human_flv_video_avc_packet_type2string(char avc_packet_type); - -/** - * get the frame type string. - * I = key frame (for AVC, a seekable frame) - * P/B = inter frame (for AVC, a non-seekable frame) - * DI = disposable inter frame (H.263 only) - * GI = generated key frame (reserved for server use only) - * VI = video info/command frame - * otherwise, "Unknown" - * @remark user never free the return char*, - * it's static shared const string. - */ -extern const char* srs_human_flv_video_frame_type2string(char frame_type); - -/** - * get the SoundFormat string. - * Format of SoundData. The following values are defined: - * LinearPCM = Linear PCM, platform endian - * ADPCM = ADPCM - * MP3 = MP3 - * LinearPCMLe = Linear PCM, little endian - * NellymoserKHz16 = Nellymoser 16 kHz mono - * NellymoserKHz8 = Nellymoser 8 kHz mono - * Nellymoser = Nellymoser - * G711APCM = G.711 A-law logarithmic PCM - * G711MuPCM = G.711 mu-law logarithmic PCM - * Reserved = reserved - * AAC = AAC - * Speex = Speex - * MP3KHz8 = MP3 8 kHz - * DeviceSpecific = Device-specific sound - * otherwise, "Unknown" - * @remark user never free the return char*, - * it's static shared const string. - */ -extern const char* srs_human_flv_audio_sound_format2string(char sound_format); - -/** - * get the SoundRate of audio tag. - * Sampling rate. The following values are defined: - * 5.5KHz = 5.5 kHz - * 11KHz = 11 kHz - * 22KHz = 22 kHz - * 44KHz = 44 kHz - * otherwise, "Unknown" - * @remark user never free the return char*, - * it's static shared const string. - */ -extern const char* srs_human_flv_audio_sound_rate2string(char sound_rate); - -/** - * get the SoundSize of audio tag. - * Size of each audio sample. This parameter only pertains to - * uncompressed formats. Compressed formats always decode - * to 16 bits internally. - * 8bit = 8-bit samples - * 16bit = 16-bit samples - * otherwise, "Unknown" - * @remark user never free the return char*, - * it's static shared const string. - */ -extern const char* srs_human_flv_audio_sound_size2string(char sound_size); - -/** - * get the SoundType of audio tag. - * Mono or stereo sound - * Mono = Mono sound - * Stereo = Stereo sound - * otherwise, "Unknown" - * @remark user never free the return char*, - * it's static shared const string. - */ -extern const char* srs_human_flv_audio_sound_type2string(char sound_type); - -/** - * get the AACPacketType of audio tag. - * The following values are defined: - * SH = AAC sequence header - * Raw = AAC raw - * otherwise, "Unknown" - * @remark user never free the return char*, - * it's static shared const string. - */ -extern const char* srs_human_flv_audio_aac_packet_type2string(char aac_packet_type); - -/** - * Format the RTMP packet to human readable buffer. - * @return Whether parse RTMP packet ok. 0, success; otherwise, failed. - */ -extern int srs_human_format_rtmp_packet(char* buffer, int nb_buffer, char type, uint32_t timestamp, char* data, int size); -extern int srs_human_format_rtmp_packet2(char* buffer, int nb_buffer, char type, uint32_t timestamp, char* data, int size, - uint32_t pre_timestamp, int64_t pre_now, int64_t starttime, int64_t nb_packets); - -/** - * Format current time to human readable string. - * @return A NULL terminated string. - */ -extern const char* srs_human_format_time(); - -#ifndef _WIN32 -// for getpid. -#include -#endif -// The log function for librtmp. -// User can disable it by define macro SRS_DISABLE_LOG. -// Or user can directly use them, or define the alias by: -// #define trace(msg, ...) srs_human_trace(msg, ##__VA_ARGS__) -// #define warn(msg, ...) srs_human_warn(msg, ##__VA_ARGS__) -// #define error(msg, ...) srs_human_error(msg, ##__VA_ARGS__) -#ifdef SRS_DISABLE_LOG -#define srs_human_trace(msg, ...) (void)0 -#define srs_human_warn(msg, ...) (void)0 -#define srs_human_error(msg, ...) (void)0 -#define srs_human_verbose(msg, ...) (void)0 -#define srs_human_raw(msg, ...) (void)0 -#else -#include -#include -#define srs_human_trace(msg, ...) \ -fprintf(stdout, "[T][%d][%s] ", getpid(), srs_human_format_time());\ -fprintf(stdout, msg, ##__VA_ARGS__); fprintf(stdout, "\n") -#define srs_human_warn(msg, ...) \ -fprintf(stdout, "[W][%d][%s][%d] ", getpid(), srs_human_format_time(), errno); \ -fprintf(stdout, msg, ##__VA_ARGS__); \ -fprintf(stdout, "\n") -#define srs_human_error(msg, ...) \ -fprintf(stderr, "[E][%d][%s][%d] ", getpid(), srs_human_format_time(), errno);\ -fprintf(stderr, msg, ##__VA_ARGS__); \ -fprintf(stderr, " (%s)\n", strerror(errno)) -#define srs_human_verbose(msg, ...) (void)0 -#define srs_human_raw(msg, ...) printf(msg, ##__VA_ARGS__) -#endif - -/************************************************************* - ************************************************************** - * IO hijack, use your specified io functions. - ************************************************************** - *************************************************************/ -// the void* will convert to your handler for io hijack. -typedef void* srs_hijack_io_t; -#ifdef SRS_HIJACK_IO -#ifndef _WIN32 -// for iovec. -#include -#endif -/** - * get the hijack io object in rtmp protocol sdk. - * @remark, user should never provides this method, srs-librtmp provides it. - */ -extern srs_hijack_io_t srs_hijack_io_get(srs_rtmp_t rtmp); -#endif -// define the following macro and functions in your module to hijack the io. -// the example @see https://github.com/ossrs/srs-bench -// which use librtmp but use its own io(use st also). -#ifdef SRS_HIJACK_IO -/** - * create hijack. - * @return NULL for error; otherwise, ok. - */ -extern srs_hijack_io_t srs_hijack_io_create(); -/** - * destroy the context, user must close the socket. - */ -extern void srs_hijack_io_destroy(srs_hijack_io_t ctx); -/** - * create socket, not connect yet. - * @param owner, the rtmp context which create this socket. - * @return 0, success; otherswise, failed. - * TODO: FIXME: Incompatible API for https://github.com/ossrs/srs/blob/2.0release/trunk/src/libs/srs_librtmp.hpp#L989 - */ -extern int srs_hijack_io_create_socket(srs_hijack_io_t ctx, srs_rtmp_t owner); -/** - * connect socket at server_ip:port. - * @return 0, success; otherswise, failed. - */ -extern int srs_hijack_io_connect(srs_hijack_io_t ctx, const char* server_ip, int port); -/** - * read from socket. - * @return 0, success; otherswise, failed. - */ -extern int srs_hijack_io_read(srs_hijack_io_t ctx, void* buf, size_t size, ssize_t* nread); -/** - * set the socket recv timeout in ms. - * @return 0, success; otherswise, failed. - */ -extern int srs_hijack_io_set_recv_timeout(srs_hijack_io_t ctx, int64_t tm); -/** - * get the socket recv timeout. - * @return 0, success; otherswise, failed. - */ -extern int64_t srs_hijack_io_get_recv_timeout(srs_hijack_io_t ctx); -/** - * get the socket recv bytes. - * @return 0, success; otherswise, failed. - */ -extern int64_t srs_hijack_io_get_recv_bytes(srs_hijack_io_t ctx); -/** - * set the socket send timeout in ms. - * @return 0, success; otherswise, failed. - */ -extern int srs_hijack_io_set_send_timeout(srs_hijack_io_t ctx, int64_t tm); -/** - * get the socket send timeout. - * @return 0, success; otherswise, failed. - */ -extern int64_t srs_hijack_io_get_send_timeout(srs_hijack_io_t ctx); -/** - * get the socket send bytes. - * @return 0, success; otherswise, failed. - */ -extern int64_t srs_hijack_io_get_send_bytes(srs_hijack_io_t ctx); -/** - * writev of socket. - * @return 0, success; otherswise, failed. - * @remark We assume that the writev always write all data to peer, like what ST or block-socket done. - */ -extern int srs_hijack_io_writev(srs_hijack_io_t ctx, const iovec *iov, int iov_size, ssize_t* nwrite); -/** - * whether the timeout is never timeout in ms. - * @return 0, with timeout specified; otherwise, never timeout. - * TODO: FIXME: Incompatible API for https://github.com/ossrs/srs/blob/2.0release/trunk/src/libs/srs_librtmp.hpp#L1039 - */ -extern int srs_hijack_io_is_never_timeout(srs_hijack_io_t ctx, int64_t tm); -/** - * read fully, fill the buf exactly size bytes. - * @return 0, success; otherswise, failed. - */ -extern int srs_hijack_io_read_fully(srs_hijack_io_t ctx, void* buf, size_t size, ssize_t* nread); -/** - * write bytes to socket. - * @return 0, success; otherswise, failed. - * @remark We assume that the write always write all data to peer, like what ST or block-socket done. - */ -extern int srs_hijack_io_write(srs_hijack_io_t ctx, void* buf, size_t size, ssize_t* nwrite); -#endif - -/************************************************************* - ************************************************************** - * Windows SRS-LIBRTMP solution - ************************************************************** - *************************************************************/ -// for srs-librtmp, @see https://github.com/ossrs/srs/issues/213 -#ifdef _WIN32 -#include -int gettimeofday(struct timeval* tv, struct timezone* tz); -#define PRId64 "lld" - -// for inet helpers. -typedef int socklen_t; -const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); - -// for mkdir(). -#include - -// for open(). -typedef int mode_t; -#define S_IRUSR 0 -#define S_IWUSR 0 -#define S_IXUSR 0 -#define S_IRGRP 0 -#define S_IWGRP 0 -#define S_IXGRP 0 -#define S_IROTH 0 -#define S_IXOTH 0 - -// for file seek. -#include -#include -#define open _open -#define close _close -#define lseek _lseek - -// for socket. -ssize_t writev(int fd, const struct iovec *iov, int iovcnt); -typedef int64_t useconds_t; -int usleep(useconds_t usec); -int socket_setup(); -int socket_cleanup(); - -// others. -#define snprintf _snprintf -#endif - -/************************************************************* - ************************************************************* - * Deprecated APIs, maybe removed in future versions. - ************************************************************* - *************************************************************/ -/** - * Deprecated, for bandwidth test check only. - */ -extern srs_rtmp_t srs_rtmp_create2(const char* url); - -/** - * Deprecated, use seperate function to retrieve information from rtmp, - * for example, use srs_rtmp_get_server_ip to get server ip. - */ -extern int srs_rtmp_connect_app2(srs_rtmp_t rtmp, - char srs_server_ip[128], char srs_server[128], - char srs_primary[128], char srs_authors[128], - char srs_version[32], int* srs_id, int* srs_pid); - -/** - * Deprecated, use srs_human_format_rtmp_packet instead. - */ -extern int srs_human_print_rtmp_packet(char type, uint32_t timestamp, char* data, int size); -extern int srs_human_print_rtmp_packet2(char type, uint32_t timestamp, char* data, int size, uint32_t pre_timestamp); -extern int srs_human_print_rtmp_packet3(char type, uint32_t timestamp, char* data, int size, uint32_t pre_timestamp, int64_t pre_now); -extern int srs_human_print_rtmp_packet4(char type, uint32_t timestamp, char* data, int size, uint32_t pre_timestamp, int64_t pre_now, - int64_t starttime, int64_t nb_packets); - -#ifdef __cplusplus -} -#endif - -#endif - diff --git a/trunk/src/main/srs_main_ingest_hls.cpp b/trunk/src/main/srs_main_ingest_hls.cpp index d5f8c458a..aabae1635 100644 --- a/trunk/src/main/srs_main_ingest_hls.cpp +++ b/trunk/src/main/srs_main_ingest_hls.cpp @@ -52,7 +52,7 @@ srs_error_t proxy_hls2rtmp(std::string hls, std::string rtmp); // @global log and context. ISrsLog* _srs_log = new SrsConsoleLog(SrsLogLevelTrace, false); -ISrsThreadContext* _srs_context = new SrsThreadContext(); +ISrsContext* _srs_context = new SrsThreadContext(); /** * main entrance. @@ -63,8 +63,8 @@ int main(int argc, char** argv) srs_assert(srs_is_little_endian()); // directly failed when compile limited. -#if defined(SRS_AUTO_GPERF_MP) || defined(SRS_AUTO_GPERF_MP) \ - || defined(SRS_AUTO_GPERF_MC) || defined(SRS_AUTO_GPERF_MP) +#if defined(SRS_GPERF_MP) || defined(SRS_GPERF_MP) \ + || defined(SRS_GPERF_MC) || defined(SRS_GPERF_MP) srs_error("donot support gmc/gmp/gcp/gprof"); exit(-1); #endif @@ -147,6 +147,7 @@ private: skip = false; sent = false; dirty = false; + duration = 0.0; } int fetch(std::string m3u8); @@ -369,7 +370,7 @@ int SrsIngestHlsInput::parseM3u8(SrsHttpUri* url, double& td, double& duration) SrsHttpClient client; srs_trace("parse input hls %s", url->get_url().c_str()); - if ((err = client.initialize(url->get_host(), url->get_port())) != srs_success) { + if ((err = client.initialize(url->get_schema(), url->get_host(), url->get_port())) != srs_success) { // TODO: FIXME: Use error ret = srs_error_code(err); srs_freep(err); @@ -608,7 +609,7 @@ int SrsIngestHlsInput::SrsTsPiece::fetch(string m3u8) } // initialize the fresh http client. - if ((ret = client.initialize(uri.get_host(), uri.get_port()) != ERROR_SUCCESS)) { + if ((ret = client.initialize(uri.get_schema(), uri.get_host(), uri.get_port()) != ERROR_SUCCESS)) { return ret; } diff --git a/trunk/src/main/srs_main_mp4_parser.cpp b/trunk/src/main/srs_main_mp4_parser.cpp index 66b5ea525..c3ccfec14 100644 --- a/trunk/src/main/srs_main_mp4_parser.cpp +++ b/trunk/src/main/srs_main_mp4_parser.cpp @@ -38,7 +38,7 @@ using namespace std; // @global log and context. ISrsLog* _srs_log = new SrsConsoleLog(SrsLogLevelTrace, false); -ISrsThreadContext* _srs_context = new SrsThreadContext(); +ISrsContext* _srs_context = new SrsThreadContext(); srs_error_t parse(std::string mp4_file, bool verbose) { diff --git a/trunk/src/main/srs_main_server.cpp b/trunk/src/main/srs_main_server.cpp index 8195d0020..65fc1cb65 100644 --- a/trunk/src/main/srs_main_server.cpp +++ b/trunk/src/main/srs_main_server.cpp @@ -30,14 +30,14 @@ #include using namespace std; -#ifdef SRS_AUTO_GPERF_MP +#ifdef SRS_GPERF_MP #include #endif -#ifdef SRS_AUTO_GPERF_CP +#ifdef SRS_GPERF_CP #include #endif -#ifdef SRS_AUTO_GPERF +#ifdef SRS_GPERF #include #endif @@ -54,11 +54,12 @@ using namespace std; #include #include #include -#ifdef SRS_AUTO_RTC +#ifdef SRS_RTC #include +#include #endif -#ifdef SRS_AUTO_SRT +#ifdef SRS_SRT #include #endif @@ -68,14 +69,17 @@ srs_error_t run_hybrid_server(); void show_macro_features(); // @global log and context. -ISrsLog* _srs_log = new SrsFastLog(); -ISrsThreadContext* _srs_context = new SrsThreadContext(); +ISrsLog* _srs_log = new SrsFileLog(); +ISrsContext* _srs_context = new SrsThreadContext(); // @global config object for app module. SrsConfig* _srs_config = new SrsConfig(); // @global version of srs, which can grep keyword "XCORE" extern const char* _srs_version; +// @global main SRS server, for debugging +SrsServer* _srs_server = NULL; + /** * main entrance. */ @@ -85,18 +89,21 @@ srs_error_t do_main(int argc, char** argv) // TODO: support both little and big endian. srs_assert(srs_is_little_endian()); + + // For RTC to generating random ICE username. + ::srandom((unsigned long)(srs_update_system_time() | (::getpid()<<13))); // for gperf gmp or gcp, // should never enable it when not enabled for performance issue. -#ifdef SRS_AUTO_GPERF_MP +#ifdef SRS_GPERF_MP HeapProfilerStart("gperf.srs.gmp"); #endif -#ifdef SRS_AUTO_GPERF_CP +#ifdef SRS_GPERF_CP ProfilerStart("gperf.srs.gcp"); #endif // directly compile error when these two macro defines. -#if defined(SRS_AUTO_GPERF_MC) && defined(SRS_AUTO_GPERF_MP) +#if defined(SRS_GPERF_MC) && defined(SRS_GPERF_MP) #error ("option --with-gmc confict with --with-gmp, " "@see: http://google-perftools.googlecode.com/svn/trunk/doc/heap_checker.html\n" "Note that since the heap-checker uses the heap-profiling framework internally, " @@ -104,7 +111,7 @@ srs_error_t do_main(int argc, char** argv) #endif // never use gmp to check memory leak. -#ifdef SRS_AUTO_GPERF_MP +#ifdef SRS_GPERF_MP #warning "gmp is not used for memory leak, please use gmc instead." #endif @@ -130,14 +137,14 @@ srs_error_t do_main(int argc, char** argv) } // config already applied to log. - srs_trace("%s, %s", RTMP_SIG_SRS_SERVER, RTMP_SIG_SRS_LICENSE); + srs_trace2(TAG_MAIN, "%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("contributors: %s", SRS_CONSTRIBUTORS); 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); + _srs_config->cwd().c_str(), cwd.c_str(), SRS_BUILD_DATE, SRS_USER_CONFIGURE, SRS_UNAME, SRS_OSX_BOOL); + srs_trace("configure detail: " SRS_CONFIGURE); +#ifdef SRS_EMBEDED_TOOL_CHAIN + srs_trace("crossbuild tool chain: " SRS_EMBEDED_TOOL_CHAIN); #endif // for memory check or detect. @@ -157,7 +164,7 @@ srs_error_t do_main(int argc, char** argv) ss << "glic mem-check env MALLOC_CHECK_ " << mcov << "=>" << mcnv << ", LIBC_FATAL_STDERR_ " << lfsov << "=>" << lfsnv << "."; #endif -#ifdef SRS_AUTO_GPERF_MC +#ifdef SRS_GPERF_MC string hcov = srs_getenv("HEAPCHECK"); if (hcov.empty()) { string cpath = _srs_config->config(); @@ -167,7 +174,7 @@ srs_error_t do_main(int argc, char** argv) } #endif -#ifdef SRS_AUTO_GPERF_MD +#ifdef SRS_GPERF_MD char* TCMALLOC_PAGE_FENCE = getenv("TCMALLOC_PAGE_FENCE"); if (!TCMALLOC_PAGE_FENCE || strcmp(TCMALLOC_PAGE_FENCE, "1")) { srs_warn("gmd enabled without env TCMALLOC_PAGE_FENCE=1"); @@ -190,7 +197,7 @@ srs_error_t do_main(int argc, char** argv) // features show_macro_features(); -#ifdef SRS_AUTO_GPERF +#ifdef SRS_GPERF // For tcmalloc, use slower release rate. if (true) { double trr = _srs_config->tcmalloc_release_rate(); @@ -208,6 +215,9 @@ srs_error_t do_main(int argc, char** argv) } int main(int argc, char** argv) { + // For background context id. + _srs_context->set_id(_srs_context->generate_id()); + srs_error_t err = do_main(argc, argv); if (err != srs_success) { @@ -233,8 +243,8 @@ void show_macro_features() ss << ", rch:" << srs_bool2switch(true); ss << ", dash:" << "on"; ss << ", hls:" << srs_bool2switch(true); - ss << ", hds:" << srs_bool2switch(SRS_AUTO_HDS_BOOL); - ss << ", srt:" << srs_bool2switch(SRS_AUTO_SRT_BOOL); + ss << ", hds:" << srs_bool2switch(SRS_HDS_BOOL); + ss << ", srt:" << srs_bool2switch(SRS_SRT_BOOL); // hc(http callback) ss << ", hc:" << srs_bool2switch(true); // ha(http api) @@ -272,7 +282,7 @@ void show_macro_features() #if defined(__aarch64__) ss << " aarch64"; #endif -#if defined(SRS_AUTO_CROSSBUILD) +#if defined(SRS_CROSSBUILD) ss << "(crossbuild)"; #endif @@ -345,14 +355,14 @@ void show_macro_features() srs_trace("system default latency(ms): mw(0-%d) + mr(0-%d) + play-queue(0-%d)", srsu2msi(SRS_PERF_MW_SLEEP), possible_mr_latency, srsu2msi(SRS_PERF_PLAY_QUEUE)); -#ifdef SRS_AUTO_MEM_WATCH +#ifdef SRS_MEM_WATCH #warning "srs memory watcher will hurts performance. user should kill by SIGTERM or init.d script." srs_warn("srs memory watcher will hurts performance. user should kill by SIGTERM or init.d script."); #endif #if VERSION_MAJOR > VERSION_STABLE - #warning "Current branch is develop." - srs_warn("%s/%s is develop", RTMP_SIG_SRS_KEY, RTMP_SIG_SRS_VERSION); + #warning "Current branch is not stable." + srs_warn("%s/%s is not stable", RTMP_SIG_SRS_KEY, RTMP_SIG_SRS_VERSION); #endif #if defined(SRS_PERF_SO_SNDBUF_SIZE) && !defined(SRS_PERF_MW_SO_SNDBUF) @@ -402,16 +412,16 @@ srs_error_t run_directly_or_daemon() // Load daemon from config, disable it for docker. // @see https://github.com/ossrs/srs/issues/1594 - bool in_daemon = _srs_config->get_daemon(); - if (in_daemon && _srs_in_docker && _srs_config->disable_daemon_for_docker()) { + bool run_as_daemon = _srs_config->get_daemon(); + if (run_as_daemon && _srs_in_docker && _srs_config->disable_daemon_for_docker()) { srs_warn("disable daemon for docker"); - in_daemon = false; + run_as_daemon = false; } - // If not daemon, directly run master. - if (!in_daemon) { + // If not daemon, directly run hybrid server. + if (!run_as_daemon) { if ((err = run_hybrid_server()) != srs_success) { - return srs_error_wrap(err, "run master"); + return srs_error_wrap(err, "run hybrid"); } return srs_success; } @@ -448,7 +458,7 @@ srs_error_t run_directly_or_daemon() srs_trace("son(daemon) process running."); if ((err = run_hybrid_server()) != srs_success) { - return srs_error_wrap(err, "daemon run master"); + return srs_error_wrap(err, "daemon run hybrid"); } return err; @@ -461,11 +471,11 @@ srs_error_t run_hybrid_server() // Create servers and register them. _srs_hybrid->register_server(new SrsServerAdapter()); -#ifdef SRS_AUTO_SRT +#ifdef SRS_SRT _srs_hybrid->register_server(new SrtServerAdapter()); #endif -#ifdef SRS_AUTO_RTC +#ifdef SRS_RTC _srs_hybrid->register_server(new RtcServerAdapter()); #endif diff --git a/trunk/src/protocol/srs_http_stack.cpp b/trunk/src/protocol/srs_http_stack.cpp index ad4f94168..fa2f36367 100644 --- a/trunk/src/protocol/srs_http_stack.cpp +++ b/trunk/src/protocol/srs_http_stack.cpp @@ -36,6 +36,7 @@ using namespace std; #include #include #include +#include #define SRS_HTTP_DEFAULT_PAGE "index.html" @@ -154,6 +155,22 @@ SrsHttpHeader::~SrsHttpHeader() void SrsHttpHeader::set(string key, string value) { + // Convert to UpperCamelCase, for example: + // transfer-encoding + // transform to: + // Transfer-Encoding + char pchar = 0; + for (int i = 0; i < (int)key.length(); i++) { + char ch = key.at(i); + + if (i == 0 || pchar == '-') { + if (ch >= 'a' && ch <= 'z') { + ((char*)key.data())[i] = ch - 32; + } + } + pchar = ch; + } + headers[key] = value; } @@ -360,6 +377,10 @@ srs_error_t SrsHttpFileServer::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMes { srs_assert(entry); + // For each HTTP session, we use short-term HTTP connection. + SrsHttpHeader* hdr = w->header(); + hdr->set("Connection", "Close"); + string upath = r->path(); string fullpath = srs_http_fs_fullpath(dir, entry->pattern, upath); @@ -453,7 +474,7 @@ srs_error_t SrsHttpFileServer::serve_file(ISrsHttpResponseWriter* w, ISrsHttpMes // write body. int64_t left = length; if ((err = copy(w, fs, r, (int)left)) != srs_success) { - return srs_error_wrap(err, "copy file=%s size=%d", fullpath.c_str(), left); + return srs_error_wrap(err, "copy file=%s size=%d", fullpath.c_str(), (int)left); } if ((err = w->final_request()) != srs_success) { @@ -553,7 +574,7 @@ srs_error_t SrsHttpFileServer::copy(ISrsHttpResponseWriter* w, SrsFileReader* fs left -= nread; if ((err = w->write(buf, (int)nread)) != srs_success) { - return srs_error_wrap(err, "write limit=%d, bytes=%d, left=%d", max_read, nread, left); + return srs_error_wrap(err, "write limit=%d, bytes=%d, left=%d", max_read, (int)nread, left); } } @@ -616,7 +637,7 @@ srs_error_t SrsHttpServeMux::initialize() void SrsHttpServeMux::hijack(ISrsHttpMatchHijacker* h) { - std::vector::iterator it = ::find(hijackers.begin(), hijackers.end(), h); + std::vector::iterator it = std::find(hijackers.begin(), hijackers.end(), h); if (it != hijackers.end()) { return; } @@ -625,7 +646,7 @@ void SrsHttpServeMux::hijack(ISrsHttpMatchHijacker* h) void SrsHttpServeMux::unhijack(ISrsHttpMatchHijacker* h) { - std::vector::iterator it = ::find(hijackers.begin(), hijackers.end(), h); + std::vector::iterator it = std::find(hijackers.begin(), hijackers.end(), h); if (it == hijackers.end()) { return; } @@ -869,7 +890,7 @@ ISrsHttpMessage::~ISrsHttpMessage() SrsHttpUri::SrsHttpUri() { - port = SRS_DEFAULT_HTTP_PORT; + port = 0; } SrsHttpUri::~SrsHttpUri() @@ -878,8 +899,6 @@ SrsHttpUri::~SrsHttpUri() srs_error_t SrsHttpUri::initialize(string _url) { - srs_error_t err = srs_success; - schema = host = path = query = ""; url = _url; @@ -887,29 +906,55 @@ srs_error_t SrsHttpUri::initialize(string _url) http_parser_url hp_u; int r0; - if((r0 = http_parser_parse_url(purl, url.length(), 0, &hp_u)) != 0){ + if ((r0 = http_parser_parse_url(purl, url.length(), 0, &hp_u)) != 0){ return srs_error_new(ERROR_HTTP_PARSE_URI, "parse url %s failed, code=%d", purl, r0); } std::string field = get_uri_field(url, &hp_u, UF_SCHEMA); - if(!field.empty()){ + if (!field.empty()){ schema = field; } host = get_uri_field(url, &hp_u, UF_HOST); field = get_uri_field(url, &hp_u, UF_PORT); - if(!field.empty()){ + if (!field.empty()) { port = atoi(field.c_str()); } - if(port<=0){ - port = SRS_DEFAULT_HTTP_PORT; + if (port <= 0) { + if (schema == "https") { + port = SRS_DEFAULT_HTTPS_PORT; + } else if (schema == "rtmp") { + port = SRS_CONSTS_RTMP_DEFAULT_PORT; + } else if (schema == "redis") { + port = SRS_DEFAULT_REDIS_PORT; + } else { + port = SRS_DEFAULT_HTTP_PORT; + } } path = get_uri_field(url, &hp_u, UF_PATH); query = get_uri_field(url, &hp_u, UF_QUERY); - return err; + username_ = get_uri_field(url, &hp_u, UF_USERINFO); + size_t pos = username_.find(":"); + if (pos != string::npos) { + password_ = username_.substr(pos+1); + username_ = username_.substr(0, pos); + } + + return parse_query(); +} + +void SrsHttpUri::set_schema(std::string v) +{ + schema = v; + + // Update url with new schema. + size_t pos = url.find("://"); + if (pos != string::npos) { + url = schema + "://" + url.substr(pos + 3); + } } string SrsHttpUri::get_url() @@ -942,6 +987,25 @@ string SrsHttpUri::get_query() return query; } +string SrsHttpUri::get_query_by_key(std::string key) +{ + map::iterator it = query_values_.find(key); + if(it == query_values_.end()) { + return ""; + } + return it->second; +} + +std::string SrsHttpUri::username() +{ + return username_; +} + +std::string SrsHttpUri::password() +{ + return password_; +} + string SrsHttpUri::get_uri_field(string uri, void* php_u, int ifield) { http_parser_url* hp_u = (http_parser_url*)php_u; @@ -957,6 +1021,331 @@ string SrsHttpUri::get_uri_field(string uri, void* php_u, int ifield) return uri.substr(offset, len); } +srs_error_t SrsHttpUri::parse_query() +{ + srs_error_t err = srs_success; + if(query.empty()) { + return err; + } + + size_t begin = query.find("?"); + if(string::npos != begin) { + begin++; + } else { + begin = 0; + } + string query_str = query.substr(begin); + query_values_.clear(); + srs_parse_query_string(query_str, query_values_); + + return err; +} + + +// @see golang net/url/url.go +namespace { + enum EncodeMode { + encodePath, + encodePathSegment, + encodeHost, + encodeZone, + encodeUserPassword, + encodeQueryComponent, + encodeFragment, + }; + + bool should_escape(uint8_t c, EncodeMode mode) { + // §2.3 Unreserved characters (alphanum) + if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9')) { + return false; + } + + if(encodeHost == mode || encodeZone == mode) { + // §3.2.2 Host allows + // sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" + // as part of reg-name. + // We add : because we include :port as part of host. + // We add [ ] because we include [ipv6]:port as part of host. + // We add < > because they're the only characters left that + // we could possibly allow, and Parse will reject them if we + // escape them (because hosts can't use %-encoding for + // ASCII bytes). + switch(c) { + case '!': + case '$': + case '&': + case '\'': + case '(': + case ')': + case '*': + case '+': + case ',': + case ';': + case '=': + case ':': + case '[': + case ']': + case '<': + case '>': + case '"': + return false; + } + } + + switch(c) { + case '-': + case '_': + case '.': + case '~': // §2.3 Unreserved characters (mark) + return false; + case '$': + case '&': + case '+': + case ',': + case '/': + case ':': + case ';': + case '=': + case '?': + case '@': // §2.2 Reserved characters (reserved) + // Different sections of the URL allow a few of + // the reserved characters to appear unescaped. + switch (mode) { + case encodePath: // §3.3 + // The RFC allows : @ & = + $ but saves / ; , for assigning + // meaning to individual path segments. This package + // only manipulates the path as a whole, so we allow those + // last three as well. That leaves only ? to escape. + return c == '?'; + + case encodePathSegment: // §3.3 + // The RFC allows : @ & = + $ but saves / ; , for assigning + // meaning to individual path segments. + return c == '/' || c == ';' || c == ',' || c == '?'; + + case encodeUserPassword: // §3.2.1 + // The RFC allows ';', ':', '&', '=', '+', '$', and ',' in + // userinfo, so we must escape only '@', '/', and '?'. + // The parsing of userinfo treats ':' as special so we must escape + // that too. + return c == '@' || c == '/' || c == '?' || c == ':'; + + case encodeQueryComponent: // §3.4 + // The RFC reserves (so we must escape) everything. + return true; + + case encodeFragment: // §4.1 + // The RFC text is silent but the grammar allows + // everything, so escape nothing. + return false; + default: + break; + } + } + + if(mode == encodeFragment) { + // RFC 3986 §2.2 allows not escaping sub-delims. A subset of sub-delims are + // included in reserved from RFC 2396 §2.2. The remaining sub-delims do not + // need to be escaped. To minimize potential breakage, we apply two restrictions: + // (1) we always escape sub-delims outside of the fragment, and (2) we always + // escape single quote to avoid breaking callers that had previously assumed that + // single quotes would be escaped. See issue #19917. + switch (c) { + case '!': + case '(': + case ')': + case '*': + return false; + } + } + + // Everything else must be escaped. + return true; + } + + bool ishex(uint8_t c) { + if( '0' <= c && c <= '9') { + return true; + } else if('a' <= c && c <= 'f') { + return true; + } else if( 'A' <= c && c <= 'F') { + return true; + } + return false; + } + + uint8_t hex_to_num(uint8_t c) { + if('0' <= c && c <= '9') { + return c - '0'; + } else if('a' <= c && c <= 'f') { + return c - 'a' + 10; + } else if('A' <= c && c <= 'F') { + return c - 'A' + 10; + } + return 0; + } + + srs_error_t unescapse(string s, string& value, EncodeMode mode) { + srs_error_t err = srs_success; + int n = 0; + bool has_plus = false; + int i = 0; + // Count %, check that they're well-formed. + while(i < (int)s.length()) { + switch (s.at(i)) { + case '%': + { + n++; + if((i+2) >= (int)s.length() || !ishex(s.at(i+1)) || !ishex(s.at(i+2))) { + string msg = s.substr(i); + if(msg.length() > 3) { + msg = msg.substr(0, 3); + } + return srs_error_new(ERROR_HTTP_URL_UNESCAPE, "invalid URL escape: %s", msg.c_str()); + } + + // Per https://tools.ietf.org/html/rfc3986#page-21 + // in the host component %-encoding can only be used + // for non-ASCII bytes. + // But https://tools.ietf.org/html/rfc6874#section-2 + // introduces %25 being allowed to escape a percent sign + // in IPv6 scoped-address literals. Yay. + if(encodeHost == mode && hex_to_num(s.at(i+1)) < 8 && s.substr(i, 3) != "%25") { + return srs_error_new(ERROR_HTTP_URL_UNESCAPE, "invalid URL escap: %s", s.substr(i, 3).c_str()); + } + + if(encodeZone == mode) { + // RFC 6874 says basically "anything goes" for zone identifiers + // and that even non-ASCII can be redundantly escaped, + // but it seems prudent to restrict %-escaped bytes here to those + // that are valid host name bytes in their unescaped form. + // That is, you can use escaping in the zone identifier but not + // to introduce bytes you couldn't just write directly. + // But Windows puts spaces here! Yay. + uint8_t v = (hex_to_num(s.at(i+1)) << 4) | (hex_to_num(s.at(i+2))); + if("%25" != s.substr(i, 3) && ' ' != v && should_escape(v, encodeHost)) { + return srs_error_new(ERROR_HTTP_URL_UNESCAPE, "invalid URL escap: %s", s.substr(i, 3).c_str()); + } + } + i += 3; + } + break; + case '+': + has_plus = encodeQueryComponent == mode; + i++; + break; + default: + if((encodeHost == mode || encodeZone == mode) && ((uint8_t)s.at(i) < 0x80) && should_escape(s.at(i), mode)) { + return srs_error_new(ERROR_HTTP_URL_UNESCAPE, "invalid character %u in host name", s.at(i)); + } + i++; + break; + } + } + + if(0 == n && !has_plus) { + value = s; + return err; + } + + value.clear(); + //value.resize(s.length() - 2*n); + for(int i = 0; i < (int)s.length(); ++i) { + switch(s.at(i)) { + case '%': + value += (hex_to_num(s.at(i+1))<<4 | hex_to_num(s.at(i+2))); + i += 2; + break; + case '+': + if(encodeQueryComponent == mode) { + value += " "; + } else { + value += "+"; + } + break; + default: + value += s.at(i); + break; + } + } + + return srs_success; + } + + string escape(string s, EncodeMode mode) { + int space_count = 0; + int hex_count = 0; + for(int i = 0; i < (int)s.length(); ++i) { + uint8_t c = s.at(i); + if(should_escape(c, mode)) { + if(' ' == c && encodeQueryComponent == mode) { + space_count++; + } else { + hex_count++; + } + } + } + + if(0 == space_count && 0 == hex_count) { + return s; + } + + string value; + if(0 == hex_count) { + value = s; + for(int i = 0; i < (int)s.length(); ++i) { + if(' ' == s.at(i)) { + value[i] = '+'; + } + } + return value; + } + + //value.resize(s.length() + 2*hex_count); + const char escape_code[] = "0123456789ABCDEF"; + //int j = 0; + for(int i = 0; i < (int)s.length(); ++i) { + uint8_t c = s.at(i); + if(' ' == c && encodeQueryComponent == mode) { + value += '+'; + } else if (should_escape(c, mode)) { + value += '%'; + value += escape_code[c>>4]; + value += escape_code[c&15]; + //j += 3; + } else { + value += s[i]; + + } + } + + return value; + } + + +} + + +string SrsHttpUri::query_escape(std::string s) +{ + return escape(s, encodeQueryComponent); +} + +string SrsHttpUri::path_escape(std::string s) +{ + return escape(s, encodePathSegment); +} + +srs_error_t SrsHttpUri::query_unescape(std::string s, std::string& value) +{ + return unescapse(s, value, encodeQueryComponent); +} + +srs_error_t SrsHttpUri::path_unescape(std::string s, std::string& value) +{ + return unescapse(s, value, encodePathSegment); +} + // For #if !defined(SRS_EXPORT_LIBRTMP) #endif diff --git a/trunk/src/protocol/srs_http_stack.hpp b/trunk/src/protocol/srs_http_stack.hpp index 9413c32af..7e4c0da0e 100644 --- a/trunk/src/protocol/srs_http_stack.hpp +++ b/trunk/src/protocol/srs_http_stack.hpp @@ -28,9 +28,6 @@ #include -// Default http listen port. -#define SRS_DEFAULT_HTTP_PORT 80 - #if !defined(SRS_EXPORT_LIBRTMP) #include @@ -449,15 +446,11 @@ public: // // There are some modes to determine the length of body: // 1. content-length and chunked. -// 2. user confirmed infinite chunked. -// 3. no body or user not confirmed infinite chunked. +// 2. infinite chunked. +// 3. no body. // For example: // ISrsHttpMessage* r = ...; // while (!r->eof()) r->read(); // Read in mode 1 or 3. -// For some server, we can confirm the body is infinite chunked: -// ISrsHttpMessage* r = ...; -// r->enter_infinite_chunked(); -// while (!r->eof()) r->read(); // Read in mode 2 // @rmark for mode 2, the infinite chunked, all left data is body. class ISrsHttpMessage { @@ -485,17 +478,13 @@ public: virtual std::string path() = 0; virtual std::string query() = 0; virtual std::string ext() = 0; - // Get the RESTful id, + // Get the RESTful id, in string, // for example, pattern is /api/v1/streams, path is /api/v1/streams/100, // then the rest id is 100. // @param pattern the handler pattern which will serve the request. - // @return the REST id; -1 if not matched. - virtual int parse_rest_id(std::string pattern) = 0; + // @return the REST id; "" if not matched. + virtual std::string parse_rest_id(std::string pattern) = 0; public: - // The left all data is chunked body, the infinite chunked mode, - // which is chunked encoding without chunked header. - // @remark error when message is in chunked or content-length specified. - virtual srs_error_t enter_infinite_chunked() = 0; // Read body to string. // @remark for small http body. virtual srs_error_t body_read_all(std::string& body) = 0; @@ -527,12 +516,17 @@ private: int port; std::string path; std::string query; + std::string username_; + std::string password_; + std::map query_values_; public: SrsHttpUri(); virtual ~SrsHttpUri(); public: // Initialize the http uri. virtual srs_error_t initialize(std::string _url); + // After parsed the message, set the schema to https. + virtual void set_schema(std::string v); public: virtual std::string get_url(); virtual std::string get_schema(); @@ -540,10 +534,19 @@ public: virtual int get_port(); virtual std::string get_path(); virtual std::string get_query(); + virtual std::string get_query_by_key(std::string key); + virtual std::string username(); + virtual std::string password(); private: // Get the parsed url field. // @return return empty string if not set. virtual std::string get_uri_field(std::string uri, void* hp_u, int field); + srs_error_t parse_query(); +public: + static std::string query_escape(std::string s); + static std::string path_escape(std::string s); + static srs_error_t query_unescape(std::string s, std::string& value); + static srs_error_t path_unescape(std::string s, std::string& value); }; // For #if !defined(SRS_EXPORT_LIBRTMP) diff --git a/trunk/src/protocol/srs_protocol_amf0.cpp b/trunk/src/protocol/srs_protocol_amf0.cpp index 3af4d2d7e..96b95f862 100644 --- a/trunk/src/protocol/srs_protocol_amf0.cpp +++ b/trunk/src/protocol/srs_protocol_amf0.cpp @@ -33,7 +33,7 @@ using namespace std; #include #include -using namespace _srs_internal; +using namespace srs_internal; // AMF0 marker #define RTMP_AMF0_Number 0x00 @@ -1703,7 +1703,7 @@ srs_error_t srs_amf0_write_undefined(SrsBuffer* stream) return err; } -namespace _srs_internal +namespace srs_internal { srs_error_t srs_amf0_read_utf8(SrsBuffer* stream, string& value) { @@ -1761,7 +1761,7 @@ namespace _srs_internal // data if (!stream->require((int)value.length())) { - return srs_error_new(ERROR_RTMP_AMF0_ENCODE, "requires %d only %d bytes", value.length(), stream->left()); + return srs_error_new(ERROR_RTMP_AMF0_ENCODE, "requires %" PRIu64 " only %d bytes", value.length(), stream->left()); } stream->write_string(value); diff --git a/trunk/src/protocol/srs_protocol_amf0.hpp b/trunk/src/protocol/srs_protocol_amf0.hpp index 7af7c700b..7ab16fc3c 100644 --- a/trunk/src/protocol/srs_protocol_amf0.hpp +++ b/trunk/src/protocol/srs_protocol_amf0.hpp @@ -36,7 +36,7 @@ class SrsAmf0StrictArray; class SrsJsonAny; // internal objects, user should never use it. -namespace _srs_internal +namespace srs_internal { class SrsUnSortedHashtable; class SrsAmf0ObjectEOF; @@ -335,8 +335,8 @@ public: class SrsAmf0Object : public SrsAmf0Any { private: - _srs_internal::SrsUnSortedHashtable* properties; - _srs_internal::SrsAmf0ObjectEOF* eof; + srs_internal::SrsUnSortedHashtable* properties; + srs_internal::SrsAmf0ObjectEOF* eof; private: friend class SrsAmf0Any; /** @@ -425,8 +425,8 @@ public: class SrsAmf0EcmaArray : public SrsAmf0Any { private: - _srs_internal::SrsUnSortedHashtable* properties; - _srs_internal::SrsAmf0ObjectEOF* eof; + srs_internal::SrsUnSortedHashtable* properties; + srs_internal::SrsAmf0ObjectEOF* eof; int32_t _count; private: friend class SrsAmf0Any; @@ -626,7 +626,7 @@ extern srs_error_t srs_amf0_read_undefined(SrsBuffer* stream); extern srs_error_t srs_amf0_write_undefined(SrsBuffer* stream); // internal objects, user should never use it. -namespace _srs_internal +namespace srs_internal { /** * read amf0 string from stream. diff --git a/trunk/src/protocol/srs_protocol_json.cpp b/trunk/src/protocol/srs_protocol_json.cpp index c742bb158..2a2117c0c 100644 --- a/trunk/src/protocol/srs_protocol_json.cpp +++ b/trunk/src/protocol/srs_protocol_json.cpp @@ -1572,11 +1572,25 @@ SrsJsonArray* SrsJsonAny::to_array() return p; } +string escape(string v) +{ + stringstream ss; + + for (int i = 0; i < (int)v.length(); i++) { + if (v.at(i) == '"') { + ss << '\\'; + } + ss << v.at(i); + } + + return ss.str(); +} + string SrsJsonAny::dumps() { switch (marker) { case SRS_JSON_String: { - return "\"" + to_str() + "\""; + return "\"" + escape(to_str()) + "\""; } case SRS_JSON_Boolean: { return to_boolean()? "true" : "false"; diff --git a/trunk/src/protocol/srs_protocol_kbps.cpp b/trunk/src/protocol/srs_protocol_kbps.cpp index 063b0dfae..11f119a53 100644 --- a/trunk/src/protocol/srs_protocol_kbps.cpp +++ b/trunk/src/protocol/srs_protocol_kbps.cpp @@ -166,22 +166,24 @@ void SrsKbps::set_io(ISrsProtocolStatistic* in, ISrsProtocolStatistic* out) int SrsKbps::get_send_kbps() { - srs_utime_t duration = clk->now() - is.starttime; + int duration = srsu2ms(clk->now() - is.starttime); if (duration <= 0) { return 0; } + int64_t bytes = get_send_bytes(); - return (int)(bytes * 8 / srsu2ms(duration)); + return (int)(bytes * 8 / duration); } int SrsKbps::get_recv_kbps() { - srs_utime_t duration = clk->now() - os.starttime; + int duration = srsu2ms(clk->now() - os.starttime); if (duration <= 0) { return 0; } + int64_t bytes = get_recv_bytes(); - return (int)(bytes * 8 / srsu2ms(duration)); + return (int)(bytes * 8 / duration); } int SrsKbps::get_send_kbps_30s() diff --git a/trunk/src/protocol/srs_protocol_utility.cpp b/trunk/src/protocol/srs_protocol_utility.cpp index 57e65ecf0..ece4ef0bc 100644 --- a/trunk/src/protocol/srs_protocol_utility.cpp +++ b/trunk/src/protocol/srs_protocol_utility.cpp @@ -148,20 +148,39 @@ void srs_parse_query_string(string q, map& query) } } +static bool _random_initialized = false; + void srs_random_generate(char* bytes, int size) { - static bool _random_initialized = false; if (!_random_initialized) { - srand(0); _random_initialized = true; + ::srandom((unsigned long)(srs_update_system_time() | (::getpid()<<13))); } for (int i = 0; i < size; i++) { // the common value in [0x0f, 0xf0] - bytes[i] = 0x0f + (rand() % (256 - 0x0f - 0x0f)); + bytes[i] = 0x0f + (random() % (256 - 0x0f - 0x0f)); } } +std::string srs_random_str(int len) +{ + if (!_random_initialized) { + _random_initialized = true; + ::srandom((unsigned long)(srs_update_system_time() | (::getpid()<<13))); + } + + static string random_table = "01234567890123456789012345678901234567890123456789abcdefghijklmnopqrstuvwxyz"; + + string ret; + ret.reserve(len); + for (int i = 0; i < len; ++i) { + ret.append(1, random_table[random() % random_table.size()]); + } + + return ret; +} + string srs_generate_tc_url(string host, string vhost, string app, int port) { string tcUrl = "rtmp://"; @@ -181,11 +200,11 @@ string srs_generate_tc_url(string host, string vhost, string app, int port) return tcUrl; } -string srs_generate_stream_with_query(string host, string vhost, string stream, string param) +string srs_generate_stream_with_query(string host, string vhost, string stream, string param, bool with_vhost) { string url = stream; string query = param; - + // If no vhost in param, try to append one. string guessVhost; if (query.find("vhost=") == string::npos) { @@ -195,11 +214,28 @@ string srs_generate_stream_with_query(string host, string vhost, string stream, guessVhost = host; } } - + // Well, if vhost exists, always append in query string. - if (!guessVhost.empty()) { + if (!guessVhost.empty() && query.find("vhost=") == string::npos) { query += "&vhost=" + guessVhost; } + + // If not pass in query, remove it. + if (!with_vhost) { + size_t pos = query.find("&vhost="); + if (pos == string::npos) { + pos = query.find("vhost="); + } + + size_t end = query.find("&", pos + 1); + if (end == string::npos) { + end = query.length(); + } + + if (pos != string::npos && end != string::npos && end > pos) { + query = query.substr(0, pos) + query.substr(end); + } + } // Remove the start & when param is empty. query = srs_string_trim_start(query, "&"); @@ -360,20 +396,6 @@ srs_error_t srs_write_large_iovs(ISrsProtocolReadWriter* skt, iovec* iovs, int s return err; } -string srs_join_vector_string(vector& vs, string separator) -{ - string str = ""; - - for (int i = 0; i < (int)vs.size(); i++) { - str += vs.at(i); - if (i != (int)vs.size() - 1) { - str += separator; - } - } - - return str; -} - bool srs_is_ipv4(string domain) { for (int i = 0; i < (int)domain.length(); i++) { diff --git a/trunk/src/protocol/srs_protocol_utility.hpp b/trunk/src/protocol/srs_protocol_utility.hpp index 3f4cc03d2..eda484e36 100644 --- a/trunk/src/protocol/srs_protocol_utility.hpp +++ b/trunk/src/protocol/srs_protocol_utility.hpp @@ -34,6 +34,7 @@ #include #include #include +#include #include @@ -65,11 +66,12 @@ extern void srs_discovery_tc_url(std::string tcUrl, std::string& schema, std::st // must format as key=value&...&keyN=valueN extern void srs_parse_query_string(std::string q, std::map& query); -/** - * generate ramdom data for handshake. - */ +// Generate ramdom data for handshake. extern void srs_random_generate(char* bytes, int size); +// Generate random string [0-9a-z] in size of len bytes. +extern std::string srs_random_str(int len); + /** * generate the tcUrl without param. * @remark Use host as tcUrl.vhost if vhost is default vhost. @@ -80,7 +82,7 @@ extern std::string srs_generate_tc_url(std::string host, std::string vhost, std: * Generate the stream with param. * @remark Append vhost in query string if not default vhost. */ -extern std::string srs_generate_stream_with_query(std::string host, std::string vhost, std::string stream, std::string param); +extern std::string srs_generate_stream_with_query(std::string host, std::string vhost, std::string stream, std::string param, bool with_vhost = true); /** * create shared ptr message from bytes. @@ -107,7 +109,20 @@ extern std::string srs_generate_rtmp_url(std::string server, int port, std::stri extern srs_error_t srs_write_large_iovs(ISrsProtocolReadWriter* skt, iovec* iovs, int size, ssize_t* pnwrite = NULL); // join string in vector with indicated separator -extern std::string srs_join_vector_string(std::vector& vs, std::string separator); +template +std::string srs_join_vector_string(std::vector& vs, std::string separator) +{ + std::stringstream ss; + + for (int i = 0; i < (int)vs.size(); i++) { + ss << vs.at(i); + if (i != (int)vs.size() - 1) { + ss << separator; + } + } + + return ss.str(); +} // Whether domain is an IPv4 address. extern bool srs_is_ipv4(std::string domain); diff --git a/trunk/src/protocol/srs_stun_stack.cpp b/trunk/src/protocol/srs_rtc_stun_stack.cpp similarity index 85% rename from trunk/src/protocol/srs_stun_stack.cpp rename to trunk/src/protocol/srs_rtc_stun_stack.cpp index ed3377762..fe7f2385f 100644 --- a/trunk/src/protocol/srs_stun_stack.cpp +++ b/trunk/src/protocol/srs_rtc_stun_stack.cpp @@ -21,7 +21,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include +#include using namespace std; @@ -92,12 +92,104 @@ SrsStunPacket::SrsStunPacket() use_candidate = false; ice_controlled = false; ice_controlling = false; + mapped_port = 0; + mapped_address = 0; } SrsStunPacket::~SrsStunPacket() { } +bool SrsStunPacket::is_binding_request() const +{ + return message_type == BindingRequest; +} + +bool SrsStunPacket::is_binding_response() const +{ + return message_type == BindingResponse; +} + +uint16_t SrsStunPacket::get_message_type() const +{ + return message_type; +} + +std::string SrsStunPacket::get_username() const +{ + return username; +} + +std::string SrsStunPacket::get_local_ufrag() const +{ + return local_ufrag; +} + +std::string SrsStunPacket::get_remote_ufrag() const +{ + return remote_ufrag; +} + +std::string SrsStunPacket::get_transcation_id() const +{ + return transcation_id; +} + +uint32_t SrsStunPacket::get_mapped_address() const +{ + return mapped_address; +} + +uint16_t SrsStunPacket::get_mapped_port() const +{ + return mapped_port; +} + +bool SrsStunPacket::get_ice_controlled() const +{ + return ice_controlled; +} + +bool SrsStunPacket::get_ice_controlling() const +{ + return ice_controlling; +} + +bool SrsStunPacket::get_use_candidate() const +{ + return use_candidate; +} + +void SrsStunPacket::set_message_type(const uint16_t& m) +{ + message_type = m; +} + +void SrsStunPacket::set_local_ufrag(const std::string& u) +{ + local_ufrag = u; +} + +void SrsStunPacket::set_remote_ufrag(const std::string& u) +{ + remote_ufrag = u; +} + +void SrsStunPacket::set_transcation_id(const std::string& t) +{ + transcation_id = t; +} + +void SrsStunPacket::set_mapped_address(const uint32_t& addr) +{ + mapped_address = addr; +} + +void SrsStunPacket::set_mapped_port(const uint32_t& port) +{ + mapped_port = port; +} + srs_error_t SrsStunPacket::decode(const char* buf, const int nb_buf) { srs_error_t err = srs_success; diff --git a/trunk/src/protocol/srs_stun_stack.hpp b/trunk/src/protocol/srs_rtc_stun_stack.hpp similarity index 72% rename from trunk/src/protocol/srs_stun_stack.hpp rename to trunk/src/protocol/srs_rtc_stun_stack.hpp index 92242018d..682a6c84d 100644 --- a/trunk/src/protocol/srs_stun_stack.hpp +++ b/trunk/src/protocol/srs_rtc_stun_stack.hpp @@ -21,8 +21,8 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef SRS_PROTOCOL_STUN_HPP -#define SRS_PROTOCOL_STUN_HPP +#ifndef SRS_PROTOCOL_RTC_STUN_HPP +#define SRS_PROTOCOL_RTC_STUN_HPP #include @@ -92,28 +92,25 @@ private: public: SrsStunPacket(); virtual ~SrsStunPacket(); - - bool is_binding_request() const { return message_type == BindingRequest; } - bool is_binding_response() const { return message_type == BindingResponse; } - - uint16_t get_message_type() const { return message_type; } - std::string get_username() const { return username; } - std::string get_local_ufrag() const { return local_ufrag; } - std::string get_remote_ufrag() const { return remote_ufrag; } - std::string get_transcation_id() const { return transcation_id; } - uint32_t get_mapped_address() const { return mapped_address; } - uint16_t get_mapped_port() const { return mapped_port; } - bool get_ice_controlled() const { return ice_controlled; } - bool get_ice_controlling() const { return ice_controlling; } - bool get_use_candidate() const { return use_candidate; } - - void set_message_type(const uint16_t& m) { message_type = m; } - void set_local_ufrag(const std::string& u) { local_ufrag = u; } - void set_remote_ufrag(const std::string& u) { remote_ufrag = u; } - void set_transcation_id(const std::string& t) { transcation_id = t; } - void set_mapped_address(const uint32_t& addr) { mapped_address = addr; } - void set_mapped_port(const uint32_t& port) { mapped_port = port; } - +public: + bool is_binding_request() const; + bool is_binding_response() const; + uint16_t get_message_type() const; + std::string get_username() const; + std::string get_local_ufrag() const; + std::string get_remote_ufrag() const; + std::string get_transcation_id() const; + uint32_t get_mapped_address() const; + uint16_t get_mapped_port() const; + bool get_ice_controlled() const; + bool get_ice_controlling() const; + bool get_use_candidate() const; + void set_message_type(const uint16_t& m); + void set_local_ufrag(const std::string& u); + void set_remote_ufrag(const std::string& u); + void set_transcation_id(const std::string& t); + void set_mapped_address(const uint32_t& addr); + void set_mapped_port(const uint32_t& port); srs_error_t decode(const char* buf, const int nb_buf); srs_error_t encode(const std::string& pwd, SrsBuffer* stream); private: diff --git a/trunk/src/protocol/srs_rtmp_handshake.cpp b/trunk/src/protocol/srs_rtmp_handshake.cpp index b7f92ae59..6736cf64f 100644 --- a/trunk/src/protocol/srs_rtmp_handshake.cpp +++ b/trunk/src/protocol/srs_rtmp_handshake.cpp @@ -34,7 +34,7 @@ #include #include -using namespace _srs_internal; +using namespace srs_internal; // for openssl_HMACsha256 #include @@ -112,7 +112,7 @@ static int DH_set_length(DH *dh, long length) #endif -namespace _srs_internal +namespace srs_internal { // 68bytes FMS key which is used to sign the sever packet. uint8_t SrsGenuineFMSKey[] = { @@ -865,6 +865,8 @@ namespace _srs_internal c1s1::c1s1() { payload = NULL; + version = 0; + time = 0; } c1s1::~c1s1() { @@ -978,7 +980,7 @@ namespace _srs_internal srs_random_generate(random, 1504); int size = snprintf(random, 1504, "%s", RTMP_SIG_SRS_HANDSHAKE); - srs_assert(++size < 1504); + srs_assert(size < 1504); snprintf(random + 1504 - size, size, "%s", RTMP_SIG_SRS_HANDSHAKE); srs_random_generate(digest, 32); @@ -1225,6 +1227,7 @@ srs_error_t SrsComplexHandshake::handshake_with_client(SrsHandshakeBytes* hs_byt } // verify s2 if ((err = s2.s2_validate(&c1, is_valid)) != srs_success || !is_valid) { + srs_freep(err); return srs_error_new(ERROR_RTMP_TRY_SIMPLE_HS, "verify s2 failed, try simple handshake"); } diff --git a/trunk/src/protocol/srs_rtmp_handshake.hpp b/trunk/src/protocol/srs_rtmp_handshake.hpp index 49100fb4c..75eef9305 100644 --- a/trunk/src/protocol/srs_rtmp_handshake.hpp +++ b/trunk/src/protocol/srs_rtmp_handshake.hpp @@ -40,7 +40,7 @@ extern HMAC_CTX *HMAC_CTX_new(void); extern void HMAC_CTX_free(HMAC_CTX *ctx); #endif -namespace _srs_internal +namespace srs_internal { // The digest key generate size. #define SRS_OpensslHashSize 512 diff --git a/trunk/src/protocol/srs_rtmp_stack.cpp b/trunk/src/protocol/srs_rtmp_stack.cpp index 4b11e5ec3..f80cf77cd 100644 --- a/trunk/src/protocol/srs_rtmp_stack.cpp +++ b/trunk/src/protocol/srs_rtmp_stack.cpp @@ -2453,7 +2453,7 @@ srs_error_t SrsRtmpServer::response_connect_app(SrsRequest *req, const char* ser } // for edge to directly get the id of client. data->set("srs_pid", SrsAmf0Any::number(getpid())); - data->set("srs_id", SrsAmf0Any::number(_srs_context->get_id())); + data->set("srs_id", SrsAmf0Any::str(_srs_context->get_id().c_str())); if ((err = protocol->send_and_free_packet(pkt, 0)) != srs_success) { return srs_error_wrap(err, "send connect app response"); @@ -3977,7 +3977,7 @@ srs_error_t SrsPlayPacket::decode(SrsBuffer* stream) SrsAmf0Any* reset_value = NULL; if ((err = srs_amf0_read_any(stream, &reset_value)) != srs_success) { - return srs_error_new(ERROR_RTMP_AMF0_DECODE, "reset"); + return srs_error_wrap(err, "reset"); } SrsAutoFree(SrsAmf0Any, reset_value); diff --git a/trunk/src/protocol/srs_rtsp_stack.cpp b/trunk/src/protocol/srs_rtsp_stack.cpp index c1e828e44..1237f6454 100644 --- a/trunk/src/protocol/srs_rtsp_stack.cpp +++ b/trunk/src/protocol/srs_rtsp_stack.cpp @@ -792,6 +792,9 @@ SrsRtspSetupResponse::SrsRtspSetupResponse(int seq) : SrsRtspResponse(seq) { local_port_min = 0; local_port_max = 0; + + client_port_min = 0; + client_port_max = 0; } SrsRtspSetupResponse::~SrsRtspSetupResponse() diff --git a/trunk/src/service/srs_service_conn.cpp b/trunk/src/protocol/srs_service_conn.cpp similarity index 89% rename from trunk/src/service/srs_service_conn.cpp rename to trunk/src/protocol/srs_service_conn.cpp index 1269acdca..66b23a49e 100644 --- a/trunk/src/service/srs_service_conn.cpp +++ b/trunk/src/protocol/srs_service_conn.cpp @@ -23,6 +23,22 @@ #include +ISrsResource::ISrsResource() +{ +} + +ISrsResource::~ISrsResource() +{ +} + +ISrsResourceManager::ISrsResourceManager() +{ +} + +ISrsResourceManager::~ISrsResourceManager() +{ +} + ISrsConnection::ISrsConnection() { } @@ -31,11 +47,3 @@ ISrsConnection::~ISrsConnection() { } -IConnectionManager::IConnectionManager() -{ -} - -IConnectionManager::~IConnectionManager() -{ -} - diff --git a/trunk/src/service/srs_service_conn.hpp b/trunk/src/protocol/srs_service_conn.hpp similarity index 74% rename from trunk/src/service/srs_service_conn.hpp rename to trunk/src/protocol/srs_service_conn.hpp index 806acb8dd..c4c2e2602 100644 --- a/trunk/src/service/srs_service_conn.hpp +++ b/trunk/src/protocol/srs_service_conn.hpp @@ -28,8 +28,32 @@ #include +// The resource managed by ISrsResourceManager. +class ISrsResource +{ +public: + ISrsResource(); + virtual ~ISrsResource(); +public: + // Get the context id of connection. + virtual const SrsContextId& get_id() = 0; + // The resource description, optional. + virtual std::string desc() = 0; +}; + +// The manager for resource. +class ISrsResourceManager +{ +public: + ISrsResourceManager(); + virtual ~ISrsResourceManager(); +public: + // Remove then free the specified connection. + virtual void remove(ISrsResource* c) = 0; +}; + // The connection interface for all HTTP/RTMP/RTSP object. -class ISrsConnection +class ISrsConnection : public ISrsResource { public: ISrsConnection(); @@ -39,16 +63,5 @@ public: virtual std::string remote_ip() = 0; }; -// The manager for connection. -class IConnectionManager -{ -public: - IConnectionManager(); - virtual ~IConnectionManager(); -public: - // Remove then free the specified connection. - virtual void remove(ISrsConnection* c) = 0; -}; - #endif diff --git a/trunk/src/protocol/srs_service_http_client.cpp b/trunk/src/protocol/srs_service_http_client.cpp new file mode 100644 index 000000000..6aabf7656 --- /dev/null +++ b/trunk/src/protocol/srs_service_http_client.cpp @@ -0,0 +1,509 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2013-2020 Winlin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include + +#include +#include +using namespace std; + +#include +#include +#include +#include +#include +#include +#include + +// The return value of verify_callback controls the strategy of the further verification process. If verify_callback +// returns 0, the verification process is immediately stopped with "verification failed" state. If SSL_VERIFY_PEER is +// set, a verification failure alert is sent to the peer and the TLS/SSL handshake is terminated. If verify_callback +// returns 1, the verification process is continued. If verify_callback always returns 1, the TLS/SSL handshake will +// not be terminated with respect to verification failures and the connection will be established. The calling process +// can however retrieve the error code of the last verification error using SSL_get_verify_result(3) or by maintaining +// its own error storage managed by verify_callback. +// @see https://www.openssl.org/docs/man1.0.2/man3/SSL_CTX_set_verify.html +int srs_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) +{ + // Always OK, we don't check the certificate of client, + // because we allow client self-sign certificate. + return 1; +} + +SrsSslClient::SrsSslClient(SrsTcpClient* tcp) +{ + transport = tcp; + ssl_ctx = NULL; + ssl = NULL; +} + +SrsSslClient::~SrsSslClient() +{ + if (ssl) { + // this function will free bio_in and bio_out + SSL_free(ssl); + ssl = NULL; + } + + if (ssl_ctx) { + SSL_CTX_free(ssl_ctx); + ssl_ctx = NULL; + } +} + +srs_error_t SrsSslClient::handshake() +{ + srs_error_t err = srs_success; + + // For HTTPS, try to connect over security transport. +#if (OPENSSL_VERSION_NUMBER < 0x10002000L) // v1.0.2 + ssl_ctx = SSL_CTX_new(TLS_method()); +#else + ssl_ctx = SSL_CTX_new(TLSv1_2_method()); +#endif + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, srs_verify_callback); + srs_assert(SSL_CTX_set_cipher_list(ssl_ctx, "ALL") == 1); + + // TODO: Setup callback, see SSL_set_ex_data and SSL_set_info_callback + if ((ssl = SSL_new(ssl_ctx)) == NULL) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "SSL_new ssl"); + } + + if ((bio_in = BIO_new(BIO_s_mem())) == NULL) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_new in"); + } + + if ((bio_out = BIO_new(BIO_s_mem())) == NULL) { + BIO_free(bio_in); + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_new out"); + } + + SSL_set_bio(ssl, bio_in, bio_out); + + // SSL setup active, as client role. + SSL_set_connect_state(ssl); + SSL_set_mode(ssl, SSL_MODE_ENABLE_PARTIAL_WRITE); + + // Send ClientHello. + int r0 = SSL_do_handshake(ssl); int r1 = SSL_get_error(ssl, r0); + if (r0 != -1 || r1 != SSL_ERROR_WANT_READ) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "handshake r0=%d, r1=%d", r0, r1); + } + + uint8_t* data = NULL; + int size = BIO_get_mem_data(bio_out, &data); + if (!data || size <= 0) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "handshake data=%p, size=%d", data, size); + } + if ((err = transport->write(data, size, NULL)) != srs_success) { + return srs_error_wrap(err, "handshake: write data=%p, size=%d", data, size); + } + if ((r0 = BIO_reset(bio_out)) != 1) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_reset r0=%d", r0); + } + + srs_info("https: ClientHello done"); + + // Receive ServerHello, Certificate, Server Key Exchange, Server Hello Done + while (true) { + char buf[512]; ssize_t nn = 0; + if ((err = transport->read(buf, sizeof(buf), &nn)) != srs_success) { + return srs_error_wrap(err, "handshake: read"); + } + + if ((r0 = BIO_write(bio_in, buf, nn)) <= 0) { + // TODO: 0 or -1 maybe block, use BIO_should_retry to check. + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_write r0=%d, data=%p, size=%d", r0, buf, nn); + } + + if ((r0 = SSL_do_handshake(ssl)) != -1 || (r1 = SSL_get_error(ssl, r0)) != SSL_ERROR_WANT_READ) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "handshake r0=%d, r1=%d", r0, r1); + } + + if ((size = BIO_get_mem_data(bio_out, &data)) > 0) { + // OK, reset it for the next write. + if ((r0 = BIO_reset(bio_in)) != 1) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_reset r0=%d", r0); + } + break; + } + } + + srs_info("https: ServerHello done"); + + // Send Client Key Exchange, Change Cipher Spec, Encrypted Handshake Message + if ((err = transport->write(data, size, NULL)) != srs_success) { + return srs_error_wrap(err, "handshake: write data=%p, size=%d", data, size); + } + if ((r0 = BIO_reset(bio_out)) != 1) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_reset r0=%d", r0); + } + + srs_info("https: Client done"); + + // Receive New Session Ticket, Change Cipher Spec, Encrypted Handshake Message + while (true) { + char buf[128]; + ssize_t nn = 0; + if ((err = transport->read(buf, sizeof(buf), &nn)) != srs_success) { + return srs_error_wrap(err, "handshake: read"); + } + + if ((r0 = BIO_write(bio_in, buf, nn)) <= 0) { + // TODO: 0 or -1 maybe block, use BIO_should_retry to check. + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "BIO_write r0=%d, data=%p, size=%d", r0, buf, nn); + } + + r0 = SSL_do_handshake(ssl); r1 = SSL_get_error(ssl, r0); + if (r0 == 1 && r1 == SSL_ERROR_NONE) { + break; + } + + if (r0 != -1 || r1 != SSL_ERROR_WANT_READ) { + return srs_error_new(ERROR_HTTPS_HANDSHAKE, "handshake r0=%d, r1=%d", r0, r1); + } + } + + srs_info("https: Server done"); + + return err; +} + +srs_error_t SrsSslClient::read(void* plaintext, size_t nn_plaintext, ssize_t* nread) +{ + srs_error_t err = srs_success; + + while (true) { + int r0 = SSL_read(ssl, plaintext, nn_plaintext); int r1 = SSL_get_error(ssl, r0); + int r2 = BIO_ctrl_pending(bio_in); int r3 = SSL_is_init_finished(ssl); + + // OK, got data. + if (r0 > 0) { + srs_assert(r0 <= (int)nn_plaintext); + if (nread) { + *nread = r0; + } + return err; + } + + // Need to read more data to feed SSL. + if (r0 == -1 && r1 == SSL_ERROR_WANT_READ) { + // TODO: Can we avoid copy? + int nn_cipher = nn_plaintext; + char* cipher = new char[nn_cipher]; + SrsAutoFreeA(char, cipher); + + // Read the cipher from SSL. + ssize_t nn = 0; + if ((err = transport->read(cipher, nn_cipher, &nn)) != srs_success) { + return srs_error_wrap(err, "https: read"); + } + + int r0 = BIO_write(bio_in, cipher, nn); + if (r0 <= 0) { + // TODO: 0 or -1 maybe block, use BIO_should_retry to check. + return srs_error_new(ERROR_HTTPS_READ, "BIO_write r0=%d, cipher=%p, size=%d", r0, cipher, nn); + } + continue; + } + + // Fail for error. + if (r0 <= 0) { + return srs_error_new(ERROR_HTTPS_READ, "SSL_read r0=%d, r1=%d, r2=%d, r3=%d", + r0, r1, r2, r3); + } + } +} + +srs_error_t SrsSslClient::write(void* plaintext, size_t nn_plaintext, ssize_t* nwrite) +{ + srs_error_t err = srs_success; + + for (char* p = (char*)plaintext; p < (char*)plaintext + nn_plaintext;) { + int left = (int)nn_plaintext - (p - (char*)plaintext); + int r0 = SSL_write(ssl, (const void*)p, left); + int r1 = SSL_get_error(ssl, r0); + if (r0 <= 0) { + return srs_error_new(ERROR_HTTPS_WRITE, "https: write data=%p, size=%d, r0=%d, r1=%d", p, left, r0, r1); + } + + // Move p to the next writing position. + p += r0; + if (nwrite) { + *nwrite += (ssize_t)r0; + } + + uint8_t* data = NULL; + int size = BIO_get_mem_data(bio_out, &data); + if ((err = transport->write(data, size, NULL)) != srs_success) { + return srs_error_wrap(err, "https: write data=%p, size=%d", data, size); + } + if ((r0 = BIO_reset(bio_out)) != 1) { + return srs_error_new(ERROR_HTTPS_WRITE, "BIO_reset r0=%d", r0); + } + } + + return err; +} + +SrsHttpClient::SrsHttpClient() +{ + transport = NULL; + ssl_transport = NULL; + clk = new SrsWallClock(); + kbps = new SrsKbps(clk); + parser = NULL; + recv_timeout = timeout = SRS_UTIME_NO_TIMEOUT; + port = 0; +} + +SrsHttpClient::~SrsHttpClient() +{ + disconnect(); + + srs_freep(kbps); + srs_freep(clk); + srs_freep(parser); +} + +srs_error_t SrsHttpClient::initialize(string schema, string h, int p, srs_utime_t tm) +{ + srs_error_t err = srs_success; + + srs_freep(parser); + parser = new SrsHttpParser(); + + if ((err = parser->initialize(HTTP_RESPONSE)) != srs_success) { + return srs_error_wrap(err, "http: init parser"); + } + + // Always disconnect the transport. + schema_ = schema; + host = h; + port = p; + recv_timeout = timeout = tm; + disconnect(); + + // ep used for host in header. + string ep = host; + if (port > 0 && port != SRS_CONSTS_HTTP_DEFAULT_PORT) { + ep += ":" + srs_int2str(port); + } + + // Set default value for headers. + headers["Host"] = ep; + headers["Connection"] = "Keep-Alive"; + headers["User-Agent"] = RTMP_SIG_SRS_SERVER; + headers["Content-Type"] = "application/json"; + + return err; +} + +SrsHttpClient* SrsHttpClient::set_header(string k, string v) +{ + headers[k] = v; + + return this; +} + +srs_error_t SrsHttpClient::post(string path, string req, ISrsHttpMessage** ppmsg) +{ + *ppmsg = NULL; + + srs_error_t err = srs_success; + + // always set the content length. + headers["Content-Length"] = srs_int2str(req.length()); + + if ((err = connect()) != srs_success) { + return srs_error_wrap(err, "http: connect server"); + } + + // send POST request to uri + // POST %s HTTP/1.1\r\nHost: %s\r\nContent-Length: %d\r\n\r\n%s + std::stringstream ss; + ss << "POST " << path << " " << "HTTP/1.1" << SRS_HTTP_CRLF; + for (map::iterator it = headers.begin(); it != headers.end(); ++it) { + string key = it->first; + string value = it->second; + ss << key << ": " << value << SRS_HTTP_CRLF; + } + ss << SRS_HTTP_CRLF << req; + + std::string data = ss.str(); + if ((err = writer()->write((void*)data.c_str(), data.length(), NULL)) != srs_success) { + // Disconnect the transport when channel error, reconnect for next operation. + disconnect(); + return srs_error_wrap(err, "http: write"); + } + + ISrsHttpMessage* msg = NULL; + if ((err = parser->parse_message(reader(), &msg)) != srs_success) { + return srs_error_wrap(err, "http: parse response"); + } + srs_assert(msg); + + if (ppmsg) { + *ppmsg = msg; + } else { + srs_freep(msg); + } + + return err; +} + +srs_error_t SrsHttpClient::get(string path, string req, ISrsHttpMessage** ppmsg) +{ + *ppmsg = NULL; + + srs_error_t err = srs_success; + + // always set the content length. + headers["Content-Length"] = srs_int2str(req.length()); + + if ((err = connect()) != srs_success) { + return srs_error_wrap(err, "http: connect server"); + } + + // send POST request to uri + // GET %s HTTP/1.1\r\nHost: %s\r\nContent-Length: %d\r\n\r\n%s + std::stringstream ss; + ss << "GET " << path << " " << "HTTP/1.1" << SRS_HTTP_CRLF; + for (map::iterator it = headers.begin(); it != headers.end(); ++it) { + string key = it->first; + string value = it->second; + ss << key << ": " << value << SRS_HTTP_CRLF; + } + ss << SRS_HTTP_CRLF << req; + + std::string data = ss.str(); + if ((err = writer()->write((void*)data.c_str(), data.length(), NULL)) != srs_success) { + // Disconnect the transport when channel error, reconnect for next operation. + disconnect(); + return srs_error_wrap(err, "http: write"); + } + + ISrsHttpMessage* msg = NULL; + if ((err = parser->parse_message(reader(), &msg)) != srs_success) { + return srs_error_wrap(err, "http: parse response"); + } + srs_assert(msg); + + if (ppmsg) { + *ppmsg = msg; + } else { + srs_freep(msg); + } + + return err; +} + +void SrsHttpClient::set_recv_timeout(srs_utime_t tm) +{ + recv_timeout = tm; +} + +void SrsHttpClient::kbps_sample(const char* label, int64_t age) +{ + kbps->sample(); + + int sr = kbps->get_send_kbps(); + int sr30s = kbps->get_send_kbps_30s(); + int sr5m = kbps->get_send_kbps_5m(); + int rr = kbps->get_recv_kbps(); + int rr30s = kbps->get_recv_kbps_30s(); + int rr5m = kbps->get_recv_kbps_5m(); + + srs_trace("<- %s time=%" PRId64 ", okbps=%d,%d,%d, ikbps=%d,%d,%d", label, age, sr, sr30s, sr5m, rr, rr30s, rr5m); +} + +void SrsHttpClient::disconnect() +{ + kbps->set_io(NULL, NULL); + srs_freep(ssl_transport); + srs_freep(transport); +} + +srs_error_t SrsHttpClient::connect() +{ + srs_error_t err = srs_success; + + // When transport connected, ignore. + if (transport) { + return err; + } + + transport = new SrsTcpClient(host, port, timeout); + if ((err = transport->connect()) != srs_success) { + disconnect(); + return srs_error_wrap(err, "http: tcp connect %s %s:%d to=%dms, rto=%dms", + schema_.c_str(), host.c_str(), port, srsu2msi(timeout), srsu2msi(recv_timeout)); + } + + // Set the recv/send timeout in srs_utime_t. + transport->set_recv_timeout(recv_timeout); + transport->set_send_timeout(timeout); + + kbps->set_io(transport, transport); + + if (schema_ != "https") { + return err; + } + +#if !defined(SRS_HTTPS) + return srs_error_new(ERROR_HTTPS_NOT_SUPPORTED, "should configure with --https=on"); +#else + srs_assert(!ssl_transport); + ssl_transport = new SrsSslClient(transport); + + srs_utime_t starttime = srs_update_system_time(); + + if ((err = ssl_transport->handshake()) != srs_success) { + disconnect(); + return srs_error_wrap(err, "http: ssl connect %s %s:%d to=%dms, rto=%dms", + schema_.c_str(), host.c_str(), port, srsu2msi(timeout), srsu2msi(recv_timeout)); + } + + int cost = srsu2msi(srs_update_system_time() - starttime); + srs_trace("https: connected to %s://%s:%d, cost=%dms", schema_.c_str(), host.c_str(), port, cost); + + return err; +#endif +} + +ISrsStreamWriter* SrsHttpClient::writer() +{ + if (ssl_transport) { + return ssl_transport; + } + return transport; +} + +ISrsReader* SrsHttpClient::reader() +{ + if (ssl_transport) { + return ssl_transport; + } + return transport; +} + diff --git a/trunk/src/service/srs_service_http_client.hpp b/trunk/src/protocol/srs_service_http_client.hpp similarity index 81% rename from trunk/src/service/srs_service_http_client.hpp rename to trunk/src/protocol/srs_service_http_client.hpp index 12193809e..351735f22 100644 --- a/trunk/src/service/srs_service_http_client.hpp +++ b/trunk/src/protocol/srs_service_http_client.hpp @@ -29,6 +29,8 @@ #include #include +#include + #include #include @@ -43,6 +45,26 @@ class SrsTcpClient; // The default timeout for http client. #define SRS_HTTP_CLIENT_TIMEOUT (30 * SRS_UTIME_SECONDS) +// The SSL client over TCP transport. +class SrsSslClient : virtual public ISrsReader, virtual public ISrsStreamWriter +{ +private: + SrsTcpClient* transport; +private: + SSL_CTX* ssl_ctx; + SSL* ssl; + BIO* bio_in; + BIO* bio_out; +public: + SrsSslClient(SrsTcpClient* tcp); + virtual ~SrsSslClient(); +public: + virtual srs_error_t handshake(); +public: + virtual srs_error_t read(void* buf, size_t size, ssize_t* nread); + virtual srs_error_t write(void* buf, size_t size, ssize_t* nwrite); +}; + // The client to GET/POST/PUT/DELETE over HTTP. // @remark We will reuse the TCP transport until initialize or channel error, // such as send/recv failed. @@ -64,17 +86,21 @@ private: // The timeout in srs_utime_t. srs_utime_t timeout; srs_utime_t recv_timeout; - // The host name or ip. + // The schema, host name or ip. + std::string schema_; std::string host; int port; +private: + SrsSslClient* ssl_transport; public: SrsHttpClient(); virtual ~SrsHttpClient(); public: // Initliaze the client, disconnect the transport, renew the HTTP parser. + // @param schema Should be http or https. // @param tm The underlayer TCP transport timeout in srs_utime_t. // @remark we will set default values in headers, which can be override by set_header. - virtual srs_error_t initialize(std::string h, int p, srs_utime_t tm = SRS_HTTP_CLIENT_TIMEOUT); + virtual srs_error_t initialize(std::string schema, std::string h, int p, srs_utime_t tm = SRS_HTTP_CLIENT_TIMEOUT); // Set HTTP request header in header[k]=v. // @return the HTTP client itself. virtual SrsHttpClient* set_header(std::string k, std::string v); @@ -98,6 +124,8 @@ public: private: virtual void disconnect(); virtual srs_error_t connect(); + ISrsStreamWriter* writer(); + ISrsReader* reader(); }; #endif diff --git a/trunk/src/service/srs_service_http_conn.cpp b/trunk/src/protocol/srs_service_http_conn.cpp similarity index 91% rename from trunk/src/service/srs_service_http_conn.cpp rename to trunk/src/protocol/srs_service_http_conn.cpp index d28e7b724..d239972b2 100644 --- a/trunk/src/service/srs_service_http_conn.cpp +++ b/trunk/src/protocol/srs_service_http_conn.cpp @@ -43,6 +43,7 @@ SrsHttpParser::SrsHttpParser() header = NULL; p_body_start = p_header_tail = NULL; + type_ = HTTP_REQUEST; } SrsHttpParser::~SrsHttpParser() @@ -51,11 +52,16 @@ SrsHttpParser::~SrsHttpParser() srs_freep(header); } -srs_error_t SrsHttpParser::initialize(enum http_parser_type type, bool allow_jsonp) +srs_error_t SrsHttpParser::initialize(enum http_parser_type type) { srs_error_t err = srs_success; - - jsonp = allow_jsonp; + + jsonp = false; + type_ = type; + + // Initialize the parser, however it's not necessary. + http_parser_init(&parser, type_); + parser.data = (void*)this; memset(&settings, 0, sizeof(settings)); settings.on_message_begin = on_message_begin; @@ -66,13 +72,14 @@ srs_error_t SrsHttpParser::initialize(enum http_parser_type type, bool allow_jso settings.on_body = on_body; settings.on_message_complete = on_message_complete; - http_parser_init(&parser, type); - // callback object ptr. - parser.data = (void*)this; - return err; } +void SrsHttpParser::set_jsonp(bool allow_jsonp) +{ + jsonp = allow_jsonp; +} + srs_error_t SrsHttpParser::parse_message(ISrsReader* reader, ISrsHttpMessage** ppmsg) { srs_error_t err = srs_success; @@ -81,7 +88,7 @@ srs_error_t SrsHttpParser::parse_message(ISrsReader* reader, ISrsHttpMessage** p // Reset request data. state = SrsHttpParseStateInit; - hp_header = http_parser(); + memset(&hp_header, 0, sizeof(http_parser)); // The body that we have read from cache. p_body_start = p_header_tail = NULL; // We must reset the field name and value, because we may get a partial value in on_header_value. @@ -89,6 +96,19 @@ srs_error_t SrsHttpParser::parse_message(ISrsReader* reader, ISrsHttpMessage** p // The header of the request. srs_freep(header); header = new SrsHttpHeader(); + + // Reset parser for each message. + // If the request is large, such as the fifth message at @utest ProtocolHTTPTest.ParsingLargeMessages, + // we got header and part of body, so the parser will stay at SrsHttpParseStateBody: + // ***MESSAGE BEGIN*** + // ***HEADERS COMPLETE*** + // Body: xxx + // when got next message, the whole next message is parsed as the body of previous one, + // and the message fail. + // @note You can comment the bellow line, the utest will fail. + http_parser_init(&parser, type_); + // callback object ptr. + parser.data = (void*)this; // do parse if ((err = parse_message_imp(reader)) != srs_success) { @@ -99,7 +119,7 @@ srs_error_t SrsHttpParser::parse_message(ISrsReader* reader, ISrsHttpMessage** p SrsHttpMessage* msg = new SrsHttpMessage(reader, buffer); // Initialize the basic information. - msg->set_basic(hp_header.method, hp_header.status_code, hp_header.content_length); + msg->set_basic(hp_header.type, hp_header.method, hp_header.status_code, hp_header.content_length); msg->set_header(header, http_should_keep_alive(&hp_header)); if ((err = msg->set_url(url, jsonp)) != srs_success) { srs_freep(msg); @@ -124,7 +144,7 @@ srs_error_t SrsHttpParser::parse_message_imp(ISrsReader* reader) enum http_errno code; if ((code = HTTP_PARSER_ERRNO(&parser)) != HPE_OK) { return srs_error_new(ERROR_HTTP_PARSE_HEADER, "parse %dB, nparsed=%d, err=%d/%s %s", - buffer->size(), consumed, code, http_errno_name(code), http_errno_description(code)); + buffer->size(), (int)consumed, code, http_errno_name(code), http_errno_description(code)); } // When buffer consumed these bytes, it's dropped so the new ptr is actually the HTTP body. But http-parser @@ -291,7 +311,6 @@ SrsHttpMessage::SrsHttpMessage(ISrsReader* reader, SrsFastStream* buffer) : ISrs { owner_conn = NULL; chunked = false; - infinite_chunked = false; _uri = new SrsHttpUri(); _body = new SrsHttpResponseReader(this, reader, buffer); @@ -305,6 +324,9 @@ SrsHttpMessage::SrsHttpMessage(ISrsReader* reader, SrsFastStream* buffer) : ISrs _content_length = -1; // From HTTP/1.1, default to keep alive. _keep_alive = true; + type_ = 0; + + schema_ = "http"; } SrsHttpMessage::~SrsHttpMessage() @@ -313,8 +335,9 @@ SrsHttpMessage::~SrsHttpMessage() srs_freep(_uri); } -void SrsHttpMessage::set_basic(uint8_t method, uint16_t status, int64_t content_length) +void SrsHttpMessage::set_basic(uint8_t type, uint8_t method, uint16_t status, int64_t content_length) { + type_ = type; _method = method; _status = status; if (_content_length == -1) { @@ -335,6 +358,15 @@ void SrsHttpMessage::set_header(SrsHttpHeader* header, bool keep_alive) if (!clv.empty()) { _content_length = ::atoll(clv.c_str()); } + + // If no size(content-length or chunked), it's infinite chunked, + // it means there is no body, so we must close the body reader. + if (!chunked && _content_length == -1) { + // The infinite chunked is only enabled for HTTP_RESPONSE, so we close the body for request. + if (type_ == HTTP_REQUEST) { + _body->close(); + } + } } srs_error_t SrsHttpMessage::set_url(string url, bool allow_jsonp) @@ -350,9 +382,12 @@ srs_error_t SrsHttpMessage::set_url(string url, bool allow_jsonp) // use server public ip when host not specified. // to make telnet happy. std::string host = _header.get("Host"); + + // If no host in header, we use local discovered IP, IPv4 first. if (host.empty()) { - host= srs_get_public_internet_address(); + host = srs_get_public_internet_address(true); } + if (!host.empty()) { uri = "http://" + host + _url; } @@ -381,6 +416,12 @@ srs_error_t SrsHttpMessage::set_url(string url, bool allow_jsonp) return err; } +void SrsHttpMessage::set_https(bool v) +{ + schema_ = v? "https" : "http"; + _uri->set_schema(schema_); +} + ISrsConnection* SrsHttpMessage::connection() { return owner_conn; @@ -391,6 +432,11 @@ void SrsHttpMessage::set_connection(ISrsConnection* conn) owner_conn = conn; } +string SrsHttpMessage::schema() +{ + return schema_; +} + uint8_t SrsHttpMessage::method() { if (jsonp && !jsonp_method.empty()) { @@ -473,11 +519,6 @@ bool SrsHttpMessage::is_keep_alive() return _keep_alive; } -bool SrsHttpMessage::is_infinite_chunked() -{ - return infinite_chunked; -} - string SrsHttpMessage::uri() { std::string uri = _uri->get_schema(); @@ -532,36 +573,19 @@ string SrsHttpMessage::ext() return _ext; } -int SrsHttpMessage::parse_rest_id(string pattern) +std::string SrsHttpMessage::parse_rest_id(string pattern) { string p = _uri->get_path(); if (p.length() <= pattern.length()) { - return -1; + return ""; } string id = p.substr((int)pattern.length()); if (!id.empty()) { - return ::atoi(id.c_str()); + return id; } - return -1; -} - -srs_error_t SrsHttpMessage::enter_infinite_chunked() -{ - srs_error_t err = srs_success; - - if (infinite_chunked) { - return err; - } - - if (is_chunked() || content_length() != -1) { - return srs_error_new(ERROR_HTTP_DATA_INVALID, "not infinited chunked"); - } - - infinite_chunked = true; - - return err; + return ""; } srs_error_t SrsHttpMessage::body_read_all(string& body) @@ -773,7 +797,7 @@ srs_error_t SrsHttpResponseWriter::write(char* data, int size) iovs[3].iov_base = (char*)SRS_HTTP_CRLF; iovs[3].iov_len = 2; - ssize_t nwrite; + ssize_t nwrite = 0; if ((err = skt->writev(iovs, 4, &nwrite)) != srs_success) { return srs_error_wrap(err, "write chunk"); } @@ -849,7 +873,7 @@ srs_error_t SrsHttpResponseWriter::writev(const iovec* iov, int iovcnt, ssize_t* iovss[2+iovcnt].iov_len = 2; // sendout all ioves. - ssize_t nwrite; + ssize_t nwrite = 0; if ((err = srs_write_large_iovs(skt, iovss, nb_iovss, &nwrite)) != srs_success) { return srs_error_wrap(err, "writev large iovs"); } @@ -935,12 +959,18 @@ SrsHttpResponseReader::SrsHttpResponseReader(SrsHttpMessage* msg, ISrsReader* re nb_total_read = 0; nb_left_chunk = 0; buffer = body; + nb_chunk = 0; } SrsHttpResponseReader::~SrsHttpResponseReader() { } +void SrsHttpResponseReader::close() +{ + is_eof = true; +} + bool SrsHttpResponseReader::eof() { return is_eof; @@ -972,16 +1002,17 @@ srs_error_t SrsHttpResponseReader::read(void* data, size_t nb_data, ssize_t* nb_ return read_specified(data, nb_data, nb_read); } - // infinite chunked mode, directly read. - if (owner->is_infinite_chunked()) { - srs_assert(!owner->is_chunked() && owner->content_length() == -1); - return read_specified(data, nb_data, nb_read); + // Infinite chunked mode. + // If not chunked encoding, and no content-length, it's infinite chunked. + // In this mode, all body is data and never EOF util socket closed. + if ((err = read_specified(data, nb_data, nb_read)) != srs_success) { + // For infinite chunked, the socket close event is EOF. + if (srs_error_code(err) == ERROR_SOCKET_READ) { + srs_freep(err); is_eof = true; + return err; + } } - - // infinite chunked mode, but user not set it, - // we think there is no data left. - is_eof = true; - + return err; } diff --git a/trunk/src/service/srs_service_http_conn.hpp b/trunk/src/protocol/srs_service_http_conn.hpp similarity index 90% rename from trunk/src/service/srs_service_http_conn.hpp rename to trunk/src/protocol/srs_service_http_conn.hpp index 2fbba8a04..0181f9294 100644 --- a/trunk/src/service/srs_service_http_conn.hpp +++ b/trunk/src/protocol/srs_service_http_conn.hpp @@ -55,6 +55,7 @@ private: http_parser hp_header; std::string url; SrsHttpHeader* header; + enum http_parser_type type_; private: // Point to the start of body. const char* p_body_start; @@ -66,8 +67,9 @@ public: public: // initialize the http parser with specified type, // one parser can only parse request or response messages. - // @param allow_jsonp whether allow jsonp parser, which indicates the method in query string. - virtual srs_error_t initialize(enum http_parser_type type, bool allow_jsonp = false); + virtual srs_error_t initialize(enum http_parser_type type); + // Whether allow jsonp parser, which indicates the method in query string. + virtual void set_jsonp(bool allow_jsonp); // always parse a http message, // that is, the *ppmsg always NOT-NULL when return success. // or error and *ppmsg must be NULL. @@ -99,12 +101,14 @@ private: // The body object, reader object. // @remark, user can get body in string by get_body(). SrsHttpResponseReader* _body; - // Whether the body is infinite chunked. - bool infinite_chunked; // Use a buffer to read and send ts file. // The transport connection, can be NULL. ISrsConnection* owner_conn; private: + // The request type defined as + // enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; + uint8_t type_; + // The HTTP method defined by HTTP_METHOD_MAP uint8_t _method; uint16_t _status; int64_t _content_length; @@ -116,6 +120,7 @@ private: // Whether the body is chunked. bool chunked; private: + std::string schema_; // The parsed url. std::string _url; // The extension of file, for example, .flv @@ -135,17 +140,21 @@ public: public: // Set the basic information for HTTP request. // @remark User must call set_basic before set_header, because the content_length will be overwrite by header. - virtual void set_basic(uint8_t method, uint16_t status, int64_t content_length); + virtual void set_basic(uint8_t type, uint8_t method, uint16_t status, int64_t content_length); // Set HTTP header and whether the request require keep alive. // @remark User must call set_header before set_url, because the Host in header is used for url. virtual void set_header(SrsHttpHeader* header, bool keep_alive); // set the original messages, then update the message. virtual srs_error_t set_url(std::string url, bool allow_jsonp); + // After parsed the message, set the schema to https. + virtual void set_https(bool v); public: // Get the owner connection, maybe NULL. virtual ISrsConnection* connection(); virtual void set_connection(ISrsConnection* conn); public: + // The schema, http or https. + virtual std::string schema(); virtual uint8_t method(); virtual uint16_t status_code(); // The method helpers. @@ -157,9 +166,6 @@ public: virtual bool is_http_options(); // Whether body is chunked encoding, for reader only. virtual bool is_chunked(); - // Whether body is infinite chunked encoding. - // @remark set by enter_infinite_chunked. - virtual bool is_infinite_chunked(); // Whether should keep the connection alive. virtual bool is_keep_alive(); // The uri contains the host and path. @@ -172,9 +178,7 @@ public: virtual std::string query(); virtual std::string ext(); // Get the RESTful matched id. - virtual int parse_rest_id(std::string pattern); -public: - virtual srs_error_t enter_infinite_chunked(); + virtual std::string parse_rest_id(std::string pattern); public: // Read body to string. // @remark for small http body. @@ -271,6 +275,11 @@ public: // while buffer is a fast cache which may have cached some data from reader. SrsHttpResponseReader(SrsHttpMessage* msg, ISrsReader* reader, SrsFastStream* buffer); virtual ~SrsHttpResponseReader(); +public: + // User close the HTTP response reader. + // For example, OPTIONS has no body, no content-length and not chunked, + // so we must close it(set to eof) to avoid reading the response body. + void close(); // Interface ISrsHttpResponseReader public: virtual bool eof(); diff --git a/trunk/src/service/srs_service_log.cpp b/trunk/src/protocol/srs_service_log.cpp similarity index 77% rename from trunk/src/service/srs_service_log.cpp rename to trunk/src/protocol/srs_service_log.cpp index 4bc0a0c2e..5dafb4c05 100644 --- a/trunk/src/service/srs_service_log.cpp +++ b/trunk/src/protocol/srs_service_log.cpp @@ -26,12 +26,14 @@ #include #include #include +#include using namespace std; #include #include +#include -#define SRS_BASIC_LOG_SIZE 1024 +#define SRS_BASIC_LOG_SIZE 8192 SrsThreadContext::SrsThreadContext() { @@ -41,47 +43,50 @@ SrsThreadContext::~SrsThreadContext() { } -int SrsThreadContext::generate_id() +SrsContextId SrsThreadContext::generate_id() { - static int id = 0; - - if (id == 0) { - id = (100 + ((uint32_t)(int64_t)this)%1000); - } - - int gid = id++; - cache[srs_thread_self()] = gid; - return gid; + SrsContextId cid = SrsContextId(); + return cid.set_value(srs_random_str(8)); } -int SrsThreadContext::get_id() +const SrsContextId& SrsThreadContext::get_id() { return cache[srs_thread_self()]; } -int SrsThreadContext::set_id(int v) +const SrsContextId& SrsThreadContext::set_id(const SrsContextId& v) { srs_thread_t self = srs_thread_self(); - - int ov = 0; - if (cache.find(self) != cache.end()) { - ov = cache[self]; + + if (cache.find(self) == cache.end()) { + cache[self] = v; + return v; } - + + const SrsContextId& ov = cache[self]; cache[self] = v; - return ov; } void SrsThreadContext::clear_cid() { srs_thread_t self = srs_thread_self(); - std::map::iterator it = cache.find(self); + std::map::iterator it = cache.find(self); if (it != cache.end()) { cache.erase(it); } } +impl_SrsContextRestore::impl_SrsContextRestore(SrsContextId cid) +{ + cid_ = cid; +} + +impl_SrsContextRestore::~impl_SrsContextRestore() +{ + _srs_context->set_id(cid_); +} + // LCOV_EXCL_START SrsConsoleLog::SrsConsoleLog(SrsLogLevel l, bool u) { @@ -105,7 +110,7 @@ void SrsConsoleLog::reopen() { } -void SrsConsoleLog::verbose(const char* tag, int context_id, const char* fmt, ...) +void SrsConsoleLog::verbose(const char* tag, SrsContextId context_id, const char* fmt, ...) { if (level > SrsLogLevelVerbose) { return; @@ -125,7 +130,7 @@ void SrsConsoleLog::verbose(const char* tag, int context_id, const char* fmt, .. fprintf(stdout, "%s\n", buffer); } -void SrsConsoleLog::info(const char* tag, int context_id, const char* fmt, ...) +void SrsConsoleLog::info(const char* tag, SrsContextId context_id, const char* fmt, ...) { if (level > SrsLogLevelInfo) { return; @@ -145,7 +150,7 @@ void SrsConsoleLog::info(const char* tag, int context_id, const char* fmt, ...) fprintf(stdout, "%s\n", buffer); } -void SrsConsoleLog::trace(const char* tag, int context_id, const char* fmt, ...) +void SrsConsoleLog::trace(const char* tag, SrsContextId context_id, const char* fmt, ...) { if (level > SrsLogLevelTrace) { return; @@ -165,7 +170,7 @@ void SrsConsoleLog::trace(const char* tag, int context_id, const char* fmt, ...) fprintf(stdout, "%s\n", buffer); } -void SrsConsoleLog::warn(const char* tag, int context_id, const char* fmt, ...) +void SrsConsoleLog::warn(const char* tag, SrsContextId context_id, const char* fmt, ...) { if (level > SrsLogLevelWarn) { return; @@ -185,7 +190,7 @@ void SrsConsoleLog::warn(const char* tag, int context_id, const char* fmt, ...) fprintf(stderr, "%s\n", buffer); } -void SrsConsoleLog::error(const char* tag, int context_id, const char* fmt, ...) +void SrsConsoleLog::error(const char* tag, SrsContextId context_id, const char* fmt, ...) { if (level > SrsLogLevelError) { return; @@ -211,7 +216,7 @@ void SrsConsoleLog::error(const char* tag, int context_id, const char* fmt, ...) } // LCOV_EXCL_STOP -bool srs_log_header(char* buffer, int size, bool utc, bool dangerous, const char* tag, int cid, const char* level, int* psize) +bool srs_log_header(char* buffer, int size, bool utc, bool dangerous, const char* tag, SrsContextId cid, const char* level, int* psize) { // clock time timeval tv; @@ -235,26 +240,26 @@ bool srs_log_header(char* buffer, int size, bool utc, bool dangerous, const char if (dangerous) { if (tag) { written = snprintf(buffer, size, - "[%d-%02d-%02d %02d:%02d:%02d.%03d][%s][%s][%d][%d][%d] ", + "[%d-%02d-%02d %02d:%02d:%02d.%03d][%s][%d][%s][%d][%s] ", 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), - level, tag, getpid(), cid, errno); + level, getpid(), cid.c_str(), errno, tag); } else { written = snprintf(buffer, size, - "[%d-%02d-%02d %02d:%02d:%02d.%03d][%s][%d][%d][%d] ", + "[%d-%02d-%02d %02d:%02d:%02d.%03d][%s][%d][%s][%d] ", 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), - level, getpid(), cid, errno); + level, getpid(), cid.c_str(), errno); } } else { if (tag) { written = snprintf(buffer, size, - "[%d-%02d-%02d %02d:%02d:%02d.%03d][%s][%s][%d][%d] ", + "[%d-%02d-%02d %02d:%02d:%02d.%03d][%s][%d][%s][%s] ", 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), - level, tag, getpid(), cid); + level, getpid(), cid.c_str(), tag); } else { written = snprintf(buffer, size, - "[%d-%02d-%02d %02d:%02d:%02d.%03d][%s][%d][%d] ", + "[%d-%02d-%02d %02d:%02d:%02d.%03d][%s][%d][%s] ", 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), - level, getpid(), cid); + level, getpid(), cid.c_str()); } } diff --git a/trunk/src/service/srs_service_log.hpp b/trunk/src/protocol/srs_service_log.hpp similarity index 65% rename from trunk/src/service/srs_service_log.hpp rename to trunk/src/protocol/srs_service_log.hpp index c262e968e..6a4e14568 100644 --- a/trunk/src/service/srs_service_log.hpp +++ b/trunk/src/protocol/srs_service_log.hpp @@ -27,27 +27,41 @@ #include #include +#include #include #include // The st thread context, get_id will get the st-thread id, // which identify the client. -class SrsThreadContext : public ISrsThreadContext +class SrsThreadContext : public ISrsContext { private: - std::map cache; + std::map cache; public: SrsThreadContext(); virtual ~SrsThreadContext(); public: - virtual int generate_id(); - virtual int get_id(); - virtual int set_id(int v); + virtual SrsContextId generate_id(); + virtual const SrsContextId& get_id(); + virtual const SrsContextId& set_id(const SrsContextId& v); public: virtual void clear_cid(); }; +// The context restore stores the context and restore it when done. +// Usage: +// SrsContextRestore(_srs_context->get_id()); +#define SrsContextRestore(cid) impl_SrsContextRestore _context_restore_instance(cid) +class impl_SrsContextRestore +{ +private: + SrsContextId cid_; +public: + impl_SrsContextRestore(SrsContextId cid); + virtual ~impl_SrsContextRestore(); +}; + // The basic console log, which write log to console. class SrsConsoleLog : public ISrsLog { @@ -63,11 +77,11 @@ public: public: virtual srs_error_t initialize(); virtual void reopen(); - virtual void verbose(const char* tag, int context_id, const char* fmt, ...); - virtual void info(const char* tag, int context_id, const char* fmt, ...); - virtual void trace(const char* tag, int context_id, const char* fmt, ...); - virtual void warn(const char* tag, int context_id, const char* fmt, ...); - virtual void error(const char* tag, int context_id, const char* fmt, ...); + virtual void verbose(const char* tag, SrsContextId context_id, const char* fmt, ...); + virtual void info(const char* tag, SrsContextId context_id, const char* fmt, ...); + virtual void trace(const char* tag, SrsContextId context_id, const char* fmt, ...); + virtual void warn(const char* tag, SrsContextId context_id, const char* fmt, ...); + virtual void error(const char* tag, SrsContextId context_id, const char* fmt, ...); }; // Generate the log header. @@ -75,6 +89,6 @@ public: // @param utc Whether use UTC time format in the log header. // @param psize Output the actual header size. // @remark It's a internal API. -bool srs_log_header(char* buffer, int size, bool utc, bool dangerous, const char* tag, int cid, const char* level, int* psize); +bool srs_log_header(char* buffer, int size, bool utc, bool dangerous, const char* tag, SrsContextId cid, const char* level, int* psize); #endif diff --git a/trunk/src/service/srs_service_rtmp_conn.cpp b/trunk/src/protocol/srs_service_rtmp_conn.cpp similarity index 93% rename from trunk/src/service/srs_service_rtmp_conn.cpp rename to trunk/src/protocol/srs_service_rtmp_conn.cpp index 01343eb51..84662fd3b 100644 --- a/trunk/src/service/srs_service_rtmp_conn.cpp +++ b/trunk/src/protocol/srs_service_rtmp_conn.cpp @@ -57,6 +57,7 @@ SrsBasicRtmpClient::~SrsBasicRtmpClient() close(); srs_freep(kbps); srs_freep(clk); + srs_freep(req); } srs_error_t SrsBasicRtmpClient::connect() @@ -122,7 +123,7 @@ srs_error_t SrsBasicRtmpClient::do_connect_app(string local_ip, bool debug) data->set("srs_version", SrsAmf0Any::str(RTMP_SIG_SRS_VERSION)); // for edge to directly get the id of client. data->set("srs_pid", SrsAmf0Any::number(getpid())); - data->set("srs_id", SrsAmf0Any::number(_srs_context->get_id())); + data->set("srs_id", SrsAmf0Any::str(_srs_context->get_id().c_str())); // local ip of edge data->set("srs_server_ip", SrsAmf0Any::str(local_ip.c_str())); @@ -147,12 +148,17 @@ srs_error_t SrsBasicRtmpClient::do_connect_app(string local_ip, bool debug) return err; } -srs_error_t SrsBasicRtmpClient::publish(int chunk_size) +srs_error_t SrsBasicRtmpClient::publish(int chunk_size, bool with_vhost, std::string* pstream) { srs_error_t err = srs_success; // Pass params in stream, @see https://github.com/ossrs/srs/issues/1031#issuecomment-409745733 - string stream = srs_generate_stream_with_query(req->host, req->vhost, req->stream, req->param); + string stream = srs_generate_stream_with_query(req->host, req->vhost, req->stream, req->param, with_vhost); + + // Return the generated stream. + if (pstream) { + *pstream = stream; + } // publish. if ((err = client->publish(stream, stream_id, chunk_size)) != srs_success) { @@ -162,12 +168,17 @@ srs_error_t SrsBasicRtmpClient::publish(int chunk_size) return err; } -srs_error_t SrsBasicRtmpClient::play(int chunk_size) +srs_error_t SrsBasicRtmpClient::play(int chunk_size, bool with_vhost, std::string* pstream) { srs_error_t err = srs_success; // Pass params in stream, @see https://github.com/ossrs/srs/issues/1031#issuecomment-409745733 - string stream = srs_generate_stream_with_query(req->host, req->vhost, req->stream, req->param); + string stream = srs_generate_stream_with_query(req->host, req->vhost, req->stream, req->param, with_vhost); + + // Return the generated stream. + if (pstream) { + *pstream = stream; + } if ((err = client->play(stream, stream_id, chunk_size)) != srs_success) { return srs_error_wrap(err, "connect with server failed, stream=%s, stream_id=%d", stream.c_str(), stream_id); diff --git a/trunk/src/service/srs_service_rtmp_conn.hpp b/trunk/src/protocol/srs_service_rtmp_conn.hpp similarity index 94% rename from trunk/src/service/srs_service_rtmp_conn.hpp rename to trunk/src/protocol/srs_service_rtmp_conn.hpp index 2ade55f9f..316b22403 100644 --- a/trunk/src/service/srs_service_rtmp_conn.hpp +++ b/trunk/src/protocol/srs_service_rtmp_conn.hpp @@ -74,8 +74,8 @@ protected: virtual srs_error_t connect_app(); virtual srs_error_t do_connect_app(std::string local_ip, bool debug); public: - virtual srs_error_t publish(int chunk_size); - virtual srs_error_t play(int chunk_size); + virtual srs_error_t publish(int chunk_size, bool with_vhost = true, std::string* pstream = NULL); + virtual srs_error_t play(int chunk_size, bool with_vhost = true, std::string* pstream = NULL); virtual void kbps_sample(const char* label, int64_t age); virtual void kbps_sample(const char* label, int64_t age, int msgs); virtual int sid(); diff --git a/trunk/src/service/srs_service_st.cpp b/trunk/src/protocol/srs_service_st.cpp similarity index 89% rename from trunk/src/service/srs_service_st.cpp rename to trunk/src/protocol/srs_service_st.cpp index d806db24e..0e2c4f586 100644 --- a/trunk/src/service/srs_service_st.cpp +++ b/trunk/src/protocol/srs_service_st.cpp @@ -69,11 +69,20 @@ srs_error_t srs_st_init() if (st_set_eventsys(ST_EVENTSYS_ALT) == -1) { return srs_error_new(ERROR_ST_SET_EPOLL, "st enable st failed, current is %s", st_get_eventsys_name()); } + + // Before ST init, we might have already initialized the background cid. + SrsContextId cid = _srs_context->get_id(); + if (cid.empty()) { + cid = _srs_context->generate_id(); + } int r0 = 0; if((r0 = st_init()) != 0){ return srs_error_new(ERROR_ST_INITIALIZE, "st initialize failed, r0=%d", r0); } + + // Switch to the background cid. + _srs_context->set_id(cid); srs_trace("st_init success, use %s", st_get_eventsys_name()); return srs_success; @@ -115,12 +124,7 @@ srs_error_t srs_fd_reuseport(int fd) #if defined(SO_REUSEPORT) int v = 1; if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &v, sizeof(int)) == -1) { - #ifdef SRS_AUTO_CROSSBUILD - srs_warn("SO_REUSEPORT disabled for crossbuild"); - return srs_success; - #else - return srs_error_new(ERROR_SOCKET_SETREUSEADDR, "SO_REUSEPORT fd=%d", fd); - #endif + srs_warn("SO_REUSEPORT failed for fd=%d", fd); } #else #warning "SO_REUSEPORT is not supported by your OS" @@ -214,7 +218,7 @@ srs_error_t do_srs_tcp_listen(int fd, addrinfo* r, srs_netfd_t* pfd) return srs_error_wrap(err, "set reuseport"); } - if (bind(fd, r->ai_addr, r->ai_addrlen) == -1) { + if (::bind(fd, r->ai_addr, r->ai_addrlen) == -1) { return srs_error_new(ERROR_SOCKET_BIND, "bind"); } @@ -279,7 +283,7 @@ srs_error_t do_srs_udp_listen(int fd, addrinfo* r, srs_netfd_t* pfd) return srs_error_wrap(err, "set reuseport"); } - if (bind(fd, r->ai_addr, r->ai_addrlen) == -1) { + if (::bind(fd, r->ai_addr, r->ai_addrlen) == -1) { return srs_error_new(ERROR_SOCKET_BIND, "bind"); } @@ -349,6 +353,11 @@ int srs_cond_signal(srs_cond_t cond) return st_cond_signal((st_cond_t)cond); } +int srs_cond_broadcast(srs_cond_t cond) +{ + return st_cond_broadcast((st_cond_t)cond); +} + srs_mutex_t srs_mutex_new() { return (srs_mutex_t)st_mutex_new(); @@ -412,53 +421,9 @@ int srs_sendmsg(srs_netfd_t stfd, const struct msghdr *msg, int flags, srs_utime return st_sendmsg((st_netfd_t)stfd, msg, flags, (st_utime_t)timeout); } -int srs_sendmmsg(srs_netfd_t stfd, struct mmsghdr *msgvec, unsigned int vlen, int flags, srs_utime_t timeout) +int srs_sendmmsg(srs_netfd_t stfd, struct srs_mmsghdr *msgvec, unsigned int vlen, int flags, srs_utime_t timeout) { -#if !defined(SRS_AUTO_HAS_SENDMMSG) || !defined(SRS_AUTO_SENDMMSG) - // @see http://man7.org/linux/man-pages/man2/sendmmsg.2.html - for (int i = 0; i < (int)vlen; ++i) { - struct mmsghdr* p = msgvec + i; - int n = srs_sendmsg(stfd, &p->msg_hdr, flags, timeout); - if (n < 0) { - // An error is returned only if no datagrams could be sent. - if (i == 0) { - return n; - } - return i + 1; - } - - p->msg_len = n; - } - // Returns the number of messages sent from msgvec; if this is less than vlen, the caller can retry with a - // further sendmmsg() call to send the remaining messages. - return vlen; -#else - if (vlen == 1) { - #if 1 - int r0 = srs_sendmsg(stfd, &msgvec->msg_hdr, flags, timeout); - if (r0 < 0) { - return r0; - } - msgvec->msg_len = r0; - #else - msgvec->msg_len = 0; - - int tolen = (int)msgvec->msg_hdr.msg_namelen; - const struct sockaddr* to = (const struct sockaddr*)msgvec->msg_hdr.msg_name; - for (int i = 0; i < (int)msgvec->msg_hdr.msg_iovlen; i++) { - iovec* iov = msgvec->msg_hdr.msg_iov + i; - int r0 = srs_sendto(stfd, (void*)iov->iov_base, (int)iov->iov_len, to, tolen, timeout); - if (r0 < 0) { - return r0; - } - msgvec->msg_len += r0; - } - #endif - - return 1; - } - return st_sendmmsg((st_netfd_t)stfd, msgvec, vlen, flags, (st_utime_t)timeout); -#endif + return st_sendmmsg((st_netfd_t)stfd, (struct st_mmsghdr*)msgvec, vlen, flags, (st_utime_t)timeout); } srs_netfd_t srs_accept(srs_netfd_t stfd, struct sockaddr *addr, int *addrlen, srs_utime_t timeout) diff --git a/trunk/src/service/srs_service_st.hpp b/trunk/src/protocol/srs_service_st.hpp similarity index 93% rename from trunk/src/service/srs_service_st.hpp rename to trunk/src/protocol/srs_service_st.hpp index 0986d553e..ed86737d6 100644 --- a/trunk/src/service/srs_service_st.hpp +++ b/trunk/src/protocol/srs_service_st.hpp @@ -74,6 +74,7 @@ extern int srs_cond_destroy(srs_cond_t cond); extern int srs_cond_wait(srs_cond_t cond); extern int srs_cond_timedwait(srs_cond_t cond, srs_utime_t timeout); extern int srs_cond_signal(srs_cond_t cond); +extern int srs_cond_broadcast(srs_cond_t cond); extern srs_mutex_t srs_mutex_new(); extern int srs_mutex_destroy(srs_mutex_t mutex); @@ -92,15 +93,13 @@ extern int srs_sendto(srs_netfd_t stfd, void *buf, int len, const struct sockadd extern int srs_recvmsg(srs_netfd_t stfd, struct msghdr *msg, int flags, srs_utime_t timeout); extern int srs_sendmsg(srs_netfd_t stfd, const struct msghdr *msg, int flags, srs_utime_t timeout); -#if !defined(SRS_AUTO_HAS_SENDMMSG) - // @see http://man7.org/linux/man-pages/man2/sendmmsg.2.html - #include - struct mmsghdr { - struct msghdr msg_hdr; /* Message header */ - unsigned int msg_len; /* Number of bytes transmitted */ - }; -#endif -extern int srs_sendmmsg(srs_netfd_t stfd, struct mmsghdr *msgvec, unsigned int vlen, int flags, srs_utime_t timeout); +// @see http://man7.org/linux/man-pages/man2/sendmmsg.2.html +#include +struct srs_mmsghdr { + struct msghdr msg_hdr; /* Message header */ + unsigned int msg_len; /* Number of bytes transmitted */ +}; +extern int srs_sendmmsg(srs_netfd_t stfd, struct srs_mmsghdr *msgvec, unsigned int vlen, int flags, srs_utime_t timeout); extern srs_netfd_t srs_accept(srs_netfd_t stfd, struct sockaddr *addr, int *addrlen, srs_utime_t timeout); @@ -110,7 +109,7 @@ extern bool srs_is_never_timeout(srs_utime_t tm); // The mutex locker. #define SrsLocker(instance) \ - impl__SrsLocker _srs_auto_free_##instance(&instance) + impl__SrsLocker _SRS_free_##instance(&instance) class impl__SrsLocker { diff --git a/trunk/src/service/srs_service_utility.cpp b/trunk/src/protocol/srs_service_utility.cpp similarity index 77% rename from trunk/src/service/srs_service_utility.cpp rename to trunk/src/protocol/srs_service_utility.cpp index 5db0f97e8..8cdb738ff 100644 --- a/trunk/src/service/srs_service_utility.cpp +++ b/trunk/src/protocol/srs_service_utility.cpp @@ -139,14 +139,34 @@ bool srs_net_device_is_internet(const sockaddr* addr) if (IN6_IS_ADDR_SITELOCAL(&a6->sin6_addr)) { return false; } + + // Others. + if (IN6_IS_ADDR_MULTICAST(&a6->sin6_addr)) { + return false; + } + if (IN6_IS_ADDR_MC_NODELOCAL(&a6->sin6_addr)) { + return false; + } + if (IN6_IS_ADDR_MC_LINKLOCAL(&a6->sin6_addr)) { + return false; + } + if (IN6_IS_ADDR_MC_SITELOCAL(&a6->sin6_addr)) { + return false; + } + if (IN6_IS_ADDR_MC_ORGLOCAL(&a6->sin6_addr)) { + return false; + } + if (IN6_IS_ADDR_MC_GLOBAL(&a6->sin6_addr)) { + return false; + } } return true; } -vector _srs_system_ips; +vector _srs_system_ips; -void discover_network_iface(ifaddrs* cur, vector& ips, stringstream& ss0, stringstream& ss1, bool ipv6) +void discover_network_iface(ifaddrs* cur, vector& ips, stringstream& ss0, stringstream& ss1, bool ipv6, bool loopback) { char saddr[64]; char* h = (char*)saddr; @@ -160,10 +180,17 @@ void discover_network_iface(ifaddrs* cur, vector& ips, stringstream& ss0 std::string ip(saddr, strlen(saddr)); ss0 << ", iface[" << (int)ips.size() << "] " << cur->ifa_name << " " << (ipv6? "ipv6":"ipv4") << " 0x" << std::hex << cur->ifa_flags << std::dec << " " << ip; - ips.push_back(ip); + + SrsIPAddress* ip_address = new SrsIPAddress(); + ip_address->ip = ip; + ip_address->is_ipv4 = !ipv6; + ip_address->is_loopback = loopback; + ip_address->ifname = cur->ifa_name; + ip_address->is_internet = srs_net_device_is_internet(cur->ifa_addr); + ips.push_back(ip_address); // set the device internet status. - if (!srs_net_device_is_internet(cur->ifa_addr)) { + if (!ip_address->is_internet) { ss1 << ", intranet "; _srs_device_ifs[cur->ifa_name] = false; } else { @@ -175,10 +202,16 @@ void discover_network_iface(ifaddrs* cur, vector& ips, stringstream& ss0 void retrieve_local_ips() { - vector& ips = _srs_system_ips; - + vector& ips = _srs_system_ips; + + // Release previous IPs. + for (int i = 0; i < (int)ips.size(); i++) { + SrsIPAddress* ip = ips[i]; + srs_freep(ip); + } ips.clear(); - + + // Get the addresses. ifaddrs* ifap; if (getifaddrs(&ifap) == -1) { srs_warn("retrieve local ips, getifaddrs failed."); @@ -207,8 +240,9 @@ void retrieve_local_ips() bool ready = (cur->ifa_flags & IFF_UP) && (cur->ifa_flags & IFF_RUNNING); // Ignore IFF_PROMISC(Interface is in promiscuous mode), which may be set by Wireshark. bool ignored = (!cur->ifa_addr) || (cur->ifa_flags & IFF_LOOPBACK) || (cur->ifa_flags & IFF_POINTOPOINT); + bool loopback = (cur->ifa_flags & IFF_LOOPBACK); if (ipv4 && ready && !ignored) { - discover_network_iface(cur, ips, ss0, ss1, false); + discover_network_iface(cur, ips, ss0, ss1, false, loopback); } } @@ -227,8 +261,9 @@ void retrieve_local_ips() bool ipv6 = (cur->ifa_addr->sa_family == AF_INET6); bool ready = (cur->ifa_flags & IFF_UP) && (cur->ifa_flags & IFF_RUNNING); bool ignored = (!cur->ifa_addr) || (cur->ifa_flags & IFF_POINTOPOINT) || (cur->ifa_flags & IFF_PROMISC) || (cur->ifa_flags & IFF_LOOPBACK); + bool loopback = (cur->ifa_flags & IFF_LOOPBACK); if (ipv6 && ready && !ignored) { - discover_network_iface(cur, ips, ss0, ss1, true); + discover_network_iface(cur, ips, ss0, ss1, true, loopback); } } @@ -248,8 +283,9 @@ void retrieve_local_ips() bool ipv4 = (cur->ifa_addr->sa_family == AF_INET); bool ready = (cur->ifa_flags & IFF_UP) && (cur->ifa_flags & IFF_RUNNING); bool ignored = (!cur->ifa_addr) || (cur->ifa_flags & IFF_POINTOPOINT) || (cur->ifa_flags & IFF_PROMISC); + bool loopback = (cur->ifa_flags & IFF_LOOPBACK); if (ipv4 && ready && !ignored) { - discover_network_iface(cur, ips, ss0, ss1, false); + discover_network_iface(cur, ips, ss0, ss1, false, loopback); } } } @@ -260,7 +296,7 @@ void retrieve_local_ips() freeifaddrs(ifap); } -vector& srs_get_local_ips() +vector& srs_get_local_ips() { if (_srs_system_ips.empty()) { retrieve_local_ips(); @@ -271,78 +307,51 @@ vector& srs_get_local_ips() std::string _public_internet_address; -string srs_get_public_internet_address() +string srs_get_public_internet_address(bool ipv4_only) { if (!_public_internet_address.empty()) { return _public_internet_address; } - std::vector& ips = srs_get_local_ips(); + std::vector& ips = srs_get_local_ips(); // find the best match public address. for (int i = 0; i < (int)ips.size(); i++) { - std::string ip = ips[i]; - // TODO: FIXME: Support ipv6. - if (ip.find(".") == string::npos) { + SrsIPAddress* ip = ips[i]; + if (!ip->is_internet) { continue; } - - in_addr_t addr = inet_addr(ip.c_str()); - uint32_t addr_h = ntohl(addr); - // lo, 127.0.0.0-127.0.0.1 - if (addr_h >= 0x7f000000 && addr_h <= 0x7f000001) { - srs_trace("ignore private address: %s", ip.c_str()); + if (ipv4_only && !ip->is_ipv4) { continue; } - // Class A 10.0.0.0-10.255.255.255 - if (addr_h >= 0x0a000000 && addr_h <= 0x0affffff) { - srs_trace("ignore private address: %s", ip.c_str()); - continue; - } - // Class B 172.16.0.0-172.31.255.255 - if (addr_h >= 0xac100000 && addr_h <= 0xac1fffff) { - srs_trace("ignore private address: %s", ip.c_str()); - continue; - } - // Class C 192.168.0.0-192.168.255.255 - if (addr_h >= 0xc0a80000 && addr_h <= 0xc0a8ffff) { - srs_trace("ignore private address: %s", ip.c_str()); - continue; - } - srs_warn("use public address as ip: %s", ip.c_str()); - - _public_internet_address = ip; - return ip; + + srs_warn("use public address as ip: %s, ifname=%s", ip->ip.c_str(), ip->ifname.c_str()); + _public_internet_address = ip->ip; + return ip->ip; } // no public address, use private address. for (int i = 0; i < (int)ips.size(); i++) { - std::string ip = ips[i]; - // TODO: FIXME: Support ipv6. - if (ip.find(".") == string::npos) { + SrsIPAddress* ip = ips[i]; + if (ip->is_loopback) { continue; } - - in_addr_t addr = inet_addr(ip.c_str()); - uint32_t addr_h = ntohl(addr); - // lo, 127.0.0.0-127.0.0.1 - if (addr_h >= 0x7f000000 && addr_h <= 0x7f000001) { - srs_trace("ignore private address: %s", ip.c_str()); + if (ipv4_only && !ip->is_ipv4) { continue; } - srs_warn("use private address as ip: %s", ip.c_str()); - - _public_internet_address = ip; - return ip; + + srs_warn("use private address as ip: %s, ifname=%s", ip->ip.c_str(), ip->ifname.c_str()); + _public_internet_address = ip->ip; + return ip->ip; } // Finally, use first whatever kind of address. if (!ips.empty() && _public_internet_address.empty()) { - string ip = ips.at(0); - srs_warn("use first address as ip: %s", ip.c_str()); - - _public_internet_address = ip; - return ip; + SrsIPAddress* ip = ips[0]; + + srs_warn("use first address as ip: %s, ifname=%s", ip->ip.c_str(), ip->ifname.c_str()); + _public_internet_address = ip->ip; + return ip->ip; } return ""; @@ -373,3 +382,21 @@ string srs_get_original_ip(ISrsHttpMessage* r) return ""; } + +std::string _srs_system_hostname; + +string srs_get_system_hostname() +{ + if (!_srs_system_hostname.empty()) { + return _srs_system_hostname; + } + + char buf[256]; + if (-1 == gethostname(buf, sizeof(buf))) { + srs_warn("gethostbyname fail"); + return ""; + } + + _srs_system_hostname = std::string(buf); + return _srs_system_hostname; +} \ No newline at end of file diff --git a/trunk/src/service/srs_service_utility.hpp b/trunk/src/protocol/srs_service_utility.hpp similarity index 80% rename from trunk/src/service/srs_service_utility.hpp rename to trunk/src/protocol/srs_service_utility.hpp index ce1acc3ed..8b35016fc 100644 --- a/trunk/src/service/srs_service_utility.hpp +++ b/trunk/src/protocol/srs_service_utility.hpp @@ -51,10 +51,23 @@ extern bool srs_string_is_rtmp(std::string url); extern bool srs_is_digit_number(std::string str); // Get local ip, fill to @param ips -extern std::vector& srs_get_local_ips(); +struct SrsIPAddress +{ + // The network interface name, such as eth0, en0, eth1. + std::string ifname; + // The IP v4 or v6 address. + std::string ip; + // Whether the ip is IPv4 address. + bool is_ipv4; + // Whether the ip is internet public IP address. + bool is_internet; + // Whether the ip is loopback, such as 127.0.0.1 + bool is_loopback; +}; +extern std::vector& srs_get_local_ips(); // Get local public ip, empty string if no public internet address found. -extern std::string srs_get_public_internet_address(); +extern std::string srs_get_public_internet_address(bool ipv4_only = false); // Detect whether specified device is internet public address. extern bool srs_net_device_is_internet(std::string ifname); @@ -63,5 +76,8 @@ extern bool srs_net_device_is_internet(const sockaddr* addr); // Get the original ip from query and header by proxy. extern std::string srs_get_original_ip(ISrsHttpMessage* r); +// Get hostname +extern std::string srs_get_system_hostname(void); + #endif diff --git a/trunk/src/protocol/srs_sip_stack.cpp b/trunk/src/protocol/srs_sip_stack.cpp index 19d959723..c6364b249 100644 --- a/trunk/src/protocol/srs_sip_stack.cpp +++ b/trunk/src/protocol/srs_sip_stack.cpp @@ -43,6 +43,65 @@ using namespace std; #include #include +#include + +/* Code (GB2312 <--> UTF-8) Convert Class */ +class CodeConverter +{ +private: + iconv_t cd; +public: + CodeConverter(const char *pFromCharset, const char *pToCharset) + { + cd = iconv_open(pToCharset, pFromCharset); + if ((iconv_t)(-1) == cd) + srs_warn("CodeConverter: iconv_open failed <%s --> %s>", pFromCharset, pToCharset); + } + ~CodeConverter() + { + if ((iconv_t)(-1) != cd) + iconv_close(cd); + } + int Convert(char *pInBuf, size_t InLen, char *pOutBuf, size_t OutLen) + { + return iconv(cd, &pInBuf, (size_t *)&InLen, &pOutBuf, (size_t *)&OutLen); + } + static bool IsUTF8(const char *pInBuf, int InLen) + { + if (InLen < 0) { + return false; + } + + int i = 0; + int nBytes = 0; + unsigned char chr = 0; + + while (i < InLen) { + chr = *(pInBuf + i); + if (nBytes == 0) { + if ((chr & 0x80) != 0) { + while ((chr & 0x80) != 0) { + chr <<= 1; + nBytes++; + } + if (nBytes < 2 || nBytes > 6) { + return false; + } + nBytes--; + } + } else { + if ((chr & 0xc0) != 0x80) { + return false; + } + nBytes--; + } + ++i; + } + + return nBytes == 0; + } +}; + unsigned int srs_sip_random(int min,int max) { //it is possible to duplicate data with time(0) @@ -202,6 +261,7 @@ SrsSipRequest::SrsSipRequest() from_realm = ""; to_realm = ""; + y_ssrc = 0; } SrsSipRequest::~SrsSipRequest() @@ -321,7 +381,7 @@ srs_error_t SrsSipStack::parse_request(SrsSipRequest** preq, const char* recv_ms return err; } -srs_error_t SrsSipStack::parse_xml(std::string xml_msg, std::map &json_map) +srs_error_t SrsSipStack::parse_xml(std::string xml_msg, std::map &json_map, std::vector > &item_list) { /* @@ -340,7 +400,16 @@ srs_error_t SrsSipStack::parse_xml(std::string xml_msg, std::map */ - + + if (CodeConverter::IsUTF8(xml_msg.c_str(), xml_msg.size()) == false) { + char *outBuf = (char *)calloc(1, xml_msg.size() * 2); + CodeConverter cc("gb2312", "utf-8"); + if (cc.Convert((char *)xml_msg.c_str(), xml_msg.size(), (char *)outBuf, xml_msg.size() * 2 - 1) != -1) + xml_msg = string(outBuf); + if (outBuf) + free(outBuf); + } + const char* start = xml_msg.c_str(); const char* end = start + xml_msg.size(); char* p = (char*)start; @@ -349,9 +418,11 @@ srs_error_t SrsSipStack::parse_xml(std::string xml_msg, std::map json_map; std::map json_key; + std::map one_item; while (p < end) { if (p[0] == '\n'){ p +=1; @@ -409,10 +480,21 @@ srs_error_t SrsSipStack::parse_xml(std::string xml_msg, std::map xml item begin flag //skip < p +=1; @@ -443,6 +525,11 @@ srs_error_t SrsSipStack::parse_xml(std::string xml_msg, std::map 1){ - body = header_body.at(1); + // SRS_RTSP_CRLFCRLF may exist in content body (h3c) + string recv_str(recv_msg); + body = recv_str.substr(recv_str.find(SRS_RTSP_CRLFCRLF) + strlen(SRS_RTSP_CRLFCRLF)); + //body = header_body.at(1); } srs_info("sip: header=%s\n", header.c_str()); @@ -588,15 +678,15 @@ srs_error_t SrsSipStack::do_parse_request(SrsSipRequest* req, const char* recv_m std::vector method_uri_ver = srs_string_split(firstline, " "); - if (method_uri_ver.empty()) { - return srs_error_new(ERROR_GB28181_SIP_PRASE_FAILED, "parse request firstline is empty"); + if (method_uri_ver.empty() || method_uri_ver.size() < 3) { + return srs_error_new(ERROR_GB28181_SIP_PRASE_FAILED, "parse request firstline is empty or less than 3 fields"); } //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.size() > 0 ? method_uri_ver.at(1) : ""; + req->status = method_uri_ver.size() > 1 ? method_uri_ver.at(1) : ""; req->version = method_uri_ver.at(0); req->uri = req->from; @@ -607,8 +697,8 @@ srs_error_t SrsSipStack::do_parse_request(SrsSipRequest* req, const char* recv_m }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.size() > 0 ? method_uri_ver.at(1) : ""; - req->version = method_uri_ver.size() > 1 ? method_uri_ver.at(2) : ""; + req->uri = method_uri_ver.size() > 1 ? method_uri_ver.at(1) : ""; + req->version = method_uri_ver.size() > 2 ? method_uri_ver.at(2) : ""; vector str = srs_string_split(req->from, "@"); std::string ss = str.empty() ? "" : str.at(0); @@ -619,49 +709,73 @@ srs_error_t SrsSipStack::do_parse_request(SrsSipRequest* req, const char* recv_m //Content-Type: Application/MANSCDP+xml if (!strcasecmp(req->content_type.c_str(),"application/manscdp+xml")){ - std::map body_map; //xml to map - if ((err = parse_xml(body, body_map)) != srs_success) { + if ((err = parse_xml(body, req->xml_body_map, req->item_list)) != srs_success) { return srs_error_wrap(err, "sip parse xml"); }; //Response Cmd - if (body_map.find("Response") != body_map.end()){ - std::string cmdtype = body_map["Response@CmdType"]; + if (req->xml_body_map.find("Response") != req->xml_body_map.end()){ + std::string cmdtype = req->xml_body_map["Response@CmdType"]; if (cmdtype == "Catalog"){ + #if 0 //Response@DeviceList@Item@DeviceID:3000001,3000002 - std::vector vec_device_id = srs_string_split(body_map["Response@DeviceList@Item@DeviceID"], ","); + std::vector vec_device_id = srs_string_split(req->xml_body_map["Response@DeviceList@Item@DeviceID"], ","); //Response@DeviceList@Item@Status:ON,OFF - std::vector vec_device_status = srs_string_split(body_map["Response@DeviceList@Item@Status"], ","); + std::vector vec_device_status = srs_string_split(req->xml_body_map["Response@DeviceList@Item@Status"], ","); //map key:devicd_id value:status - for(int i=0 ; i i) { + if ((int)vec_device_status.size() > i) { status = vec_device_status.at(i); } req->device_list_map[vec_device_id.at(i)] = status; } + #endif + for(int i=0 ; i< (int)req->item_list.size(); i++){ + std::map one_item = req->item_list.at(i); + std::string status; + if (one_item.find("Status") != one_item.end() && one_item.find("Name") != one_item.end()) { + status = one_item["Status"] + "," + one_item["Name"]; + } else { + // if no Status, it's not a camera but a group + continue; + } + req->device_list_map[one_item["DeviceID"]] = status; + } }else{ //TODO: fixme srs_trace("sip: Response cmdtype=%s not processed", cmdtype.c_str()); } } //Notify Cmd - else if (body_map.find("Notify") != body_map.end()){ - std::string cmdtype = body_map["Notify@CmdType"]; + else if (req->xml_body_map.find("Notify") != req->xml_body_map.end()){ + std::string cmdtype = req->xml_body_map["Notify@CmdType"]; if (cmdtype == "Keepalive"){ //TODO: ???? - std::vector vec_device_id = srs_string_split(body_map["Notify@Info@DeviceID"], ","); - for(int i=0; i vec_device_id = srs_string_split(req->xml_body_map["Notify@Info@DeviceID"], ","); + for(int i=0; i< (int)vec_device_id.size(); i++){ //req->device_list_map[vec_device_id.at(i)] = "OFF"; } }else{ //TODO: fixme srs_trace("sip: Notify cmdtype=%s not processed", cmdtype.c_str()); } - }// end if(body_map) + }// end if(req->xml_body_map) }//end if (!strcasecmp) + else if (!strcasecmp(req->content_type.c_str(),"application/sdp")) { + std::vector sdp_lines = srs_string_split(body.c_str(), SRS_RTSP_CRLF); + for(int i=0 ; i< (int)sdp_lines.size(); i++){ + if (!strncasecmp(sdp_lines.at(i).c_str(), "y=", 2)) { + string yline = sdp_lines.at(i); + string ssrc = yline.substr(2); + req->y_ssrc = strtoul(ssrc.c_str(), NULL, 10); + srs_trace("gb28181: ssrc in y line is %u:%x", req->y_ssrc, req->y_ssrc); + break; + } + } + } 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()); @@ -842,7 +956,7 @@ void SrsSipStack::resp_status(stringstream& ss, SrsSipRequest *req) } -void SrsSipStack::req_invite(stringstream& ss, SrsSipRequest *req, string ip, int port, uint32_t ssrc) +void SrsSipStack::req_invite(stringstream& ss, SrsSipRequest *req, string ip, int port, uint32_t ssrc, bool tcpFlag) { /* //request: sip-agent <-------INVITE------ sip-server @@ -919,23 +1033,45 @@ void SrsSipStack::req_invite(stringstream& ss, SrsSipRequest *req, string ip, in sprintf(_ssrc, "%010d", ssrc); std::stringstream sdp; - sdp << "v=0" << SRS_RTSP_CRLF - << "o=" << req->serial << " 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; + if (!tcpFlag){ + sdp << "v=0" << SRS_RTSP_CRLF + << "o=" << req->serial << " 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; + } else { + sdp << "v=0" << SRS_RTSP_CRLF + << "o=" << req->serial << " 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 + << "m=video " << port << " TCP/RTP/AVP 96" << SRS_RTSP_CRLF + //<< "m=video " << port << " TCP/RTP/AVP 98" << 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; + } std::stringstream from, to, uri; diff --git a/trunk/src/protocol/srs_sip_stack.hpp b/trunk/src/protocol/srs_sip_stack.hpp index a91897d18..c6d4e1131 100644 --- a/trunk/src/protocol/srs_sip_stack.hpp +++ b/trunk/src/protocol/srs_sip_stack.hpp @@ -30,6 +30,7 @@ #include #include +#include #include #include @@ -105,6 +106,9 @@ public: std::map xml_body_map; std::map device_list_map; + // add an item_list, you can do a lot of other things + // used by DeviceList, Alarmstatus, RecordList in "GB/T 28181—2016" + std::vector > item_list; public: std::string serial; @@ -120,6 +124,7 @@ public: std::string from_realm; std::string to_realm; + uint32_t y_ssrc; public: SrsRtspSdp* sdp; @@ -152,7 +157,7 @@ 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); - virtual srs_error_t parse_xml(std::string xml_msg, std::map &json_map); + virtual srs_error_t parse_xml(std::string xml_msg, std::map &json_map, std::vector > &item_list); private: //response from @@ -169,7 +174,7 @@ public: //request: request sent by the sip-server, wait for sip-agent response virtual void req_invite(std::stringstream& ss, SrsSipRequest *req, std::string ip, - int port, uint32_t ssrc); + int port, uint32_t ssrc, bool tcpFlag); virtual void req_ack(std::stringstream& ss, SrsSipRequest *req); virtual void req_bye(std::stringstream& ss, SrsSipRequest *req); virtual void req_401_unauthorized(std::stringstream& ss, SrsSipRequest *req); diff --git a/trunk/src/service/srs_service_http_client.cpp b/trunk/src/service/srs_service_http_client.cpp deleted file mode 100644 index 527aa4f43..000000000 --- a/trunk/src/service/srs_service_http_client.cpp +++ /dev/null @@ -1,237 +0,0 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2013-2020 Winlin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#include - -#include -#include -using namespace std; - -#include -#include -#include -#include -#include -#include -#include - -SrsHttpClient::SrsHttpClient() -{ - transport = NULL; - clk = new SrsWallClock(); - kbps = new SrsKbps(clk); - parser = NULL; - recv_timeout = timeout = SRS_UTIME_NO_TIMEOUT; - port = 0; -} - -SrsHttpClient::~SrsHttpClient() -{ - disconnect(); - - srs_freep(kbps); - srs_freep(clk); - srs_freep(parser); -} - -srs_error_t SrsHttpClient::initialize(string h, int p, srs_utime_t tm) -{ - srs_error_t err = srs_success; - - srs_freep(parser); - parser = new SrsHttpParser(); - - if ((err = parser->initialize(HTTP_RESPONSE, false)) != srs_success) { - return srs_error_wrap(err, "http: init parser"); - } - - // Always disconnect the transport. - host = h; - port = p; - recv_timeout = timeout = tm; - disconnect(); - - // ep used for host in header. - string ep = host; - if (port > 0 && port != SRS_CONSTS_HTTP_DEFAULT_PORT) { - ep += ":" + srs_int2str(port); - } - - // Set default value for headers. - headers["Host"] = ep; - headers["Connection"] = "Keep-Alive"; - headers["User-Agent"] = RTMP_SIG_SRS_SERVER; - headers["Content-Type"] = "application/json"; - - return err; -} - -SrsHttpClient* SrsHttpClient::set_header(string k, string v) -{ - headers[k] = v; - - return this; -} - -srs_error_t SrsHttpClient::post(string path, string req, ISrsHttpMessage** ppmsg) -{ - *ppmsg = NULL; - - srs_error_t err = srs_success; - - // always set the content length. - headers["Content-Length"] = srs_int2str(req.length()); - - if ((err = connect()) != srs_success) { - return srs_error_wrap(err, "http: connect server"); - } - - // send POST request to uri - // POST %s HTTP/1.1\r\nHost: %s\r\nContent-Length: %d\r\n\r\n%s - std::stringstream ss; - ss << "POST " << path << " " << "HTTP/1.1" << SRS_HTTP_CRLF; - for (map::iterator it = headers.begin(); it != headers.end(); ++it) { - string key = it->first; - string value = it->second; - ss << key << ": " << value << SRS_HTTP_CRLF; - } - ss << SRS_HTTP_CRLF << req; - - std::string data = ss.str(); - if ((err = transport->write((void*)data.c_str(), data.length(), NULL)) != srs_success) { - // Disconnect the transport when channel error, reconnect for next operation. - disconnect(); - return srs_error_wrap(err, "http: write"); - } - - ISrsHttpMessage* msg = NULL; - if ((err = parser->parse_message(transport, &msg)) != srs_success) { - return srs_error_wrap(err, "http: parse response"); - } - srs_assert(msg); - - if (ppmsg) { - *ppmsg = msg; - } else { - srs_freep(msg); - } - - return err; -} - -srs_error_t SrsHttpClient::get(string path, string req, ISrsHttpMessage** ppmsg) -{ - *ppmsg = NULL; - - srs_error_t err = srs_success; - - // always set the content length. - headers["Content-Length"] = srs_int2str(req.length()); - - if ((err = connect()) != srs_success) { - return srs_error_wrap(err, "http: connect server"); - } - - // send POST request to uri - // GET %s HTTP/1.1\r\nHost: %s\r\nContent-Length: %d\r\n\r\n%s - std::stringstream ss; - ss << "GET " << path << " " << "HTTP/1.1" << SRS_HTTP_CRLF; - for (map::iterator it = headers.begin(); it != headers.end(); ++it) { - string key = it->first; - string value = it->second; - ss << key << ": " << value << SRS_HTTP_CRLF; - } - ss << SRS_HTTP_CRLF << req; - - std::string data = ss.str(); - if ((err = transport->write((void*)data.c_str(), data.length(), NULL)) != srs_success) { - // Disconnect the transport when channel error, reconnect for next operation. - disconnect(); - return srs_error_wrap(err, "http: write"); - } - - ISrsHttpMessage* msg = NULL; - if ((err = parser->parse_message(transport, &msg)) != srs_success) { - return srs_error_wrap(err, "http: parse response"); - } - srs_assert(msg); - - if (ppmsg) { - *ppmsg = msg; - } else { - srs_freep(msg); - } - - return err; -} - -void SrsHttpClient::set_recv_timeout(srs_utime_t tm) -{ - recv_timeout = tm; -} - -void SrsHttpClient::kbps_sample(const char* label, int64_t age) -{ - kbps->sample(); - - int sr = kbps->get_send_kbps(); - int sr30s = kbps->get_send_kbps_30s(); - int sr5m = kbps->get_send_kbps_5m(); - int rr = kbps->get_recv_kbps(); - int rr30s = kbps->get_recv_kbps_30s(); - int rr5m = kbps->get_recv_kbps_5m(); - - srs_trace("<- %s time=%" PRId64 ", okbps=%d,%d,%d, ikbps=%d,%d,%d", label, age, sr, sr30s, sr5m, rr, rr30s, rr5m); -} - -void SrsHttpClient::disconnect() -{ - kbps->set_io(NULL, NULL); - srs_freep(transport); -} - -srs_error_t SrsHttpClient::connect() -{ - srs_error_t err = srs_success; - - // When transport connected, ignore. - if (transport) { - return err; - } - - transport = new SrsTcpClient(host, port, timeout); - if ((err = transport->connect()) != srs_success) { - disconnect(); - return srs_error_wrap(err, "http: tcp connect %s:%d to=%dms, rto=%dms", - host.c_str(), port, srsu2msi(timeout), srsu2msi(recv_timeout)); - } - - // Set the recv/send timeout in srs_utime_t. - transport->set_recv_timeout(recv_timeout); - transport->set_send_timeout(timeout); - - kbps->set_io(transport, transport); - - return err; -} - diff --git a/trunk/src/srt/srt_handle.cpp b/trunk/src/srt/srt_handle.cpp index 50f702051..b4830ee49 100644 --- a/trunk/src/srt/srt_handle.cpp +++ b/trunk/src/srt/srt_handle.cpp @@ -153,6 +153,9 @@ void srt_handle::add_newconn(SRT_CONN_PTR conn_ptr, int events) { int val_i; int opt_len = sizeof(int); + int64_t val_i64; + int opt64_len = sizeof(int64_t); + srt_getsockopt(conn_ptr->get_conn(), 0, SRTO_LATENCY, &val_i, &opt_len); srs_trace("srto SRTO_LATENCY=%d", val_i); @@ -165,8 +168,8 @@ void srt_handle::add_newconn(SRT_CONN_PTR conn_ptr, int events) { srs_trace("srto SRTO_SNDBUF=%d", val_i); srt_getsockopt(conn_ptr->get_conn(), 0, SRTO_RCVBUF, &val_i, &opt_len); srs_trace("srto SRTO_RCVBUF=%d", val_i); - srt_getsockopt(conn_ptr->get_conn(), 0, SRTO_MAXBW, &val_i, &opt_len); - srs_trace("srto SRTO_MAXBW=%d", val_i); + srt_getsockopt(conn_ptr->get_conn(), 0, SRTO_MAXBW, &val_i64, &opt64_len); + srs_trace("srto SRTO_MAXBW=%d", val_i64); srs_trace("srt mix_correct is %s.", _srs_config->get_srt_mix_correct() ? "enable" : "disable"); srs_trace("srt h264 sei filter is %s.", _srs_config->get_srt_sei_filter() ? "enable" : "disable"); diff --git a/trunk/src/utest/srs_utest.cpp b/trunk/src/utest/srs_utest.cpp index ec5eee727..8e400f1f0 100644 --- a/trunk/src/utest/srs_utest.cpp +++ b/trunk/src/utest/srs_utest.cpp @@ -28,6 +28,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include #include +#include #include using namespace std; @@ -41,9 +42,9 @@ srs_utime_t _srs_tmp_timeout = (100 * SRS_UTIME_MILLISECONDS); // kernel module. ISrsLog* _srs_log = new MockEmptyLog(SrsLogLevelDisabled); -ISrsThreadContext* _srs_context = new ISrsThreadContext(); +ISrsContext* _srs_context = new SrsThreadContext(); // app module. -SrsConfig* _srs_config = NULL; +SrsConfig* _srs_config = new SrsConfig(); SrsServer* _srs_server = NULL; bool _srs_in_docker = false; @@ -57,6 +58,10 @@ srs_error_t prepare_main() { return srs_error_wrap(err, "init st"); } + if ((err = _srs_rtc_dtls_certificate->initialize()) != srs_success) { + return srs_error_wrap(err, "rtc dtls certificate initialize"); + } + srs_freep(_srs_context); _srs_context = new SrsThreadContext(); @@ -129,3 +134,55 @@ VOID TEST(SampleTest, FastSampleMacrosTest) EXPECT_NEAR(10, 15, 5); } +VOID TEST(SampleTest, StringEQTest) +{ + string str = "100"; + EXPECT_TRUE("100" == str); + EXPECT_EQ("100", str); + EXPECT_STREQ("100", str.c_str()); +} + +class MockSrsContextId +{ +public: + MockSrsContextId() { + bind_ = NULL; + } + MockSrsContextId(const MockSrsContextId& cp){ + bind_ = NULL; + if (cp.bind_) { + bind_ = cp.bind_->copy(); + } + } + MockSrsContextId& operator= (const MockSrsContextId& cp) { + srs_freep(bind_); + if (cp.bind_) { + bind_ = cp.bind_->copy(); + } + return *this; + } + virtual ~MockSrsContextId() { + srs_freep(bind_); + } +public: + MockSrsContextId* copy() const { + MockSrsContextId* cp = new MockSrsContextId(); + if (bind_) { + cp->bind_ = bind_->copy(); + } + return cp; + } +private: + MockSrsContextId* bind_; +}; + +VOID TEST(SampleTest, ContextTest) +{ + MockSrsContextId cid; + cid.bind_ = new MockSrsContextId(); + + static std::map cache; + cache[0] = cid; + cache[0] = cid; +} + diff --git a/trunk/src/utest/srs_utest.hpp b/trunk/src/utest/srs_utest.hpp index ffda614d9..5f30930b3 100644 --- a/trunk/src/utest/srs_utest.hpp +++ b/trunk/src/utest/srs_utest.hpp @@ -24,6 +24,12 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #ifndef SRS_UTEST_PUBLIC_SHARED_HPP #define SRS_UTEST_PUBLIC_SHARED_HPP +// Before define the private/protected, we must include some system header files. +// Or it may fail with: +// redeclared with different access struct __xfer_bufptrs +// @see https://stackoverflow.com/questions/47839718/sstream-redeclared-with-public-access-compiler-error +#include "gtest/gtest.h" + // Public all private and protected members. #define private public #define protected public @@ -33,7 +39,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include -#include "gtest/gtest.h" #include using namespace std; @@ -51,23 +56,28 @@ extern int _srs_tmp_port; extern srs_utime_t _srs_tmp_timeout; // For errors. +// @remark we directly delete the err, because we allow user to append message if fail. #define HELPER_EXPECT_SUCCESS(x) \ if ((err = x) != srs_success) fprintf(stderr, "err %s", srs_error_desc(err).c_str()); \ - EXPECT_TRUE(srs_success == err); \ - srs_freep(err) -#define HELPER_EXPECT_FAILED(x) EXPECT_TRUE(srs_success != (err = x)); srs_freep(err) + if (err != srs_success) delete err; \ + EXPECT_TRUE(srs_success == err) +#define HELPER_EXPECT_FAILED(x) \ + if ((err = x) != srs_success) delete err; \ + EXPECT_TRUE(srs_success != err) // For errors, assert. -// @remark The err is leak when error, but it's ok in utest. +// @remark we directly delete the err, because we allow user to append message if fail. #define HELPER_ASSERT_SUCCESS(x) \ if ((err = x) != srs_success) fprintf(stderr, "err %s", srs_error_desc(err).c_str()); \ - ASSERT_TRUE(srs_success == err); \ - srs_freep(err) -#define HELPER_ASSERT_FAILED(x) ASSERT_TRUE(srs_success != (err = x)); srs_freep(err) + if (err != srs_success) delete err; \ + ASSERT_TRUE(srs_success == err) +#define HELPER_ASSERT_FAILED(x) \ + if ((err = x) != srs_success) delete err; \ + ASSERT_TRUE(srs_success != err) // For init array data. #define HELPER_ARRAY_INIT(buf, sz, val) \ - for (int i = 0; i < (int)sz; i++) (buf)[i]=val + for (int _iii = 0; _iii < (int)sz; _iii++) (buf)[_iii] = val // Dump simple stream to string. #define HELPER_BUFFER2STR(io) \ @@ -95,7 +105,7 @@ extern srs_utime_t _srs_tmp_timeout; // print the bytes. void srs_bytes_print(char* pa, int size); -class MockEmptyLog : public SrsFastLog +class MockEmptyLog : public SrsFileLog { public: MockEmptyLog(SrsLogLevel l); diff --git a/trunk/src/utest/srs_utest_amf0.cpp b/trunk/src/utest/srs_utest_amf0.cpp index 4b65e13c4..9c322d227 100644 --- a/trunk/src/utest/srs_utest_amf0.cpp +++ b/trunk/src/utest/srs_utest_amf0.cpp @@ -32,7 +32,7 @@ using namespace std; #include #include #include -using namespace _srs_internal; +using namespace srs_internal; /** * main scenario to use amf0. @@ -1323,7 +1323,7 @@ VOID TEST(ProtocolAMF0Test, InterfacesString) } if (true) { - SrsBuffer b; + SrsBuffer b(NULL, 0); SrsAmf0Any* o = SrsAmf0Any::str(); HELPER_EXPECT_FAILED(o->write(&b)); srs_freep(o); @@ -1354,7 +1354,7 @@ VOID TEST(ProtocolAMF0Test, InterfacesString) } if (true) { - SrsBuffer b; + SrsBuffer b(NULL, 0); SrsAmf0Any* o = SrsAmf0Any::str(); HELPER_EXPECT_FAILED(o->read(&b)); srs_freep(o); @@ -1452,7 +1452,7 @@ VOID TEST(ProtocolAMF0Test, InterfacesBoolean) } if (true) { - SrsBuffer b; + SrsBuffer b(NULL, 0); SrsAmf0Any* o = SrsAmf0Any::boolean(); HELPER_EXPECT_FAILED(o->write(&b)); srs_freep(o); @@ -1467,7 +1467,7 @@ VOID TEST(ProtocolAMF0Test, InterfacesBoolean) } if (true) { - SrsBuffer b; + SrsBuffer b(NULL, 0); SrsAmf0Any* o = SrsAmf0Any::boolean(); HELPER_EXPECT_FAILED(o->read(&b)); srs_freep(o); @@ -1549,7 +1549,7 @@ VOID TEST(ProtocolAMF0Test, InterfacesNumber) } if (true) { - SrsBuffer b; + SrsBuffer b(NULL, 0); SrsAmf0Any* o = SrsAmf0Any::number(); HELPER_EXPECT_FAILED(o->write(&b)); srs_freep(o); @@ -1564,7 +1564,7 @@ VOID TEST(ProtocolAMF0Test, InterfacesNumber) } if (true) { - SrsBuffer b; + SrsBuffer b(NULL, 0); SrsAmf0Any* o = SrsAmf0Any::number(); HELPER_EXPECT_FAILED(o->read(&b)); srs_freep(o); @@ -1673,14 +1673,14 @@ VOID TEST(ProtocolAMF0Test, InterfacesNull) } if (true) { - SrsBuffer b; + SrsBuffer b(NULL, 0); SrsAmf0Any* o = SrsAmf0Any::null(); HELPER_EXPECT_FAILED(o->write(&b)); srs_freep(o); } if (true) { - SrsBuffer b; + SrsBuffer b(NULL, 0); SrsAmf0Any* o = SrsAmf0Any::null(); HELPER_EXPECT_FAILED(o->read(&b)); srs_freep(o); @@ -1739,14 +1739,14 @@ VOID TEST(ProtocolAMF0Test, InterfacesUndefined) } if (true) { - SrsBuffer b; + SrsBuffer b(NULL, 0); SrsAmf0Any* o = SrsAmf0Any::undefined(); HELPER_EXPECT_FAILED(o->write(&b)); srs_freep(o); } if (true) { - SrsBuffer b; + SrsBuffer b(NULL, 0); SrsAmf0Any* o = SrsAmf0Any::undefined(); HELPER_EXPECT_FAILED(o->read(&b)); srs_freep(o); @@ -1811,14 +1811,14 @@ VOID TEST(ProtocolAMF0Test, InterfacesObject) } if (true) { - SrsBuffer b; + SrsBuffer b(NULL, 0); SrsAmf0Object* o = SrsAmf0Any::object(); HELPER_EXPECT_FAILED(o->read(&b)); srs_freep(o); } if (true) { - SrsBuffer b; + SrsBuffer b(NULL, 0); SrsAmf0Object* o = SrsAmf0Any::object(); HELPER_EXPECT_FAILED(o->write(&b)); srs_freep(o); @@ -2014,14 +2014,14 @@ VOID TEST(ProtocolAMF0Test, InterfacesObjectEOF) } if (true) { - SrsBuffer b; + SrsBuffer b(NULL, 0); SrsAmf0ObjectEOF* o = new SrsAmf0ObjectEOF(); HELPER_EXPECT_FAILED(o->read(&b)); srs_freep(o); } if (true) { - SrsBuffer b; + SrsBuffer b(NULL, 0); SrsAmf0ObjectEOF* o = new SrsAmf0ObjectEOF(); HELPER_EXPECT_FAILED(o->write(&b)); srs_freep(o); @@ -2106,14 +2106,14 @@ VOID TEST(ProtocolAMF0Test, InterfacesEcmaArray) } if (true) { - SrsBuffer b; + SrsBuffer b(NULL, 0); SrsAmf0EcmaArray* o = SrsAmf0Any::ecma_array(); HELPER_EXPECT_FAILED(o->read(&b)); srs_freep(o); } if (true) { - SrsBuffer b; + SrsBuffer b(NULL, 0); SrsAmf0EcmaArray* o = SrsAmf0Any::ecma_array(); HELPER_EXPECT_FAILED(o->write(&b)); srs_freep(o); @@ -2233,14 +2233,14 @@ VOID TEST(ProtocolAMF0Test, InterfacesStrictArray) } if (true) { - SrsBuffer b; + SrsBuffer b(NULL, 0); SrsAmf0StrictArray* o = SrsAmf0Any::strict_array(); HELPER_EXPECT_FAILED(o->read(&b)); srs_freep(o); } if (true) { - SrsBuffer b; + SrsBuffer b(NULL, 0); SrsAmf0StrictArray* o = SrsAmf0Any::strict_array(); HELPER_EXPECT_FAILED(o->write(&b)); srs_freep(o); @@ -2346,7 +2346,7 @@ VOID TEST(ProtocolAMF0Test, InterfacesError) srs_error_t err; if (true) { - SrsBuffer b; + SrsBuffer b(NULL, 0); HELPER_EXPECT_FAILED(SrsAmf0Any::discovery(&b, NULL)); } @@ -2490,7 +2490,7 @@ VOID TEST(ProtocolAMF0Test, Amf0Object2) } if (true) { - SrsBuffer b; + SrsBuffer b(NULL, 0); SrsAmf0Any* eof = SrsAmf0Any::object_eof(); HELPER_EXPECT_FAILED(srs_amf0_write_object_eof(&b, (SrsAmf0ObjectEOF*)eof)); srs_freep(eof); diff --git a/trunk/src/utest/srs_utest_app.cpp b/trunk/src/utest/srs_utest_app.cpp index 98c34a29a..010ed4272 100644 --- a/trunk/src/utest/srs_utest_app.cpp +++ b/trunk/src/utest/srs_utest_app.cpp @@ -36,7 +36,7 @@ VOID TEST(AppCoroutineTest, Dummy) SrsDummyCoroutine dc; if (true) { - EXPECT_EQ(0, dc.cid()); + EXPECT_TRUE(dc.cid().empty()); srs_error_t err = dc.pull(); EXPECT_TRUE(err != srs_success); @@ -52,7 +52,7 @@ VOID TEST(AppCoroutineTest, Dummy) if (true) { dc.stop(); - EXPECT_EQ(0, dc.cid()); + EXPECT_TRUE(dc.cid().empty()); srs_error_t err = dc.pull(); EXPECT_TRUE(err != srs_success); @@ -68,7 +68,7 @@ VOID TEST(AppCoroutineTest, Dummy) if (true) { dc.interrupt(); - EXPECT_EQ(0, dc.cid()); + EXPECT_TRUE(dc.cid().empty()); srs_error_t err = dc.pull(); EXPECT_TRUE(err != srs_success); @@ -88,11 +88,12 @@ public: srs_error_t err; srs_cond_t running; srs_cond_t exited; - int cid; + SrsContextId cid; // Quit without error. bool quit; public: - MockCoroutineHandler() : trd(NULL), err(srs_success), cid(0), quit(false) { + MockCoroutineHandler() : trd(NULL), err(srs_success), quit(false) { + cid.set_value("0"); running = srs_cond_new(); exited = srs_cond_new(); } @@ -128,12 +129,12 @@ VOID TEST(AppCoroutineTest, StartStop) MockCoroutineHandler ch; SrsSTCoroutine sc("test", &ch); ch.trd = ≻ - EXPECT_EQ(0, sc.cid()); + EXPECT_TRUE(sc.cid().empty()); // Thread stop after created. sc.stop(); - EXPECT_EQ(0, sc.cid()); + EXPECT_TRUE(sc.cid().empty()); srs_error_t err = sc.pull(); EXPECT_TRUE(srs_success != err); @@ -151,13 +152,13 @@ VOID TEST(AppCoroutineTest, StartStop) MockCoroutineHandler ch; SrsSTCoroutine sc("test", &ch); ch.trd = ≻ - EXPECT_EQ(0, sc.cid()); + EXPECT_TRUE(sc.cid().empty()); EXPECT_TRUE(srs_success == sc.start()); EXPECT_TRUE(srs_success == sc.pull()); srs_cond_timedwait(ch.running, 100 * SRS_UTIME_MILLISECONDS); - EXPECT_TRUE(sc.cid() > 0); + EXPECT_TRUE(!sc.cid().empty()); // Thread stop after started. sc.stop(); @@ -178,7 +179,7 @@ VOID TEST(AppCoroutineTest, StartStop) MockCoroutineHandler ch; SrsSTCoroutine sc("test", &ch); ch.trd = ≻ - EXPECT_EQ(0, sc.cid()); + EXPECT_TRUE(sc.cid().empty()); EXPECT_TRUE(srs_success == sc.start()); EXPECT_TRUE(srs_success == sc.pull()); @@ -220,16 +221,17 @@ VOID TEST(AppCoroutineTest, Cycle) if (true) { MockCoroutineHandler ch; - SrsSTCoroutine sc("test", &ch, 250); + SrsContextId cid; + SrsSTCoroutine sc("test", &ch, cid.set_value("250")); ch.trd = ≻ - EXPECT_EQ(250, sc.cid()); + EXPECT_TRUE(!sc.cid().compare(cid)); EXPECT_TRUE(srs_success == sc.start()); EXPECT_TRUE(srs_success == sc.pull()); // After running, the cid in cycle should equal to the thread. srs_cond_timedwait(ch.running, 100 * SRS_UTIME_MILLISECONDS); - EXPECT_EQ(250, ch.cid); + EXPECT_TRUE(!ch.cid.compare(cid)); } if (true) { diff --git a/trunk/src/utest/srs_utest_config.hpp b/trunk/src/utest/srs_utest_config.hpp index b3355691c..fc07fb97a 100644 --- a/trunk/src/utest/srs_utest_config.hpp +++ b/trunk/src/utest/srs_utest_config.hpp @@ -35,7 +35,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define _MIN_OK_CONF "listen 1935; " -class MockSrsConfigBuffer : public _srs_internal::SrsConfigBuffer +class MockSrsConfigBuffer : public srs_internal::SrsConfigBuffer { public: MockSrsConfigBuffer(std::string buf); diff --git a/trunk/src/utest/srs_utest_core.cpp b/trunk/src/utest/srs_utest_core.cpp index 2dce41d36..40ed33243 100644 --- a/trunk/src/utest/srs_utest_core.cpp +++ b/trunk/src/utest/srs_utest_core.cpp @@ -41,25 +41,25 @@ VOID TEST(CoreAutoFreeTest, Free) VOID TEST(CoreMacroseTest, Check) { -#ifndef SRS_AUTO_BUILD_TS +#ifndef SRS_BUILD_TS EXPECT_TRUE(false); #endif -#ifndef SRS_AUTO_BUILD_DATE +#ifndef SRS_BUILD_DATE EXPECT_TRUE(false); #endif -#ifndef SRS_AUTO_UNAME +#ifndef SRS_UNAME EXPECT_TRUE(false); #endif -#ifndef SRS_AUTO_USER_CONFIGURE +#ifndef SRS_USER_CONFIGURE EXPECT_TRUE(false); #endif -#ifndef SRS_AUTO_CONFIGURE +#ifndef SRS_CONFIGURE EXPECT_TRUE(false); #endif -#ifndef SRS_AUTO_PREFIX +#ifndef SRS_PREFIX EXPECT_TRUE(false); #endif -#ifndef SRS_AUTO_CONSTRIBUTORS +#ifndef SRS_CONSTRIBUTORS EXPECT_TRUE(false); #endif } diff --git a/trunk/src/utest/srs_utest_http.cpp b/trunk/src/utest/srs_utest_http.cpp index 60f91a04a..c5dfea841 100644 --- a/trunk/src/utest/srs_utest_http.cpp +++ b/trunk/src/utest/srs_utest_http.cpp @@ -34,6 +34,7 @@ using namespace std; #include #include #include +#include class MockMSegmentsReader : public ISrsReader { @@ -357,7 +358,7 @@ VOID TEST(ProtocolHTTPTest, ChunkSmallBuffer) io.append(mock_http_response2(200, "0d\r\n")); io.append("Hello, world!\r\n"); - SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE, false)); + SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE)); ISrsHttpMessage* msg = NULL; HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &msg)); char buf[32]; ssize_t nread = 0; @@ -381,7 +382,7 @@ VOID TEST(ProtocolHTTPTest, ChunkSmallBuffer) io.append(mock_http_response2(200, "0d\r\n")); io.append("Hello, world!\r\n0\r\n\r\n"); - SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE, false)); + SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE)); ISrsHttpMessage* msg = NULL; HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &msg)); char buf[32]; ssize_t nread = 0; @@ -408,7 +409,7 @@ VOID TEST(ProtocolHTTPTest, ChunkSmallBuffer) io.append(mock_http_response2(200, "0d\r\n")); io.append("Hello, world!\r\n0\r\n\r\n"); - SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE, false)); + SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE)); ISrsHttpMessage* msg = NULL; HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &msg)); char buf[32]; ssize_t nread = 0; @@ -435,7 +436,7 @@ VOID TEST(ProtocolHTTPTest, ClientSmallBuffer) io.append(" world!"); io.append("\r\n0\r\n\r\n"); - SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE, false)); + SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE)); ISrsHttpMessage* msg = NULL; HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &msg)); char buf[32]; ssize_t nread = 0; @@ -460,7 +461,7 @@ VOID TEST(ProtocolHTTPTest, ClientSmallBuffer) io.append(mock_http_response2(200, "0d\r\n")); io.append("Hello, world!\r\n0\r\n\r\n"); - SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE, false)); + SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE)); ISrsHttpMessage* msg = NULL; HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &msg)); char buf[32]; ssize_t nread = 0; @@ -477,7 +478,7 @@ VOID TEST(ProtocolHTTPTest, ClientSmallBuffer) // If buffer is smaller than chunk, we could read N times to get the whole chunk. if (true) { MockBufferIO io; io.append(mock_http_response2(200, "0d\r\nHello, world!\r\n0\r\n\r\n")); - SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE, false)); + SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE)); ISrsHttpMessage* msg = NULL; HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &msg)); char buf[32]; ssize_t nread = 0; @@ -512,7 +513,7 @@ VOID TEST(ProtocolHTTPTest, ClientRequest) // Normal case, with chunked encoding. if (true) { MockBufferIO io; io.append(mock_http_response2(200, "0d\r\nHello, world!\r\n0\r\n\r\n")); - SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE, false)); + SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE)); ISrsHttpMessage* msg = NULL; HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &msg)); string res; HELPER_ASSERT_SUCCESS(msg->body_read_all(res)); EXPECT_EQ(200, msg->status_code()); @@ -521,7 +522,7 @@ VOID TEST(ProtocolHTTPTest, ClientRequest) } if (true) { MockBufferIO io; io.append(mock_http_response2(200, "6\r\nHello!\r\n0\r\n\r\n")); - SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE, false)); + SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE)); ISrsHttpMessage* msg = NULL; HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &msg)); string res; HELPER_ASSERT_SUCCESS(msg->body_read_all(res)); EXPECT_EQ(200, msg->status_code()); @@ -532,7 +533,7 @@ VOID TEST(ProtocolHTTPTest, ClientRequest) // Normal case, with specified content-length. if (true) { MockBufferIO io; io.append(mock_http_response(200, "Hello, world!")); - SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE, false)); + SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE)); ISrsHttpMessage* msg = NULL; HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &msg)); string res; HELPER_ASSERT_SUCCESS(msg->body_read_all(res)); EXPECT_EQ(200, msg->status_code()); @@ -991,7 +992,7 @@ VOID TEST(ProtocolHTTPTest, HTTPServerMuxerCORS) MockResponseWriter w; SrsHttpMessage r(NULL, NULL); - r.set_basic(HTTP_POST, 200, -1); + r.set_basic(HTTP_REQUEST, HTTP_POST, 200, -1); HELPER_ASSERT_SUCCESS(r.set_url("/index.html", false)); SrsHttpCorsMux cs; @@ -1011,7 +1012,7 @@ VOID TEST(ProtocolHTTPTest, HTTPServerMuxerCORS) MockResponseWriter w; SrsHttpMessage r(NULL, NULL); - r.set_basic(HTTP_OPTIONS, 200, -1); + r.set_basic(HTTP_REQUEST, HTTP_OPTIONS, 200, -1); HELPER_ASSERT_SUCCESS(r.set_url("/index.html", false)); SrsHttpCorsMux cs; @@ -1031,7 +1032,7 @@ VOID TEST(ProtocolHTTPTest, HTTPServerMuxerCORS) MockResponseWriter w; SrsHttpMessage r(NULL, NULL); - r.set_basic(HTTP_POST, 200, -1); + r.set_basic(HTTP_REQUEST, HTTP_POST, 200, -1); HELPER_ASSERT_SUCCESS(r.set_url("/index.html", false)); SrsHttpCorsMux cs; @@ -1051,7 +1052,7 @@ VOID TEST(ProtocolHTTPTest, HTTPServerMuxerCORS) MockResponseWriter w; SrsHttpMessage r(NULL, NULL); - r.set_basic(HTTP_OPTIONS, 200, -1); + r.set_basic(HTTP_REQUEST, HTTP_OPTIONS, 200, -1); HELPER_ASSERT_SUCCESS(r.set_url("/index.html", false)); SrsHttpCorsMux cs; @@ -1417,7 +1418,7 @@ VOID TEST(ProtocolHTTPTest, HTTPMessageParser) r.in_bytes.push_back("\r\n"); SrsHttpParser p; - HELPER_ASSERT_SUCCESS(p.initialize(HTTP_REQUEST, false)); + HELPER_ASSERT_SUCCESS(p.initialize(HTTP_REQUEST)); ISrsHttpMessage* msg = NULL; HELPER_ASSERT_SUCCESS(p.parse_message(&r, &msg)); @@ -1435,7 +1436,7 @@ VOID TEST(ProtocolHTTPTest, HTTPMessageParser) r.in_bytes.push_back("\r\n"); SrsHttpParser p; - HELPER_ASSERT_SUCCESS(p.initialize(HTTP_REQUEST, false)); + HELPER_ASSERT_SUCCESS(p.initialize(HTTP_REQUEST)); ISrsHttpMessage* msg = NULL; HELPER_ASSERT_SUCCESS(p.parse_message(&r, &msg)); @@ -1454,7 +1455,7 @@ VOID TEST(ProtocolHTTPTest, HTTPMessageParser) r.in_bytes.push_back("\r\n"); SrsHttpParser p; - HELPER_ASSERT_SUCCESS(p.initialize(HTTP_REQUEST, false)); + HELPER_ASSERT_SUCCESS(p.initialize(HTTP_REQUEST)); ISrsHttpMessage* msg = NULL; HELPER_ASSERT_SUCCESS(p.parse_message(&r, &msg)); @@ -1475,7 +1476,7 @@ VOID TEST(ProtocolHTTPTest, HTTPMessageParser) r.in_bytes.push_back("\r\n"); SrsHttpParser p; - HELPER_ASSERT_SUCCESS(p.initialize(HTTP_REQUEST, false)); + HELPER_ASSERT_SUCCESS(p.initialize(HTTP_REQUEST)); ISrsHttpMessage* msg = NULL; HELPER_ASSERT_SUCCESS(p.parse_message(&r, &msg)); @@ -1497,7 +1498,7 @@ VOID TEST(ProtocolHTTPTest, HTTPMessageParser) r.in_bytes.push_back("\r\n"); SrsHttpParser p; - HELPER_ASSERT_SUCCESS(p.initialize(HTTP_REQUEST, false)); + HELPER_ASSERT_SUCCESS(p.initialize(HTTP_REQUEST)); ISrsHttpMessage* msg = NULL; HELPER_ASSERT_SUCCESS(p.parse_message(&r, &msg)); @@ -1515,7 +1516,7 @@ VOID TEST(ProtocolHTTPTest, HTTPMessageParser) r.in_bytes.push_back("\r\n"); SrsHttpParser p; - HELPER_ASSERT_SUCCESS(p.initialize(HTTP_REQUEST, false)); + HELPER_ASSERT_SUCCESS(p.initialize(HTTP_REQUEST)); ISrsHttpMessage* msg = NULL; HELPER_ASSERT_SUCCESS(p.parse_message(&r, &msg)); @@ -1662,7 +1663,7 @@ VOID TEST(ProtocolHTTPTest, HTTPMessageUpdate) SrsHttpMessage m; m.set_header(&h, false); - m.set_basic(HTTP_POST, 200, 2); + m.set_basic(HTTP_REQUEST, HTTP_POST, 200, 2); EXPECT_EQ(10, m.content_length()); } @@ -1672,7 +1673,7 @@ VOID TEST(ProtocolHTTPTest, HTTPMessageUpdate) h.set("Content-Length", "10"); SrsHttpMessage m; - m.set_basic(HTTP_POST, 200, 2); + m.set_basic(HTTP_REQUEST, HTTP_POST, 200, 2); m.set_header(&h, false); EXPECT_EQ(10, m.content_length()); } @@ -1706,7 +1707,7 @@ VOID TEST(ProtocolHTTPTest, HTTPMessageUpdate) if (true) { SrsHttpMessage m; - m.set_basic(HTTP_POST, 200, 0); + m.set_basic(HTTP_REQUEST, HTTP_POST, 200, 0); EXPECT_EQ(0, m.content_length()); } @@ -1767,3 +1768,260 @@ VOID TEST(ProtocolHTTPTest, GetOriginalIP) EXPECT_STREQ("", srs_get_original_ip(&m).c_str()); } } + +VOID TEST(ProtocolHTTPTest, ParsingLargeMessages) +{ + srs_error_t err; + + uint8_t data[] = { + 0x50, 0x4f, 0x53, 0x54, 0x20, 0x2f, 0x6a, 0x61, 0x6e, 0x75, 0x73, 0x2f, 0x32, 0x35, 0x38, 0x30, 0x34, 0x39, 0x32, 0x38, 0x30, 0x38, 0x36, 0x34, 0x32, 0x39, 0x31, 0x31, 0x39, 0x30, 0x32, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31, 0x0d, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x3a, 0x20, 0x33, 0x33, 0x2e, 0x31, 0x38, 0x2e, 0x34, 0x2e, 0x31, 0x32, 0x33, 0x3a, 0x31, 0x39, 0x38, 0x35, 0x0d, 0x0a, 0x55, 0x73, 0x65, 0x72, 0x2d, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x47, 0x6f, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x2d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2f, 0x31, 0x2e, 0x31, 0x0d, 0x0a, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x3a, 0x20, 0x33, 0x31, 0x35, 0x0d, 0x0a, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x67, 0x7a, 0x69, 0x70, 0x0d, 0x0a, 0x0d, 0x0a, 0x7b, 0x22, 0x6a, 0x61, 0x6e, 0x75, 0x73, 0x22, 0x3a, 0x22, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x22, 0x2c, 0x22, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x22, 0x33, 0x39, 0x39, 0x62, 0x31, 0x33, 0x33, 0x63, 0x31, 0x38, 0x35, 0x62, 0x65, 0x64, 0x66, 0x66, 0x37, 0x66, 0x35, 0x39, 0x35, 0x33, 0x65, 0x38, 0x39, 0x64, 0x31, 0x61, 0x62, 0x62, 0x61, 0x35, 0x22, 0x2c, 0x22, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x69, 0x64, 0x22, 0x3a, 0x22, 0x30, 0x64, 0x69, 0x65, 0x31, 0x61, 0x6f, 0x36, 0x65, 0x35, 0x35, 0x64, 0x30, 0x72, 0x66, 0x66, 0x78, 0x6b, 0x31, 0x33, 0x79, 0x6d, 0x78, 0x66, 0x30, 0x6d, 0x30, 0x37, 0x62, 0x66, 0x6f, 0x67, 0x22, 0x2c, 0x22, 0x72, 0x70, 0x63, 0x69, 0x64, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x22, 0x3a, 0x22, 0x73, 0x69, 0x67, 0x22, 0x2c, 0x22, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x22, 0x3a, 0x22, 0x6a, 0x61, 0x6e, 0x75, 0x73, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0x2c, 0x22, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x5f, 0x69, 0x64, 0x22, 0x3a, 0x22, 0x64, 0x64, 0x30, 0x66, 0x32, 0x33, 0x31, 0x39, 0x62, 0x34, 0x37, 0x63, 0x65, 0x35, 0x38, 0x64, 0x65, 0x63, 0x65, 0x38, 0x64, 0x64, 0x34, 0x64, 0x36, 0x64, 0x65, 0x62, 0x64, 0x38, 0x65, 0x33, 0x22, 0x2c, 0x22, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x2d, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x22, 0x3a, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x22, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x2d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x6d, 0x75, 0x78, 0x22, 0x3a, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x22, 0x63, 0x61, 0x6c, 0x6c, 0x49, 0x44, 0x22, 0x3a, 0x22, 0x30, 0x62, 0x61, 0x66, 0x7a, 0x34, 0x36, 0x6a, 0x76, 0x76, 0x34, 0x61, 0x30, 0x6a, 0x34, 0x6b, 0x70, 0x35, 0x68, 0x69, 0x64, 0x6e, 0x77, 0x6a, 0x30, 0x61, 0x65, 0x31, 0x66, 0x6e, 0x70, 0x65, 0x22, 0x7d, 0x50, 0x4f, 0x53, 0x54, 0x20, 0x2f, 0x6a, 0x61, 0x6e, 0x75, 0x73, 0x2f, 0x32, 0x35, 0x38, 0x30, 0x34, 0x39, 0x32, 0x38, 0x30, 0x38, 0x36, 0x34, 0x32, 0x39, 0x31, 0x31, 0x39, 0x30, 0x32, 0x2f, 0x31, 0x31, 0x31, 0x32, 0x34, 0x36, 0x37, 0x34, 0x36, 0x33, 0x37, 0x34, 0x34, 0x39, 0x36, 0x37, 0x31, 0x32, 0x32, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31, 0x0d, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x3a, 0x20, 0x33, 0x33, 0x2e, 0x31, 0x38, 0x2e, 0x34, 0x2e, 0x31, 0x32, 0x33, 0x3a, 0x31, 0x39, 0x38, 0x35, 0x0d, 0x0a, 0x55, 0x73, 0x65, 0x72, 0x2d, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x47, 0x6f, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x2d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2f, 0x31, 0x2e, 0x31, 0x0d, 0x0a, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x3a, 0x20, 0x36, 0x38, 0x33, 0x0d, 0x0a, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x67, 0x7a, 0x69, 0x70, 0x0d, 0x0a, 0x0d, 0x0a, 0x7b, 0x22, 0x6a, 0x61, 0x6e, 0x75, 0x73, 0x22, 0x3a, 0x22, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x2c, 0x22, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x22, 0x38, 0x35, 0x63, 0x37, 0x36, 0x62, 0x62, 0x33, 0x39, 0x37, 0x34, 0x33, 0x38, 0x39, 0x36, 0x64, 0x35, 0x37, 0x61, 0x31, 0x63, 0x38, 0x36, 0x66, 0x34, 0x64, 0x30, 0x31, 0x65, 0x30, 0x30, 0x61, 0x22, 0x2c, 0x22, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x69, 0x64, 0x22, 0x3a, 0x22, 0x30, 0x64, 0x69, 0x65, 0x31, 0x61, 0x6f, 0x36, 0x65, 0x35, 0x35, 0x64, 0x30, 0x72, 0x66, 0x66, 0x78, 0x6b, 0x31, 0x33, 0x79, 0x6d, 0x78, 0x66, 0x30, 0x6d, 0x30, 0x37, 0x62, 0x66, 0x6f, 0x67, 0x22, 0x2c, 0x22, 0x72, 0x70, 0x63, 0x69, 0x64, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x22, 0x3a, 0x22, 0x73, 0x69, 0x67, 0x22, 0x2c, 0x22, 0x62, 0x6f, 0x64, 0x79, 0x22, 0x3a, 0x7b, 0x22, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x3a, 0x22, 0x6a, 0x6f, 0x69, 0x6e, 0x22, 0x2c, 0x22, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0x3a, 0x39, 0x39, 0x38, 0x31, 0x2c, 0x22, 0x70, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x22, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x22, 0x2c, 0x22, 0x66, 0x65, 0x65, 0x64, 0x22, 0x3a, 0x31, 0x32, 0x37, 0x39, 0x33, 0x36, 0x37, 0x31, 0x33, 0x33, 0x35, 0x39, 0x39, 0x35, 0x34, 0x33, 0x32, 0x39, 0x36, 0x2c, 0x22, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x22, 0x3a, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x22, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x22, 0x3a, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x22, 0x6f, 0x66, 0x66, 0x65, 0x72, 0x5f, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x22, 0x3a, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x22, 0x6f, 0x66, 0x66, 0x65, 0x72, 0x5f, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x22, 0x3a, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x22, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x6d, 0x73, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x22, 0x3a, 0x22, 0x38, 0x4f, 0x33, 0x72, 0x44, 0x46, 0x70, 0x32, 0x51, 0x57, 0x77, 0x47, 0x42, 0x43, 0x78, 0x69, 0x4a, 0x36, 0x45, 0x4e, 0x65, 0x36, 0x4f, 0x6f, 0x73, 0x4e, 0x53, 0x47, 0x51, 0x77, 0x65, 0x47, 0x32, 0x66, 0x33, 0x6d, 0x22, 0x2c, 0x22, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x22, 0x3a, 0x22, 0x62, 0x31, 0x36, 0x64, 0x32, 0x37, 0x36, 0x63, 0x2d, 0x34, 0x64, 0x61, 0x33, 0x2d, 0x34, 0x66, 0x35, 0x39, 0x2d, 0x39, 0x66, 0x37, 0x35, 0x2d, 0x35, 0x66, 0x33, 0x61, 0x31, 0x32, 0x32, 0x38, 0x66, 0x39, 0x31, 0x34, 0x22, 0x2c, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x22, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x22, 0x2c, 0x22, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x3a, 0x22, 0x45, 0x4e, 0x47, 0x49, 0x4e, 0x45, 0x5f, 0x44, 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x22, 0x7d, 0x2c, 0x7b, 0x22, 0x6d, 0x73, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x22, 0x3a, 0x22, 0x38, 0x4f, 0x33, 0x72, 0x44, 0x46, 0x70, 0x32, 0x51, 0x57, 0x77, 0x47, 0x42, 0x43, 0x78, 0x69, 0x4a, 0x36, 0x45, 0x4e, 0x65, 0x36, 0x4f, 0x6f, 0x73, 0x4e, 0x53, 0x47, 0x51, 0x77, 0x65, 0x47, 0x32, 0x66, 0x33, 0x6d, 0x22, 0x2c, 0x22, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x22, 0x3a, 0x22, 0x31, 0x30, 0x33, 0x36, 0x33, 0x61, 0x65, 0x39, 0x2d, 0x64, 0x34, 0x39, 0x37, 0x2d, 0x34, 0x33, 0x66, 0x32, 0x2d, 0x39, 0x32, 0x38, 0x38, 0x2d, 0x37, 0x61, 0x32, 0x37, 0x31, 0x65, 0x65, 0x31, 0x64, 0x63, 0x30, 0x61, 0x22, 0x2c, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x22, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x22, 0x2c, 0x22, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x3a, 0x22, 0x53, 0x44, 0x5f, 0x33, 0x36, 0x30, 0x5f, 0x36, 0x34, 0x30, 0x50, 0x5f, 0x31, 0x35, 0x22, 0x7d, 0x5d, 0x7d, 0x7d, 0x50, 0x4f, 0x53, 0x54, 0x20, 0x2f, 0x6a, 0x61, 0x6e, 0x75, 0x73, 0x2f, 0x32, 0x35, 0x38, 0x30, 0x34, 0x39, 0x32, 0x38, 0x30, 0x38, 0x36, 0x34, 0x32, 0x39, 0x31, 0x31, 0x39, 0x30, 0x32, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31, 0x0d, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x3a, 0x20, 0x33, 0x33, 0x2e, 0x31, 0x38, 0x2e, 0x34, 0x2e, 0x31, 0x32, 0x33, 0x3a, 0x31, 0x39, 0x38, 0x35, 0x0d, 0x0a, 0x55, 0x73, 0x65, 0x72, 0x2d, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x47, 0x6f, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x2d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2f, 0x31, 0x2e, 0x31, 0x0d, 0x0a, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x3a, 0x20, 0x33, 0x31, 0x35, 0x0d, 0x0a, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x67, 0x7a, 0x69, 0x70, 0x0d, 0x0a, 0x0d, 0x0a, 0x7b, 0x22, 0x6a, 0x61, 0x6e, 0x75, 0x73, 0x22, 0x3a, 0x22, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x22, 0x2c, 0x22, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x22, 0x30, 0x30, 0x39, 0x37, 0x34, 0x31, 0x65, 0x32, 0x39, 0x36, 0x61, 0x32, 0x34, 0x30, 0x61, 0x33, 0x65, 0x63, 0x61, 0x65, 0x66, 0x39, 0x64, 0x32, 0x63, 0x66, 0x62, 0x39, 0x36, 0x39, 0x36, 0x38, 0x22, 0x2c, 0x22, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x69, 0x64, 0x22, 0x3a, 0x22, 0x30, 0x6f, 0x77, 0x65, 0x67, 0x75, 0x35, 0x6f, 0x74, 0x66, 0x75, 0x30, 0x68, 0x64, 0x67, 0x6e, 0x68, 0x72, 0x6d, 0x6d, 0x76, 0x34, 0x63, 0x30, 0x79, 0x6b, 0x35, 0x67, 0x6e, 0x74, 0x38, 0x39, 0x22, 0x2c, 0x22, 0x72, 0x70, 0x63, 0x69, 0x64, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x22, 0x3a, 0x22, 0x73, 0x69, 0x67, 0x22, 0x2c, 0x22, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x22, 0x3a, 0x22, 0x6a, 0x61, 0x6e, 0x75, 0x73, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0x2c, 0x22, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x5f, 0x69, 0x64, 0x22, 0x3a, 0x22, 0x37, 0x35, 0x65, 0x36, 0x64, 0x37, 0x38, 0x32, 0x39, 0x61, 0x30, 0x32, 0x30, 0x63, 0x36, 0x30, 0x37, 0x61, 0x30, 0x38, 0x34, 0x37, 0x63, 0x32, 0x63, 0x62, 0x36, 0x37, 0x38, 0x65, 0x34, 0x35, 0x22, 0x2c, 0x22, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x2d, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x22, 0x3a, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x22, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x2d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x6d, 0x75, 0x78, 0x22, 0x3a, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x22, 0x63, 0x61, 0x6c, 0x6c, 0x49, 0x44, 0x22, 0x3a, 0x22, 0x30, 0x34, 0x31, 0x67, 0x67, 0x6d, 0x77, 0x6f, 0x69, 0x30, 0x30, 0x68, 0x30, 0x71, 0x31, 0x6c, 0x72, 0x67, 0x79, 0x69, 0x30, 0x77, 0x6e, 0x30, 0x39, 0x75, 0x39, 0x37, 0x65, 0x66, 0x6f, 0x70, 0x22, 0x7d, 0x50, 0x4f, 0x53, 0x54, 0x20, 0x2f, 0x6a, 0x61, 0x6e, 0x75, 0x73, 0x2f, 0x32, 0x35, 0x38, 0x30, 0x34, 0x39, 0x32, 0x38, 0x30, 0x38, 0x36, 0x34, 0x32, 0x39, 0x31, 0x31, 0x39, 0x30, 0x32, 0x2f, 0x34, 0x32, 0x36, 0x38, 0x30, 0x33, 0x37, 0x39, 0x33, 0x30, 0x36, 0x34, 0x39, 0x33, 0x33, 0x36, 0x30, 0x38, 0x36, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31, 0x0d, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x3a, 0x20, 0x33, 0x33, 0x2e, 0x31, 0x38, 0x2e, 0x34, 0x2e, 0x31, 0x32, 0x33, 0x3a, 0x31, 0x39, 0x38, 0x35, 0x0d, 0x0a, 0x55, 0x73, 0x65, 0x72, 0x2d, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x47, 0x6f, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x2d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2f, 0x31, 0x2e, 0x31, 0x0d, 0x0a, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x3a, 0x20, 0x32, 0x35, 0x35, 0x0d, 0x0a, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x67, 0x7a, 0x69, 0x70, 0x0d, 0x0a, 0x0d, 0x0a, 0x7b, 0x22, 0x6a, 0x61, 0x6e, 0x75, 0x73, 0x22, 0x3a, 0x22, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x2c, 0x22, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x22, 0x34, 0x35, 0x33, 0x61, 0x63, 0x63, 0x30, 0x33, 0x62, 0x34, 0x34, 0x32, 0x34, 0x61, 0x33, 0x37, 0x35, 0x37, 0x31, 0x61, 0x30, 0x36, 0x38, 0x65, 0x64, 0x39, 0x35, 0x39, 0x37, 0x36, 0x37, 0x34, 0x22, 0x2c, 0x22, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x69, 0x64, 0x22, 0x3a, 0x22, 0x30, 0x6f, 0x77, 0x65, 0x67, 0x75, 0x35, 0x6f, 0x74, 0x66, 0x75, 0x30, 0x68, 0x64, 0x67, 0x6e, 0x68, 0x72, 0x6d, 0x6d, 0x76, 0x34, 0x63, 0x30, 0x79, 0x6b, 0x35, 0x67, 0x6e, 0x74, 0x38, 0x39, 0x22, 0x2c, 0x22, 0x72, 0x70, 0x63, 0x69, 0x64, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x22, 0x3a, 0x22, 0x73, 0x69, 0x67, 0x22, 0x2c, 0x22, 0x62, 0x6f, 0x64, 0x79, 0x22, 0x3a, 0x7b, 0x22, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x3a, 0x22, 0x6a, 0x6f, 0x69, 0x6e, 0x22, 0x2c, 0x22, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0x3a, 0x39, 0x39, 0x38, 0x31, 0x2c, 0x22, 0x70, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x22, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, 0x72, 0x22, 0x2c, 0x22, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x22, 0x3a, 0x22, 0x39, 0x61, 0x37, 0x62, 0x34, 0x22, 0x2c, 0x22, 0x66, 0x65, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x22, 0x3a, 0x31, 0x32, 0x37, 0x39, 0x33, 0x36, 0x37, 0x35, 0x33, 0x31, 0x39, 0x30, 0x33, 0x32, 0x33, 0x34, 0x30, 0x34, 0x38, 0x7d, 0x7d, 0x50, 0x4f, 0x53, 0x54, 0x20, 0x2f, 0x6a, 0x61, 0x6e, 0x75, 0x73, 0x2f, 0x32, 0x35, 0x38, 0x30, 0x34, 0x39, 0x32, 0x38, 0x30, 0x38, 0x36, 0x34, 0x32, 0x39, 0x31, 0x31, 0x39, 0x30, 0x32, 0x2f, 0x34, 0x32, 0x36, 0x38, 0x30, 0x33, 0x37, 0x39, 0x33, 0x30, 0x36, 0x34, 0x39, 0x33, 0x33, 0x36, 0x30, 0x38, 0x36, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31, 0x0d, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x3a, 0x20, 0x33, 0x33, 0x2e, 0x31, 0x38, 0x2e, 0x34, 0x2e, 0x31, 0x32, 0x33, 0x3a, 0x31, 0x39, 0x38, 0x35, 0x0d, 0x0a, 0x55, 0x73, 0x65, 0x72, 0x2d, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x47, 0x6f, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x2d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2f, 0x31, 0x2e, 0x31, 0x0d, 0x0a, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x3a, 0x20, 0x36, 0x33, 0x31, 0x37, 0x0d, 0x0a, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x67, 0x7a, 0x69, 0x70, 0x0d, 0x0a, 0x0d, 0x0a, 0x7b, 0x22, 0x6a, 0x61, 0x6e, 0x75, 0x73, 0x22, 0x3a, 0x22, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x2c, 0x22, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x22, 0x61, 0x39, 0x65, 0x37, 0x64, 0x32, 0x31, 0x62, 0x36, 0x62, 0x33, 0x63, 0x61, 0x63, 0x66, 0x64, 0x33, 0x65, 0x36, 0x35, 0x32, 0x62, 0x33, 0x39, 0x39, 0x62, 0x64, 0x34, 0x34, 0x64, 0x30, 0x35, 0x22, 0x2c, 0x22, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x69, 0x64, 0x22, 0x3a, 0x22, 0x30, 0x6f, 0x77, 0x65, 0x67, 0x75, 0x35, 0x6f, 0x74, 0x66, 0x75, 0x30, 0x68, 0x64, 0x67, 0x6e, 0x68, 0x72, 0x6d, 0x6d, 0x76, 0x34, 0x63, 0x30, 0x79, 0x6b, 0x35, 0x67, 0x6e, 0x74, 0x38, 0x39, 0x22, 0x2c, 0x22, 0x72, 0x70, 0x63, 0x69, 0x64, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x22, 0x3a, 0x22, 0x73, 0x69, 0x67, 0x22, 0x2c, 0x22, 0x62, 0x6f, 0x64, 0x79, 0x22, 0x3a, 0x7b, 0x22, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x3a, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x22, 0x2c, 0x22, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x22, 0x3a, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x22, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x22, 0x3a, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x22, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x22, 0x3a, 0x5b, 0x7b, 0x22, 0x6d, 0x73, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x22, 0x3a, 0x22, 0x76, 0x72, 0x79, 0x33, 0x6b, 0x78, 0x38, 0x4f, 0x44, 0x50, 0x77, 0x48, 0x45, 0x55, 0x71, 0x74, 0x67, 0x34, 0x6a, 0x51, 0x65, 0x37, 0x6d, 0x63, 0x4b, 0x5a, 0x58, 0x6d, 0x34, 0x41, 0x6b, 0x79, 0x54, 0x4f, 0x49, 0x42, 0x22, 0x2c, 0x22, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x22, 0x3a, 0x22, 0x66, 0x31, 0x31, 0x34, 0x31, 0x62, 0x61, 0x31, 0x2d, 0x37, 0x62, 0x36, 0x65, 0x2d, 0x34, 0x30, 0x30, 0x30, 0x2d, 0x38, 0x64, 0x38, 0x37, 0x2d, 0x38, 0x39, + 0x34, 0x63, 0x63, 0x62, 0x36, 0x63, 0x63, 0x66, 0x37, 0x38, 0x22, 0x2c, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x22, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x22, 0x2c, 0x22, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x3a, 0x22, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x22, 0x2c, 0x22, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x3a, 0x22, 0x45, 0x4e, 0x47, 0x49, 0x4e, 0x45, 0x5f, 0x44, 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x22, 0x7d, 0x2c, 0x7b, 0x22, 0x6d, 0x73, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x22, 0x3a, 0x22, 0x76, 0x72, 0x79, 0x33, 0x6b, 0x78, 0x38, 0x4f, 0x44, 0x50, 0x77, 0x48, 0x45, 0x55, 0x71, 0x74, 0x67, 0x34, 0x6a, 0x51, 0x65, 0x37, 0x6d, 0x63, 0x4b, 0x5a, 0x58, 0x6d, 0x34, 0x41, 0x6b, 0x79, 0x54, 0x4f, 0x49, 0x42, 0x22, 0x2c, 0x22, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x22, 0x3a, 0x22, 0x37, 0x63, 0x37, 0x31, 0x37, 0x66, 0x36, 0x39, 0x2d, 0x62, 0x63, 0x33, 0x35, 0x2d, 0x34, 0x30, 0x38, 0x39, 0x2d, 0x38, 0x34, 0x63, 0x65, 0x2d, 0x34, 0x31, 0x65, 0x61, 0x38, 0x65, 0x31, 0x63, 0x34, 0x33, 0x39, 0x31, 0x22, 0x2c, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x22, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x22, 0x2c, 0x22, 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x22, 0x3a, 0x31, 0x2c, 0x22, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x22, 0x3a, 0x31, 0x2c, 0x22, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x3a, 0x22, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x22, 0x2c, 0x22, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x3a, 0x22, 0x53, 0x44, 0x5f, 0x33, 0x36, 0x30, 0x5f, 0x36, 0x34, 0x30, 0x50, 0x5f, 0x31, 0x35, 0x22, 0x2c, 0x22, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x3a, 0x22, 0x22, 0x7d, 0x5d, 0x7d, 0x2c, 0x22, 0x6a, 0x73, 0x65, 0x70, 0x22, 0x3a, 0x7b, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x22, 0x6f, 0x66, 0x66, 0x65, 0x72, 0x22, 0x2c, 0x22, 0x73, 0x64, 0x70, 0x22, 0x3a, 0x22, 0x76, 0x3d, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x6f, 0x3d, 0x2d, 0x20, 0x39, 0x34, 0x30, 0x30, 0x30, 0x38, 0x34, 0x31, 0x31, 0x37, 0x30, 0x30, 0x34, 0x34, 0x37, 0x33, 0x30, 0x37, 0x20, 0x32, 0x20, 0x49, 0x4e, 0x20, 0x49, 0x50, 0x34, 0x20, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x5c, 0x72, 0x5c, 0x6e, 0x73, 0x3d, 0x2d, 0x5c, 0x72, 0x5c, 0x6e, 0x74, 0x3d, 0x30, 0x20, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x3a, 0x42, 0x55, 0x4e, 0x44, 0x4c, 0x45, 0x20, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x20, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x6d, 0x73, 0x69, 0x64, 0x2d, 0x73, 0x65, 0x6d, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x3a, 0x20, 0x57, 0x4d, 0x53, 0x20, 0x76, 0x72, 0x79, 0x33, 0x6b, 0x78, 0x38, 0x4f, 0x44, 0x50, 0x77, 0x48, 0x45, 0x55, 0x71, 0x74, 0x67, 0x34, 0x6a, 0x51, 0x65, 0x37, 0x6d, 0x63, 0x4b, 0x5a, 0x58, 0x6d, 0x34, 0x41, 0x6b, 0x79, 0x54, 0x4f, 0x49, 0x42, 0x5c, 0x72, 0x5c, 0x6e, 0x6d, 0x3d, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x20, 0x39, 0x20, 0x55, 0x44, 0x50, 0x2f, 0x54, 0x4c, 0x53, 0x2f, 0x52, 0x54, 0x50, 0x2f, 0x53, 0x41, 0x56, 0x50, 0x46, 0x20, 0x31, 0x31, 0x31, 0x20, 0x31, 0x30, 0x33, 0x20, 0x31, 0x30, 0x34, 0x20, 0x39, 0x20, 0x30, 0x20, 0x38, 0x20, 0x31, 0x30, 0x36, 0x20, 0x31, 0x30, 0x35, 0x20, 0x31, 0x33, 0x20, 0x31, 0x31, 0x30, 0x20, 0x31, 0x31, 0x32, 0x20, 0x31, 0x31, 0x33, 0x20, 0x31, 0x32, 0x36, 0x5c, 0x72, 0x5c, 0x6e, 0x63, 0x3d, 0x49, 0x4e, 0x20, 0x49, 0x50, 0x34, 0x20, 0x30, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x3a, 0x39, 0x20, 0x49, 0x4e, 0x20, 0x49, 0x50, 0x34, 0x20, 0x30, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x69, 0x63, 0x65, 0x2d, 0x75, 0x66, 0x72, 0x61, 0x67, 0x3a, 0x67, 0x4b, 0x2f, 0x62, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x69, 0x63, 0x65, 0x2d, 0x70, 0x77, 0x64, 0x3a, 0x36, 0x49, 0x42, 0x68, 0x6c, 0x50, 0x6e, 0x61, 0x38, 0x73, 0x7a, 0x32, 0x59, 0x76, 0x64, 0x6a, 0x6e, 0x33, 0x6a, 0x34, 0x49, 0x57, 0x73, 0x4d, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x69, 0x63, 0x65, 0x2d, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x3a, 0x74, 0x72, 0x69, 0x63, 0x6b, 0x6c, 0x65, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x73, 0x68, 0x61, 0x2d, 0x32, 0x35, 0x36, 0x20, 0x33, 0x43, 0x3a, 0x32, 0x43, 0x3a, 0x37, 0x42, 0x3a, 0x45, 0x39, 0x3a, 0x33, 0x45, 0x3a, 0x38, 0x44, 0x3a, 0x32, 0x39, 0x3a, 0x44, 0x38, 0x3a, 0x44, 0x42, 0x3a, 0x39, 0x36, 0x3a, 0x38, 0x46, 0x3a, 0x41, 0x41, 0x3a, 0x42, 0x31, 0x3a, 0x34, 0x32, 0x3a, 0x35, 0x32, 0x3a, 0x34, 0x46, 0x3a, 0x37, 0x36, 0x3a, 0x37, 0x31, 0x3a, 0x43, 0x44, 0x3a, 0x34, 0x42, 0x3a, 0x38, 0x31, 0x3a, 0x34, 0x36, 0x3a, 0x39, 0x35, 0x3a, 0x33, 0x36, 0x3a, 0x33, 0x38, 0x3a, 0x33, 0x45, 0x3a, 0x36, 0x37, 0x3a, 0x45, 0x41, 0x3a, 0x41, 0x43, 0x3a, 0x41, 0x42, 0x3a, 0x37, 0x46, 0x3a, 0x31, 0x35, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x73, 0x65, 0x74, 0x75, 0x70, 0x3a, 0x61, 0x63, 0x74, 0x70, 0x61, 0x73, 0x73, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x6d, 0x69, 0x64, 0x3a, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x65, 0x78, 0x74, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x20, 0x75, 0x72, 0x6e, 0x3a, 0x69, 0x65, 0x74, 0x66, 0x3a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x3a, 0x72, 0x74, 0x70, 0x2d, 0x68, 0x64, 0x72, 0x65, 0x78, 0x74, 0x3a, 0x73, 0x73, 0x72, 0x63, 0x2d, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x2d, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x65, 0x78, 0x74, 0x6d, 0x61, 0x70, 0x3a, 0x32, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x65, 0x62, 0x72, 0x74, 0x63, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x72, 0x74, 0x70, 0x2d, 0x68, 0x64, 0x72, 0x65, 0x78, 0x74, 0x2f, 0x61, 0x62, 0x73, 0x2d, 0x73, 0x65, 0x6e, 0x64, 0x2d, 0x74, 0x69, 0x6d, 0x65, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x65, 0x78, 0x74, 0x6d, 0x61, 0x70, 0x3a, 0x33, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x65, 0x74, 0x66, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x69, 0x64, 0x2f, 0x64, 0x72, 0x61, 0x66, 0x74, 0x2d, 0x68, 0x6f, 0x6c, 0x6d, 0x65, 0x72, 0x2d, 0x72, 0x6d, 0x63, 0x61, 0x74, 0x2d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2d, 0x77, 0x69, 0x64, 0x65, 0x2d, 0x63, 0x63, 0x2d, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2d, 0x30, 0x31, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x73, 0x65, 0x6e, 0x64, 0x6f, 0x6e, 0x6c, 0x79, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x6d, 0x75, 0x78, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x31, 0x31, 0x20, 0x6f, 0x70, 0x75, 0x73, 0x2f, 0x34, 0x38, 0x30, 0x30, 0x30, 0x2f, 0x32, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x31, 0x31, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2d, 0x63, 0x63, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x66, 0x6d, 0x74, 0x70, 0x3a, 0x31, 0x31, 0x31, 0x20, 0x6d, 0x69, 0x6e, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x3d, 0x31, 0x30, 0x3b, 0x75, 0x73, 0x65, 0x69, 0x6e, 0x62, 0x61, 0x6e, 0x64, 0x66, 0x65, 0x63, 0x3d, 0x31, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x30, 0x33, 0x20, 0x49, 0x53, 0x41, 0x43, 0x2f, 0x31, 0x36, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x30, 0x34, 0x20, 0x49, 0x53, 0x41, 0x43, 0x2f, 0x33, 0x32, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x39, 0x20, 0x47, 0x37, 0x32, 0x32, 0x2f, 0x38, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x30, 0x20, 0x50, 0x43, 0x4d, 0x55, 0x2f, 0x38, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x38, 0x20, 0x50, 0x43, 0x4d, 0x41, 0x2f, 0x38, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x30, 0x36, 0x20, 0x43, 0x4e, 0x2f, 0x33, 0x32, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x30, 0x35, 0x20, 0x43, 0x4e, 0x2f, 0x31, 0x36, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x33, 0x20, 0x43, 0x4e, 0x2f, 0x38, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x31, 0x30, 0x20, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x2d, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x34, 0x38, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x31, 0x32, 0x20, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x2d, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x33, 0x32, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x31, 0x33, 0x20, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x2d, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x31, 0x36, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x32, 0x36, 0x20, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x2d, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f, 0x38, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x73, 0x73, 0x72, 0x63, 0x3a, 0x33, 0x34, 0x39, 0x34, 0x31, 0x34, 0x31, 0x34, 0x31, 0x30, 0x20, 0x63, 0x6e, 0x61, 0x6d, 0x65, 0x3a, 0x33, 0x4c, 0x75, 0x6d, 0x55, 0x62, 0x6a, 0x6c, 0x70, 0x50, 0x4d, 0x79, 0x77, 0x45, 0x31, 0x52, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x73, 0x73, 0x72, 0x63, 0x3a, 0x33, 0x34, 0x39, 0x34, 0x31, 0x34, 0x31, 0x34, 0x31, 0x30, 0x20, 0x6d, 0x73, 0x69, 0x64, 0x3a, 0x76, 0x72, 0x79, 0x33, 0x6b, 0x78, 0x38, 0x4f, 0x44, 0x50, 0x77, 0x48, 0x45, 0x55, 0x71, 0x74, 0x67, 0x34, 0x6a, 0x51, 0x65, 0x37, 0x6d, 0x63, 0x4b, 0x5a, 0x58, 0x6d, 0x34, 0x41, 0x6b, 0x79, 0x54, 0x4f, 0x49, 0x42, 0x20, 0x66, 0x31, 0x31, 0x34, 0x31, 0x62, 0x61, 0x31, 0x2d, 0x37, 0x62, 0x36, 0x65, 0x2d, 0x34, 0x30, 0x30, 0x30, 0x2d, 0x38, 0x64, 0x38, 0x37, 0x2d, 0x38, 0x39, 0x34, 0x63, 0x63, 0x62, 0x36, 0x63, 0x63, 0x66, 0x37, 0x38, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x73, 0x73, 0x72, 0x63, 0x3a, 0x33, 0x34, 0x39, 0x34, 0x31, 0x34, 0x31, 0x34, 0x31, 0x30, 0x20, 0x6d, 0x73, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x76, 0x72, 0x79, 0x33, 0x6b, 0x78, 0x38, 0x4f, 0x44, 0x50, 0x77, 0x48, 0x45, 0x55, 0x71, 0x74, 0x67, 0x34, 0x6a, 0x51, 0x65, 0x37, 0x6d, 0x63, 0x4b, 0x5a, 0x58, 0x6d, 0x34, 0x41, 0x6b, 0x79, 0x54, 0x4f, 0x49, 0x42, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x73, 0x73, 0x72, 0x63, 0x3a, 0x33, 0x34, 0x39, 0x34, 0x31, 0x34, 0x31, 0x34, 0x31, 0x30, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x66, 0x31, 0x31, 0x34, 0x31, 0x62, 0x61, 0x31, 0x2d, 0x37, 0x62, 0x36, 0x65, 0x2d, 0x34, 0x30, 0x30, 0x30, 0x2d, 0x38, 0x64, 0x38, 0x37, 0x2d, 0x38, 0x39, 0x34, 0x63, 0x63, 0x62, 0x36, 0x63, 0x63, 0x66, 0x37, 0x38, 0x5c, 0x72, 0x5c, 0x6e, 0x6d, 0x3d, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x20, 0x39, 0x20, 0x55, 0x44, 0x50, 0x2f, 0x54, 0x4c, 0x53, 0x2f, 0x52, 0x54, 0x50, 0x2f, 0x53, 0x41, 0x56, 0x50, 0x46, 0x20, 0x39, 0x36, 0x20, 0x39, 0x37, 0x20, 0x39, 0x38, 0x20, 0x39, 0x39, 0x20, 0x31, 0x30, 0x30, 0x20, 0x31, 0x30, 0x31, 0x20, 0x31, 0x30, 0x32, 0x20, 0x31, 0x32, 0x32, 0x20, 0x31, 0x32, 0x37, 0x20, 0x31, 0x32, 0x31, 0x20, 0x31, 0x32, 0x35, 0x20, 0x31, 0x30, 0x37, 0x20, 0x31, 0x30, 0x38, 0x20, 0x31, 0x30, 0x39, 0x20, 0x31, 0x32, 0x34, 0x20, 0x31, 0x32, 0x30, 0x20, 0x31, 0x32, 0x33, 0x20, 0x31, 0x31, 0x39, 0x20, 0x31, 0x31, 0x34, 0x20, 0x31, 0x31, 0x35, 0x20, 0x31, 0x31, 0x36, 0x5c, 0x72, 0x5c, 0x6e, 0x63, 0x3d, 0x49, 0x4e, 0x20, 0x49, 0x50, 0x34, 0x20, 0x30, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x3a, 0x39, 0x20, 0x49, 0x4e, 0x20, 0x49, 0x50, 0x34, 0x20, 0x30, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x69, 0x63, 0x65, 0x2d, 0x75, 0x66, 0x72, 0x61, 0x67, 0x3a, 0x67, 0x4b, 0x2f, 0x62, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x69, 0x63, 0x65, 0x2d, 0x70, 0x77, 0x64, 0x3a, 0x36, 0x49, 0x42, 0x68, 0x6c, 0x50, 0x6e, 0x61, 0x38, 0x73, 0x7a, 0x32, 0x59, 0x76, 0x64, 0x6a, 0x6e, 0x33, 0x6a, 0x34, 0x49, 0x57, 0x73, 0x4d, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x69, 0x63, 0x65, 0x2d, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x3a, 0x74, 0x72, 0x69, 0x63, 0x6b, 0x6c, 0x65, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x3a, 0x73, 0x68, 0x61, 0x2d, 0x32, 0x35, 0x36, 0x20, 0x33, 0x43, 0x3a, 0x32, 0x43, 0x3a, 0x37, 0x42, 0x3a, 0x45, 0x39, 0x3a, 0x33, 0x45, 0x3a, 0x38, 0x44, 0x3a, 0x32, 0x39, 0x3a, 0x44, 0x38, 0x3a, 0x44, 0x42, 0x3a, 0x39, 0x36, 0x3a, 0x38, 0x46, 0x3a, 0x41, 0x41, 0x3a, 0x42, 0x31, 0x3a, 0x34, 0x32, 0x3a, 0x35, 0x32, 0x3a, 0x34, 0x46, 0x3a, 0x37, 0x36, 0x3a, 0x37, 0x31, 0x3a, 0x43, 0x44, 0x3a, 0x34, 0x42, 0x3a, 0x38, 0x31, 0x3a, 0x34, 0x36, 0x3a, 0x39, 0x35, 0x3a, 0x33, 0x36, 0x3a, 0x33, 0x38, 0x3a, 0x33, 0x45, 0x3a, 0x36, 0x37, 0x3a, 0x45, 0x41, 0x3a, 0x41, 0x43, 0x3a, 0x41, 0x42, 0x3a, 0x37, 0x46, 0x3a, 0x31, 0x35, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x73, 0x65, 0x74, 0x75, 0x70, 0x3a, 0x61, 0x63, 0x74, 0x70, 0x61, 0x73, 0x73, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x6d, 0x69, 0x64, 0x3a, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x65, 0x78, 0x74, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x34, 0x20, 0x75, 0x72, 0x6e, 0x3a, 0x69, 0x65, 0x74, 0x66, 0x3a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x3a, 0x72, 0x74, 0x70, 0x2d, 0x68, 0x64, 0x72, 0x65, 0x78, 0x74, 0x3a, 0x74, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x65, 0x78, 0x74, 0x6d, 0x61, 0x70, 0x3a, 0x32, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x65, 0x62, 0x72, 0x74, 0x63, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x72, 0x74, 0x70, 0x2d, 0x68, 0x64, 0x72, 0x65, 0x78, 0x74, 0x2f, 0x61, 0x62, 0x73, 0x2d, 0x73, 0x65, 0x6e, 0x64, 0x2d, 0x74, 0x69, 0x6d, 0x65, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x65, 0x78, 0x74, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x33, 0x20, 0x75, 0x72, 0x6e, 0x3a, 0x33, 0x67, 0x70, 0x70, 0x3a, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2d, 0x6f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x65, 0x78, 0x74, 0x6d, 0x61, 0x70, 0x3a, 0x33, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x65, 0x74, 0x66, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x69, 0x64, 0x2f, 0x64, 0x72, 0x61, 0x66, 0x74, 0x2d, 0x68, 0x6f, 0x6c, 0x6d, 0x65, 0x72, 0x2d, 0x72, 0x6d, 0x63, 0x61, 0x74, 0x2d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2d, 0x77, 0x69, 0x64, 0x65, 0x2d, 0x63, 0x63, 0x2d, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2d, 0x30, 0x31, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x65, 0x78, 0x74, 0x6d, 0x61, 0x70, 0x3a, 0x35, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x65, 0x62, 0x72, 0x74, 0x63, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x72, 0x74, 0x70, 0x2d, 0x68, 0x64, 0x72, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x2d, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x65, 0x78, 0x74, 0x6d, 0x61, 0x70, 0x3a, 0x36, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x65, 0x62, 0x72, 0x74, 0x63, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x72, 0x74, 0x70, 0x2d, 0x68, 0x64, 0x72, 0x65, 0x78, 0x74, 0x2f, 0x76, + 0x69, 0x64, 0x65, 0x6f, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x65, 0x78, 0x74, 0x6d, 0x61, 0x70, 0x3a, 0x37, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x65, 0x62, 0x72, 0x74, 0x63, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x72, 0x74, 0x70, 0x2d, 0x68, 0x64, 0x72, 0x65, 0x78, 0x74, 0x2f, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2d, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x65, 0x78, 0x74, 0x6d, 0x61, 0x70, 0x3a, 0x38, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x74, 0x6f, 0x6f, 0x6c, 0x73, 0x2e, 0x69, 0x65, 0x74, 0x66, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x2f, 0x64, 0x72, 0x61, 0x66, 0x74, 0x2d, 0x69, 0x65, 0x74, 0x66, 0x2d, 0x61, 0x76, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x2d, 0x30, 0x37, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x65, 0x78, 0x74, 0x6d, 0x61, 0x70, 0x3a, 0x39, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x65, 0x62, 0x72, 0x74, 0x63, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x72, 0x74, 0x70, 0x2d, 0x68, 0x64, 0x72, 0x65, 0x78, 0x74, 0x2f, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x73, 0x65, 0x6e, 0x64, 0x6f, 0x6e, 0x6c, 0x79, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x6d, 0x75, 0x78, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x72, 0x73, 0x69, 0x7a, 0x65, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x39, 0x36, 0x20, 0x56, 0x50, 0x38, 0x2f, 0x39, 0x30, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x39, 0x36, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x2d, 0x72, 0x65, 0x6d, 0x62, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x39, 0x36, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2d, 0x63, 0x63, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x39, 0x36, 0x20, 0x63, 0x63, 0x6d, 0x20, 0x66, 0x69, 0x72, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x39, 0x36, 0x20, 0x6e, 0x61, 0x63, 0x6b, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x39, 0x36, 0x20, 0x6e, 0x61, 0x63, 0x6b, 0x20, 0x70, 0x6c, 0x69, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x39, 0x37, 0x20, 0x72, 0x74, 0x78, 0x2f, 0x39, 0x30, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x66, 0x6d, 0x74, 0x70, 0x3a, 0x39, 0x37, 0x20, 0x61, 0x70, 0x74, 0x3d, 0x39, 0x36, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x39, 0x38, 0x20, 0x56, 0x50, 0x39, 0x2f, 0x39, 0x30, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x39, 0x38, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x2d, 0x72, 0x65, 0x6d, 0x62, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x39, 0x38, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2d, 0x63, 0x63, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x39, 0x38, 0x20, 0x63, 0x63, 0x6d, 0x20, 0x66, 0x69, 0x72, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x39, 0x38, 0x20, 0x6e, 0x61, 0x63, 0x6b, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x39, 0x38, 0x20, 0x6e, 0x61, 0x63, 0x6b, 0x20, 0x70, 0x6c, 0x69, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x66, 0x6d, 0x74, 0x70, 0x3a, 0x39, 0x38, 0x20, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2d, 0x69, 0x64, 0x3d, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x39, 0x39, 0x20, 0x72, 0x74, 0x78, 0x2f, 0x39, 0x30, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x66, 0x6d, 0x74, 0x70, 0x3a, 0x39, 0x39, 0x20, 0x61, 0x70, 0x74, 0x3d, 0x39, 0x38, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x30, 0x30, 0x20, 0x56, 0x50, 0x39, 0x2f, 0x39, 0x30, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x30, 0x30, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x2d, 0x72, 0x65, 0x6d, 0x62, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x30, 0x30, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2d, 0x63, 0x63, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x30, 0x30, 0x20, 0x63, 0x63, 0x6d, 0x20, 0x66, 0x69, 0x72, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x30, 0x30, 0x20, 0x6e, 0x61, 0x63, 0x6b, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x30, 0x30, 0x20, 0x6e, 0x61, 0x63, 0x6b, 0x20, 0x70, 0x6c, 0x69, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x66, 0x6d, 0x74, 0x70, 0x3a, 0x31, 0x30, 0x30, 0x20, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2d, 0x69, 0x64, 0x3d, 0x32, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x30, 0x31, 0x20, 0x72, 0x74, 0x78, 0x2f, 0x39, 0x30, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x66, 0x6d, 0x74, 0x70, 0x3a, 0x31, 0x30, 0x31, 0x20, 0x61, 0x70, 0x74, 0x3d, 0x31, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x30, 0x32, 0x20, 0x48, 0x32, 0x36, 0x34, 0x2f, 0x39, 0x30, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x30, 0x32, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x2d, 0x72, 0x65, 0x6d, 0x62, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x30, 0x32, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2d, 0x63, 0x63, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x30, 0x32, 0x20, 0x63, 0x63, 0x6d, 0x20, 0x66, 0x69, 0x72, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x30, 0x32, 0x20, 0x6e, 0x61, 0x63, 0x6b, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x30, 0x32, 0x20, 0x6e, 0x61, 0x63, 0x6b, 0x20, 0x70, 0x6c, 0x69, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x66, 0x6d, 0x74, 0x70, 0x3a, 0x31, 0x30, 0x32, 0x20, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x2d, 0x61, 0x73, 0x79, 0x6d, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x3d, 0x31, 0x3b, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x6d, 0x6f, 0x64, 0x65, 0x3d, 0x31, 0x3b, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2d, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x2d, 0x69, 0x64, 0x3d, 0x34, 0x32, 0x30, 0x30, 0x31, 0x66, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x32, 0x32, 0x20, 0x72, 0x74, 0x78, 0x2f, 0x39, 0x30, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x66, 0x6d, 0x74, 0x70, 0x3a, 0x31, 0x32, 0x32, 0x20, 0x61, 0x70, 0x74, 0x3d, 0x31, 0x30, 0x32, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x32, 0x37, 0x20, 0x48, 0x32, 0x36, 0x34, 0x2f, 0x39, 0x30, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x32, 0x37, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x2d, 0x72, 0x65, 0x6d, 0x62, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x32, 0x37, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2d, 0x63, 0x63, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x32, 0x37, 0x20, 0x63, 0x63, 0x6d, 0x20, 0x66, 0x69, 0x72, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x32, 0x37, 0x20, 0x6e, 0x61, 0x63, 0x6b, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x32, 0x37, 0x20, 0x6e, 0x61, 0x63, 0x6b, 0x20, 0x70, 0x6c, 0x69, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x66, 0x6d, 0x74, 0x70, 0x3a, 0x31, 0x32, 0x37, 0x20, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x2d, 0x61, 0x73, 0x79, 0x6d, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x3d, 0x31, 0x3b, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x6d, 0x6f, 0x64, 0x65, 0x3d, 0x30, 0x3b, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2d, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x2d, 0x69, 0x64, 0x3d, 0x34, 0x32, 0x30, 0x30, 0x31, 0x66, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x32, 0x31, 0x20, 0x72, 0x74, 0x78, 0x2f, 0x39, 0x30, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x66, 0x6d, 0x74, 0x70, 0x3a, 0x31, 0x32, 0x31, 0x20, 0x61, 0x70, 0x74, 0x3d, 0x31, 0x32, 0x37, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x32, 0x35, 0x20, 0x48, 0x32, 0x36, 0x34, 0x2f, 0x39, 0x30, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x32, 0x35, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x2d, 0x72, 0x65, 0x6d, 0x62, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x32, 0x35, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2d, 0x63, 0x63, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x32, 0x35, 0x20, 0x63, 0x63, 0x6d, 0x20, 0x66, 0x69, 0x72, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x32, 0x35, 0x20, 0x6e, 0x61, 0x63, 0x6b, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x32, 0x35, 0x20, 0x6e, 0x61, 0x63, 0x6b, 0x20, 0x70, 0x6c, 0x69, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x66, 0x6d, 0x74, 0x70, 0x3a, 0x31, 0x32, 0x35, 0x20, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x2d, 0x61, 0x73, 0x79, 0x6d, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x3d, 0x31, 0x3b, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x6d, 0x6f, 0x64, 0x65, 0x3d, 0x31, 0x3b, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2d, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x2d, 0x69, 0x64, 0x3d, 0x34, 0x32, 0x65, 0x30, 0x31, 0x66, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x30, 0x37, 0x20, 0x72, 0x74, 0x78, 0x2f, 0x39, 0x30, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x66, 0x6d, 0x74, 0x70, 0x3a, 0x31, 0x30, 0x37, 0x20, 0x61, 0x70, 0x74, 0x3d, 0x31, 0x32, 0x35, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x30, 0x38, 0x20, 0x48, 0x32, 0x36, 0x34, 0x2f, 0x39, 0x30, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x30, 0x38, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x2d, 0x72, 0x65, 0x6d, 0x62, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x30, 0x38, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2d, 0x63, 0x63, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x30, 0x38, 0x20, 0x63, 0x63, 0x6d, 0x20, 0x66, 0x69, 0x72, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x30, 0x38, 0x20, 0x6e, 0x61, 0x63, 0x6b, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x30, 0x38, 0x20, 0x6e, 0x61, 0x63, 0x6b, 0x20, 0x70, 0x6c, 0x69, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x66, 0x6d, 0x74, 0x70, 0x3a, 0x31, 0x30, 0x38, 0x20, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x2d, 0x61, 0x73, 0x79, 0x6d, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x3d, 0x31, 0x3b, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x6d, 0x6f, 0x64, 0x65, 0x3d, 0x30, 0x3b, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2d, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x2d, 0x69, 0x64, 0x3d, 0x34, 0x32, 0x65, 0x30, 0x31, 0x66, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x30, 0x39, 0x20, 0x72, 0x74, 0x78, 0x2f, 0x39, 0x30, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x66, 0x6d, 0x74, 0x70, 0x3a, 0x31, 0x30, 0x39, 0x20, 0x61, 0x70, 0x74, 0x3d, 0x31, 0x30, 0x38, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x32, 0x34, 0x20, 0x48, 0x32, 0x36, 0x34, 0x2f, 0x39, 0x30, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x32, 0x34, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x2d, 0x72, 0x65, 0x6d, 0x62, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x32, 0x34, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2d, 0x63, 0x63, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x32, 0x34, 0x20, 0x63, 0x63, 0x6d, 0x20, 0x66, 0x69, 0x72, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x32, 0x34, 0x20, 0x6e, 0x61, 0x63, 0x6b, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x32, 0x34, 0x20, 0x6e, 0x61, 0x63, 0x6b, 0x20, 0x70, 0x6c, 0x69, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x66, 0x6d, 0x74, 0x70, 0x3a, 0x31, 0x32, 0x34, 0x20, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x2d, 0x61, 0x73, 0x79, 0x6d, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x3d, 0x31, 0x3b, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x6d, 0x6f, 0x64, 0x65, 0x3d, 0x31, 0x3b, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2d, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x2d, 0x69, 0x64, 0x3d, 0x34, 0x64, 0x30, 0x30, 0x33, 0x32, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x32, 0x30, 0x20, 0x72, 0x74, 0x78, 0x2f, 0x39, 0x30, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x66, 0x6d, 0x74, 0x70, 0x3a, 0x31, 0x32, 0x30, 0x20, 0x61, 0x70, 0x74, 0x3d, 0x31, 0x32, 0x34, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x32, 0x33, 0x20, 0x48, 0x32, 0x36, 0x34, 0x2f, 0x39, 0x30, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x32, 0x33, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x2d, 0x72, 0x65, 0x6d, 0x62, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x32, 0x33, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2d, 0x63, 0x63, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x32, 0x33, 0x20, 0x63, 0x63, 0x6d, 0x20, 0x66, 0x69, 0x72, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x32, 0x33, 0x20, 0x6e, 0x61, 0x63, 0x6b, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x63, 0x70, 0x2d, 0x66, 0x62, 0x3a, 0x31, 0x32, 0x33, 0x20, 0x6e, 0x61, 0x63, 0x6b, 0x20, 0x70, 0x6c, 0x69, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x66, 0x6d, 0x74, 0x70, 0x3a, 0x31, 0x32, 0x33, 0x20, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x2d, 0x61, 0x73, 0x79, 0x6d, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x3d, 0x31, 0x3b, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x6d, 0x6f, 0x64, 0x65, 0x3d, 0x31, 0x3b, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2d, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x2d, 0x69, 0x64, 0x3d, 0x36, 0x34, 0x30, 0x30, 0x33, 0x32, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x31, 0x39, 0x20, 0x72, 0x74, 0x78, 0x2f, 0x39, 0x30, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, + 0x66, 0x6d, 0x74, 0x70, 0x3a, 0x31, 0x31, 0x39, 0x20, 0x61, 0x70, 0x74, 0x3d, 0x31, 0x32, 0x33, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x31, 0x34, 0x20, 0x72, 0x65, 0x64, 0x2f, 0x39, 0x30, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x31, 0x35, 0x20, 0x72, 0x74, 0x78, 0x2f, 0x39, 0x30, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x66, 0x6d, 0x74, 0x70, 0x3a, 0x31, 0x31, 0x35, 0x20, 0x61, 0x70, 0x74, 0x3d, 0x31, 0x31, 0x34, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x72, 0x74, 0x70, 0x6d, 0x61, 0x70, 0x3a, 0x31, 0x31, 0x36, 0x20, 0x75, 0x6c, 0x70, 0x66, 0x65, 0x63, 0x2f, 0x39, 0x30, 0x30, 0x30, 0x30, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x73, 0x73, 0x72, 0x63, 0x2d, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x3a, 0x46, 0x49, 0x44, 0x20, 0x31, 0x34, 0x34, 0x33, 0x36, 0x34, 0x38, 0x35, 0x32, 0x33, 0x20, 0x31, 0x30, 0x39, 0x31, 0x38, 0x39, 0x32, 0x33, 0x39, 0x39, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x73, 0x73, 0x72, 0x63, 0x3a, 0x31, 0x34, 0x34, 0x33, 0x36, 0x34, 0x38, 0x35, 0x32, 0x33, 0x20, 0x63, 0x6e, 0x61, 0x6d, 0x65, 0x3a, 0x33, 0x4c, 0x75, 0x6d, 0x55, 0x62, 0x6a, 0x6c, 0x70, 0x50, 0x4d, 0x79, 0x77, 0x45, 0x31, 0x52, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x73, 0x73, 0x72, 0x63, 0x3a, 0x31, 0x34, 0x34, 0x33, 0x36, 0x34, 0x38, 0x35, 0x32, 0x33, 0x20, 0x6d, 0x73, 0x69, 0x64, 0x3a, 0x76, 0x72, 0x79, 0x33, 0x6b, 0x78, 0x38, 0x4f, 0x44, 0x50, 0x77, 0x48, 0x45, 0x55, 0x71, 0x74, 0x67, 0x34, 0x6a, 0x51, 0x65, 0x37, 0x6d, 0x63, 0x4b, 0x5a, 0x58, 0x6d, 0x34, 0x41, 0x6b, 0x79, 0x54, 0x4f, 0x49, 0x42, 0x20, 0x37, 0x63, 0x37, 0x31, 0x37, 0x66, 0x36, 0x39, 0x2d, 0x62, 0x63, 0x33, 0x35, 0x2d, 0x34, 0x30, 0x38, 0x39, 0x2d, 0x38, 0x34, 0x63, 0x65, 0x2d, 0x34, 0x31, 0x65, 0x61, 0x38, 0x65, 0x31, 0x63, 0x34, 0x33, 0x39, 0x31, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x73, 0x73, 0x72, 0x63, 0x3a, 0x31, 0x34, 0x34, 0x33, 0x36, 0x34, 0x38, 0x35, 0x32, 0x33, 0x20, 0x6d, 0x73, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x76, 0x72, 0x79, 0x33, 0x6b, 0x78, 0x38, 0x4f, 0x44, 0x50, 0x77, 0x48, 0x45, 0x55, 0x71, 0x74, 0x67, 0x34, 0x6a, 0x51, 0x65, 0x37, 0x6d, 0x63, 0x4b, 0x5a, 0x58, 0x6d, 0x34, 0x41, 0x6b, 0x79, 0x54, 0x4f, 0x49, 0x42, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x73, 0x73, 0x72, 0x63, 0x3a, 0x31, 0x34, 0x34, 0x33, 0x36, 0x34, 0x38, 0x35, 0x32, 0x33, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x37, 0x63, 0x37, 0x31, 0x37, 0x66, 0x36, 0x39, 0x2d, 0x62, 0x63, 0x33, 0x35, 0x2d, 0x34, 0x30, 0x38, 0x39, 0x2d, 0x38, 0x34, 0x63, 0x65, 0x2d, 0x34, 0x31, 0x65, 0x61, 0x38, 0x65, 0x31, 0x63, 0x34, 0x33, 0x39, 0x31, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x73, 0x73, 0x72, 0x63, 0x3a, 0x31, 0x30, 0x39, 0x31, 0x38, 0x39, 0x32, 0x33, 0x39, 0x39, 0x20, 0x63, 0x6e, 0x61, 0x6d, 0x65, 0x3a, 0x33, 0x4c, 0x75, 0x6d, 0x55, 0x62, 0x6a, 0x6c, 0x70, 0x50, 0x4d, 0x79, 0x77, 0x45, 0x31, 0x52, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x73, 0x73, 0x72, 0x63, 0x3a, 0x31, 0x30, 0x39, 0x31, 0x38, 0x39, 0x32, 0x33, 0x39, 0x39, 0x20, 0x6d, 0x73, 0x69, 0x64, 0x3a, 0x76, 0x72, 0x79, 0x33, 0x6b, 0x78, 0x38, 0x4f, 0x44, 0x50, 0x77, 0x48, 0x45, 0x55, 0x71, 0x74, 0x67, 0x34, 0x6a, 0x51, 0x65, 0x37, 0x6d, 0x63, 0x4b, 0x5a, 0x58, 0x6d, 0x34, 0x41, 0x6b, 0x79, 0x54, 0x4f, 0x49, 0x42, 0x20, 0x37, 0x63, 0x37, 0x31, 0x37, 0x66, 0x36, 0x39, 0x2d, 0x62, 0x63, 0x33, 0x35, 0x2d, 0x34, 0x30, 0x38, 0x39, 0x2d, 0x38, 0x34, 0x63, 0x65, 0x2d, 0x34, 0x31, 0x65, 0x61, 0x38, 0x65, 0x31, 0x63, 0x34, 0x33, 0x39, 0x31, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x73, 0x73, 0x72, 0x63, 0x3a, 0x31, 0x30, 0x39, 0x31, 0x38, 0x39, 0x32, 0x33, 0x39, 0x39, 0x20, 0x6d, 0x73, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x76, 0x72, 0x79, 0x33, 0x6b, 0x78, 0x38, 0x4f, 0x44, 0x50, 0x77, 0x48, 0x45, 0x55, 0x71, 0x74, 0x67, 0x34, 0x6a, 0x51, 0x65, 0x37, 0x6d, 0x63, 0x4b, 0x5a, 0x58, 0x6d, 0x34, 0x41, 0x6b, 0x79, 0x54, 0x4f, 0x49, 0x42, 0x5c, 0x72, 0x5c, 0x6e, 0x61, 0x3d, 0x73, 0x73, 0x72, 0x63, 0x3a, 0x31, 0x30, 0x39, 0x31, 0x38, 0x39, 0x32, 0x33, 0x39, 0x39, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x37, 0x63, 0x37, 0x31, 0x37, 0x66, 0x36, 0x39, 0x2d, 0x62, 0x63, 0x33, 0x35, 0x2d, 0x34, 0x30, 0x38, 0x39, 0x2d, 0x38, 0x34, 0x63, 0x65, 0x2d, 0x34, 0x31, 0x65, 0x61, 0x38, 0x65, 0x31, 0x63, 0x34, 0x33, 0x39, 0x31, 0x5c, 0x72, 0x5c, 0x6e, 0x22, 0x7d, 0x7d, 0x50, 0x4f, 0x53, 0x54, 0x20, 0x2f, 0x6a, 0x61, 0x6e, 0x75, 0x73, 0x2f, 0x32, 0x35, 0x38, 0x30, 0x34, 0x39, 0x32, 0x38, 0x30, 0x38, 0x36, 0x34, 0x32, 0x39, 0x31, 0x31, 0x39, 0x30, 0x32, 0x2f, 0x34, 0x32, 0x36, 0x38, 0x30, 0x33, 0x37, 0x39, 0x33, 0x30, 0x36, 0x34, 0x39, 0x33, 0x33, 0x36, 0x30, 0x38, 0x36, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31, 0x0d, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x3a, 0x20, 0x33, 0x33, 0x2e, 0x31, 0x38, 0x2e, 0x34, 0x2e, 0x31, 0x32, 0x33, 0x3a, 0x31, 0x39, 0x38, 0x35, 0x0d, 0x0a, 0x55, 0x73, 0x65, 0x72, 0x2d, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x3a, 0x20, 0x47, 0x6f, 0x2d, 0x68, 0x74, 0x74, 0x70, 0x2d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2f, 0x31, 0x2e, 0x31, 0x0d, 0x0a, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x3a, 0x20, 0x33, 0x35, 0x34, 0x0d, 0x0a, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x67, 0x7a, 0x69, 0x70, 0x0d, 0x0a, 0x0d, 0x0a, 0x7b, 0x22, 0x6a, 0x61, 0x6e, 0x75, 0x73, 0x22, 0x3a, 0x22, 0x74, 0x72, 0x69, 0x63, 0x6b, 0x6c, 0x65, 0x22, 0x2c, 0x22, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x22, 0x62, 0x33, 0x31, 0x38, 0x65, 0x30, 0x32, 0x65, 0x32, 0x35, 0x34, 0x39, 0x35, 0x33, 0x30, 0x33, 0x61, 0x66, 0x39, 0x37, 0x66, 0x64, 0x36, 0x38, 0x38, 0x39, 0x35, 0x65, 0x35, 0x30, 0x62, 0x64, 0x22, 0x2c, 0x22, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x69, 0x64, 0x22, 0x3a, 0x22, 0x30, 0x6f, 0x77, 0x65, 0x67, 0x75, 0x35, 0x6f, 0x74, 0x66, 0x75, 0x30, 0x68, 0x64, 0x67, 0x6e, 0x68, 0x72, 0x6d, 0x6d, 0x76, 0x34, 0x63, 0x30, 0x79, 0x6b, 0x35, 0x67, 0x6e, 0x74, 0x38, 0x39, 0x22, 0x2c, 0x22, 0x72, 0x70, 0x63, 0x69, 0x64, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x22, 0x3a, 0x22, 0x73, 0x69, 0x67, 0x22, 0x2c, 0x22, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x22, 0x3a, 0x7b, 0x22, 0x73, 0x64, 0x70, 0x4d, 0x4c, 0x69, 0x6e, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x3a, 0x30, 0x2c, 0x22, 0x73, 0x64, 0x70, 0x4d, 0x69, 0x64, 0x22, 0x3a, 0x22, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x22, 0x2c, 0x22, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x22, 0x3a, 0x22, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x3a, 0x32, 0x35, 0x31, 0x30, 0x36, 0x35, 0x35, 0x31, 0x30, 0x39, 0x20, 0x31, 0x20, 0x75, 0x64, 0x70, 0x20, 0x34, 0x31, 0x38, 0x38, 0x35, 0x36, 0x39, 0x35, 0x20, 0x31, 0x31, 0x2e, 0x31, 0x33, 0x33, 0x2e, 0x31, 0x37, 0x32, 0x2e, 0x32, 0x34, 0x31, 0x20, 0x31, 0x30, 0x38, 0x30, 0x31, 0x20, 0x74, 0x79, 0x70, 0x20, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x72, 0x61, 0x64, 0x64, 0x72, 0x20, 0x30, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x30, 0x20, 0x72, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x30, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x30, 0x20, 0x75, 0x66, 0x72, 0x61, 0x67, 0x20, 0x67, 0x4b, 0x2f, 0x62, 0x20, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2d, 0x69, 0x64, 0x20, 0x31, 0x20, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2d, 0x63, 0x6f, 0x73, 0x74, 0x20, 0x31, 0x30, 0x22, 0x7d, 0x7d + }; + uint8_t* p = data; MockBufferIO io; SrsHttpParser hp; + HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_REQUEST)); + + if (true) { + // First message, 144 header + 315 body. + io.append(p, 144 + 315); p += 144 + 315; + ISrsHttpMessage* msg = NULL; SrsAutoFree(ISrsHttpMessage, msg); HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &msg)); + EXPECT_EQ(315, msg->content_length()); + + string body; HELPER_ASSERT_SUCCESS(msg->body_read_all(body)); + EXPECT_EQ(315, (int)body.length()); + } + + if (true) { + // Second message, 164 header + 683 body. + io.append(p, 164 + 683); p += 164 + 683; + ISrsHttpMessage* msg = NULL; SrsAutoFree(ISrsHttpMessage, msg); HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &msg)); + EXPECT_EQ(683, msg->content_length()); + + string body; HELPER_ASSERT_SUCCESS(msg->body_read_all(body)); + EXPECT_EQ(683, (int)body.length()); + } + + if (true) { + // Thrid message, 144 header + 315 body. + io.append(p, 144 + 315); p += 144 + 315; + ISrsHttpMessage* msg = NULL; SrsAutoFree(ISrsHttpMessage, msg); HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &msg)); + EXPECT_EQ(315, msg->content_length()); + + string body; HELPER_ASSERT_SUCCESS(msg->body_read_all(body)); + EXPECT_EQ(315, (int)body.length()); + } + + if (true) { + // Forth message, 164 header + 255 body. + io.append(p, 164 + 255); p += 164 + 255; + ISrsHttpMessage* msg = NULL; SrsAutoFree(ISrsHttpMessage, msg); HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &msg)); + EXPECT_EQ(255, msg->content_length()); + + string body; HELPER_ASSERT_SUCCESS(msg->body_read_all(body)); + EXPECT_EQ(255, (int)body.length()); + } + + if (true) { + // Fifth message, 165 header + 6317 body. + MockMSegmentsReader r; + // First, we got 4k bytes, then got the left bytes, to simulate the network read. + r.in_bytes.push_back(string((char*)p, 4096)); p += 4096; + r.in_bytes.push_back(string((char*)p, 165 + 6317 - 4096)); p += 165 + 6317 - 4096; + ISrsHttpMessage* msg = NULL; SrsAutoFree(ISrsHttpMessage, msg); HELPER_ASSERT_SUCCESS(hp.parse_message(&r, &msg)); + EXPECT_EQ(6317, msg->content_length()); + + string body; HELPER_ASSERT_SUCCESS(msg->body_read_all(body)); + EXPECT_EQ(6317, (int)body.length()); + } + + if (true) { + // Last message, 164 header + 354 body. + io.append(p, 164 + 354); p += 164 + 354; + EXPECT_EQ((int)sizeof(data), p - data); + + ISrsHttpMessage* msg = NULL; SrsAutoFree(ISrsHttpMessage, msg); HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &msg)); + EXPECT_EQ(354, msg->content_length()); + + string body; HELPER_ASSERT_SUCCESS(msg->body_read_all(body)); + EXPECT_EQ(354, (int)body.length()); + } +} + +VOID TEST(ProtocolHTTPTest, ParseUri) +{ + srs_error_t err; + + if (true) { + SrsHttpUri uri; + HELPER_EXPECT_SUCCESS(uri.initialize("http://ossrs.net/index.html")); + EXPECT_STREQ("http", uri.get_schema().c_str()); + EXPECT_STREQ("ossrs.net", uri.get_host().c_str()); + EXPECT_EQ(80, uri.get_port()); + EXPECT_STREQ("/index.html", uri.get_path().c_str()); + } + + if (true) { + SrsHttpUri uri; + HELPER_EXPECT_SUCCESS(uri.initialize("rtmp://ossrs.net/live/livestream")); + EXPECT_STREQ("rtmp", uri.get_schema().c_str()); + EXPECT_STREQ("ossrs.net", uri.get_host().c_str()); + EXPECT_EQ(1935, uri.get_port()); + EXPECT_STREQ("/live/livestream", uri.get_path().c_str()); + } + + if (true) { + SrsHttpUri uri; + HELPER_EXPECT_SUCCESS(uri.initialize("http://user:passwd@ossrs.net/index.html")); + EXPECT_STREQ("http", uri.get_schema().c_str()); + EXPECT_STREQ("ossrs.net", uri.get_host().c_str()); + EXPECT_STREQ("user", uri.username().c_str()); + EXPECT_STREQ("passwd", uri.password().c_str()); + EXPECT_EQ(80, uri.get_port()); + EXPECT_STREQ("/index.html", uri.get_path().c_str()); + } + + if (true) { + SrsHttpUri uri; + HELPER_EXPECT_SUCCESS(uri.initialize("https://user:passwd@ossrs.net/index.html")); + EXPECT_STREQ("https", uri.get_schema().c_str()); + EXPECT_STREQ("ossrs.net", uri.get_host().c_str()); + EXPECT_STREQ("user", uri.username().c_str()); + EXPECT_STREQ("passwd", uri.password().c_str()); + EXPECT_EQ(443, uri.get_port()); + EXPECT_STREQ("/index.html", uri.get_path().c_str()); + } + + if (true) { + SrsHttpUri uri; + HELPER_EXPECT_SUCCESS(uri.initialize("redis://user:passwd@ossrs.net/0")); + EXPECT_STREQ("redis", uri.get_schema().c_str()); + EXPECT_STREQ("ossrs.net", uri.get_host().c_str()); + EXPECT_STREQ("user", uri.username().c_str()); + EXPECT_STREQ("passwd", uri.password().c_str()); + EXPECT_EQ(6379, uri.get_port()); + EXPECT_STREQ("/0", uri.get_path().c_str()); + } +} + +VOID TEST(ProtocolHTTPTest, ParseHttpUri) +{ + srs_error_t err = srs_success; + + std::string url_str = "http://me.com"; + url_str += "?xxxIdxxx=xxxxxCPrzDUzxxxxx&Action=DescribeXXX"; + url_str += "&XXXUid=000140000&AppId=xxxx2rxxx&Caller=rtc&Format=JSON&QueryAppId=xxx9xrxxx"; + url_str += "&Region=cn-hangzhou&RequestId=xxx6i4bmxxx74kxxx" ; + url_str += "&SignatureMethod=HMAC-SHA1&SignatureNonce=xxxk1q0t42v37ske24j329xxxx"; + url_str += "&SignatureVersion=1.0&Timestamp=2020-11-02T09:10:28Z&Version=2018-01-11"; + url_str += "&Signature=xxxGXBBGnoR4vHsTcUxxx+tRM"; + SrsHttpUri uri; + HELPER_ASSERT_SUCCESS(uri.initialize(url_str)); + EXPECT_STREQ("http", uri.get_schema().c_str()); + EXPECT_EQ(80, uri.get_port()); + EXPECT_STREQ("me.com", uri.get_host().c_str()); + EXPECT_STREQ("", uri.get_path().c_str()); + EXPECT_STREQ("xxxxxCPrzDUzxxxxx", uri.get_query_by_key("xxxIdxxx").c_str()); + EXPECT_STREQ("DescribeXXX", uri.get_query_by_key("Action").c_str()); + EXPECT_STREQ("000140000", uri.get_query_by_key("XXXUid").c_str()); + EXPECT_STREQ("rtc", uri.get_query_by_key("Caller").c_str()); + EXPECT_STREQ("xxxx2rxxx", uri.get_query_by_key("AppId").c_str()); + EXPECT_STREQ("JSON", uri.get_query_by_key("Format").c_str()); + EXPECT_STREQ("xxx9xrxxx", uri.get_query_by_key("QueryAppId").c_str()); + EXPECT_STREQ("cn-hangzhou", uri.get_query_by_key("Region").c_str()); + EXPECT_STREQ("xxx6i4bmxxx74kxxx", uri.get_query_by_key("RequestId").c_str()); + EXPECT_STREQ("HMAC-SHA1", uri.get_query_by_key("SignatureMethod").c_str()); + EXPECT_STREQ("xxxk1q0t42v37ske24j329xxxx", uri.get_query_by_key("SignatureNonce").c_str()); + EXPECT_STREQ("1.0", uri.get_query_by_key("SignatureVersion").c_str()); + EXPECT_STREQ("2020-11-02T09:10:28Z", uri.get_query_by_key("Timestamp").c_str()); + EXPECT_STREQ("2018-01-11", uri.get_query_by_key("Version").c_str()); + EXPECT_STREQ("xxxGXBBGnoR4vHsTcUxxx+tRM", uri.get_query_by_key("Signature").c_str()); + +} + +struct EscapeTest { + string in; + string out; + srs_error_t err; +}; + +VOID TEST(ProtocolHTTPTest, QueryEscape) +{ + srs_error_t err = srs_success; + //Test query unescapse + if(true) { + struct EscapeTest unescape[] = { + {"", "", srs_success}, + {"abc", "abc", srs_success}, + {"1%41", "1A", srs_success}, + {"1%41%42%43", "1ABC", srs_success}, + {"%4a", "J", srs_success}, + {"%6F", "o", srs_success}, + {"%"/* not enough characters after %*/, "", srs_error_new(ERROR_HTTP_URL_UNESCAPE, "%")}, + {"%a"/* not enough characters after % */, "", srs_error_new(ERROR_HTTP_URL_UNESCAPE, "%a")}, + {"%1" /* not enough characters after % */, "", srs_error_new(ERROR_HTTP_URL_UNESCAPE, "%1")}, + {"123%45%6"/* not enough characters after % */, "", srs_error_new(ERROR_HTTP_URL_UNESCAPE, "%6")}, + {"%zzzzz"/* invalid hex digits */, "", srs_error_new(ERROR_HTTP_URL_UNESCAPE, "%zz")}, + {"a+b", "a b", srs_success}, + {"a%20b", "a b", srs_success} + }; + + for(int i = 0; i < (int)(sizeof(unescape) / sizeof(struct EscapeTest)); ++i) { + struct EscapeTest& d = unescape[i]; + string value; + if(srs_success == d.err) { + HELPER_ASSERT_SUCCESS(SrsHttpUri::query_unescape(d.in, value)); + EXPECT_STREQ(d.out.c_str(), value.c_str()); + } else { + HELPER_ASSERT_FAILED(SrsHttpUri::query_unescape(d.in, value)); + } + } + } + + //Test Escape + if(true) { + struct EscapeTest escape[] = { + {"", "", srs_success}, + {"abc", "abc", srs_success}, + {"one two", "one+two", srs_success}, + {"10%", "10%25", srs_success}, + {" ?&=#+%!<>#\"{}|\\^[]`☺\t:/@$'()*,;", + "+%3F%26%3D%23%2B%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%E2%98%BA%09%3A%2F%40%24%27%28%29%2A%2C%3B", srs_success}, + }; + for(int i = 0; i < (int)(sizeof(escape) / sizeof(struct EscapeTest)); ++i) { + struct EscapeTest& d = escape[i]; + EXPECT_STREQ(d.out.c_str(), SrsHttpUri::query_escape(d.in).c_str()); + + string value; + HELPER_ASSERT_SUCCESS(SrsHttpUri::query_unescape(d.out, value)); + EXPECT_STREQ(d.in.c_str(), value.c_str()); + } + } +} + +VOID TEST(ProtocolHTTPTest, PathEscape) +{ + srs_error_t err = srs_success; + struct EscapeTest path[] = { + {"", "", srs_success}, + {"abc", "abc", srs_success}, + {"abc+def", "abc+def", srs_success}, + {"a/b", "a%2Fb", srs_success}, + {"one two", "one%20two", srs_success}, + {"10%", "10%25", srs_success}, + {" ?&=#+%!<>#\"{}|\\^[]`☺\t:/@$'()*,;", + "%20%3F&=%23+%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%E2%98%BA%09:%2F@$%27%28%29%2A%2C%3B", + srs_success}, + }; + + for(int i = 0; i < (int)(sizeof(path) / sizeof(struct EscapeTest)); ++i) { + struct EscapeTest& d = path[i]; + EXPECT_STREQ(d.out.c_str(), SrsHttpUri::path_escape(d.in).c_str()); + + string value; + HELPER_ASSERT_SUCCESS(SrsHttpUri::path_unescape(d.out, value)); + EXPECT_STREQ(d.in.c_str(), value.c_str()); + EXPECT_EQ(0, memcmp(d.in.c_str(), value.c_str(), d.in.length())); + } + +} diff --git a/trunk/src/utest/srs_utest_kernel.cpp b/trunk/src/utest/srs_utest_kernel.cpp index 7607dfcc3..f246bd99d 100644 --- a/trunk/src/utest/srs_utest_kernel.cpp +++ b/trunk/src/utest/srs_utest_kernel.cpp @@ -361,7 +361,7 @@ MockSrsCodec::~MockSrsCodec() { } -int MockSrsCodec::nb_bytes() +uint64_t MockSrsCodec::nb_bytes() { return 0; } @@ -2393,6 +2393,89 @@ VOID TEST(KernelUtility, Base64Decode) EXPECT_TRUE(expect == plaintext); } +VOID TEST(KernelUtility, Base64Encode) +{ + srs_error_t err; + + string expect = "dXNlcjpwYXNzd29yZA=="; + string plaintext = "user:password"; + + string cipher; + HELPER_EXPECT_SUCCESS(srs_av_base64_encode(plaintext, cipher)); + EXPECT_TRUE(expect == cipher); +} + +VOID TEST(KernelUtility, Base64) +{ + srs_error_t err = srs_success; + struct testpair { + string decoded; + string encoded; + }; + + struct testpair data[] = { + // RFC 3548 examples + {"\x14\xfb\x9c\x03\xd9\x7e", "FPucA9l+"}, + {"\x14\xfb\x9c\x03\xd9", "FPucA9k="}, + {"\x14\xfb\x9c\x03", "FPucAw=="}, + + // RFC 4648 examples + {"", ""}, + {"f", "Zg=="}, + {"fo", "Zm8="}, + {"foo", "Zm9v"}, + {"foob", "Zm9vYg=="}, + {"fooba", "Zm9vYmE="}, + {"foobar", "Zm9vYmFy"}, + + // Wikipedia examples + {"sure.", "c3VyZS4="}, + {"sure", "c3VyZQ=="}, + {"sur", "c3Vy"}, + {"su", "c3U="}, + {"leasure.", "bGVhc3VyZS4="}, + {"easure.", "ZWFzdXJlLg=="}, + {"asure.", "YXN1cmUu"}, + {"sure.", "c3VyZS4="}, + {"Twas brillig, and the slithy toves", "VHdhcyBicmlsbGlnLCBhbmQgdGhlIHNsaXRoeSB0b3Zlcw=="} + }; + + for(int i = 0; i < (int)(sizeof(data) / sizeof(struct testpair)); ++i) { + struct testpair& d = data[i]; + string cipher; + HELPER_EXPECT_SUCCESS(srs_av_base64_encode(d.decoded, cipher)); + EXPECT_STREQ(d.encoded.c_str(), cipher.c_str()); + + string plaintext; + HELPER_EXPECT_SUCCESS(srs_av_base64_decode(d.encoded, plaintext)); + EXPECT_STREQ(d.decoded.c_str(), plaintext.c_str()); + } + + string expected = "sure"; + string examples[11] = { + "c3VyZQ==", + "c3VyZQ==\r", + "c3VyZQ==\n", + "c3VyZQ==\r\n", + "c3VyZ\r\nQ==", + "c3V\ryZ\nQ==", + "c3V\nyZ\rQ==", + "c3VyZ\nQ==", + "c3VyZQ\n==", + "c3VyZQ=\n=", + "c3VyZQ=\r\n\r\n=", + }; + + for(int i = 0; i < 11; ++i) { + string& encoded_str = examples[i]; + string plaintext; + HELPER_EXPECT_SUCCESS(srs_av_base64_decode(encoded_str, plaintext)); + EXPECT_STREQ(expected.c_str(), plaintext.c_str()); + } + + +} + VOID TEST(KernelUtility, StringToHex) { if (true) { @@ -2543,12 +2626,127 @@ VOID TEST(KernelUtility, StringUtils) flags.push_back("x"); EXPECT_TRUE("" == srs_string_min_match("srs", flags)); } - +} + +VOID TEST(KernelUtility, StringSplitUtils) +{ + if (true) { + vector ss = srs_string_split("ossrs", "r"); + EXPECT_EQ(2, (int)ss.size()); + EXPECT_STREQ("oss", ss.at(0).c_str()); + EXPECT_STREQ("s", ss.at(1).c_str()); + } + + if (true) { + vector ss = srs_string_split("ossrs", ""); + EXPECT_EQ(1, (int)ss.size()); + EXPECT_STREQ("ossrs", ss.at(0).c_str()); + } + + if (true) { + vector ss = srs_string_split("ossrs", "live"); + EXPECT_EQ(1, (int)ss.size()); + EXPECT_STREQ("ossrs", ss.at(0).c_str()); + } + + if (true) { + vector ss = srs_string_split("srs,live,rtc", ","); + EXPECT_EQ(3, (int)ss.size()); + EXPECT_STREQ("srs", ss.at(0).c_str()); + EXPECT_STREQ("live", ss.at(1).c_str()); + EXPECT_STREQ("rtc", ss.at(2).c_str()); + } + + if (true) { + vector ss = srs_string_split("srs,,rtc", ","); + EXPECT_EQ(3, (int)ss.size()); + EXPECT_STREQ("srs", ss.at(0).c_str()); + EXPECT_STREQ("", ss.at(1).c_str()); + EXPECT_STREQ("rtc", ss.at(2).c_str()); + } + + if (true) { + vector ss = srs_string_split("srs,,,rtc", ","); + EXPECT_EQ(4, (int)ss.size()); + EXPECT_STREQ("srs", ss.at(0).c_str()); + EXPECT_STREQ("", ss.at(1).c_str()); + EXPECT_STREQ("", ss.at(2).c_str()); + EXPECT_STREQ("rtc", ss.at(3).c_str()); + } + + if (true) { + vector ss = srs_string_split("srs,live,", ","); + EXPECT_EQ(3, (int)ss.size()); + EXPECT_STREQ("srs", ss.at(0).c_str()); + EXPECT_STREQ("live", ss.at(1).c_str()); + EXPECT_STREQ("", ss.at(2).c_str()); + } + + if (true) { + vector ss = srs_string_split(",live,rtc", ","); + EXPECT_EQ(3, (int)ss.size()); + EXPECT_STREQ("", ss.at(0).c_str()); + EXPECT_STREQ("live", ss.at(1).c_str()); + EXPECT_STREQ("rtc", ss.at(2).c_str()); + } + if (true) { EXPECT_TRUE("srs" == srs_string_split("srs", "").at(0)); EXPECT_TRUE("s" == srs_string_split("srs", "r").at(0)); EXPECT_TRUE("s" == srs_string_split("srs", "rs").at(0)); } + + if (true) { + vector ss = srs_string_split("/xxx/yyy", "/"); + EXPECT_EQ(3, (int)ss.size()); + EXPECT_STREQ("", ss.at(0).c_str()); + EXPECT_STREQ("xxx", ss.at(1).c_str()); + EXPECT_STREQ("yyy", ss.at(2).c_str()); + } +} + +VOID TEST(KernelUtility, StringSplitUtils2) +{ + if (true) { + vector flags; + flags.push_back("e"); + flags.push_back("wo"); + vector ss = srs_string_split("hello, world", flags); + EXPECT_EQ(3, (int)ss.size()); + EXPECT_STREQ("h", ss.at(0).c_str()); + EXPECT_STREQ("llo, ", ss.at(1).c_str()); + EXPECT_STREQ("rld", ss.at(2).c_str()); + } + + if (true) { + vector flags; + flags.push_back(""); + flags.push_back(""); + vector ss = srs_string_split("hello, world", flags); + EXPECT_EQ(1, (int)ss.size()); + EXPECT_STREQ("hello, world", ss.at(0).c_str()); + } + + if (true) { + vector flags; + flags.push_back(","); + flags.push_back(" "); + vector ss = srs_string_split("hello, world", flags); + EXPECT_EQ(3, (int)ss.size()); + EXPECT_STREQ("hello", ss.at(0).c_str()); + EXPECT_STREQ("", ss.at(1).c_str()); + EXPECT_STREQ("world", ss.at(2).c_str()); + } + + if (true) { + vector flags; + flags.push_back(","); + vector ss = srs_string_split("hello,,world", flags); + EXPECT_EQ(3, (int)ss.size()); + EXPECT_STREQ("hello", ss.at(0).c_str()); + EXPECT_STREQ("", ss.at(1).c_str()); + EXPECT_STREQ("world", ss.at(2).c_str()); + } } VOID TEST(KernelUtility, BytesUtils) @@ -2596,7 +2794,7 @@ VOID TEST(KernelUtility, AnnexbUtils) if (true) { EXPECT_TRUE(!srs_avc_startswith_annexb(NULL, NULL)); - SrsBuffer buf; + SrsBuffer buf(NULL, 0); EXPECT_TRUE(!srs_avc_startswith_annexb(&buf, NULL)); } @@ -2654,7 +2852,7 @@ VOID TEST(KernelUtility, AdtsUtils) if (true) { EXPECT_TRUE(!srs_aac_startswith_adts(NULL)); - SrsBuffer buf; + SrsBuffer buf(NULL, 0); EXPECT_TRUE(!srs_aac_startswith_adts(&buf)); } @@ -3713,11 +3911,11 @@ VOID TEST(KernelFileTest, FileWriteReader) } // Mock the system call hooks. -extern _srs_open_t _srs_open_fn; -extern _srs_write_t _srs_write_fn; -extern _srs_read_t _srs_read_fn; -extern _srs_lseek_t _srs_lseek_fn; -extern _srs_close_t _srs_close_fn; +extern srs_open_t _srs_open_fn; +extern srs_write_t _srs_write_fn; +extern srs_read_t _srs_read_fn; +extern srs_lseek_t _srs_lseek_fn; +extern srs_close_t _srs_close_fn; int mock_open(const char* /*path*/, int /*oflag*/, ...) { return -1; @@ -3742,13 +3940,13 @@ int mock_close(int /*fildes*/) { class MockSystemIO { private: - _srs_open_t oo; - _srs_write_t ow; - _srs_read_t _or; - _srs_lseek_t os; - _srs_close_t oc; + srs_open_t oo; + srs_write_t ow; + srs_read_t _or; + srs_lseek_t os; + srs_close_t oc; public: - MockSystemIO(_srs_open_t o = NULL, _srs_write_t w = NULL, _srs_read_t r = NULL, _srs_lseek_t s = NULL, _srs_close_t c = NULL) { + MockSystemIO(srs_open_t o = NULL, srs_write_t w = NULL, srs_read_t r = NULL, srs_lseek_t s = NULL, srs_close_t c = NULL) { oo = _srs_open_fn; ow = _srs_write_fn; os = _srs_lseek_fn; @@ -3843,7 +4041,7 @@ VOID TEST(KernelFileWriterTest, WriteSpecialCase) off_t seeked = 0; HELPER_EXPECT_SUCCESS(f.lseek(0, SEEK_CUR, &seeked)); -#ifdef SRS_AUTO_OSX +#ifdef SRS_OSX EXPECT_EQ(10, seeked); #else EXPECT_EQ(0, seeked); @@ -4090,28 +4288,6 @@ VOID TEST(KernelFLVTest, CoverSharedPtrMessage) } } -VOID TEST(KernelLogTest, CoverAll) -{ - srs_error_t err; - - if (true) { - ISrsLog l; - HELPER_EXPECT_SUCCESS(l.initialize()); - - l.reopen(); - l.verbose("TAG", 0, "log"); - l.info("TAG", 0, "log"); - l.trace("TAG", 0, "log"); - l.warn("TAG", 0, "log"); - l.error("TAG", 0, "log"); - - ISrsThreadContext ctx; - ctx.set_id(10); - EXPECT_EQ(0, ctx.get_id()); - EXPECT_EQ(0, ctx.generate_id()); - } -} - VOID TEST(KernelMp3Test, CoverAll) { srs_error_t err; @@ -4212,8 +4388,8 @@ VOID TEST(KernelUtilityTest, CoverBitsBufferAll) } } -#ifndef SRS_AUTO_OSX -extern _srs_gettimeofday_t _srs_gettimeofday; +#ifndef SRS_OSX +extern srs_gettimeofday_t _srs_gettimeofday; int mock_gettimeofday(struct timeval* /*tp*/, struct timezone* /*tzp*/) { return -1; } @@ -4221,9 +4397,9 @@ int mock_gettimeofday(struct timeval* /*tp*/, struct timezone* /*tzp*/) { class MockTime { private: - _srs_gettimeofday_t ot; + srs_gettimeofday_t ot; public: - MockTime(_srs_gettimeofday_t t = NULL) { + MockTime(srs_gettimeofday_t t = NULL) { ot = _srs_gettimeofday; if (t) { _srs_gettimeofday = t; @@ -4422,17 +4598,6 @@ VOID TEST(KernelUtilityTest, CoverTimeUtilityAll) EXPECT_STREQ("off", srs_bool2switch(false).c_str()); } - if (true) { - vector flags; - flags.push_back("e"); - flags.push_back("wo"); - vector ss = srs_string_split("hello, world", flags); - EXPECT_EQ(3, (int)ss.size()); - EXPECT_STREQ("h", ss.at(0).c_str()); - EXPECT_STREQ("llo, ", ss.at(1).c_str()); - EXPECT_STREQ("rld", ss.at(2).c_str()); - } - if (true) { EXPECT_EQ('H', av_toupper('h')); } @@ -4506,7 +4671,7 @@ VOID TEST(KernelTSTest, CoverContextUtility) SrsTsMessage m(&c, &p); m.PES_packet_length = 8; - SrsBuffer b; + SrsBuffer b(NULL, 0); int nb_bytes = 0; HELPER_EXPECT_SUCCESS(m.dump(&b, &nb_bytes)); @@ -4625,7 +4790,7 @@ VOID TEST(KernelTSTest, CoverContextEncode) MockTsHandler h; if (true) { - SrsBuffer b; + SrsBuffer b(NULL, 0); HELPER_EXPECT_SUCCESS(ctx.decode(&b, &h)); EXPECT_TRUE(NULL == h.msg); } diff --git a/trunk/src/utest/srs_utest_kernel.hpp b/trunk/src/utest/srs_utest_kernel.hpp index 90efb4ee9..f32d1f063 100644 --- a/trunk/src/utest/srs_utest_kernel.hpp +++ b/trunk/src/utest/srs_utest_kernel.hpp @@ -130,7 +130,7 @@ public: MockSrsCodec(); virtual ~MockSrsCodec(); public: - virtual int nb_bytes(); + virtual uint64_t nb_bytes(); virtual srs_error_t encode(SrsBuffer* buf); virtual srs_error_t decode(SrsBuffer* buf); }; diff --git a/trunk/src/utest/srs_utest_mp4.cpp b/trunk/src/utest/srs_utest_mp4.cpp index 8779f8672..c5861b88b 100644 --- a/trunk/src/utest/srs_utest_mp4.cpp +++ b/trunk/src/utest/srs_utest_mp4.cpp @@ -220,7 +220,7 @@ VOID TEST(KernelMp4Test, DiscoveryBox) SrsMp4Box* pbox; if (true) { - SrsBuffer b; + SrsBuffer b(NULL, 0); HELPER_ASSERT_FAILED(SrsMp4Box::discovery(&b, &pbox)); } @@ -419,7 +419,7 @@ VOID TEST(KernelMp4Test, UUIDBoxDecode) } if (true) { - SrsBuffer b; + SrsBuffer b(NULL, 0); SrsMp4Box box; HELPER_ASSERT_FAILED(box.decode(&b)); } @@ -464,7 +464,7 @@ VOID TEST(KernelMp4Test, UUIDBoxEncode) SrsMp4Box box; box.type = SrsMp4BoxTypeFREE; box.usertype.resize(8); - ASSERT_EQ(8, box.nb_bytes()); + ASSERT_EQ(8, (int)box.nb_bytes()); HELPER_ASSERT_SUCCESS(box.encode(&b)); } @@ -475,7 +475,7 @@ VOID TEST(KernelMp4Test, UUIDBoxEncode) SrsMp4Box box; box.type = SrsMp4BoxTypeUUID; box.usertype.resize(16); - ASSERT_EQ(24, box.nb_bytes()); + ASSERT_EQ(24, (int)box.nb_bytes()); HELPER_ASSERT_SUCCESS(box.encode(&b)); } } @@ -499,7 +499,7 @@ VOID TEST(KernelMp4Test, FullBoxDump) SrsMp4FileTypeBox box; box.major_brand = SrsMp4BoxBrandISO2; box.compatible_brands.push_back(SrsMp4BoxBrandISOM); - EXPECT_EQ(20, box.update_size()); + EXPECT_EQ(20, (int)box.update_size()); stringstream ss; SrsMp4DumpContext dc; @@ -514,7 +514,7 @@ VOID TEST(KernelMp4Test, FullBoxDump) box.type = SrsMp4BoxTypeFTYP; box.version = 1; box.flags = 0x02; - EXPECT_EQ(12, box.update_size()); + EXPECT_EQ(12, (int)box.update_size()); stringstream ss; SrsMp4DumpContext dc; @@ -528,7 +528,7 @@ VOID TEST(KernelMp4Test, FullBoxDump) SrsMp4FullBox box; box.type = SrsMp4BoxTypeFTYP; box.version = 1; - EXPECT_EQ(12, box.update_size()); + EXPECT_EQ(12, (int)box.update_size()); stringstream ss; SrsMp4DumpContext dc; @@ -541,7 +541,7 @@ VOID TEST(KernelMp4Test, FullBoxDump) if (true) { SrsMp4FullBox box; box.type = SrsMp4BoxTypeFTYP; - EXPECT_EQ(12, box.update_size()); + EXPECT_EQ(12, (int)box.update_size()); stringstream ss; SrsMp4DumpContext dc; @@ -570,7 +570,7 @@ VOID TEST(KernelMp4Test, MFHDBox) if (true) { SrsMp4MovieFragmentHeaderBox box; box.sequence_number = 3; - EXPECT_EQ(16, box.update_size()); + EXPECT_EQ(16, (int)box.update_size()); stringstream ss; SrsMp4DumpContext dc; @@ -596,7 +596,7 @@ VOID TEST(KernelMp4Test, TFHDBox) if (true) { SrsMp4TrackFragmentHeaderBox box; box.track_id = 100; - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -631,7 +631,7 @@ VOID TEST(KernelMp4Test, TFHDBox) box.default_sample_duration = 12; box.default_sample_size = 13; box.default_sample_flags = 14; - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -667,7 +667,7 @@ VOID TEST(KernelMp4Test, TFDTBox) if (true) { SrsMp4TrackFragmentDecodeTimeBox box; box.base_media_decode_time = 100; - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -694,7 +694,7 @@ VOID TEST(KernelMp4Test, TFDTBox) SrsMp4TrackFragmentDecodeTimeBox box; box.version = 1; box.base_media_decode_time = 100; - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -724,7 +724,7 @@ VOID TEST(KernelMp4Test, TRUNBox) if (true) { SrsMp4TrackFragmentRunBox box; - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -754,7 +754,7 @@ VOID TEST(KernelMp4Test, TRUNBox) entry->sample_duration = 1000; box.entries.push_back(entry); - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -788,7 +788,7 @@ VOID TEST(KernelMp4Test, FreeBox) if (true) { SrsMp4FreeSpaceBox box(SrsMp4BoxTypeFREE); box.data.resize(4); - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -818,7 +818,7 @@ VOID TEST(KernelMp4Test, MOOVBox) if (true) { SrsMp4MovieBox box; - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -891,7 +891,7 @@ VOID TEST(KernelMp4Test, TREXBox) SrsMp4TrackExtendsBox box; box.track_ID = 1; box.default_sample_description_index = 2; box.default_sample_size = 3; box.default_sample_duration = 4; box.default_sample_flags = 5; - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -933,7 +933,7 @@ VOID TEST(KernelMp4Test, TKHDBox) if (true) { SrsMp4TrackHeaderBox box; box.track_ID = 1; - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -960,7 +960,7 @@ VOID TEST(KernelMp4Test, TKHDBox) SrsMp4TrackHeaderBox box; box.version = 1; box.track_ID = 1; - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -990,7 +990,7 @@ VOID TEST(KernelMp4Test, ELSTBox) if (true) { SrsMp4EditListBox box; - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -1018,7 +1018,7 @@ VOID TEST(KernelMp4Test, ELSTBox) SrsMp4ElstEntry entry; box.entries.push_back(entry); } - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -1054,7 +1054,7 @@ VOID TEST(KernelMp4Test, MDHDBox) if (true) { SrsMp4MediaHeaderBox box; - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -1081,7 +1081,7 @@ VOID TEST(KernelMp4Test, MDHDBox) box.set_language0('C'); box.set_language1('N'); box.set_language2('E'); - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -1136,7 +1136,7 @@ VOID TEST(KernelMp4Test, HDLRBox) if (true) { SrsMp4HandlerReferenceBox box; box.handler_type = SrsMp4HandlerTypeSOUN; - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -1162,7 +1162,7 @@ VOID TEST(KernelMp4Test, HDLRBox) if (true) { SrsMp4HandlerReferenceBox box; box.handler_type = SrsMp4HandlerTypeVIDE; - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -1189,7 +1189,7 @@ VOID TEST(KernelMp4Test, HDLRBox) SrsMp4HandlerReferenceBox box; box.handler_type = SrsMp4HandlerTypeVIDE; box.name = "srs"; - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -1247,7 +1247,7 @@ VOID TEST(KernelMp4Test, URLBox) if (true) { SrsMp4DataEntryUrlBox box; - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -1271,7 +1271,7 @@ VOID TEST(KernelMp4Test, URLBox) if (true) { SrsMp4DataEntryUrnBox box; - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -1304,7 +1304,7 @@ VOID TEST(KernelMp4Test, URLBox) SrsMp4DataReferenceBox box; SrsMp4DataEntryUrnBox* urn = new SrsMp4DataEntryUrnBox(); box.append(urn); - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -1341,7 +1341,7 @@ VOID TEST(KernelMp4Test, SampleDescBox) if (true) { SrsMp4VisualSampleEntry box; box.data_reference_index = 1; - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -1365,7 +1365,7 @@ VOID TEST(KernelMp4Test, SampleDescBox) if (true) { SrsMp4AvccBox box; - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -1390,7 +1390,7 @@ VOID TEST(KernelMp4Test, SampleDescBox) if (true) { SrsMp4AudioSampleEntry box; box.data_reference_index = 1; - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -1420,7 +1420,7 @@ VOID TEST(KernelMp4Test, SpecificInfoBox) if (true) { SrsMp4DecoderSpecificInfo box; box.asc.resize(2); - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -1444,7 +1444,7 @@ VOID TEST(KernelMp4Test, SpecificInfoBox) if (true) { SrsMp4DecoderConfigDescriptor box; - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -1468,7 +1468,7 @@ VOID TEST(KernelMp4Test, SpecificInfoBox) if (true) { SrsMp4ES_Descriptor box; - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -1498,7 +1498,7 @@ VOID TEST(KernelMp4Test, STSDBox) if (true) { SrsMp4SampleDescriptionBox box; box.entries.push_back(new SrsMp4SampleEntry()); - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -1523,7 +1523,7 @@ VOID TEST(KernelMp4Test, STSDBox) if (true) { SrsMp4DecodingTime2SampleBox box; box.entries.push_back(SrsMp4SttsEntry()); - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -1548,7 +1548,7 @@ VOID TEST(KernelMp4Test, STSDBox) if (true) { SrsMp4CompositionTime2SampleBox box; box.entries.push_back(SrsMp4CttsEntry()); - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -1572,7 +1572,7 @@ VOID TEST(KernelMp4Test, STSDBox) if (true) { SrsMp4SyncSampleBox box; - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -1596,7 +1596,7 @@ VOID TEST(KernelMp4Test, STSDBox) if (true) { SrsMp4Sample2ChunkBox box; - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -1620,7 +1620,7 @@ VOID TEST(KernelMp4Test, STSDBox) if (true) { SrsMp4ChunkOffsetBox box; - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -1644,7 +1644,7 @@ VOID TEST(KernelMp4Test, STSDBox) if (true) { SrsMp4ChunkLargeOffsetBox box; - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -1668,7 +1668,7 @@ VOID TEST(KernelMp4Test, STSDBox) if (true) { SrsMp4SampleSizeBox box; - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -1693,7 +1693,7 @@ VOID TEST(KernelMp4Test, STSDBox) if (true) { SrsMp4UserDataBox box; box.data.resize(2); - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -1717,7 +1717,7 @@ VOID TEST(KernelMp4Test, STSDBox) if (true) { SrsMp4SegmentIndexBox box; - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -1741,7 +1741,7 @@ VOID TEST(KernelMp4Test, STSDBox) if (true) { SrsMp4MovieHeaderBox box; - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; @@ -1772,7 +1772,7 @@ VOID TEST(KernelMp4Test, STSDBox) if (true) { SrsMp4CompositionTime2SampleBox box; - EXPECT_EQ((int)sizeof(buf), box.nb_bytes()); + EXPECT_EQ((int)sizeof(buf), (int)box.nb_bytes()); HELPER_EXPECT_SUCCESS(box.encode(&b)); stringstream ss; diff --git a/trunk/src/utest/srs_utest_protocol.cpp b/trunk/src/utest/srs_utest_protocol.cpp index ebd260aa1..faaff3b20 100644 --- a/trunk/src/utest/srs_utest_protocol.cpp +++ b/trunk/src/utest/srs_utest_protocol.cpp @@ -352,7 +352,7 @@ VOID TEST(ProtocolHandshakeTest, OpensslSha256) // verify the dh key VOID TEST(ProtocolHandshakeTest, DHKey) { - _srs_internal::SrsDH dh; + srs_internal::SrsDH dh; ASSERT_TRUE(ERROR_SUCCESS == dh.initialize(true)); @@ -368,7 +368,7 @@ VOID TEST(ProtocolHandshakeTest, DHKey) EXPECT_TRUE(srs_bytes_equals(pub_key1, pub_key2, 128)); // another dh - _srs_internal::SrsDH dh0; + srs_internal::SrsDH dh0; ASSERT_TRUE(ERROR_SUCCESS == dh0.initialize(true)); @@ -5999,15 +5999,17 @@ VOID TEST(ProtocolHTTPTest, HTTPParser) VOID TEST(ProtocolHTTPTest, ParseHTTPMessage) { + srs_error_t err = srs_success; + if (true) { MockBufferIO bio; SrsHttpParser hp; bio.append("GET /gslb/v1/versions HTTP/1.1\r\nContent-Length: 5\r\n\r\nHello"); - EXPECT_TRUE(0 == hp.initialize(HTTP_REQUEST, false)); + HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_REQUEST)); ISrsHttpMessage* req = NULL; - ASSERT_TRUE(0 == hp.parse_message(&bio, &req)); + HELPER_ASSERT_SUCCESS(hp.parse_message(&bio, &req)); // We should read body, or next parsing message will fail. // @see https://github.com/ossrs/srs/issues/1181 @@ -6019,7 +6021,7 @@ VOID TEST(ProtocolHTTPTest, ParseHTTPMessage) // Should fail because there is body which not read. // @see https://github.com/ossrs/srs/issues/1181 - ASSERT_FALSE(0 == hp.parse_message(&bio, &req)); + HELPER_ASSERT_FAILED(hp.parse_message(&bio, &req)); srs_freep(req); } @@ -6028,11 +6030,11 @@ VOID TEST(ProtocolHTTPTest, ParseHTTPMessage) SrsHttpParser hp; bio.append("GET"); - EXPECT_TRUE(0 == hp.initialize(HTTP_REQUEST, false)); + HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_REQUEST)); // Should fail if not completed message. ISrsHttpMessage* req = NULL; - ASSERT_FALSE(0 == hp.parse_message(&bio, &req)); + HELPER_ASSERT_FAILED(hp.parse_message(&bio, &req)); srs_freep(req); } @@ -6041,14 +6043,14 @@ VOID TEST(ProtocolHTTPTest, ParseHTTPMessage) SrsHttpParser hp; bio.append("GET /gslb/v1/versions HTTP/1.1\r\nContent-Length: 5\r\n\r\nHello"); - ASSERT_TRUE(0 == hp.initialize(HTTP_REQUEST, false)); + HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_REQUEST)); ISrsHttpMessage* req = NULL; SrsAutoFree(ISrsHttpMessage, req); - ASSERT_TRUE(0 == hp.parse_message(&bio, &req)); + HELPER_ASSERT_SUCCESS(hp.parse_message(&bio, &req)); char v[64] = {0}; - EXPECT_TRUE(0 == req->body_reader()->read(v, sizeof(v), NULL)); + HELPER_ASSERT_SUCCESS(req->body_reader()->read(v, sizeof(v), NULL)); EXPECT_TRUE(string("Hello") == string(v)); EXPECT_TRUE(req->body_reader()->eof()); @@ -6059,11 +6061,11 @@ VOID TEST(ProtocolHTTPTest, ParseHTTPMessage) SrsHttpParser hp; bio.append("GET /gslb/v1/versions HTTP/1.1\r\nContent-Length: 0\r\n\r\n"); - ASSERT_TRUE(0 == hp.initialize(HTTP_REQUEST, false)); + HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_REQUEST)); ISrsHttpMessage* req = NULL; SrsAutoFree(ISrsHttpMessage, req); - EXPECT_TRUE(0 == hp.parse_message(&bio, &req)); + HELPER_ASSERT_SUCCESS(hp.parse_message(&bio, &req)); } if (true) { @@ -6071,11 +6073,11 @@ VOID TEST(ProtocolHTTPTest, ParseHTTPMessage) SrsHttpParser hp; bio.append("GET /gslb/v1/versions HTTP/1.1\r\n\r\n"); - ASSERT_TRUE(0 == hp.initialize(HTTP_REQUEST, false)); + HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_REQUEST)); ISrsHttpMessage* req = NULL; SrsAutoFree(ISrsHttpMessage, req); - EXPECT_TRUE(0 == hp.parse_message(&bio, &req)); + HELPER_ASSERT_SUCCESS(hp.parse_message(&bio, &req)); } if (true) { @@ -6083,11 +6085,11 @@ VOID TEST(ProtocolHTTPTest, ParseHTTPMessage) SrsHttpParser hp; bio.append("GET /gslb/v1/versions HTTP/1.1\r\n\r\n"); - ASSERT_TRUE(0 == hp.initialize(HTTP_REQUEST, false)); + HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_REQUEST)); ISrsHttpMessage* req = NULL; SrsAutoFree(ISrsHttpMessage, req); - EXPECT_TRUE(0 == hp.parse_message(&bio, &req)); + HELPER_ASSERT_SUCCESS(hp.parse_message(&bio, &req)); } } diff --git a/trunk/src/utest/srs_utest_protocol.hpp b/trunk/src/utest/srs_utest_protocol.hpp index 4a133127d..f81b3eb3b 100644 --- a/trunk/src/utest/srs_utest_protocol.hpp +++ b/trunk/src/utest/srs_utest_protocol.hpp @@ -37,7 +37,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include -using namespace _srs_internal; +using namespace srs_internal; #include diff --git a/trunk/src/utest/srs_utest_rtc.cpp b/trunk/src/utest/srs_utest_rtc.cpp new file mode 100644 index 000000000..577e2bd98 --- /dev/null +++ b/trunk/src/utest/srs_utest_rtc.cpp @@ -0,0 +1,1674 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2020 Winlin + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +using namespace std; + +VOID TEST(KernelRTCTest, RtpSTAPPayloadException) +{ + srs_error_t err = srs_success; + + unsigned char rtp_pkt[328] = { + 0x90, 0xe0, 0x65, 0x8d, 0x37, 0xbc, 0x20, 0xb7, 0xd8, 0xf7, 0xae, 0x77, 0xbe, 0xde, 0x00, 0x03, + 0x51, 0x06, 0x4f, 0xd5, 0x2f, 0x0e, 0xe1, 0x90, 0x75, 0xc3, 0x00, 0x00, 0xd8, 0x01, 0x00, 0x03, + 0xef, 0x93, 0xc7, 0x6a, 0x23, 0x45, 0xdc, 0xb0, 0xce, 0x2b, 0x51, 0x1a, 0x8a, 0xd1, 0x35, 0xab, + 0x11, 0xa7, 0x15, 0xc4, 0xd6, 0xe4, 0x5d, 0x12, 0x6c, 0x04, 0x86, 0x25, 0xd3, 0x88, 0x76, 0xa2, + 0xb8, 0x58, 0x47, 0x0d, 0x0a, 0xd6, 0x2b, 0x85, 0x04, 0x6a, 0x09, 0x2a, 0x4a, 0xce, 0x22, 0xa2, + 0x05, 0x78, 0x8e, 0x71, 0x5c, 0x22, 0x23, 0x58, 0x9e, 0x16, 0x15, 0xe1, 0x5f, 0xff, 0xfd, 0x32, + 0x0a, 0xe2, 0xb8, 0xea, 0xd6, 0xba, 0xd5, 0x7e, 0x5a, 0xd6, 0x61, 0x1c, 0x82, 0x38, 0xce, 0x4a, + 0xd7, 0xe2, 0xea, 0xaa, 0xab, 0xa8, 0x83, 0xf6, 0x7f, 0x10, 0xf1, 0x7c, 0x55, 0x4d, 0xeb, 0xaa, + 0xf8, 0xfd, 0x35, 0xaa, 0xeb, 0x59, 0x8e, 0xf8, 0x8f, 0x12, 0xb9, 0xdd, 0x39, 0xfa, 0x3f, 0x62, + 0x9e, 0x23, 0x96, 0xab, 0x5e, 0xc4, 0xce, 0x97, 0x55, 0x43, 0x65, 0x29, 0xde, 0x8f, 0xe2, 0xb9, + 0x0f, 0xb8, 0xd0, 0xee, 0x00, 0x31, 0x35, 0xdb, 0x5a, 0xff, 0xff, 0xf8, 0x10, 0xa9, 0x3c, 0xf7, + 0x90, 0x8c, 0xf7, 0x3f, 0x5f, 0xd7, 0x15, 0xac, 0xee, 0xa8, 0xfe, 0x23, 0x84, 0x8b, 0xe6, 0x97, + 0x2a, 0x61, 0x38, 0xba, 0xd3, 0xee, 0x7b, 0x49, 0xfa, 0x81, 0xcb, 0x3f, 0x72, 0xd5, 0x56, 0x8f, + 0xe7, 0x7b, 0x1d, 0xda, 0x85, 0x71, 0xbc, 0x45, 0x75, 0x5d, 0x55, 0x47, 0xc5, 0xf5, 0x36, 0xe4, + 0xa9, 0x17, 0x4a, 0x84, 0xf9, 0xdd, 0xd0, 0xa5, 0xb1, 0xcf, 0x69, 0xcf, 0xcd, 0x1d, 0xac, 0xe4, + 0xc6, 0x3d, 0xd0, 0x95, 0xa3, 0xbd, 0x0a, 0xd4, 0xa2, 0xb9, 0x05, 0x78, 0xae, 0x5a, 0x92, 0xb5, + 0x90, 0x4b, 0xa6, 0x85, 0x3c, 0x27, 0xb3, 0x4d, 0xd2, 0x5c, 0xfa, 0x61, 0x01, 0x4a, 0xa6, 0xd9, + 0x26, 0xf3, 0x78, 0x44, 0x57, 0x2e, 0x79, 0xc5, 0x71, 0x42, 0xb5, 0x34, 0x87, 0x94, 0x57, 0x8a, + 0xe1, 0x09, 0xb3, 0x8a, 0xe7, 0x0b, 0x7f, 0xfc, 0xff, 0xec, 0x28, 0xe3, 0x4c, 0xff, 0xff, 0xa6, + 0x6a, 0xca, 0x2b, 0x84, 0xab, 0x0a, 0xd7, 0xf1, 0xf5, 0x9a, 0x47, 0x08, 0x54, 0xd5, 0xac, 0x9a, + 0xf5, 0x09, 0x5a, 0x29, 0x35, 0x52, 0x79, 0xe0, + }; + + int nb_buf = sizeof(rtp_pkt); + SrsBuffer buf((char*)rtp_pkt, nb_buf); + + SrsRtpHeader header; + EXPECT_TRUE((err = header.decode(&buf)) == srs_success); + + // We must skip the padding bytes before parsing payload. + uint8_t padding = header.get_padding(); + EXPECT_TRUE(buf.require(padding)); + buf.set_size(buf.size() - padding); + + SrsAvcNaluType nalu_type = SrsAvcNaluTypeReserved; + // Try to parse the NALU type for video decoder. + if (!buf.empty()) { + nalu_type = SrsAvcNaluType((uint8_t)(buf.head()[0] & kNalTypeMask)); + } + + EXPECT_TRUE(nalu_type == kStapA); + ISrsRtpPayloader* payload = new SrsRtpSTAPPayload(); + + EXPECT_TRUE((err = payload->decode(&buf)) != srs_success); + srs_freep(payload); +} + +class MockResource : public ISrsDisposingHandler, public ISrsResource +{ +public: + SrsResourceManager* manager_; + MockResource(SrsResourceManager* manager) { + manager_ = manager; + if (manager_) { + manager_->subscribe(this); + } + } + virtual ~MockResource() { + if (manager_) { + manager_->unsubscribe(this); + } + } + virtual const SrsContextId& get_id() { + return _srs_context->get_id(); + } + virtual std::string desc() { + return ""; + } +}; + +class MockResourceHookOwner : public MockResource +{ +public: + ISrsResource* owner_; + MockResourceHookOwner(SrsResourceManager* manager) : MockResource(manager) { + owner_ = NULL; + } + virtual ~MockResourceHookOwner() { + } + virtual void on_before_dispose(ISrsResource* c) { + if (c == owner_) { // Remove self if its owner is disposing. + manager_->remove(this); + } + } + virtual void on_disposing(ISrsResource* c) { + } +}; + +class MockResourceSelf : public MockResource +{ +public: + bool remove_in_before_dispose; + bool remove_in_disposing; + MockResourceSelf(SrsResourceManager* manager) : MockResource(manager) { + remove_in_before_dispose = remove_in_disposing = false; + } + virtual ~MockResourceSelf() { + } + virtual void on_before_dispose(ISrsResource* c) { + if (remove_in_before_dispose) { + manager_->remove(this); + } + } + virtual void on_disposing(ISrsResource* c) { + if (remove_in_disposing) { + manager_->remove(this); + } + } +}; + +class MockResourceUnsubscribe : public MockResource +{ +public: + int nn_before_dispose; + int nn_disposing; + bool unsubscribe_in_before_dispose; + bool unsubscribe_in_disposing; + MockResourceUnsubscribe* result; + MockResourceUnsubscribe(SrsResourceManager* manager) : MockResource(manager) { + unsubscribe_in_before_dispose = unsubscribe_in_disposing = false; + nn_before_dispose = nn_disposing = 0; + result = NULL; + } + virtual ~MockResourceUnsubscribe() { + if (result) { // Copy result before disposing it. + *result = *this; + } + } + virtual void on_before_dispose(ISrsResource* c) { + nn_before_dispose++; + if (unsubscribe_in_before_dispose) { + manager_->unsubscribe(this); + } + } + virtual void on_disposing(ISrsResource* c) { + nn_disposing++; + if (unsubscribe_in_disposing) { + manager_->unsubscribe(this); + } + } +}; + +VOID TEST(KernelRTCTest, ConnectionManagerTest) +{ + srs_error_t err = srs_success; + + // When notifying, the handlers changed, disposing event may lost. + if (true) { + SrsResourceManager manager("mgr"); + HELPER_EXPECT_SUCCESS(manager.start()); + EXPECT_EQ(0, (int)manager.size()); EXPECT_TRUE(manager.empty()); + + MockResourceUnsubscribe* conn0 = new MockResourceUnsubscribe(&manager); + conn0->unsubscribe_in_disposing = true; + manager.add(conn0); + + MockResourceUnsubscribe* conn1 = new MockResourceUnsubscribe(&manager); + manager.add(conn1); + + MockResourceUnsubscribe* conn2 = new MockResourceUnsubscribe(&manager); + manager.add(conn2); + + // When removing conn0, it will unsubscribe and change the handlers, + // which should not cause the conn1 lost event. + manager.remove(conn0); + srs_usleep(0); + ASSERT_EQ(2, (int)manager.size()); + + EXPECT_EQ(1, conn1->nn_before_dispose); + EXPECT_EQ(1, conn1->nn_disposing); // Should get event. + + EXPECT_EQ(1, conn2->nn_before_dispose); + EXPECT_EQ(1, conn2->nn_disposing); + } + + // When notifying, the handlers changed, before-dispose event may lost. + if (true) { + SrsResourceManager manager("mgr"); + HELPER_EXPECT_SUCCESS(manager.start()); + EXPECT_EQ(0, (int)manager.size()); EXPECT_TRUE(manager.empty()); + + MockResourceUnsubscribe* conn0 = new MockResourceUnsubscribe(&manager); + conn0->unsubscribe_in_before_dispose = true; + manager.add(conn0); + + MockResourceUnsubscribe* conn1 = new MockResourceUnsubscribe(&manager); + manager.add(conn1); + + MockResourceUnsubscribe* conn2 = new MockResourceUnsubscribe(&manager); + manager.add(conn2); + + // When removing conn0, it will unsubscribe and change the handlers, + // which should not cause the conn1 lost event. + manager.remove(conn0); + srs_usleep(0); + ASSERT_EQ(2, (int)manager.size()); + + EXPECT_EQ(1, conn1->nn_before_dispose); // Should get event. + EXPECT_EQ(1, conn1->nn_disposing); + + EXPECT_EQ(1, conn2->nn_before_dispose); + EXPECT_EQ(1, conn2->nn_disposing); + } + + // Subscribe or unsubscribe for multiple times. + if (true) { + SrsResourceManager manager("mgr"); + HELPER_EXPECT_SUCCESS(manager.start()); + EXPECT_EQ(0, (int)manager.size()); EXPECT_TRUE(manager.empty()); + + MockResourceUnsubscribe* resource = new MockResourceUnsubscribe(&manager); + resource->unsubscribe_in_before_dispose = true; + manager.add(resource); + + MockResourceUnsubscribe result(NULL); // No manager for result. + resource->result = &result; + + manager.remove(resource); + srs_usleep(0); + ASSERT_EQ(0, (int)manager.size()); + + EXPECT_EQ(1, result.nn_before_dispose); + EXPECT_EQ(0, result.nn_disposing); // No disposing event, because we unsubscribe in before-dispose. + } + + // Count the event for disposing. + if (true) { + SrsResourceManager manager("mgr"); + HELPER_EXPECT_SUCCESS(manager.start()); + EXPECT_EQ(0, (int)manager.size()); EXPECT_TRUE(manager.empty()); + + MockResourceUnsubscribe* resource = new MockResourceUnsubscribe(&manager); + manager.add(resource); + + MockResourceUnsubscribe result(NULL); // No manager for result. + resource->result = &result; + + manager.remove(resource); + srs_usleep(0); + ASSERT_EQ(0, (int)manager.size()); + + EXPECT_EQ(1, result.nn_before_dispose); + EXPECT_EQ(1, result.nn_disposing); + } + + // When hooks disposing, remove itself again. + if (true) { + SrsResourceManager manager("mgr"); + HELPER_EXPECT_SUCCESS(manager.start()); + EXPECT_EQ(0, (int)manager.size()); EXPECT_TRUE(manager.empty()); + + MockResourceSelf* resource = new MockResourceSelf(&manager); + resource->remove_in_disposing = true; + manager.add(resource); + EXPECT_EQ(1, (int)manager.size()); + + manager.remove(resource); + srs_usleep(0); + ASSERT_EQ(0, (int)manager.size()); + } + + // When hooks before-dispose, remove itself again. + if (true) { + SrsResourceManager manager("mgr"); + HELPER_EXPECT_SUCCESS(manager.start()); + EXPECT_EQ(0, (int)manager.size()); EXPECT_TRUE(manager.empty()); + + MockResourceSelf* resource = new MockResourceSelf(&manager); + resource->remove_in_before_dispose = true; + manager.add(resource); + EXPECT_EQ(1, (int)manager.size()); + + manager.remove(resource); + srs_usleep(0); + ASSERT_EQ(0, (int)manager.size()); + } + + // Cover all normal scenarios. + if (true) { + SrsResourceManager manager("mgr", true); + HELPER_EXPECT_SUCCESS(manager.start()); + EXPECT_EQ(0, (int)manager.size()); EXPECT_TRUE(manager.empty()); + + // Resource without id or name. + manager.add_with_id("100", new MockSrsConnection()); + manager.add_with_id("101", new MockSrsConnection()); + manager.add_with_name("srs", new MockSrsConnection()); + manager.add_with_name("av", new MockSrsConnection()); + ASSERT_EQ(4, (int)manager.size()); + + manager.remove(manager.at(3)); + manager.remove(manager.at(2)); + manager.remove(manager.at(1)); + manager.remove(manager.at(0)); + srs_usleep(0); + ASSERT_EQ(0, (int)manager.size()); + } + + // Callback: Remove worker when its master is disposing. + if (true) { + SrsResourceManager manager("mgr"); + HELPER_EXPECT_SUCCESS(manager.start()); + EXPECT_EQ(0, (int)manager.size()); EXPECT_TRUE(manager.empty()); + + MockResourceHookOwner* master = new MockResourceHookOwner(&manager); + manager.add(master); + EXPECT_EQ(1, (int)manager.size()); + + MockResourceHookOwner* worker = new MockResourceHookOwner(&manager); + worker->owner_ = master; // When disposing master, worker will hook the event and remove itself. + manager.add(worker); + EXPECT_EQ(2, (int)manager.size()); + + manager.remove(master); + srs_usleep(0); // Trigger the disposing. + + // Both master and worker should be disposed. + EXPECT_EQ(0, (int)manager.size()); EXPECT_TRUE(manager.empty()); + } + + // Normal scenario, free object by manager. + if (true) { + SrsResourceManager manager("mgr"); + HELPER_EXPECT_SUCCESS(manager.start()); + EXPECT_EQ(0, (int)manager.size()); EXPECT_TRUE(manager.empty()); + + MockSrsConnection* conn = new MockSrsConnection(); + manager.add(conn); + EXPECT_EQ(1, (int)manager.size()); EXPECT_FALSE(manager.empty()); + + manager.remove(conn); + srs_usleep(0); // Switch context for manager to dispose connections. + EXPECT_EQ(0, (int)manager.size()); EXPECT_TRUE(manager.empty()); + } + + // Resource with id or name. + if (true) { + SrsResourceManager manager("mgr"); + HELPER_EXPECT_SUCCESS(manager.start()); + EXPECT_EQ(0, (int)manager.size()); EXPECT_TRUE(manager.empty()); + + // Resource without id or name. + MockSrsConnection* conn = new MockSrsConnection(); + manager.add(conn); + ASSERT_EQ(1, (int)manager.size()); + EXPECT_TRUE(manager.at(0)); + EXPECT_TRUE(!manager.at(1)); + EXPECT_TRUE(!manager.find_by_id("100")); + EXPECT_TRUE(!manager.find_by_name("srs")); + + manager.remove(conn); + srs_usleep(0); + ASSERT_EQ(0, (int)manager.size()); + + // Resource with id. + if (true) { + MockSrsConnection* id = new MockSrsConnection(); + manager.add_with_id("100", id); + EXPECT_EQ(1, (int)manager.size()); + EXPECT_TRUE(manager.find_by_id("100")); + EXPECT_TRUE(!manager.find_by_id("101")); + EXPECT_TRUE(!manager.find_by_name("100")); + + manager.remove(id); + srs_usleep(0); + ASSERT_EQ(0, (int)manager.size()); + } + + // Resource with name. + if (true) { + MockSrsConnection* name = new MockSrsConnection(); + manager.add_with_name("srs", name); + EXPECT_EQ(1, (int)manager.size()); + EXPECT_TRUE(manager.find_by_name("srs")); + EXPECT_TRUE(!manager.find_by_name("srs0")); + EXPECT_TRUE(!manager.find_by_id("srs")); + + manager.remove(name); + srs_usleep(0); + ASSERT_EQ(0, (int)manager.size()); + } + + // Resource with id and name. + if (true) { + MockSrsConnection* id_name = new MockSrsConnection(); + manager.add_with_id("100", id_name); + manager.add_with_id("200", id_name); + manager.add_with_name("srs", id_name); + manager.add_with_name("av", id_name); + EXPECT_EQ(1, (int)manager.size()); + EXPECT_TRUE(manager.find_by_name("srs")); + EXPECT_TRUE(manager.find_by_name("av")); + EXPECT_TRUE(manager.find_by_id("100")); + EXPECT_TRUE(manager.find_by_id("200")); + EXPECT_TRUE(!manager.find_by_name("srs0")); + EXPECT_TRUE(!manager.find_by_id("101")); + + manager.remove(id_name); + srs_usleep(0); + ASSERT_EQ(0, (int)manager.size()); + } + + // Resource with same id or name. + if (true) { + MockSrsConnection* conn0 = new MockSrsConnection(); + MockSrsConnection* conn1 = new MockSrsConnection(); + manager.add_with_id("100", conn0); + manager.add_with_id("100", conn1); + + EXPECT_TRUE(conn0 != manager.find_by_id("100")); + EXPECT_TRUE(conn1 == manager.find_by_id("100")); + + manager.remove(conn0); + srs_usleep(0); + ASSERT_EQ(1, (int)manager.size()); + + manager.remove(conn1); + srs_usleep(0); + ASSERT_EQ(0, (int)manager.size()); + } + } + + // Coroutine switch context, signal is lost. + if (true) { + SrsResourceManager manager("mgr"); + HELPER_EXPECT_SUCCESS(manager.start()); + EXPECT_EQ(0, (int)manager.size()); EXPECT_TRUE(manager.empty()); + + if (true) { // First connection, which will switch context when deleting. + MockSrsConnection* conn = new MockSrsConnection(); + conn->do_switch = true; + manager.add(conn); + EXPECT_EQ(1, (int)manager.size()); EXPECT_EQ(0, (int)manager.zombies_.size()); + + manager.remove(conn); // Remove conn to zombies. + EXPECT_EQ(1, (int)manager.size()); EXPECT_EQ(1, (int)manager.zombies_.size()); + + srs_usleep(0); // Switch to manager coroutine to try to free zombies. + EXPECT_EQ(0, (int)manager.size()); EXPECT_EQ(0, (int)manager.zombies_.size()); + } + + if (true) { // Now the previous conn switch back to here, and lost the signal. + MockSrsConnection* conn = new MockSrsConnection(); + manager.add(conn); + EXPECT_EQ(1, (int)manager.size()); EXPECT_EQ(0, (int)manager.zombies_.size()); + + manager.remove(conn); // Remove conn to zombies, signal is lost. + EXPECT_EQ(1, (int)manager.size()); EXPECT_EQ(1, (int)manager.zombies_.size()); + + srs_usleep(0); // Switch to manager, but no signal is triggered before, so conn will be freed by loop. + EXPECT_EQ(0, (int)manager.size()); EXPECT_EQ(0, (int)manager.zombies_.size()); + } + } +} + +VOID TEST(KernelRTCTest, StringDumpHexTest) +{ + // Typical normal case. + if (false) { + char data[8]; + data[0] = (char)0x3c; data[sizeof(data) - 2] = (char)0x67; data[sizeof(data) - 1] = (char)0xc3; + string r = srs_string_dumps_hex(data, sizeof(data), INT_MAX, 0, 0, 0); + EXPECT_EQ(16, (int)r.length()); + EXPECT_EQ('3', r.at(0)); EXPECT_EQ('c', r.at(1)); + EXPECT_EQ('c', r.at(r.length() - 2)); EXPECT_EQ('3', r.at(r.length() - 1)); + EXPECT_EQ('6', r.at(r.length() - 4)); EXPECT_EQ('7', r.at(r.length() - 3)); + } + + // Fill all buffer. + if (false) { + char data[8 * 1024]; + data[0] = (char)0x3c; data[sizeof(data) - 2] = (char)0x67; data[sizeof(data) - 1] = (char)0xc3; + string r = srs_string_dumps_hex(data, sizeof(data), INT_MAX, 0, 0, 0); + EXPECT_EQ(16 * 1024, (int)r.length()); + EXPECT_EQ('3', r.at(0)); EXPECT_EQ('c', r.at(1)); + EXPECT_EQ('c', r.at(r.length() - 2)); EXPECT_EQ('3', r.at(r.length() - 1)); + EXPECT_EQ('6', r.at(r.length() - 4)); EXPECT_EQ('7', r.at(r.length() - 3)); + } + + // Overflow 1 byte. + if (true) { + char data[8 * 1024 + 1]; + data[0] = (char)0x3c; data[sizeof(data) - 2] = (char)0x67; data[sizeof(data) - 1] = (char)0xc3; + string r = srs_string_dumps_hex(data, sizeof(data), INT_MAX, 0, 0, 0); + EXPECT_EQ(16 * 1024, (int)r.length()); + EXPECT_EQ('3', r.at(0)); EXPECT_EQ('c', r.at(1)); + EXPECT_EQ('6', r.at(r.length() - 2)); EXPECT_EQ('7', r.at(r.length() - 1)); + } + + // Fill all buffer, with seperator. + if (true) { + char data[5461]; + data[0] = (char)0x3c; data[sizeof(data) - 2] = (char)0x67; data[sizeof(data) - 1] = (char)0xc3; + string r = srs_string_dumps_hex(data, sizeof(data), INT_MAX, ',', 0, 0); + EXPECT_EQ(16383 - 1, (int)r.length()); + EXPECT_EQ('3', r.at(0)); EXPECT_EQ('c', r.at(1)); + EXPECT_EQ('c', r.at(r.length() - 2)); EXPECT_EQ('3', r.at(r.length() - 1)); + EXPECT_EQ('6', r.at(r.length() - 5)); EXPECT_EQ('7', r.at(r.length() - 4)); + } + + // Overflow 1 byte, with seperator. + if (true) { + char data[5461 + 1]; + data[0] = (char)0x3c; data[sizeof(data) - 2] = (char)0x67; data[sizeof(data) - 1] = (char)0xc3; + string r = srs_string_dumps_hex(data, sizeof(data), INT_MAX, ',', 0, 0); + EXPECT_EQ(16383 - 1, (int)r.length()); + EXPECT_EQ('3', r.at(0)); EXPECT_EQ('c', r.at(1)); + EXPECT_EQ('6', r.at(r.length() - 2)); EXPECT_EQ('7', r.at(r.length() - 1)); + } + + // Overflow 1 byte, with seperator and newline. + if (true) { + char data[5461 + 1]; + data[0] = (char)0x3c; data[sizeof(data) - 2] = (char)0x67; data[sizeof(data) - 1] = (char)0xc3; + string r = srs_string_dumps_hex(data, sizeof(data), INT_MAX, ',', 5461, '\n'); + EXPECT_EQ(16383 - 1, (int)r.length()); + EXPECT_EQ('3', r.at(0)); EXPECT_EQ('c', r.at(1)); + EXPECT_EQ('6', r.at(r.length() - 2)); EXPECT_EQ('7', r.at(r.length() - 1)); + } +} + +extern SSL_CTX* srs_build_dtls_ctx(SrsDtlsVersion version); + +class MockDtls +{ +public: + SSL_CTX* dtls_ctx; + SSL* dtls; + BIO* bio_in; + BIO* bio_out; + ISrsDtlsCallback* callback_; + bool handshake_done_for_us; + SrsDtlsRole role_; + SrsDtlsVersion version_; +public: + MockDtls(ISrsDtlsCallback* callback); + virtual ~MockDtls(); + srs_error_t initialize(std::string role, std::string version); + srs_error_t start_active_handshake(); + srs_error_t on_dtls(char* data, int nb_data); + srs_error_t do_handshake(); +}; + +MockDtls::MockDtls(ISrsDtlsCallback* callback) +{ + dtls_ctx = NULL; + dtls = NULL; + + callback_ = callback; + handshake_done_for_us = false; + + role_ = SrsDtlsRoleServer; + version_ = SrsDtlsVersionAuto; +} + +MockDtls::~MockDtls() +{ + if (dtls_ctx) { + SSL_CTX_free(dtls_ctx); + dtls_ctx = NULL; + } + + if (dtls) { + SSL_free(dtls); + dtls = NULL; + } +} + +srs_error_t MockDtls::initialize(std::string role, std::string version) +{ + role_ = SrsDtlsRoleServer; + if (role == "active") { + role_ = SrsDtlsRoleClient; + } + + if (version == "dtls1.0") { + version_ = SrsDtlsVersion1_0; + } else if (version == "dtls1.2") { + version_ = SrsDtlsVersion1_2; + } else { + version_ = SrsDtlsVersionAuto; + } + + dtls_ctx = srs_build_dtls_ctx(version_); + dtls = SSL_new(dtls_ctx); + srs_assert(dtls); + + if (role_ == SrsDtlsRoleClient) { + SSL_set_connect_state(dtls); + SSL_set_max_send_fragment(dtls, kRtpPacketSize); + } else { + SSL_set_accept_state(dtls); + } + + bio_in = BIO_new(BIO_s_mem()); + srs_assert(bio_in); + + bio_out = BIO_new(BIO_s_mem()); + srs_assert(bio_out); + + SSL_set_bio(dtls, bio_in, bio_out); + return srs_success; +} + +srs_error_t MockDtls::start_active_handshake() +{ + if (role_ == SrsDtlsRoleClient) { + return do_handshake(); + } + return srs_success; +} + +srs_error_t MockDtls::on_dtls(char* data, int nb_data) +{ + srs_error_t err = srs_success; + + srs_assert(BIO_reset(bio_in) == 1); + srs_assert(BIO_reset(bio_out) == 1); + srs_assert(BIO_write(bio_in, data, nb_data) > 0); + + if ((err = do_handshake()) != srs_success) { + return srs_error_wrap(err, "do handshake"); + } + + while (BIO_ctrl_pending(bio_in) > 0) { + char buf[8092]; + int nb = SSL_read(dtls, buf, sizeof(buf)); + if (nb <= 0) { + continue; + } + + if ((err = callback_->on_dtls_application_data(buf, nb)) != srs_success) { + return srs_error_wrap(err, "on DTLS data, size=%u", nb); + } + } + + return err; +} + +srs_error_t MockDtls::do_handshake() +{ + srs_error_t err = srs_success; + + int r0 = SSL_do_handshake(dtls); + int r1 = SSL_get_error(dtls, r0); + if (r0 < 0 && (r1 != SSL_ERROR_NONE && r1 != SSL_ERROR_WANT_READ && r1 != SSL_ERROR_WANT_WRITE)) { + return srs_error_new(ERROR_RTC_DTLS, "handshake r0=%d, r1=%d", r0, r1); + } + if (r1 == SSL_ERROR_NONE) { + handshake_done_for_us = true; + } + + uint8_t* data = NULL; + int size = BIO_get_mem_data(bio_out, &data); + + if (size > 0 && (err = callback_->write_dtls_data(data, size)) != srs_success) { + return srs_error_wrap(err, "dtls send size=%u", size); + } + + if (handshake_done_for_us) { + return callback_->on_dtls_handshake_done(); + } + + return err; +} + +class MockDtlsCallback : virtual public ISrsDtlsCallback, virtual public ISrsCoroutineHandler +{ +public: + SrsDtls* peer; + MockDtls* peer2; + SrsCoroutine* trd; + srs_error_t r0; + bool done; + std::vector samples; +public: + int nn_client_hello_lost; + int nn_server_hello_lost; + int nn_certificate_lost; + int nn_new_session_lost; + int nn_change_cipher_lost; +public: + // client -> server + int nn_client_hello; + // server -> client + int nn_server_hello; + // client -> server + int nn_certificate; + // server -> client + int nn_new_session; + int nn_change_cipher; +public: + MockDtlsCallback(); + virtual ~MockDtlsCallback(); + virtual srs_error_t on_dtls_handshake_done(); + virtual srs_error_t on_dtls_application_data(const char* data, const int len); + virtual srs_error_t write_dtls_data(void* data, int size); + virtual srs_error_t on_dtls_alert(std::string type, std::string desc); + virtual srs_error_t cycle(); +}; + +MockDtlsCallback::MockDtlsCallback() +{ + peer = NULL; + peer2 = NULL; + r0 = srs_success; + done = false; + trd = new SrsSTCoroutine("mock", this); + srs_assert(trd->start() == srs_success); + + nn_client_hello_lost = 0; + nn_server_hello_lost = 0; + nn_certificate_lost = 0; + nn_new_session_lost = 0; + nn_change_cipher_lost = 0; + + nn_client_hello = 0; + nn_server_hello = 0; + nn_certificate = 0; + nn_new_session = 0; + nn_change_cipher = 0; +} + +MockDtlsCallback::~MockDtlsCallback() +{ + srs_freep(trd); + srs_freep(r0); + for (vector::iterator it = samples.begin(); it != samples.end(); ++it) { + delete[] it->bytes; + } +} + +srs_error_t MockDtlsCallback::on_dtls_handshake_done() +{ + done = true; + return srs_success; +} + +srs_error_t MockDtlsCallback::on_dtls_application_data(const char* data, const int len) +{ + return srs_success; +} + +srs_error_t MockDtlsCallback::write_dtls_data(void* data, int size) +{ + int nn_lost = 0; + if (true) { + uint8_t content_type = 0; + if (size >= 1) { + content_type = (uint8_t)((uint8_t*)data)[0]; + } + + uint8_t handshake_type = 0; + if (size >= 14) { + handshake_type = (uint8_t)((uint8_t*)data)[13]; + } + + if (content_type == 22) { + if (handshake_type == 1) { + nn_lost = nn_client_hello_lost--; + nn_client_hello++; + } else if (handshake_type == 2) { + nn_lost = nn_server_hello_lost--; + nn_server_hello++; + } else if (handshake_type == 11) { + nn_lost = nn_certificate_lost--; + nn_certificate++; + } else if (handshake_type == 4) { + nn_lost = nn_new_session_lost--; + nn_new_session++; + } + } else if (content_type == 20) { + nn_lost = nn_change_cipher_lost--; + nn_change_cipher++; + } + } + + // Simulate to drop packet. + if (nn_lost > 0) { + return srs_success; + } + + // Send out it. + char* cp = new char[size]; + memcpy(cp, data, size); + + samples.push_back(SrsSample((char*)cp, size)); + + return srs_success; +} + +srs_error_t MockDtlsCallback::on_dtls_alert(std::string type, std::string desc) +{ + return srs_success; +} + +srs_error_t MockDtlsCallback::cycle() +{ + srs_error_t err = srs_success; + + while (err == srs_success) { + if ((err = trd->pull()) != srs_success) { + break; + } + + if (samples.empty()) { + srs_usleep(0); + continue; + } + + SrsSample p = samples.at(0); + samples.erase(samples.begin()); + + if (peer) { + err = peer->on_dtls((char*)p.bytes, p.size); + } else if (peer2) { + err = peer2->on_dtls((char*)p.bytes, p.size); + } + + srs_freepa(p.bytes); + } + + // Copy it for utest to check it. + r0 = srs_error_copy(err); + + return err; +} + +// Wait for mock io to done, try to switch to coroutine many times. +void mock_wait_dtls_io_done(int count = 100, int interval = 0) +{ + for (int i = 0; i < count; i++) { + srs_usleep(interval * SRS_UTIME_MILLISECONDS); + } +} + +// To avoid the crash when peer or peer2 is freed before io. +class MockBridgeDtlsIO +{ +private: + MockDtlsCallback* io_; +public: + MockBridgeDtlsIO(MockDtlsCallback* io, SrsDtls* peer, MockDtls* peer2) { + io_ = io; + io->peer = peer; + io->peer2 = peer2; + } + virtual ~MockBridgeDtlsIO() { + io_->peer = NULL; + io_->peer2 = NULL; + } +}; + +VOID TEST(KernelRTCTest, DTLSARQLimitTest) +{ + srs_error_t err = srs_success; + + // ClientHello lost, client retransmit the ClientHello. + if (true) { + MockDtlsCallback cio; SrsDtls client(&cio); + MockDtlsCallback sio; SrsDtls server(&sio); + MockBridgeDtlsIO b0(&cio, &server, NULL); MockBridgeDtlsIO b1(&sio, &client, NULL); + HELPER_EXPECT_SUCCESS(client.initialize("active", "dtls1.0")); + HELPER_EXPECT_SUCCESS(server.initialize("passive", "dtls1.0")); + + // Use very short interval for utest. + dynamic_cast(client.impl)->arq_interval = 1 * SRS_UTIME_MILLISECONDS; + HELPER_ARRAY_INIT(dynamic_cast(client.impl)->arq_to_ratios, 8, 1); + + // Lost 10 packets, total packets should be 9(max to 9). + // Note that only one server hello. + cio.nn_client_hello_lost = 10; + + HELPER_EXPECT_SUCCESS(client.start_active_handshake()); + mock_wait_dtls_io_done(10, 3); + + EXPECT_TRUE(sio.r0 == srs_success); + EXPECT_TRUE(cio.r0 == srs_success); + + EXPECT_FALSE(cio.done); + EXPECT_FALSE(sio.done); + + EXPECT_EQ(9, cio.nn_client_hello); + EXPECT_EQ(0, sio.nn_server_hello); + EXPECT_EQ(0, cio.nn_certificate); + EXPECT_EQ(0, sio.nn_new_session); + EXPECT_EQ(0, sio.nn_change_cipher); + } + + // Certificate lost, client retransmit the Certificate. + if (true) { + MockDtlsCallback cio; SrsDtls client(&cio); + MockDtlsCallback sio; SrsDtls server(&sio); + MockBridgeDtlsIO b0(&cio, &server, NULL); MockBridgeDtlsIO b1(&sio, &client, NULL); + HELPER_EXPECT_SUCCESS(client.initialize("active", "dtls1.0")); + HELPER_EXPECT_SUCCESS(server.initialize("passive", "dtls1.0")); + + // Use very short interval for utest. + dynamic_cast(client.impl)->arq_interval = 1 * SRS_UTIME_MILLISECONDS; + HELPER_ARRAY_INIT(dynamic_cast(client.impl)->arq_to_ratios, 8, 1); + + // Lost 10 packets, total packets should be 9(max to 9). + // Note that only one server NewSessionTicket. + cio.nn_certificate_lost = 10; + + HELPER_EXPECT_SUCCESS(client.start_active_handshake()); + mock_wait_dtls_io_done(10, 3); + + EXPECT_TRUE(sio.r0 == srs_success); + EXPECT_TRUE(cio.r0 == srs_success); + + EXPECT_FALSE(cio.done); + EXPECT_FALSE(sio.done); + + EXPECT_EQ(1, cio.nn_client_hello); + EXPECT_EQ(1, sio.nn_server_hello); + EXPECT_EQ(9, cio.nn_certificate); + EXPECT_EQ(0, sio.nn_new_session); + EXPECT_EQ(0, sio.nn_change_cipher); + } + + // ServerHello lost, client retransmit the ClientHello. + if (true) { + MockDtlsCallback cio; SrsDtls client(&cio); + MockDtlsCallback sio; SrsDtls server(&sio); + MockBridgeDtlsIO b0(&cio, &server, NULL); MockBridgeDtlsIO b1(&sio, &client, NULL); + HELPER_EXPECT_SUCCESS(client.initialize("active", "dtls1.0")); + HELPER_EXPECT_SUCCESS(server.initialize("passive", "dtls1.0")); + + // Use very short interval for utest. + dynamic_cast(client.impl)->arq_interval = 1 * SRS_UTIME_MILLISECONDS; + HELPER_ARRAY_INIT(dynamic_cast(client.impl)->arq_to_ratios, 8, 1); + + // Lost 10 packets, total packets should be 9(max to 9). + sio.nn_server_hello_lost = 10; + + HELPER_EXPECT_SUCCESS(client.start_active_handshake()); + mock_wait_dtls_io_done(10, 3); + + EXPECT_TRUE(sio.r0 == srs_success); + EXPECT_TRUE(cio.r0 == srs_success); + + EXPECT_FALSE(cio.done); + EXPECT_FALSE(sio.done); + + EXPECT_EQ(9, cio.nn_client_hello); + EXPECT_EQ(9, sio.nn_server_hello); + EXPECT_EQ(0, cio.nn_certificate); + EXPECT_EQ(0, sio.nn_new_session); + EXPECT_EQ(0, sio.nn_change_cipher); + } + + // NewSessionTicket lost, client retransmit the Certificate. + if (true) { + MockDtlsCallback cio; SrsDtls client(&cio); + MockDtlsCallback sio; SrsDtls server(&sio); + MockBridgeDtlsIO b0(&cio, &server, NULL); MockBridgeDtlsIO b1(&sio, &client, NULL); + HELPER_EXPECT_SUCCESS(client.initialize("active", "dtls1.0")); + HELPER_EXPECT_SUCCESS(server.initialize("passive", "dtls1.0")); + + // Use very short interval for utest. + dynamic_cast(client.impl)->arq_interval = 1 * SRS_UTIME_MILLISECONDS; + HELPER_ARRAY_INIT(dynamic_cast(client.impl)->arq_to_ratios, 8, 1); + + // Lost 10 packets, total packets should be 9(max to 9). + sio.nn_new_session_lost = 10; + + HELPER_EXPECT_SUCCESS(client.start_active_handshake()); + mock_wait_dtls_io_done(10, 3); + + EXPECT_TRUE(sio.r0 == srs_success); + EXPECT_TRUE(cio.r0 == srs_success); + + // Although the packet is lost, but it's done for server, and not done for client. + EXPECT_FALSE(cio.done); + EXPECT_TRUE(sio.done); + + EXPECT_EQ(1, cio.nn_client_hello); + EXPECT_EQ(1, sio.nn_server_hello); + EXPECT_EQ(9, cio.nn_certificate); + EXPECT_EQ(9, sio.nn_new_session); + EXPECT_EQ(0, sio.nn_change_cipher); + } +} + +VOID TEST(KernelRTCTest, DTLSClientARQTest) +{ + srs_error_t err = srs_success; + + // No ARQ, check the number of packets. + if (true) { + MockDtlsCallback cio; SrsDtls client(&cio); + MockDtlsCallback sio; SrsDtls server(&sio); + cio.peer = &server; sio.peer = &client; + HELPER_EXPECT_SUCCESS(client.initialize("active", "dtls1.0")); + HELPER_EXPECT_SUCCESS(server.initialize("passive", "dtls1.0")); + + HELPER_EXPECT_SUCCESS(client.start_active_handshake()); + mock_wait_dtls_io_done(30, 1); + + EXPECT_TRUE(sio.r0 == srs_success); + EXPECT_TRUE(cio.r0 == srs_success); + + EXPECT_TRUE(cio.done); + EXPECT_TRUE(sio.done); + + EXPECT_EQ(1, cio.nn_client_hello); + EXPECT_EQ(1, sio.nn_server_hello); + EXPECT_EQ(1, cio.nn_certificate); + EXPECT_EQ(1, sio.nn_new_session); + EXPECT_EQ(0, sio.nn_change_cipher); + } + + // ClientHello lost, client retransmit the ClientHello. + if (true) { + MockDtlsCallback cio; SrsDtls client(&cio); + MockDtlsCallback sio; SrsDtls server(&sio); + MockBridgeDtlsIO b0(&cio, &server, NULL); MockBridgeDtlsIO b1(&sio, &client, NULL); + HELPER_EXPECT_SUCCESS(client.initialize("active", "dtls1.0")); + HELPER_EXPECT_SUCCESS(server.initialize("passive", "dtls1.0")); + + // Use very short interval for utest. + dynamic_cast(client.impl)->arq_interval = 1 * SRS_UTIME_MILLISECONDS; + HELPER_ARRAY_INIT(dynamic_cast(client.impl)->arq_to_ratios, 8, 1); + + // Lost 2 packets, total packets should be 3. + // Note that only one server hello. + cio.nn_client_hello_lost = 2; + + HELPER_EXPECT_SUCCESS(client.start_active_handshake()); + mock_wait_dtls_io_done(10, 3); + + EXPECT_TRUE(sio.r0 == srs_success); + EXPECT_TRUE(cio.r0 == srs_success); + + EXPECT_TRUE(cio.done); + EXPECT_TRUE(sio.done); + + EXPECT_EQ(3, cio.nn_client_hello); + EXPECT_EQ(1, sio.nn_server_hello); + EXPECT_EQ(1, cio.nn_certificate); + EXPECT_EQ(1, sio.nn_new_session); + EXPECT_EQ(0, sio.nn_change_cipher); + } + + // Certificate lost, client retransmit the Certificate. + if (true) { + MockDtlsCallback cio; SrsDtls client(&cio); + MockDtlsCallback sio; SrsDtls server(&sio); + MockBridgeDtlsIO b0(&cio, &server, NULL); MockBridgeDtlsIO b1(&sio, &client, NULL); + HELPER_EXPECT_SUCCESS(client.initialize("active", "dtls1.0")); + HELPER_EXPECT_SUCCESS(server.initialize("passive", "dtls1.0")); + + // Use very short interval for utest. + dynamic_cast(client.impl)->arq_interval = 1 * SRS_UTIME_MILLISECONDS; + HELPER_ARRAY_INIT(dynamic_cast(client.impl)->arq_to_ratios, 8, 1); + + // Lost 2 packets, total packets should be 3. + // Note that only one server NewSessionTicket. + cio.nn_certificate_lost = 2; + + HELPER_EXPECT_SUCCESS(client.start_active_handshake()); + mock_wait_dtls_io_done(10, 3); + + EXPECT_TRUE(sio.r0 == srs_success); + EXPECT_TRUE(cio.r0 == srs_success); + + EXPECT_TRUE(cio.done); + EXPECT_TRUE(sio.done); + + EXPECT_EQ(1, cio.nn_client_hello); + EXPECT_EQ(1, sio.nn_server_hello); + EXPECT_EQ(3, cio.nn_certificate); + EXPECT_EQ(1, sio.nn_new_session); + EXPECT_EQ(0, sio.nn_change_cipher); + } +} + +VOID TEST(KernelRTCTest, DTLSServerARQTest) +{ + srs_error_t err = srs_success; + + // No ARQ, check the number of packets. + if (true) { + MockDtlsCallback cio; SrsDtls client(&cio); + MockDtlsCallback sio; SrsDtls server(&sio); + cio.peer = &server; sio.peer = &client; + HELPER_EXPECT_SUCCESS(client.initialize("active", "dtls1.0")); + HELPER_EXPECT_SUCCESS(server.initialize("passive", "dtls1.0")); + + HELPER_EXPECT_SUCCESS(client.start_active_handshake()); + mock_wait_dtls_io_done(30, 1); + + EXPECT_TRUE(sio.r0 == srs_success); + EXPECT_TRUE(cio.r0 == srs_success); + + EXPECT_TRUE(cio.done); + EXPECT_TRUE(sio.done); + + EXPECT_EQ(1, cio.nn_client_hello); + EXPECT_EQ(1, sio.nn_server_hello); + EXPECT_EQ(1, cio.nn_certificate); + EXPECT_EQ(1, sio.nn_new_session); + EXPECT_EQ(0, sio.nn_change_cipher); + } + + // ServerHello lost, client retransmit the ClientHello. + if (true) { + MockDtlsCallback cio; SrsDtls client(&cio); + MockDtlsCallback sio; SrsDtls server(&sio); + MockBridgeDtlsIO b0(&cio, &server, NULL); MockBridgeDtlsIO b1(&sio, &client, NULL); + HELPER_EXPECT_SUCCESS(client.initialize("active", "dtls1.0")); + HELPER_EXPECT_SUCCESS(server.initialize("passive", "dtls1.0")); + + // Use very short interval for utest. + dynamic_cast(client.impl)->arq_interval = 1 * SRS_UTIME_MILLISECONDS; + HELPER_ARRAY_INIT(dynamic_cast(client.impl)->arq_to_ratios, 8, 1); + + // Lost 2 packets, total packets should be 3. + sio.nn_server_hello_lost = 2; + + HELPER_EXPECT_SUCCESS(client.start_active_handshake()); + mock_wait_dtls_io_done(10, 3); + + EXPECT_TRUE(sio.r0 == srs_success); + EXPECT_TRUE(cio.r0 == srs_success); + + EXPECT_TRUE(cio.done); + EXPECT_TRUE(sio.done); + + EXPECT_EQ(3, cio.nn_client_hello); + EXPECT_EQ(3, sio.nn_server_hello); + EXPECT_EQ(1, cio.nn_certificate); + EXPECT_EQ(1, sio.nn_new_session); + EXPECT_EQ(0, sio.nn_change_cipher); + } + + // NewSessionTicket lost, client retransmit the Certificate. + if (true) { + MockDtlsCallback cio; SrsDtls client(&cio); + MockDtlsCallback sio; SrsDtls server(&sio); + MockBridgeDtlsIO b0(&cio, &server, NULL); MockBridgeDtlsIO b1(&sio, &client, NULL); + HELPER_EXPECT_SUCCESS(client.initialize("active", "dtls1.0")); + HELPER_EXPECT_SUCCESS(server.initialize("passive", "dtls1.0")); + + // Use very short interval for utest. + dynamic_cast(client.impl)->arq_interval = 1 * SRS_UTIME_MILLISECONDS; + HELPER_ARRAY_INIT(dynamic_cast(client.impl)->arq_to_ratios, 8, 1); + + // Lost 2 packets, total packets should be 3. + sio.nn_new_session_lost = 2; + + HELPER_EXPECT_SUCCESS(client.start_active_handshake()); + mock_wait_dtls_io_done(10, 3); + + EXPECT_TRUE(sio.r0 == srs_success); + EXPECT_TRUE(cio.r0 == srs_success); + + EXPECT_TRUE(cio.done); + EXPECT_TRUE(sio.done); + + EXPECT_EQ(1, cio.nn_client_hello); + EXPECT_EQ(1, sio.nn_server_hello); + EXPECT_EQ(3, cio.nn_certificate); + EXPECT_EQ(3, sio.nn_new_session); + EXPECT_EQ(0, sio.nn_change_cipher); + } +} + +struct DTLSFlowCase +{ + int id; + + string ClientVersion; + string ServerVersion; + + bool ClientDone; + bool ServerDone; + + bool ClientError; + bool ServerError; +}; + +std::ostream& operator<< (std::ostream& stream, const DTLSFlowCase& c) +{ + stream << "Case #" << c.id + << ", client(" << c.ClientVersion << ",done=" << c.ClientDone << ",err=" << c.ClientError << ")" + << ", server(" << c.ServerVersion << ",done=" << c.ServerDone << ",err=" << c.ServerError << ")"; + return stream; +} + +VOID TEST(KernelRTCTest, DTLSClientFlowTest) +{ + srs_error_t err = srs_success; + + DTLSFlowCase cases[] = { + // OK, Client, Server: DTLS v1.0 + {0, "dtls1.0", "dtls1.0", true, true, false, false}, + // OK, Client, Server: DTLS v1.2 + {1, "dtls1.2", "dtls1.2", true, true, false, false}, + // OK, Client: DTLS v1.0, Server: DTLS auto(v1.0 or v1.2). + {2, "dtls1.0", "auto", true, true, false, false}, + // OK, Client: DTLS v1.2, Server: DTLS auto(v1.0 or v1.2). + {3, "dtls1.2", "auto", true, true, false, false}, + // OK, Client: DTLS auto(v1.0 or v1.2), Server: DTLS v1.0 + {4, "auto", "dtls1.0", true, true, false, false}, + // OK, Client: DTLS auto(v1.0 or v1.2), Server: DTLS v1.0 + {5, "auto", "dtls1.2", true, true, false, false}, + // Fail, Client: DTLS v1.0, Server: DTLS v1.2 + {6, "dtls1.0", "dtls1.2", false, false, false, true}, + // Fail, Client: DTLS v1.2, Server: DTLS v1.0 + {7, "dtls1.2", "dtls1.0", false, false, true, false}, + }; + + for (int i = 0; i < (int)(sizeof(cases) / sizeof(DTLSFlowCase)); i++) { + DTLSFlowCase c = cases[i]; + + MockDtlsCallback cio; SrsDtls client(&cio); + MockDtlsCallback sio; MockDtls server(&sio); + MockBridgeDtlsIO b0(&cio, NULL, &server); MockBridgeDtlsIO b1(&sio, &client, NULL); + HELPER_EXPECT_SUCCESS(client.initialize("active", c.ClientVersion)) << c; + HELPER_EXPECT_SUCCESS(server.initialize("passive", c.ServerVersion)) << c; + + HELPER_EXPECT_SUCCESS(client.start_active_handshake()) << c; + mock_wait_dtls_io_done(); + + // Note that the cio error is generated from server, vice versa. + EXPECT_EQ(c.ClientError, sio.r0 != srs_success) << c; + EXPECT_EQ(c.ServerError, cio.r0 != srs_success) << c; + + EXPECT_EQ(c.ClientDone, cio.done) << c; + EXPECT_EQ(c.ServerDone, sio.done) << c; + } +} + +VOID TEST(KernelRTCTest, DTLSServerFlowTest) +{ + srs_error_t err = srs_success; + + DTLSFlowCase cases[] = { + // OK, Client, Server: DTLS v1.0 + {0, "dtls1.0", "dtls1.0", true, true, false, false}, + // OK, Client, Server: DTLS v1.2 + {1, "dtls1.2", "dtls1.2", true, true, false, false}, + // OK, Client: DTLS v1.0, Server: DTLS auto(v1.0 or v1.2). + {2, "dtls1.0", "auto", true, true, false, false}, + // OK, Client: DTLS v1.2, Server: DTLS auto(v1.0 or v1.2). + {3, "dtls1.2", "auto", true, true, false, false}, + // OK, Client: DTLS auto(v1.0 or v1.2), Server: DTLS v1.0 + {4, "auto", "dtls1.0", true, true, false, false}, + // OK, Client: DTLS auto(v1.0 or v1.2), Server: DTLS v1.0 + {5, "auto", "dtls1.2", true, true, false, false}, + // Fail, Client: DTLS v1.0, Server: DTLS v1.2 + {6, "dtls1.0", "dtls1.2", false, false, false, true}, + // Fail, Client: DTLS v1.2, Server: DTLS v1.0 + {7, "dtls1.2", "dtls1.0", false, false, true, false}, + }; + + for (int i = 0; i < (int)(sizeof(cases) / sizeof(DTLSFlowCase)); i++) { + DTLSFlowCase c = cases[i]; + + MockDtlsCallback cio; MockDtls client(&cio); + MockDtlsCallback sio; SrsDtls server(&sio); + MockBridgeDtlsIO b0(&cio, &server, NULL); MockBridgeDtlsIO b1(&sio, NULL, &client); + HELPER_EXPECT_SUCCESS(client.initialize("active", c.ClientVersion)) << c; + HELPER_EXPECT_SUCCESS(server.initialize("passive", c.ServerVersion)) << c; + + HELPER_EXPECT_SUCCESS(client.start_active_handshake()) << c; + mock_wait_dtls_io_done(); + + // Note that the cio error is generated from server, vice versa. + EXPECT_EQ(c.ClientError, sio.r0 != srs_success) << c; + EXPECT_EQ(c.ServerError, cio.r0 != srs_success) << c; + + EXPECT_EQ(c.ClientDone, cio.done) << c; + EXPECT_EQ(c.ServerDone, sio.done) << c; + } +} + +VOID TEST(KernelRTCTest, SequenceCompare) +{ + if (true) { + EXPECT_EQ(0, srs_rtp_seq_distance(0, 0)); + EXPECT_EQ(0, srs_rtp_seq_distance(1, 1)); + EXPECT_EQ(0, srs_rtp_seq_distance(3, 3)); + + EXPECT_EQ(1, srs_rtp_seq_distance(0, 1)); + EXPECT_EQ(-1, srs_rtp_seq_distance(1, 0)); + EXPECT_EQ(1, srs_rtp_seq_distance(65535, 0)); + } + + if (true) { + EXPECT_FALSE(srs_rtp_seq_distance(1, 1) > 0); + EXPECT_TRUE(srs_rtp_seq_distance(65534, 65535) > 0); + EXPECT_TRUE(srs_rtp_seq_distance(0, 1) > 0); + EXPECT_TRUE(srs_rtp_seq_distance(255, 256) > 0); + + EXPECT_TRUE(srs_rtp_seq_distance(65535, 0) > 0); + EXPECT_TRUE(srs_rtp_seq_distance(65280, 0) > 0); + EXPECT_TRUE(srs_rtp_seq_distance(65535, 255) > 0); + EXPECT_TRUE(srs_rtp_seq_distance(65280, 255) > 0); + + EXPECT_FALSE(srs_rtp_seq_distance(0, 65535) > 0); + EXPECT_FALSE(srs_rtp_seq_distance(0, 65280) > 0); + EXPECT_FALSE(srs_rtp_seq_distance(255, 65535) > 0); + EXPECT_FALSE(srs_rtp_seq_distance(255, 65280) > 0); + + // Note that srs_rtp_seq_distance(0, 32768)>0 is TRUE by https://mp.weixin.qq.com/s/JZTInmlB9FUWXBQw_7NYqg + // but for WebRTC jitter buffer it's FALSE and we follow it. + EXPECT_FALSE(srs_rtp_seq_distance(0, 32768) > 0); + // It's FALSE definitely. + EXPECT_FALSE(srs_rtp_seq_distance(32768, 0) > 0); + } + + if (true) { + EXPECT_FALSE(srs_seq_is_newer(1, 1)); + EXPECT_TRUE(srs_seq_is_newer(65535, 65534)); + EXPECT_TRUE(srs_seq_is_newer(1, 0)); + EXPECT_TRUE(srs_seq_is_newer(256, 255)); + + EXPECT_TRUE(srs_seq_is_newer(0, 65535)); + EXPECT_TRUE(srs_seq_is_newer(0, 65280)); + EXPECT_TRUE(srs_seq_is_newer(255, 65535)); + EXPECT_TRUE(srs_seq_is_newer(255, 65280)); + + EXPECT_FALSE(srs_seq_is_newer(65535, 0)); + EXPECT_FALSE(srs_seq_is_newer(65280, 0)); + EXPECT_FALSE(srs_seq_is_newer(65535, 255)); + EXPECT_FALSE(srs_seq_is_newer(65280, 255)); + + EXPECT_FALSE(srs_seq_is_newer(32768, 0)); + EXPECT_FALSE(srs_seq_is_newer(0, 32768)); + } + + if (true) { + EXPECT_FALSE(srs_seq_distance(1, 1) > 0); + EXPECT_TRUE(srs_seq_distance(65535, 65534) > 0); + EXPECT_TRUE(srs_seq_distance(1, 0) > 0); + EXPECT_TRUE(srs_seq_distance(256, 255) > 0); + + EXPECT_TRUE(srs_seq_distance(0, 65535) > 0); + EXPECT_TRUE(srs_seq_distance(0, 65280) > 0); + EXPECT_TRUE(srs_seq_distance(255, 65535) > 0); + EXPECT_TRUE(srs_seq_distance(255, 65280) > 0); + + EXPECT_FALSE(srs_seq_distance(65535, 0) > 0); + EXPECT_FALSE(srs_seq_distance(65280, 0) > 0); + EXPECT_FALSE(srs_seq_distance(65535, 255) > 0); + EXPECT_FALSE(srs_seq_distance(65280, 255) > 0); + + EXPECT_FALSE(srs_seq_distance(32768, 0) > 0); + EXPECT_FALSE(srs_seq_distance(0, 32768) > 0); + } + + if (true) { + EXPECT_FALSE(srs_seq_is_rollback(1, 1)); + EXPECT_FALSE(srs_seq_is_rollback(65535, 65534)); + EXPECT_FALSE(srs_seq_is_rollback(1, 0)); + EXPECT_FALSE(srs_seq_is_rollback(256, 255)); + + EXPECT_TRUE(srs_seq_is_rollback(0, 65535)); + EXPECT_TRUE(srs_seq_is_rollback(0, 65280)); + EXPECT_TRUE(srs_seq_is_rollback(255, 65535)); + EXPECT_TRUE(srs_seq_is_rollback(255, 65280)); + + EXPECT_FALSE(srs_seq_is_rollback(65535, 0)); + EXPECT_FALSE(srs_seq_is_rollback(65280, 0)); + EXPECT_FALSE(srs_seq_is_rollback(65535, 255)); + EXPECT_FALSE(srs_seq_is_rollback(65280, 255)); + + EXPECT_FALSE(srs_seq_is_rollback(32768, 0)); + EXPECT_FALSE(srs_seq_is_rollback(0, 32768)); + } +} + +VOID TEST(KernelRTCTest, DecodeHeaderWithPadding) +{ + srs_error_t err = srs_success; + + // RTP packet cipher with padding. + uint8_t data[] = { + 0xb0, 0x66, 0x0a, 0x97, 0x7e, 0x32, 0x10, 0xee, 0x7d, 0xe6, 0xd0, 0xe6, 0xbe, 0xde, 0x00, 0x01, 0x31, 0x00, 0x16, 0x00, 0x25, 0xcd, 0xef, 0xce, 0xd7, 0x24, 0x57, 0xd9, 0x3c, 0xfd, 0x0f, 0x77, 0xea, 0x89, 0x61, 0xcb, 0x67, 0xa1, 0x65, 0x4a, 0x7d, 0x1f, 0x10, 0x4e, 0xed, 0x5e, 0x74, 0xe8, 0x7e, 0xce, 0x4d, 0xcf, 0xd5, 0x58, 0xd1, 0x2c, 0x30, 0xf1, 0x26, 0x62, 0xd3, 0x0c, 0x6a, 0x48, + 0x29, 0x83, 0xd2, 0x3d, 0x30, 0xa1, 0x7c, 0x6f, 0xa1, 0x5c, 0x9f, 0x08, 0x43, 0x50, 0x34, 0x2b, 0x3c, 0xa1, 0xf0, 0xb0, 0xe2, 0x0e, 0xc8, 0xf9, 0x79, 0x06, 0x51, 0xfe, 0xbb, 0x13, 0x54, 0x3e, 0xb4, 0x37, 0x91, 0x96, 0x94, 0xb7, 0x61, 0x2e, 0x97, 0x09, 0xb8, 0x27, 0x10, 0x6a, 0x2e, 0xe0, 0x62, 0xe4, 0x37, 0x41, 0xab, 0x4f, 0xbf, 0x06, 0x0a, 0x89, 0x80, 0x18, 0x0d, 0x6e, 0x0a, 0xd1, + 0x9f, 0xf1, 0xdd, 0x12, 0xbd, 0x1a, 0x70, 0x72, 0x33, 0xcc, 0xaa, 0x82, 0xdf, 0x92, 0x90, 0x45, 0xda, 0x3e, 0x88, 0x1c, 0x63, 0x83, 0xbc, 0xc8, 0xff, 0xfd, 0x64, 0xe3, 0xd4, 0x68, 0xe6, 0xc8, 0xdc, 0x81, 0x72, 0x5f, 0x38, 0x5b, 0xab, 0x63, 0x7b, 0x96, 0x03, 0x03, 0x54, 0xc5, 0xe6, 0x35, 0xf6, 0x86, 0xcc, 0xac, 0x74, 0xb0, 0xf4, 0x07, 0x9e, 0x19, 0x30, 0x4f, 0x90, 0xd6, 0xdb, 0x8b, + 0x0d, 0xcb, 0x76, 0x71, 0x55, 0xc7, 0x4a, 0x6e, 0x1b, 0xb4, 0x42, 0xf4, 0xae, 0x81, 0x17, 0x08, 0xb7, 0x50, 0x61, 0x5a, 0x42, 0xde, 0x1f, 0xf3, 0xfd, 0xe2, 0x30, 0xff, 0xb7, 0x07, 0xdd, 0x4b, 0xb1, 0x00, 0xd9, 0x6c, 0x43, 0xa0, 0x9a, 0xfa, 0xbb, 0xec, 0xdf, 0x51, 0xce, 0x33, 0x79, 0x4b, 0xa7, 0x02, 0xf3, 0x96, 0x62, 0x42, 0x25, 0x28, 0x85, 0xa7, 0xe7, 0xd1, 0xd3, 0xf3, + }; + + // If not plaintext, the padding in body is invalid, + // so it will fail if decoding the header with padding. + if (true) { + SrsBuffer b((char*)data, sizeof(data)); SrsRtpHeader h; + HELPER_EXPECT_FAILED(h.decode(&b)); + } + + // Should ok if ignore padding. + if (true) { + SrsBuffer b((char*)data, sizeof(data)); SrsRtpHeader h; + h.ignore_padding(true); + HELPER_EXPECT_SUCCESS(h.decode(&b)); + } +} + +VOID TEST(KernelRTCTest, DumpsHexToString) +{ + if (true) { + EXPECT_STREQ("", srs_string_dumps_hex(NULL, 0).c_str()); + } + + if (true) { + uint8_t data[] = {0, 0, 0, 0}; + EXPECT_STREQ("00 00 00 00", srs_string_dumps_hex((const char*)data, sizeof(data)).c_str()); + } + + if (true) { + uint8_t data[] = {0, 1, 2, 3}; + EXPECT_STREQ("00 01 02 03", srs_string_dumps_hex((const char*)data, sizeof(data)).c_str()); + } + + if (true) { + uint8_t data[] = {0xa, 3, 0xf, 3}; + EXPECT_STREQ("0a 03 0f 03", srs_string_dumps_hex((const char*)data, sizeof(data)).c_str()); + } + + if (true) { + uint8_t data[] = {0xa, 3, 0xf, 3}; + EXPECT_STREQ("0a,03,0f,03", srs_string_dumps_hex((const char*)data, sizeof(data), INT_MAX, ',', 0, 0).c_str()); + EXPECT_STREQ("0a030f03", srs_string_dumps_hex((const char*)data, sizeof(data), INT_MAX, '\0', 0, 0).c_str()); + EXPECT_STREQ("0a,03,\n0f,03", srs_string_dumps_hex((const char*)data, sizeof(data), INT_MAX, ',', 2, '\n').c_str()); + EXPECT_STREQ("0a,03,0f,03", srs_string_dumps_hex((const char*)data, sizeof(data), INT_MAX, ',', 2,'\0').c_str()); + } + + if (true) { + uint8_t data[] = {0xa, 3, 0xf}; + EXPECT_STREQ("0a 03", srs_string_dumps_hex((const char*)data, sizeof(data), 2).c_str()); + } +} + +VOID TEST(KernelRTCTest, NACKFetchRTPPacket) +{ + SrsRtcConnection s(NULL, SrsContextId()); + SrsRtcPlayStream play(&s, SrsContextId()); + + SrsRtcTrackDescription ds; + SrsRtcVideoSendTrack *track = new SrsRtcVideoSendTrack(&s, &ds); + + // The RTP queue will free the packet. + if (true) { + SrsRtpPacket2* pkt = new SrsRtpPacket2(); + pkt->header.set_sequence(100); + track->rtp_queue_->set(pkt->header.get_sequence(), pkt); + } + + // If sequence not match, packet not found. + if (true) { + SrsRtpPacket2* pkt = track->fetch_rtp_packet(10); + EXPECT_TRUE(pkt == NULL); + } + + // The sequence matched, we got the packet. + if (true) { + SrsRtpPacket2* pkt = track->fetch_rtp_packet(100); + EXPECT_TRUE(pkt != NULL); + } + + // NACK special case. + if (true) { + // The sequence is the "same", 1100%1000 is 100, + // so we can also get it from the RTP queue. + SrsRtpPacket2* pkt = track->rtp_queue_->at(1100); + EXPECT_TRUE(pkt != NULL); + + // But the track requires exactly match, so it returns NULL. + pkt = track->fetch_rtp_packet(1100); + EXPECT_TRUE(pkt == NULL); + } +} + +extern bool srs_is_stun(const uint8_t* data, size_t size); +extern bool srs_is_dtls(const uint8_t* data, size_t len); +extern bool srs_is_rtp_or_rtcp(const uint8_t* data, size_t len); +extern bool srs_is_rtcp(const uint8_t* data, size_t len); + +#define mock_arr_push(arr, elem) arr.push_back(vector(elem, elem + sizeof(elem))) + +VOID TEST(KernelRTCTest, TestPacketType) +{ + // DTLS packet. + vector< vector > dtlss; + if (true) { uint8_t data[13] = {20}; mock_arr_push(dtlss, data); } // change_cipher_spec(20) + if (true) { uint8_t data[13] = {21}; mock_arr_push(dtlss, data); } // alert(21) + if (true) { uint8_t data[13] = {22}; mock_arr_push(dtlss, data); } // handshake(22) + if (true) { uint8_t data[13] = {23}; mock_arr_push(dtlss, data); } // application_data(23) + for (int i = 0; i < (int)dtlss.size(); i++) { + vector elem = dtlss.at(i); + EXPECT_TRUE(srs_is_dtls(&elem[0], (size_t)elem.size())); + } + + for (int i = 0; i < (int)dtlss.size(); i++) { + vector elem = dtlss.at(i); + EXPECT_FALSE(srs_is_dtls(&elem[0], 1)); + + // All DTLS should not be other packets. + EXPECT_FALSE(srs_is_stun(&elem[0], (size_t)elem.size())); + EXPECT_TRUE(srs_is_dtls(&elem[0], (size_t)elem.size())); + EXPECT_FALSE(srs_is_rtp_or_rtcp(&elem[0], (size_t)elem.size())); + EXPECT_FALSE(srs_is_rtcp(&elem[0], (size_t)elem.size())); + } + + // STUN packet. + vector< vector > stuns; + if (true) { uint8_t data[1] = {0}; mock_arr_push(stuns, data); } // binding request. + if (true) { uint8_t data[1] = {1}; mock_arr_push(stuns, data); } // binding success response. + for (int i = 0; i < (int)stuns.size(); i++) { + vector elem = stuns.at(i); + EXPECT_TRUE(srs_is_stun(&elem[0], (size_t)elem.size())); + } + + for (int i = 0; i < (int)stuns.size(); i++) { + vector elem = stuns.at(i); + EXPECT_FALSE(srs_is_stun(&elem[0], 0)); + + // All STUN should not be other packets. + EXPECT_TRUE(srs_is_stun(&elem[0], (size_t)elem.size())); + EXPECT_FALSE(srs_is_dtls(&elem[0], (size_t)elem.size())); + EXPECT_FALSE(srs_is_rtp_or_rtcp(&elem[0], (size_t)elem.size())); + EXPECT_FALSE(srs_is_rtcp(&elem[0], (size_t)elem.size())); + } + + // RTCP packet. + vector< vector > rtcps; + if (true) { uint8_t data[12] = {0x80, 192}; mock_arr_push(rtcps, data); } + if (true) { uint8_t data[12] = {0x80, 200}; mock_arr_push(rtcps, data); } // SR + if (true) { uint8_t data[12] = {0x80, 201}; mock_arr_push(rtcps, data); } // RR + if (true) { uint8_t data[12] = {0x80, 202}; mock_arr_push(rtcps, data); } // SDES + if (true) { uint8_t data[12] = {0x80, 203}; mock_arr_push(rtcps, data); } // BYE + if (true) { uint8_t data[12] = {0x80, 204}; mock_arr_push(rtcps, data); } // APP + if (true) { uint8_t data[12] = {0x80, 223}; mock_arr_push(rtcps, data); } + for (int i = 0; i < (int)rtcps.size(); i++) { + vector elem = rtcps.at(i); + EXPECT_TRUE(srs_is_rtcp(&elem[0], (size_t)elem.size())); + } + + for (int i = 0; i < (int)rtcps.size(); i++) { + vector elem = rtcps.at(i); + EXPECT_FALSE(srs_is_rtcp(&elem[0], 2)); + + // All RTCP should not be other packets. + EXPECT_FALSE(srs_is_stun(&elem[0], (size_t)elem.size())); + EXPECT_FALSE(srs_is_dtls(&elem[0], (size_t)elem.size())); + EXPECT_TRUE(srs_is_rtp_or_rtcp(&elem[0], (size_t)elem.size())); + EXPECT_TRUE(srs_is_rtcp(&elem[0], (size_t)elem.size())); + } + + // RTP packet. + vector< vector > rtps; + if (true) { uint8_t data[12] = {0x80, 96}; mock_arr_push(rtps, data); } + if (true) { uint8_t data[12] = {0x80, 127}; mock_arr_push(rtps, data); } + if (true) { uint8_t data[12] = {0x80, 224}; mock_arr_push(rtps, data); } + if (true) { uint8_t data[12] = {0x80, 255}; mock_arr_push(rtps, data); } + for (int i = 0; i < (int)rtps.size(); i++) { + vector elem = rtps.at(i); + EXPECT_TRUE(srs_is_rtp_or_rtcp(&elem[0], (size_t)elem.size())); + EXPECT_FALSE(srs_is_rtcp(&elem[0], (size_t)elem.size())); + } + + for (int i = 0; i < (int)rtps.size(); i++) { + vector elem = rtps.at(i); + EXPECT_FALSE(srs_is_rtp_or_rtcp(&elem[0], 2)); + + // All RTP should not be other packets. + EXPECT_FALSE(srs_is_stun(&elem[0], (size_t)elem.size())); + EXPECT_FALSE(srs_is_dtls(&elem[0], (size_t)elem.size())); + EXPECT_TRUE(srs_is_rtp_or_rtcp(&elem[0], (size_t)elem.size())); + EXPECT_FALSE(srs_is_rtcp(&elem[0], (size_t)elem.size())); + } +} + +VOID TEST(KernelRTCTest, DefaultTrackStatus) +{ + // By default, track is disabled. + if (true) { + SrsRtcTrackDescription td; + + // The track must default to disable, that is, the active is false. + EXPECT_FALSE(td.is_active_); + } + + // Enable it by player. + if (true) { + SrsRtcConnection s(NULL, SrsContextId()); SrsRtcPlayStream play(&s, SrsContextId()); + SrsRtcAudioSendTrack* audio; SrsRtcVideoSendTrack *video; + + if (true) { + SrsRtcTrackDescription ds; ds.type_ = "audio"; ds.id_ = "NSNWOn19NDn12o8nNeji2"; ds.ssrc_ = 100; + play.audio_tracks_[ds.ssrc_] = audio = new SrsRtcAudioSendTrack(&s, &ds); + } + if (true) { + SrsRtcTrackDescription ds; ds.type_ = "video"; ds.id_ = "VMo22nfLDn122nfnDNL2"; ds.ssrc_ = 200; + play.video_tracks_[ds.ssrc_] = video = new SrsRtcVideoSendTrack(&s, &ds); + } + EXPECT_FALSE(audio->get_track_status()); + EXPECT_FALSE(video->get_track_status()); + + play.set_all_tracks_status(true); + EXPECT_TRUE(audio->get_track_status()); + EXPECT_TRUE(video->get_track_status()); + } + + // Enable it by publisher. + if (true) { + SrsRtcConnection s(NULL, SrsContextId()); SrsRtcPublishStream publish(&s, SrsContextId()); + SrsRtcAudioRecvTrack* audio; SrsRtcVideoRecvTrack *video; + + if (true) { + SrsRtcTrackDescription ds; ds.type_ = "audio"; ds.id_ = "NSNWOn19NDn12o8nNeji2"; ds.ssrc_ = 100; + audio = new SrsRtcAudioRecvTrack(&s, &ds); publish.audio_tracks_.push_back(audio); + } + if (true) { + SrsRtcTrackDescription ds; ds.type_ = "video"; ds.id_ = "VMo22nfLDn122nfnDNL2"; ds.ssrc_ = 200; + video = new SrsRtcVideoRecvTrack(&s, &ds); publish.video_tracks_.push_back(video); + } + EXPECT_FALSE(audio->get_track_status()); + EXPECT_FALSE(video->get_track_status()); + + publish.set_all_tracks_status(true); + EXPECT_TRUE(audio->get_track_status()); + EXPECT_TRUE(video->get_track_status()); + } +} + diff --git a/trunk/src/utest/srs_utest_rtc.hpp b/trunk/src/utest/srs_utest_rtc.hpp new file mode 100644 index 000000000..4fa39fbb8 --- /dev/null +++ b/trunk/src/utest/srs_utest_rtc.hpp @@ -0,0 +1,33 @@ +/* +The MIT License (MIT) + +Copyright (c) 2013-2020 Winlin + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef SRS_UTEST_RTC_HPP +#define SRS_UTEST_RTC_HPP + +/* +#include +*/ +#include + +#endif + diff --git a/trunk/src/utest/srs_utest_rtmp.cpp b/trunk/src/utest/srs_utest_rtmp.cpp index a365673c1..3450d5ba8 100644 --- a/trunk/src/utest/srs_utest_rtmp.cpp +++ b/trunk/src/utest/srs_utest_rtmp.cpp @@ -92,7 +92,7 @@ VOID TEST(ProtocolRTMPTest, PacketEncode) MockPacket pkt; pkt.size = 1024; - SrsBuffer b; + SrsBuffer b(NULL, 0); HELPER_EXPECT_FAILED(pkt.decode(&b)); } @@ -3139,7 +3139,7 @@ VOID TEST(ProtocolRTMPTest, MergeReadHandler) char* _strcpy(const char* src) { - return strcpy(new char[strlen(src)], src); + return strcpy(new char[strlen(src) + 1], src); } VOID TEST(ProtocolRTMPTest, CreateRTMPMessage) diff --git a/trunk/src/utest/srs_utest_service.cpp b/trunk/src/utest/srs_utest_service.cpp index da8c8cec0..4562d27bf 100644 --- a/trunk/src/utest/srs_utest_service.cpp +++ b/trunk/src/utest/srs_utest_service.cpp @@ -38,20 +38,36 @@ using namespace std; #include #include #include +#include #include #include -class MockSrsConnection : public ISrsConnection +MockSrsConnection::MockSrsConnection() { -public: - MockSrsConnection() { + do_switch = false; +} + +MockSrsConnection::~MockSrsConnection() +{ + if (do_switch) { + srs_usleep(0); } - virtual ~MockSrsConnection() { - } - virtual std::string remote_ip() { - return "127.0.0.1"; - } -}; +} + +const SrsContextId& MockSrsConnection::get_id() +{ + return _srs_context->get_id(); +} + +std::string MockSrsConnection::desc() +{ + return "Mock"; +} + +std::string MockSrsConnection::remote_ip() +{ + return "127.0.0.1"; +} VOID TEST(ServiceTimeTest, TimeUnit) { @@ -110,7 +126,7 @@ VOID TEST(TCPServerTest, PingPong) SrsTcpClient c(_srs_tmp_host, _srs_tmp_port, _srs_tmp_timeout); HELPER_EXPECT_SUCCESS(c.connect()); - srs_usleep(100 * SRS_UTIME_MILLISECONDS); + srs_usleep(30 * SRS_UTIME_MILLISECONDS); EXPECT_TRUE(h.fd != NULL); } @@ -123,8 +139,8 @@ VOID TEST(TCPServerTest, PingPong) HELPER_EXPECT_SUCCESS(c.connect()); SrsStSocket skt; - srs_usleep(100 * SRS_UTIME_MILLISECONDS); -#ifdef SRS_AUTO_OSX + srs_usleep(30 * SRS_UTIME_MILLISECONDS); +#ifdef SRS_OSX ASSERT_TRUE(h.fd != NULL); #endif HELPER_EXPECT_SUCCESS(skt.initialize(h.fd)); @@ -145,8 +161,8 @@ VOID TEST(TCPServerTest, PingPong) HELPER_EXPECT_SUCCESS(c.connect()); SrsStSocket skt; - srs_usleep(100 * SRS_UTIME_MILLISECONDS); -#ifdef SRS_AUTO_OSX + srs_usleep(30 * SRS_UTIME_MILLISECONDS); +#ifdef SRS_OSX ASSERT_TRUE(h.fd != NULL); #endif HELPER_EXPECT_SUCCESS(skt.initialize(h.fd)); @@ -169,8 +185,8 @@ VOID TEST(TCPServerTest, PingPong) HELPER_EXPECT_SUCCESS(c.connect()); SrsStSocket skt; - srs_usleep(100 * SRS_UTIME_MILLISECONDS); -#ifdef SRS_AUTO_OSX + srs_usleep(30 * SRS_UTIME_MILLISECONDS); +#ifdef SRS_OSX ASSERT_TRUE(h.fd != NULL); #endif HELPER_EXPECT_SUCCESS(skt.initialize(h.fd)); @@ -204,8 +220,8 @@ VOID TEST(TCPServerTest, PingPongWithTimeout) HELPER_EXPECT_SUCCESS(c.connect()); SrsStSocket skt; - srs_usleep(100 * SRS_UTIME_MILLISECONDS); -#ifdef SRS_AUTO_OSX + srs_usleep(30 * SRS_UTIME_MILLISECONDS); +#ifdef SRS_OSX ASSERT_TRUE(h.fd != NULL); #endif HELPER_EXPECT_SUCCESS(skt.initialize(h.fd)); @@ -226,8 +242,8 @@ VOID TEST(TCPServerTest, PingPongWithTimeout) HELPER_EXPECT_SUCCESS(c.connect()); SrsStSocket skt; - srs_usleep(100 * SRS_UTIME_MILLISECONDS); -#ifdef SRS_AUTO_OSX + srs_usleep(30 * SRS_UTIME_MILLISECONDS); +#ifdef SRS_OSX ASSERT_TRUE(h.fd != NULL); #endif HELPER_EXPECT_SUCCESS(skt.initialize(h.fd)); @@ -248,8 +264,8 @@ VOID TEST(TCPServerTest, PingPongWithTimeout) HELPER_EXPECT_SUCCESS(c.connect()); SrsStSocket skt; - srs_usleep(100 * SRS_UTIME_MILLISECONDS); -#ifdef SRS_AUTO_OSX + srs_usleep(30 * SRS_UTIME_MILLISECONDS); +#ifdef SRS_OSX ASSERT_TRUE(h.fd != NULL); #endif HELPER_EXPECT_SUCCESS(skt.initialize(h.fd)); @@ -382,7 +398,7 @@ VOID TEST(TCPServerTest, StringIsHex) char* str = (char*)"!1234567890"; char* parsed = str; errno = 0; EXPECT_EQ(0x0, ::strtol(str, &parsed, 16)); -#ifndef SRS_AUTO_OSX +#ifndef SRS_OSX EXPECT_EQ(0, errno); #endif EXPECT_EQ(str, parsed); @@ -400,7 +416,7 @@ VOID TEST(TCPServerTest, StringIsHex) char* str = (char*)""; char* parsed = str; errno = 0; EXPECT_EQ(0x0, ::strtol(str, &parsed, 16)); -#ifndef SRS_AUTO_OSX +#ifndef SRS_OSX EXPECT_EQ(0, errno); #endif EXPECT_EQ(str, parsed); @@ -428,8 +444,8 @@ VOID TEST(TCPServerTest, WritevIOVC) HELPER_EXPECT_SUCCESS(c.connect()); SrsStSocket skt; - srs_usleep(100 * SRS_UTIME_MILLISECONDS); -#ifdef SRS_AUTO_OSX + srs_usleep(30 * SRS_UTIME_MILLISECONDS); +#ifdef SRS_OSX ASSERT_TRUE(h.fd != NULL); #endif HELPER_EXPECT_SUCCESS(skt.initialize(h.fd)); @@ -458,8 +474,8 @@ VOID TEST(TCPServerTest, WritevIOVC) HELPER_EXPECT_SUCCESS(c.connect()); SrsStSocket skt; - srs_usleep(100 * SRS_UTIME_MILLISECONDS); -#ifdef SRS_AUTO_OSX + srs_usleep(30 * SRS_UTIME_MILLISECONDS); +#ifdef SRS_OSX ASSERT_TRUE(h.fd != NULL); #endif HELPER_EXPECT_SUCCESS(skt.initialize(h.fd)); @@ -480,7 +496,7 @@ VOID TEST(TCPServerTest, WritevIOVC) } } -VOID TEST(TCPServerTest, MessageConnection) +VOID TEST(HTTPServerTest, MessageConnection) { srs_error_t err; @@ -523,18 +539,17 @@ VOID TEST(TCPServerTest, MessageConnection) if (true) { SrsHttpMessage m; - m.set_basic(100, 0, 0); EXPECT_STREQ("OTHER", m.method_str().c_str()); - m.set_basic(SRS_CONSTS_HTTP_GET, 0, 0); EXPECT_EQ(SRS_CONSTS_HTTP_GET, m.method()); EXPECT_STREQ("GET", m.method_str().c_str()); - m.set_basic(SRS_CONSTS_HTTP_PUT, 0, 0); EXPECT_EQ(SRS_CONSTS_HTTP_PUT, m.method()); EXPECT_STREQ("PUT", m.method_str().c_str()); - m.set_basic(SRS_CONSTS_HTTP_POST, 0, 0); EXPECT_EQ(SRS_CONSTS_HTTP_POST, m.method()); EXPECT_STREQ("POST", m.method_str().c_str()); - m.set_basic(SRS_CONSTS_HTTP_DELETE, 0, 0); EXPECT_EQ(SRS_CONSTS_HTTP_DELETE, m.method()); EXPECT_STREQ("DELETE", m.method_str().c_str()); - m.set_basic(SRS_CONSTS_HTTP_OPTIONS, 0, 0); EXPECT_EQ(SRS_CONSTS_HTTP_OPTIONS, m.method()); EXPECT_STREQ("OPTIONS", m.method_str().c_str()); + m.set_basic(HTTP_REQUEST, 100, 0, 0); EXPECT_STREQ("OTHER", m.method_str().c_str()); + m.set_basic(HTTP_REQUEST, SRS_CONSTS_HTTP_GET, 0, 0); EXPECT_EQ(SRS_CONSTS_HTTP_GET, m.method()); EXPECT_STREQ("GET", m.method_str().c_str()); + m.set_basic(HTTP_REQUEST, SRS_CONSTS_HTTP_PUT, 0, 0); EXPECT_EQ(SRS_CONSTS_HTTP_PUT, m.method()); EXPECT_STREQ("PUT", m.method_str().c_str()); + m.set_basic(HTTP_REQUEST, SRS_CONSTS_HTTP_POST, 0, 0); EXPECT_EQ(SRS_CONSTS_HTTP_POST, m.method()); EXPECT_STREQ("POST", m.method_str().c_str()); + m.set_basic(HTTP_REQUEST, SRS_CONSTS_HTTP_DELETE, 0, 0); EXPECT_EQ(SRS_CONSTS_HTTP_DELETE, m.method()); EXPECT_STREQ("DELETE", m.method_str().c_str()); + m.set_basic(HTTP_REQUEST, SRS_CONSTS_HTTP_OPTIONS, 0, 0); EXPECT_EQ(SRS_CONSTS_HTTP_OPTIONS, m.method()); EXPECT_STREQ("OPTIONS", m.method_str().c_str()); } if (true) { SrsHttpMessage m; EXPECT_TRUE(m.is_keep_alive()); - EXPECT_FALSE(m.is_infinite_chunked()); } if (true) { @@ -558,46 +573,17 @@ VOID TEST(TCPServerTest, MessageConnection) if (true) { SrsHttpMessage m; HELPER_EXPECT_SUCCESS(m.set_url("http://127.0.0.1/v1/streams/100", false)); - EXPECT_EQ(100, m.parse_rest_id("/v1/streams/")); EXPECT_FALSE(m.is_jsonp()); + EXPECT_STREQ("100", m.parse_rest_id("/v1/streams/").c_str()); EXPECT_FALSE(m.is_jsonp()); + } + + if (true) { + SrsHttpMessage m; + HELPER_EXPECT_SUCCESS(m.set_url("http://127.0.0.1/v1/streams/abc", false)); + EXPECT_STREQ("abc", m.parse_rest_id("/v1/streams/").c_str()); EXPECT_FALSE(m.is_jsonp()); } } -VOID TEST(TCPServerTest, MessageInfinityChunked) -{ - srs_error_t err; - - if (true) { - SrsHttpMessage m; - EXPECT_FALSE(m.is_infinite_chunked()); - HELPER_EXPECT_SUCCESS(m.enter_infinite_chunked()); - EXPECT_TRUE(m.is_infinite_chunked()); - } - - if (true) { - SrsHttpMessage m; - HELPER_EXPECT_SUCCESS(m.enter_infinite_chunked()); - HELPER_EXPECT_SUCCESS(m.enter_infinite_chunked()); - EXPECT_TRUE(m.is_infinite_chunked()); - } - - if (true) { - SrsHttpMessage m; - SrsHttpHeader hdr; - hdr.set("Transfer-Encoding", "chunked"); - m.set_header(&hdr, false); - HELPER_EXPECT_FAILED(m.enter_infinite_chunked()); - } - - if (true) { - SrsHttpMessage m; - SrsHttpHeader hdr; - hdr.set("Content-Length", "100"); - m.set_header(&hdr, false); - HELPER_EXPECT_FAILED(m.enter_infinite_chunked()); - } -} - -VOID TEST(TCPServerTest, MessageTurnRequest) +VOID TEST(HTTPServerTest, MessageTurnRequest) { srs_error_t err; @@ -645,7 +631,63 @@ VOID TEST(TCPServerTest, MessageTurnRequest) } } -VOID TEST(TCPServerTest, MessageWritev) +VOID TEST(HTTPServerTest, ContentLength) +{ + srs_error_t err; + + if (true) { + MockBufferIO io; + io.append("HTTP/1.1 200 OK\r\nContent-Length: 11\r\n\r\n"); + + SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE)); + ISrsHttpMessage* msg = NULL; HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &msg)); + + char buf[32]; ssize_t nread = 0; + ISrsHttpResponseReader* r = msg->body_reader(); + + io.append("Hello"); + HELPER_ARRAY_INIT(buf, sizeof(buf), 0); + HELPER_ASSERT_SUCCESS(r->read(buf, 5, &nread)); + EXPECT_EQ(5, nread); + EXPECT_STREQ("Hello", buf); + + io.append("World!"); + HELPER_ARRAY_INIT(buf, sizeof(buf), 0); + HELPER_ASSERT_SUCCESS(r->read(buf, 6, &nread)); + EXPECT_EQ(6, nread); + EXPECT_STREQ("World!", buf); + } +} + +VOID TEST(HTTPServerTest, HTTPChunked) +{ + srs_error_t err; + + if (true) { + MockBufferIO io; + io.append("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"); + + SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE)); + ISrsHttpMessage* msg = NULL; HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &msg)); + + char buf[32]; ssize_t nread = 0; + ISrsHttpResponseReader* r = msg->body_reader(); + + io.append("5\r\nHello\r\n"); + HELPER_ARRAY_INIT(buf, sizeof(buf), 0); + HELPER_ASSERT_SUCCESS(r->read(buf, 5, &nread)); + EXPECT_EQ(5, nread); + EXPECT_STREQ("Hello", buf); + + io.append("6\r\nWorld!\r\n"); + HELPER_ARRAY_INIT(buf, sizeof(buf), 0); + HELPER_ASSERT_SUCCESS(r->read(buf, 6, &nread)); + EXPECT_EQ(6, nread); + EXPECT_STREQ("World!", buf); + } +} + +VOID TEST(HTTPServerTest, InfiniteChunked) { srs_error_t err; @@ -654,14 +696,9 @@ VOID TEST(TCPServerTest, MessageWritev) MockBufferIO io; io.append("HTTP/1.1 200 OK\r\n\r\n"); - SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE, false)); + SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE)); ISrsHttpMessage* msg = NULL; HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &msg)); - - if (true) { - SrsHttpMessage* hm = dynamic_cast(msg); - ASSERT_TRUE(hm != NULL); - hm->enter_infinite_chunked(); - } + SrsAutoFree(ISrsHttpMessage, msg); char buf[32]; ssize_t nread = 0; ISrsHttpResponseReader* r = msg->body_reader(); @@ -677,8 +714,109 @@ VOID TEST(TCPServerTest, MessageWritev) HELPER_ASSERT_SUCCESS(r->read(buf, 8, &nread)); EXPECT_EQ(8, nread); EXPECT_STREQ("\r\nWorld!", buf); + + EXPECT_FALSE(r->eof()); } + // If read error, it's EOF. + if (true) { + MockBufferIO io; + io.append("HTTP/1.1 200 OK\r\n\r\n"); + + SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE)); + ISrsHttpMessage* msg = NULL; HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &msg)); + + char buf[32]; ssize_t nread = 0; + ISrsHttpResponseReader* r = msg->body_reader(); + + io.append("Hello"); + HELPER_ARRAY_INIT(buf, sizeof(buf), 0); + HELPER_ASSERT_SUCCESS(r->read(buf, 10, &nread)); + EXPECT_EQ(5, nread); + EXPECT_STREQ("Hello", buf); + + io.in_err = srs_error_new(ERROR_SOCKET_READ, "EOF"); + HELPER_ASSERT_SUCCESS(r->read(buf, 10, &nread)); + EXPECT_TRUE(r->eof()); + } +} + +VOID TEST(HTTPServerTest, OPTIONSRead) +{ + srs_error_t err; + + // If request, it has no content-length, not chunked, it's not infinite chunked, + // actually, it has no body. + if (true) { + MockBufferIO io; + io.append("OPTIONS /rtc/v1/play HTTP/1.1\r\n\r\n"); + + SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_REQUEST)); + ISrsHttpMessage* req = NULL; HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &req)); + SrsAutoFree(ISrsHttpMessage, req); + + ISrsHttpResponseReader* br = req->body_reader(); + EXPECT_TRUE(br->eof()); + } + + // If response, it has no content-length, not chunked, it's infinite chunked, + if (true) { + MockBufferIO io; + io.append("HTTP/1.1 200 OK\r\n\r\n"); + + SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_RESPONSE)); + ISrsHttpMessage* req = NULL; HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &req)); + SrsAutoFree(ISrsHttpMessage, req); + + ISrsHttpResponseReader* br = req->body_reader(); + EXPECT_FALSE(br->eof()); + } + + // So if OPTIONS has body, with chunked or content-length, it's ok to parsing it. + if (true) { + MockBufferIO io; + io.append("OPTIONS /rtc/v1/play HTTP/1.1\r\nContent-Length: 5\r\n\r\nHello"); + + SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_REQUEST)); + ISrsHttpMessage* req = NULL; HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &req)); + SrsAutoFree(ISrsHttpMessage, req); + + ISrsHttpResponseReader* br = req->body_reader(); + EXPECT_FALSE(br->eof()); + + string b; HELPER_ASSERT_SUCCESS(req->body_read_all(b)); + EXPECT_STREQ("Hello", b.c_str()); + + // The body will use as next HTTP request message. + io.append("GET /rtc/v1/play HTTP/1.1\r\n\r\n"); + ISrsHttpMessage* req2 = NULL; HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &req2)); + SrsAutoFree(ISrsHttpMessage, req2); + } + + // So if OPTIONS has body, but not specified the size, we think it has no body, + // and the body is parsed fail as the next parsing. + if (true) { + MockBufferIO io; + io.append("OPTIONS /rtc/v1/play HTTP/1.1\r\n\r\n"); + + SrsHttpParser hp; HELPER_ASSERT_SUCCESS(hp.initialize(HTTP_REQUEST)); + ISrsHttpMessage* req = NULL; HELPER_ASSERT_SUCCESS(hp.parse_message(&io, &req)); + SrsAutoFree(ISrsHttpMessage, req); + + ISrsHttpResponseReader* br = req->body_reader(); + EXPECT_TRUE(br->eof()); + + // The body will use as next HTTP request message. + io.append("Hello"); + ISrsHttpMessage* req2 = NULL; HELPER_ASSERT_FAILED(hp.parse_message(&io, &req2)); + SrsAutoFree(ISrsHttpMessage, req2); + } +} + +VOID TEST(HTTPServerTest, MessageWritev) +{ + srs_error_t err; + // Directly writev, merge to one chunk. if (true) { MockResponseWriter w; @@ -782,7 +920,7 @@ class MockOnCycleThread : public ISrsCoroutineHandler public: SrsSTCoroutine trd; srs_cond_t cond; - MockOnCycleThread() : trd("mock", this, 0) { + MockOnCycleThread() : trd("mock", this) { cond = srs_cond_new(); }; virtual ~MockOnCycleThread() { @@ -822,7 +960,7 @@ class MockOnCycleThread2 : public ISrsCoroutineHandler public: SrsSTCoroutine trd; srs_mutex_t lock; - MockOnCycleThread2() : trd("mock", this, 0) { + MockOnCycleThread2() : trd("mock", this) { lock = srs_mutex_new(); }; virtual ~MockOnCycleThread2() { @@ -863,7 +1001,7 @@ class MockOnCycleThread3 : public ISrsCoroutineHandler public: SrsSTCoroutine trd; srs_netfd_t fd; - MockOnCycleThread3() : trd("mock", this, 0) { + MockOnCycleThread3() : trd("mock", this) { fd = NULL; }; virtual ~MockOnCycleThread3() { @@ -1000,8 +1138,6 @@ VOID TEST(TCPServerTest, CoverUtility) EXPECT_FALSE(srs_net_device_is_internet((sockaddr*)r->ai_addr)); } - EXPECT_FALSE(srs_net_device_is_internet("eth0")); - if (true) { sockaddr_in addr; addr.sin_family = AF_INET; @@ -1116,7 +1252,7 @@ class MockOnCycleThread4 : public ISrsCoroutineHandler public: SrsSTCoroutine trd; srs_netfd_t fd; - MockOnCycleThread4() : trd("mock", this, 0) { + MockOnCycleThread4() : trd("mock", this) { fd = NULL; }; virtual ~MockOnCycleThread4() { @@ -1176,7 +1312,7 @@ public: } }; -VOID TEST(TCPServerTest, HTTPClientUtility) +VOID TEST(HTTPClientTest, HTTPClientUtility) { srs_error_t err; @@ -1186,7 +1322,7 @@ VOID TEST(TCPServerTest, HTTPClientUtility) HELPER_ASSERT_SUCCESS(trd.start("127.0.0.1", 8080)); SrsHttpClient client; - HELPER_ASSERT_SUCCESS(client.initialize("127.0.0.1", 8080, 1*SRS_UTIME_SECONDS)); + HELPER_ASSERT_SUCCESS(client.initialize("http", "127.0.0.1", 8080, 1*SRS_UTIME_SECONDS)); ISrsHttpMessage* res = NULL; SrsAutoFree(ISrsHttpMessage, res); @@ -1208,7 +1344,7 @@ VOID TEST(TCPServerTest, HTTPClientUtility) HELPER_ASSERT_SUCCESS(trd.start("127.0.0.1", 8080)); SrsHttpClient client; - HELPER_ASSERT_SUCCESS(client.initialize("127.0.0.1", 8080, 1*SRS_UTIME_SECONDS)); + HELPER_ASSERT_SUCCESS(client.initialize("http", "127.0.0.1", 8080, 1*SRS_UTIME_SECONDS)); ISrsHttpMessage* res = NULL; SrsAutoFree(ISrsHttpMessage, res); @@ -1230,7 +1366,7 @@ VOID TEST(TCPServerTest, HTTPClientUtility) HELPER_ASSERT_SUCCESS(trd.start("127.0.0.1", 8080)); SrsHttpClient client; - HELPER_ASSERT_SUCCESS(client.initialize("127.0.0.1", 8080, 1*SRS_UTIME_SECONDS)); + HELPER_ASSERT_SUCCESS(client.initialize("http", "127.0.0.1", 8080, 1*SRS_UTIME_SECONDS)); client.set_recv_timeout(1 * SRS_UTIME_SECONDS); client.set_header("agent", "srs"); @@ -1251,7 +1387,7 @@ VOID TEST(TCPServerTest, HTTPClientUtility) } } -class MockConnectionManager : public IConnectionManager +class MockConnectionManager : public ISrsResourceManager { public: MockConnectionManager() { @@ -1259,7 +1395,7 @@ public: virtual ~MockConnectionManager() { } public: - virtual void remove(ISrsConnection* /*c*/) { + virtual void remove(ISrsResource* /*c*/) { } }; @@ -1268,38 +1404,56 @@ VOID TEST(TCPServerTest, ContextUtility) if (true) { SrsThreadContext ctx; - EXPECT_EQ(0, ctx.set_id(100)); - EXPECT_EQ(100, ctx.set_id(1000)); - EXPECT_EQ(1000, ctx.get_id()); + if (true) { + SrsContextId cid; + EXPECT_TRUE(!ctx.set_id(cid.set_value("100")).compare(cid)); + } + if (true) { + SrsContextId cid; + EXPECT_TRUE(!ctx.set_id(cid.set_value("1000")).compare(cid)); + } + if (true) { + SrsContextId cid; + EXPECT_TRUE(!ctx.get_id().compare(cid.set_value("1000"))); + } ctx.clear_cid(); - EXPECT_EQ(0, ctx.set_id(100)); + if (true) { + SrsContextId cid; + EXPECT_TRUE(!ctx.set_id(cid.set_value("100")).compare(cid)); + } } + SrsContextId cid; + cid.set_value("100"); + int base_size = 0; if (true) { + errno = 0; int size = 0; char buf[1024]; HELPER_ARRAY_INIT(buf, 1024, 0); - ASSERT_TRUE(srs_log_header(buf, 1024, true, true, "SRS", 100, "Trace", &size)); + ASSERT_TRUE(srs_log_header(buf, 1024, true, true, "SRS", cid, "Trace", &size)); base_size = size; EXPECT_TRUE(base_size > 0); } if (true) { + errno = 0; int size = 0; char buf[1024]; HELPER_ARRAY_INIT(buf, 1024, 0); - ASSERT_TRUE(srs_log_header(buf, 1024, false, true, "SRS", 100, "Trace", &size)); + ASSERT_TRUE(srs_log_header(buf, 1024, false, true, "SRS", cid, "Trace", &size)); EXPECT_EQ(base_size, size); } if (true) { errno = 0; int size = 0; char buf[1024]; HELPER_ARRAY_INIT(buf, 1024, 0); - ASSERT_TRUE(srs_log_header(buf, 1024, false, true, NULL, 100, "Trace", &size)); + ASSERT_TRUE(srs_log_header(buf, 1024, false, true, NULL, cid, "Trace", &size)); EXPECT_EQ(base_size - 5, size); } if (true) { + errno = 0; int size = 0; char buf[1024]; HELPER_ARRAY_INIT(buf, 1024, 0); - ASSERT_TRUE(srs_log_header(buf, 1024, false, false, NULL, 100, "Trace", &size)); + ASSERT_TRUE(srs_log_header(buf, 1024, false, false, NULL, cid, "Trace", &size)); EXPECT_EQ(base_size - 8, size); } diff --git a/trunk/src/utest/srs_utest_service.hpp b/trunk/src/utest/srs_utest_service.hpp index 995b75e83..ab4ed8ea3 100644 --- a/trunk/src/utest/srs_utest_service.hpp +++ b/trunk/src/utest/srs_utest_service.hpp @@ -30,6 +30,22 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include +#include + +class MockSrsConnection : public ISrsConnection +{ +public: + // Whether switch the coroutine context when free the object, for special case test. + bool do_switch; +public: + MockSrsConnection(); + virtual ~MockSrsConnection(); +// Interface ISrsConnection. +public: + virtual const SrsContextId& get_id(); + virtual std::string desc(); + virtual std::string remote_ip(); +}; #endif