mirror of
https://github.com/ossrs/srs.git
synced 2025-11-24 03:44:02 +08:00
for issue #4418, #4151, #4076 .DVR Missing First Few Seconds of Audio/Video ### Root Cause When recording WebRTC streams to FLV files using DVR, the first 4-6 seconds of audio/video are missing. This occurs because: 1. **Packets are discarded before A/V sync is available**: The RTC-to-RTMP conversion pipeline actively discards all RTP packets when avsync_time <= 0. 2. **Original algorithm requires 2 RTCP SR packets**: The previous implementation needed to receive two RTCP Sender Report (SR) packets before it could calculate the rate for audio/video synchronization timestamp conversion. 3. **Delay causes packet loss**: Since RTCP SR packets typically arrive every 2-3 seconds, waiting for 2 SRs means 4-6 seconds of packets are discarded before A/V sync becomes available. 4. **Audio SR arrives slower than video SR**: As reported in the issue, video RTCP SR packets arrive much faster than audio SR packets. This asymmetry causes audio packets to be discarded for a longer period, resulting in the audio loss observed in DVR recordings. ### Solution 1. **Initialize rate from SDP**: Use the sample rate from SDP (Session Description Protocol) to calculate the initial rate immediately when the track is created. Audio (Opus): 48000 Hz → rate = 48 (RTP units per millisecond) Video (H.264/H.265): 90000 Hz → rate = 90 (RTP units per millisecond) 2. **Enable immediate A/V sync:** With the SDP rate available, cal_avsync_time() can calculate valid timestamps from the very first RTP packet, eliminating packet loss. 3. **Smooth transition to precise rate**: After receiving the 2nd RTCP SR, update to the precisely calculated rate based on actual RTP/NTP timestamp mapping. ## Configuration Added new configuration option `init_rate_from_sdp` in the RTC vhost section: ```nginx vhost rtc.vhost.srs.com { rtc { # Whether initialize RTP rate from SDP sample rate for immediate A/V sync. # When enabled, the RTP rate (units per millisecond) is initialized from the SDP # sample rate (e.g., 90 for video 90kHz, 48 for audio 48kHz) before receiving # 2 RTCP SR packets. This allows immediate audio/video synchronization. # The rate will be updated to a more precise value after receiving the 2nd SR. # Overwrite by env SRS_VHOST_RTC_INIT_RATE_FROM_SDP for all vhosts. # Default: off init_rate_from_sdp off; } } ``` **⚠️ Important Note**: This config defaults to **off** because: - ✅ When **enabled**: Fixes the audio loss problem (no missing first 4-6 seconds) - ❌ When **enabled**: VLC on macOS cannot play the video properly - ✅ Other platforms work fine (Windows, Linux) - ✅ FFplay works fine on all platforms Users experiencing audio loss in DVR recordings can enable this option if they don't need VLC macOS compatibility. We're investigating the VLC macOS issue to make this feature safe to enable by default in the future. --------- Co-authored-by: winlin <winlinvip@gmail.com> Co-authored-by: OSSRS-AI <winlinam@gmail.com>
765 lines
29 KiB
Bash
Executable File
765 lines
29 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
#####################################################################################
|
|
# the main output dir, all configure and make output are in this dir.
|
|
#####################################################################################
|
|
# Configure SRS from other directory, for example:
|
|
# env SRS_WORKDIR=~/git/srs/trunk ./configure
|
|
if [[ -z $SRS_WORKDIR ]]; then SRS_WORKDIR="."; fi
|
|
# Set the objs to other directory, for example:
|
|
# env SRS_OUTPUT=$(pwd) ./configure
|
|
if [[ -z $SRS_OUTPUT ]]; then
|
|
SRS_OBJS="${SRS_WORKDIR}/objs" && SRS_MAKEFILE=${SRS_WORKDIR}/Makefile
|
|
else
|
|
SRS_OBJS="${SRS_OUTPUT}/objs" && SRS_MAKEFILE=${SRS_OUTPUT}/Makefile
|
|
fi
|
|
|
|
# linux shell color support.
|
|
RED="\\033[31m"
|
|
GREEN="\\033[32m"
|
|
YELLOW="\\033[33m"
|
|
BLACK="\\033[0m"
|
|
|
|
#####################################################################################
|
|
# parse user options, set the variables like:
|
|
# srs features: SRS_SSL/SRS_HLS/SRS_HTTP_CALLBACK/......
|
|
# build options: SRS_JOBS
|
|
#####################################################################################
|
|
# parse options, exit with error when parse options invalid.
|
|
. $SRS_WORKDIR/auto/options.sh
|
|
|
|
# setup variables when options parsed.
|
|
. $SRS_WORKDIR/auto/setup_variables.sh
|
|
|
|
# We don't need to cleanup the exists files.
|
|
rm -f ${SRS_MAKEFILE}
|
|
|
|
# Create objs and platform directories.
|
|
mkdir -p ${SRS_OBJS}/${SRS_PLATFORM}
|
|
# If only generate objs directory, quit without error.
|
|
if [[ $SRS_GENERATE_OBJS == YES ]]; then exit 0; fi
|
|
|
|
# apply user options.
|
|
. $SRS_WORKDIR/auto/depends.sh
|
|
|
|
# the auto generated variables.
|
|
. $SRS_WORKDIR/auto/auto_headers.sh
|
|
|
|
#####################################################################################
|
|
# generate Makefile.
|
|
#####################################################################################
|
|
# ubuntu echo in Makefile cannot display color, use bash instead
|
|
SRS_BUILD_SUMMARY="_srs_build_summary.sh"
|
|
# utest make entry, (cd utest; make)
|
|
SrsUtestMakeEntry="@echo -e \"ignore utest for it's disabled\""
|
|
if [[ $SRS_UTEST == YES ]]; then SrsUtestMakeEntry="\$(MAKE)\$(JOBS) -C ${SRS_OBJS}/${SRS_PLATFORM}/utest"; fi
|
|
|
|
# generate extra phony for each modules.
|
|
cat << END > ${SRS_OBJS}/Makefile
|
|
|
|
.PHONY: $__mphonys
|
|
|
|
END
|
|
|
|
#####################################################################################
|
|
# build tools or compiler args.
|
|
# enable gdb debug
|
|
GDBDebug=" -g -O0"
|
|
# the warning level.
|
|
WarnLevel=" -Wall"
|
|
# the compile standard.
|
|
CppStd="-ansi"
|
|
if [[ $SRS_CXX11 == YES ]]; then
|
|
CppStd="-std=c++11"
|
|
fi
|
|
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
|
|
# performance of gperf
|
|
SrsGperf=""; SrsGperfLink=""; if [[ $SRS_GPERF == YES ]]; then SrsGperfLink=" -lpthread"; fi
|
|
# the cxx flag generated.
|
|
CXXFLAGS="${CXXFLAGS} ${CppStd}${WarnLevel}${GDBDebug}${LibraryCompile}${SrsGprof}"
|
|
if [[ $SRS_GPERF == YES ]]; then
|
|
CXXFLAGS="${CXXFLAGS} -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-free";
|
|
fi
|
|
# For coverage.
|
|
if [[ $SRS_GCOV == YES ]]; then
|
|
SrsGcov="-fprofile-arcs -ftest-coverage"
|
|
fi
|
|
if [[ $SRS_GCOV == YES ]]; then
|
|
CXXFLAGS="${CXXFLAGS} ${SrsGcov}";
|
|
fi
|
|
# User configed options.
|
|
if [[ $SRS_EXTRA_FLAGS != '' ]]; then
|
|
CXXFLAGS="${CXXFLAGS} $SRS_EXTRA_FLAGS";
|
|
fi
|
|
|
|
# For Sanitizer
|
|
# @doc: https://github.com/google/sanitizers/wiki/AddressSanitizer
|
|
if [[ $SRS_SANITIZER == YES ]]; then
|
|
CXXFLAGS="${CXXFLAGS} -fsanitize=address -fno-omit-frame-pointer";
|
|
fi
|
|
|
|
# Start to generate the Makefile.
|
|
cat << END >> ${SRS_OBJS}/Makefile
|
|
CC = ${SRS_TOOL_CC}
|
|
CXX = ${SRS_TOOL_CXX}
|
|
AR = ${SRS_TOOL_AR}
|
|
LINK = ${SRS_TOOL_CXX}
|
|
CXXFLAGS = ${CXXFLAGS}
|
|
|
|
.PHONY: default srs srs_ingest_hls
|
|
|
|
default:
|
|
|
|
END
|
|
|
|
#####################################################################################
|
|
# Libraries, external library to build in srs,
|
|
# header(.h): add to ModuleLibIncs if need the specified library. for example, LibSTRoot
|
|
# library(.a): add to ModuleLibFiles if binary need the specifeid library. for example, LibSTfile
|
|
#
|
|
|
|
# the link options, always use static link
|
|
SrsLinkOptions="-ldl -lpthread";
|
|
|
|
# st(state-threads) the basic network library for SRS.
|
|
LibSTRoot="${SRS_OBJS}/st"; LibSTfile="${LibSTRoot}/libst.a"
|
|
if [[ $SRS_SHARED_ST == YES ]]; then LibSTfile="-L${LibSTRoot} -lst"; fi
|
|
|
|
# srtp
|
|
LibSrtpRoot="${SRS_OBJS}/srtp2/include"; LibSrtpFile="${SRS_OBJS}/srtp2/lib/libsrtp2.a"
|
|
if [[ $SRS_USE_SYS_SRTP == YES ]]; then
|
|
LibSrtpRoot=""; LibSrtpFile="libsrtp2.a"
|
|
if [[ $SRS_SHARED_SRTP == YES ]]; then
|
|
LibSrtpFile="";
|
|
SrsLinkOptions="${SrsLinkOptions} -lsrtp2";
|
|
fi
|
|
fi
|
|
|
|
# FFMPEG for WebRTC transcoding, such as aac to opus.
|
|
if [[ $SRS_FFMPEG_FIT == YES ]]; then
|
|
LibFfmpegRoot="${SRS_OBJS}/ffmpeg/include"
|
|
LibFfmpegFile="${SRS_OBJS}/ffmpeg/lib/libavcodec.a ${SRS_OBJS}/ffmpeg/lib/libswresample.a ${SRS_OBJS}/ffmpeg/lib/libavutil.a"
|
|
if [[ $SRS_FFMPEG_OPUS != YES ]]; then
|
|
LibFfmpegFile="${LibFfmpegFile} ${SRS_OBJS}/opus/lib/libopus.a"
|
|
fi
|
|
if [[ $SRS_SHARED_FFMPEG == YES ]]; then
|
|
LibFfmpegFile="-L${SRS_OBJS}/ffmpeg/lib -lavcodec -lswresample -lavutil";
|
|
if [[ $SRS_FFMPEG_OPUS != YES ]]; then
|
|
LibFfmpegFile="$LibFfmpegFile -L${SRS_OBJS}/opus/lib -lopus"
|
|
fi
|
|
fi
|
|
if [[ $SRS_USE_SYS_FFMPEG == YES ]]; then
|
|
LibFfmpegRoot=""
|
|
LibFfmpegFile="libavcodec.a libswresample.a libavutil.a libopus.a";
|
|
if [[ $SRS_SHARED_FFMPEG == YES ]]; then
|
|
LibFfmpegFile=""
|
|
SrsLinkOptions="${SrsLinkOptions} -lavcodec -lswresample -lavutil -lopus";
|
|
fi
|
|
fi
|
|
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}/openssl/include"; LibSSLfile="${SRS_OBJS}/openssl/lib/libssl.a ${SRS_OBJS}/openssl/lib/libcrypto.a";
|
|
fi
|
|
|
|
# gperftools-2.1, for mem check and mem/cpu profile
|
|
LibGperfRoot=""; LibGperfFile=""
|
|
if [[ $SRS_GPERF == YES ]]; then
|
|
LibGperfRoot="${SRS_OBJS}/gperf/include"; LibGperfFile="${SRS_OBJS}/gperf/lib/libtcmalloc_and_profiler.a";
|
|
fi
|
|
if [[ $SRS_GPERF_MD == YES ]]; then
|
|
LibGperfFile="${SRS_OBJS}/gperf/lib/libtcmalloc_debug.a";
|
|
fi
|
|
|
|
# srt code path - always enabled
|
|
LibSRTRoot="${SRS_OBJS}/srt/include"; LibSRTfile="${SRS_OBJS}/srt/lib/libsrt.a"
|
|
if [[ $SRS_SHARED_SRT == YES ]]; then LibSRTfile="-L${SRS_OBJS}/srt/lib -lsrt"; fi
|
|
if [[ $SRS_USE_SYS_SRT == YES ]]; then
|
|
LibSRTRoot=""; LibSRTfile="libsrt.a"
|
|
if [[ $SRS_SHARED_SRT == YES ]]; then
|
|
LibSRTfile="";
|
|
SrsLinkOptions="${SrsLinkOptions} -lsrt";
|
|
fi
|
|
fi
|
|
|
|
if [[ $SRS_SSL == YES && $SRS_USE_SYS_SSL == YES ]]; then
|
|
SrsLinkOptions="${SrsLinkOptions} -lssl -lcrypto";
|
|
fi
|
|
|
|
# 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-libstdc++";
|
|
fi
|
|
|
|
# For coverage.
|
|
if [[ $SRS_GCOV == YES ]]; then
|
|
SrsLinkOptions="${SrsLinkOptions} ${SrsGcov}";
|
|
fi
|
|
|
|
# For FFMPEG/RTC on Linux.
|
|
if [[ $SRS_OSX != YES && $SRS_FFMPEG_FIT == YES ]]; then
|
|
SrsLinkOptions="${SrsLinkOptions} -lrt";
|
|
fi
|
|
|
|
# The backtrace symbol option.
|
|
if [[ $SRS_BACKTRACE == YES ]]; then
|
|
SrsLinkOptions="${SrsLinkOptions} -rdynamic";
|
|
fi
|
|
|
|
# For address sanitizer
|
|
# @doc: https://github.com/google/sanitizers/wiki/AddressSanitizer
|
|
if [[ $SRS_SANITIZER == YES ]]; then
|
|
SrsLinkOptions="${SrsLinkOptions} -fsanitize=address -fno-omit-frame-pointer";
|
|
if [[ $SRS_SANITIZER_STATIC == YES ]]; then
|
|
SrsLinkOptions="${SrsLinkOptions} -static-libasan";
|
|
fi
|
|
fi
|
|
|
|
|
|
|
|
# User configed options.
|
|
if [[ $SRS_EXTRA_LDFLAGS != '' ]]; then
|
|
SrsLinkOptions="${SrsLinkOptions} $SRS_EXTRA_LDFLAGS";
|
|
fi
|
|
|
|
#####################################################################################
|
|
# Modules, compile each module, then link to binary
|
|
#
|
|
#Core, depends only on system apis.
|
|
MODULE_ID="CORE"
|
|
MODULE_DEPENDS=()
|
|
ModuleLibIncs=(${SRS_OBJS})
|
|
MODULE_FILES=("srs_core" "srs_core_version" "srs_core_version7" "srs_core_autofree"
|
|
"srs_core_time" "srs_core_platform" "srs_core_deprecated" "srs_core_performance")
|
|
CORE_INCS="src/core"; MODULE_DIR=${CORE_INCS} . $SRS_WORKDIR/auto/modules.sh
|
|
CORE_OBJS="${MODULE_OBJS[@]}"
|
|
#
|
|
#Kernel, depends on core, provides error/log/config, nothing about stream information.
|
|
MODULE_ID="KERNEL"
|
|
MODULE_DEPENDS=("CORE")
|
|
ModuleLibIncs=(${SRS_OBJS} ${LibSSLRoot})
|
|
MODULE_FILES=("srs_kernel_error" "srs_kernel_log" "srs_kernel_buffer"
|
|
"srs_kernel_utility" "srs_kernel_flv" "srs_kernel_codec" "srs_kernel_io"
|
|
"srs_kernel_consts" "srs_kernel_aac" "srs_kernel_mp3" "srs_kernel_ts"
|
|
"srs_kernel_stream" "srs_kernel_balance" "srs_kernel_mp4" "srs_kernel_file"
|
|
"srs_kernel_kbps" "srs_kernel_rtc_rtp" "srs_kernel_rtc_rtcp" "srs_kernel_packet"
|
|
"srs_kernel_st" "srs_kernel_factory" "srs_kernel_hourglass" "srs_kernel_ps"
|
|
"srs_kernel_pithy_print" "srs_kernel_rtc_queue" "srs_kernel_resource")
|
|
KERNEL_INCS="src/kernel"; MODULE_DIR=${KERNEL_INCS} . $SRS_WORKDIR/auto/modules.sh
|
|
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} ${LibSTRoot} ${LibSSLRoot})
|
|
MODULE_FILES=("srs_protocol_amf0" "srs_protocol_io" "srs_protocol_conn" "srs_protocol_rtmp_handshake"
|
|
"srs_protocol_rtmp_stack" "srs_protocol_utility" "srs_protocol_rtmp_msg_array"
|
|
"srs_protocol_raw_avc" "srs_protocol_http_stack" "srs_protocol_json" "srs_protocol_stream"
|
|
"srs_protocol_format" "srs_protocol_log" "srs_protocol_st" "srs_protocol_http_client"
|
|
"srs_protocol_http_conn" "srs_protocol_rtmp_conn" "srs_protocol_protobuf"
|
|
"srs_protocol_http_stack_llhttp" "srs_protocol_http_stack_llhttpapi"
|
|
"srs_protocol_http_stack_llhttpadapter" "srs_protocol_http_stack_llhttphttp")
|
|
# Always include SRT protocol
|
|
MODULE_FILES+=("srs_protocol_srt")
|
|
ModuleLibIncs+=(${LibSRTRoot})
|
|
MODULE_FILES+=("srs_protocol_rtc_stun" "srs_protocol_rtp" "srs_protocol_sdp")
|
|
if [[ $SRS_RTSP == YES ]]; then
|
|
MODULE_FILES+=("srs_protocol_rtsp_stack")
|
|
fi
|
|
PROTOCOL_INCS="src/protocol"; MODULE_DIR=${PROTOCOL_INCS} . $SRS_WORKDIR/auto/modules.sh
|
|
PROTOCOL_OBJS="${MODULE_OBJS[@]}"
|
|
|
|
#
|
|
#App Module, for SRS server only.
|
|
MODULE_ID="APP"
|
|
MODULE_DEPENDS=("CORE" "KERNEL" "PROTOCOL")
|
|
ModuleLibIncs=(${SRS_OBJS} ${LibSSLRoot} ${LibSrtpRoot})
|
|
if [[ $SRS_GPERF == YES ]]; then
|
|
ModuleLibIncs+=(${LibGperfRoot})
|
|
fi
|
|
if [[ $SRS_FFMPEG_FIT == YES ]]; then
|
|
ModuleLibIncs+=("${LibFfmpegRoot[*]}")
|
|
fi
|
|
MODULE_FILES=("srs_app_server" "srs_app_rtmp_conn" "srs_app_rtmp_source"
|
|
"srs_app_refer" "srs_app_hls" "srs_app_forward" "srs_app_encoder"
|
|
"srs_app_st" "srs_app_log" "srs_app_config" "srs_app_stream_bridge"
|
|
"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_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_listener" "srs_app_async_call"
|
|
"srs_app_caster_flv" "srs_app_latest_version" "srs_app_process"
|
|
"srs_app_dash" "srs_app_fragment" "srs_app_dvr" "srs_app_ng_exec"
|
|
"srs_app_coworkers" "srs_app_circuit_breaker" "srs_app_factory"
|
|
"srs_app_stream_token" "srs_app_http_stream")
|
|
# Always include SRT app modules
|
|
MODULE_FILES+=("srs_app_srt_server" "srs_app_srt_listener" "srs_app_srt_conn" "srs_app_srt_source")
|
|
MODULE_FILES+=("srs_app_rtc_conn" "srs_app_rtc_dtls" "srs_app_rtc_network"
|
|
"srs_app_rtc_server" "srs_app_rtc_source" "srs_app_rtc_api")
|
|
if [[ $SRS_RTSP == YES ]]; then
|
|
MODULE_FILES+=("srs_app_rtsp_source" "srs_app_rtsp_conn")
|
|
fi
|
|
|
|
if [[ $SRS_FFMPEG_FIT == YES ]]; then
|
|
MODULE_FILES+=("srs_app_rtc_codec")
|
|
fi
|
|
if [[ $SRS_GB28181 == YES ]]; then
|
|
MODULE_FILES+=("srs_app_gb28181")
|
|
fi
|
|
|
|
DEFINES=""
|
|
# add each modules for app
|
|
APP_INCS="src/app"; MODULE_DIR=${APP_INCS} . $SRS_WORKDIR/auto/modules.sh
|
|
APP_OBJS="${MODULE_OBJS[@]}"
|
|
#
|
|
#Server Module, for SRS only.
|
|
MODULE_ID="SERVER"
|
|
MODULE_DEPENDS=("CORE" "KERNEL" "PROTOCOL" "APP")
|
|
ModuleLibIncs=(${SRS_OBJS} ${LibGperfRoot} ${LibSSLRoot} ${LibSrtpRoot})
|
|
if [[ $SRS_FFMPEG_FIT == YES ]]; then
|
|
ModuleLibIncs+=("${LibFfmpegRoot[*]}")
|
|
fi
|
|
MODULE_FILES=("srs_main_server")
|
|
SERVER_INCS="src/main"; MODULE_DIR=${SERVER_INCS} . $SRS_WORKDIR/auto/modules.sh
|
|
SERVER_OBJS="${MODULE_OBJS[@]}"
|
|
|
|
#####################################################################################
|
|
# Binaries, main entrances, link the module and its depends modules,
|
|
# then link to a binary, for example, objs/srs
|
|
#
|
|
# all main entrances
|
|
MAIN_ENTRANCES=("srs_main_server")
|
|
#
|
|
# all depends libraries
|
|
ModuleLibFiles=(${LibSTfile} ${LibSSLfile} ${LibGperfFile} ${LibSrtpFile})
|
|
if [[ $SRS_FFMPEG_FIT == YES ]]; then
|
|
ModuleLibFiles+=("${LibFfmpegFile[*]}")
|
|
fi
|
|
# Always include SRT library files
|
|
ModuleLibFiles+=("${LibSRTfile[*]}")
|
|
# all depends objects
|
|
MODULE_OBJS="${CORE_OBJS[@]} ${KERNEL_OBJS[@]} ${PROTOCOL_OBJS[@]} ${APP_OBJS[@]} ${SERVER_OBJS[@]}"
|
|
ModuleLibIncs=(${SRS_OBJS} ${LibSTRoot} ${LibGperfRoot} ${LibSSLRoot} ${LibSrtpRoot})
|
|
if [[ $SRS_FFMPEG_FIT == YES ]]; then
|
|
ModuleLibIncs+=("${LibFfmpegRoot[*]}")
|
|
fi
|
|
# Always include SRT libraries
|
|
ModuleLibIncs+=(${LibSRTRoot})
|
|
MODULE_OBJS="${MODULE_OBJS} ${SRT_OBJS[@]}"
|
|
LINK_OPTIONS="${LDFLAGS} ${SrsLinkOptions}${SrsGprofLink}${SrsGperfLink}"
|
|
#
|
|
# srs: srs(simple rtmp server) over st(state-threads)
|
|
BUILD_KEY="srs" APP_MAIN="srs_main_server" APP_NAME="srs" . $SRS_WORKDIR/auto/apps.sh
|
|
#
|
|
# For modules, with the app module.
|
|
MODULE_OBJS="${CORE_OBJS[@]} ${KERNEL_OBJS[@]} ${PROTOCOL_OBJS[@]} ${APP_OBJS[@]} ${MAIN_OBJS[@]}"
|
|
ModuleLibFiles=(${LibSTfile} ${LibSSLfile} ${LibGperfFile} ${LibSrtpFile})
|
|
if [[ $SRS_FFMPEG_FIT == YES ]]; then
|
|
ModuleLibFiles+=("${LibFfmpegFile[*]}")
|
|
fi
|
|
# Always include SRT library files
|
|
ModuleLibFiles+=("${LibSRTfile[*]}")
|
|
# For utest on mac.
|
|
# @see https://github.com/protocolbuffers/protobuf/issues/51#issuecomment-111044468
|
|
if [[ $SRS_OSX == YES ]]; then
|
|
UTEST_EXTRA_DEFINES="-DGTEST_USE_OWN_TR1_TUPLE=1"
|
|
fi
|
|
#
|
|
# utest, the unit-test cases of srs, base on gtest1.6
|
|
if [[ $SRS_UTEST == YES ]]; then
|
|
MODULE_FILES=("srs_utest" "srs_utest_manual_amf0" "srs_utest_manual_kernel" "srs_utest_manual_core" "srs_utest_manual_srt"
|
|
"srs_utest_manual_config" "srs_utest_manual_rtmp" "srs_utest_manual_http" "srs_utest_manual_avc" "srs_utest_manual_reload"
|
|
"srs_utest_manual_mp4" "srs_utest_manual_service" "srs_utest_manual_app_rtc2rtmp" "srs_utest_manual_rtc" "srs_utest_manual_config2"
|
|
"srs_utest_manual_protocol" "srs_utest_manual_protocol2" "srs_utest_manual_kernel2" "srs_utest_manual_st" "srs_utest_manual_fmp4"
|
|
"srs_utest_manual_source_lock" "srs_utest_manual_stream_token" "srs_utest_manual_rtc_recv_track" "srs_utest_manual_st2"
|
|
"srs_utest_manual_hevc_structs" "srs_utest_manual_coworkers" "srs_utest_manual_pithy_print" "srs_utest_manual_protocol3"
|
|
"srs_utest_manual_app" "srs_utest_manual_mock")
|
|
MODULE_FILES+=("srs_utest_workflow_rtc_playstream" "srs_utest_workflow_rtc_publishstream"
|
|
"srs_utest_workflow_rtc_conn" "srs_utest_workflow_rtmp_conn" "srs_utest_workflow_srt_conn" "srs_utest_workflow_http_conn"
|
|
"srs_utest_workflow_forward" "srs_utest_workflow_rtc2rtmp" "srs_utest_workflow_rtmp2rtc")
|
|
MODULE_FILES+=("srs_utest_ai01" "srs_utest_ai02" "srs_utest_ai03" "srs_utest_ai04" "srs_utest_ai05"
|
|
"srs_utest_ai06" "srs_utest_ai07" "srs_utest_ai08" "srs_utest_ai09" "srs_utest_ai10" "srs_utest_ai11"
|
|
"srs_utest_ai12" "srs_utest_ai13" "srs_utest_ai14" "srs_utest_ai15" "srs_utest_ai16" "srs_utest_ai17"
|
|
"srs_utest_ai18" "srs_utest_ai19" "srs_utest_ai20" "srs_utest_ai24")
|
|
if [[ $SRS_GB28181 == YES ]]; then
|
|
MODULE_FILES+=("srs_utest_manual_gb28181" "srs_utest_ai23")
|
|
fi
|
|
if [[ $SRS_RTSP == YES ]]; then
|
|
MODULE_FILES+=("srs_utest_ai21" "srs_utest_ai22")
|
|
fi
|
|
ModuleLibIncs=(${SRS_OBJS} ${LibSTRoot} ${LibSSLRoot} ${LibSrtpRoot})
|
|
if [[ $SRS_FFMPEG_FIT == YES ]]; then
|
|
ModuleLibIncs+=("${LibFfmpegRoot[*]}")
|
|
fi
|
|
# Always include SRT library includes
|
|
ModuleLibIncs+=("${LibSRTRoot[*]}")
|
|
ModuleLibFiles=(${LibSTfile} ${LibSSLfile} ${LibSrtpFile})
|
|
if [[ $SRS_FFMPEG_FIT == YES ]]; then
|
|
ModuleLibFiles+=("${LibFfmpegFile[*]}")
|
|
fi
|
|
# Always include SRT library files
|
|
ModuleLibFiles+=("${LibSRTfile[*]}")
|
|
MODULE_DEPENDS=("CORE" "KERNEL" "PROTOCOL" "APP")
|
|
MODULE_OBJS="${CORE_OBJS[@]} ${KERNEL_OBJS[@]} ${PROTOCOL_OBJS[@]} ${APP_OBJS[@]} ${SRT_OBJS[@]}"
|
|
LINK_OPTIONS="${LDFLAGS} -lpthread ${SrsLinkOptions}" MODULE_DIR="src/utest" APP_NAME="srs_utest" . $SRS_WORKDIR/auto/utest.sh
|
|
fi
|
|
|
|
#####################################################################################
|
|
# generate colorful summary script
|
|
. $SRS_WORKDIR/auto/summary.sh
|
|
|
|
#####################################################################################
|
|
# makefile
|
|
echo "Generate Makefile"
|
|
|
|
# backup old makefile.
|
|
rm -f ${SRS_MAKEFILE}.bk &&
|
|
mv ${SRS_MAKEFILE} ${SRS_MAKEFILE}.bk
|
|
|
|
# generate phony header
|
|
cat << END > ${SRS_MAKEFILE}
|
|
.PHONY: default all _default install help clean destroy server utest _prepare_dir $__mphonys
|
|
.PHONY: clean_srs clean_openssl clean_srtp2 clean_opus clean_ffmpeg clean_st
|
|
.PHONY: st ffmpeg
|
|
|
|
CC = ${SRS_TOOL_CC}
|
|
CXX = ${SRS_TOOL_CXX}
|
|
AR = ${SRS_TOOL_AR}
|
|
LINK = ${SRS_TOOL_LD}
|
|
RANDLIB = ${SRS_TOOL_RANDLIB}
|
|
CXXFLAGS = ${CXXFLAGS}
|
|
LDFLAGS = ${LDFLAGS}
|
|
|
|
# install prefix.
|
|
SRS_PREFIX=${SRS_PREFIX}
|
|
SRS_DEFAULT_CONFIG=${SRS_DEFAULT_CONFIG}
|
|
__REAL_INSTALL=\$(DESTDIR)\$(SRS_PREFIX)
|
|
|
|
SRS_FORCE_MAKE_JOBS=${SRS_FORCE_MAKE_JOBS}
|
|
END
|
|
|
|
if [[ $SRS_FORCE_MAKE_JOBS == YES ]]; then
|
|
cat << END >> ${SRS_MAKEFILE}
|
|
JOBS=\$(shell echo \$(MAKEFLAGS)| grep -qE '\-j[0-9]+' || echo " ${SRS_JOBS}")
|
|
END
|
|
fi
|
|
|
|
# the real entry for all platform:
|
|
cat << END >> ${SRS_MAKEFILE}
|
|
|
|
default: server
|
|
|
|
all: _default
|
|
|
|
_default: server utest
|
|
|
|
help:
|
|
@echo "Usage: make <help>|<clean>|<destroy>|<server>|<utest>|<install>|<uninstall>"
|
|
@echo " help Display this help menu"
|
|
@echo " clean Cleanup project and all depends"
|
|
@echo " destroy Cleanup all files for this platform in ${SRS_OBJS}/${SRS_PLATFORM}"
|
|
@echo " server Build the srs and other modules in main"
|
|
@echo " utest Build the utest for srs"
|
|
@echo " install Install srs to the prefix path"
|
|
@echo " uninstall Uninstall srs from prefix path"
|
|
@echo "To rebuild special module:"
|
|
@echo " st Rebuild st-srs in ${SRS_OBJS}/${SRS_PLATFORM}/st-srs"
|
|
@echo " ffmpeg Rebuild ffmpeg in ${SRS_OBJS}/${SRS_PLATFORM}/ffmpeg-4.2-fit"
|
|
@echo "To reconfigure special depends:"
|
|
@echo " clean_openssl Remove the openssl cache."
|
|
@echo " clean_srtp2 Remove the libsrtp2 cache."
|
|
@echo " clean_opus Remove the opus cache."
|
|
@echo " clean_ffmpeg Remove the FFmpeg cache."
|
|
@echo " clean_st Remove the ST cache."
|
|
@echo "For example:"
|
|
@echo " make"
|
|
@echo " make help"
|
|
|
|
doclean:
|
|
(cd ${SRS_OBJS} && rm -rf srs srs_utest srs.exe srs_utest.exe $__mcleanups)
|
|
(cd ${SRS_OBJS} && rm -rf src/* include lib)
|
|
(mkdir -p ${SRS_OBJS}/utest && cd ${SRS_OBJS}/utest && rm -rf *.o *.a)
|
|
|
|
clean: clean_srs
|
|
|
|
destroy:
|
|
(cd ${SRS_OBJS} && rm -rf ${SRS_PLATFORM})
|
|
|
|
clean_srs:
|
|
@(cd ${SRS_OBJS} && rm -rf srs srs_utest src/* utest/*)
|
|
|
|
clean_openssl:
|
|
@rm -rf ${SRS_OBJS}/${SRS_PLATFORM}/3rdparty/openssl
|
|
@echo "Please rebuild openssl by: ./configure"
|
|
|
|
clean_srtp2:
|
|
@rm -rf ${SRS_OBJS}/${SRS_PLATFORM}/3rdparty/srtp2
|
|
@echo "Please rebuild libsrtp2 by: ./configure"
|
|
|
|
clean_opus:
|
|
@rm -rf ${SRS_OBJS}/${SRS_PLATFORM}/3rdparty/opus
|
|
@echo "Please rebuild opus by: ./configure"
|
|
|
|
clean_ffmpeg:
|
|
@rm -rf ${SRS_OBJS}/${SRS_PLATFORM}/3rdparty/ffmpeg
|
|
@echo "Please rebuild FFmpeg by: ./configure"
|
|
|
|
clean_st:
|
|
@rm -rf ${SRS_OBJS}/${SRS_PLATFORM}/3rdparty/st
|
|
@echo "Please rebuild ST by: ./configure"
|
|
|
|
st:
|
|
@rm -f ${SRS_OBJS}/srs srs_utest
|
|
@\$(MAKE)\$(JOBS) -C ${SRS_OBJS}/${SRS_PLATFORM}/st-srs clean
|
|
@env EXTRA_CFLAGS="${_ST_EXTRA_CFLAGS}" \$(MAKE)\$(JOBS) -C ${SRS_OBJS}/${SRS_PLATFORM}/st-srs ${_ST_MAKE_ARGS} CC=\$(CC) CXX=\$(CXX) AR=\$(AR) LD=\$(LINK) RANDLIB=\$(RANDLIB)
|
|
@echo "Please rebuild srs by: make"
|
|
|
|
ffmpeg:
|
|
@rm -f ${SRS_OBJS}/srs srs_utest
|
|
\$(MAKE)\$(JOBS) -C ${SRS_OBJS}/${SRS_PLATFORM}/ffmpeg-4.2-fit
|
|
\$(MAKE)\$(JOBS) -C ${SRS_OBJS}/${SRS_PLATFORM}/ffmpeg-4.2-fit install-libs
|
|
@echo "Please rebuild srs by: make"
|
|
|
|
END
|
|
|
|
# Generate Makefile entry for srs-server.
|
|
cat << END >> ${SRS_MAKEFILE}
|
|
server: _prepare_dir
|
|
@echo "Build the SRS server, JOBS=\${JOBS}, FORCE_MAKE_JOBS=${SRS_FORCE_MAKE_JOBS}"
|
|
\$(MAKE)\$(JOBS) -f ${SRS_OBJS}/Makefile srs
|
|
@bash objs/_srs_build_summary.sh
|
|
|
|
END
|
|
|
|
# install entry
|
|
cat << END >> ${SRS_MAKEFILE}
|
|
uninstall:
|
|
@echo "rmdir \$(SRS_PREFIX)"
|
|
@rm -rf \$(SRS_PREFIX)
|
|
|
|
install:
|
|
@echo "Now mkdir \$(__REAL_INSTALL)"
|
|
@mkdir -p \$(__REAL_INSTALL)
|
|
@echo "Now make the http root dir"
|
|
@mkdir -p \$(__REAL_INSTALL)/objs/nginx/html
|
|
@cp -f research/index.html \$(__REAL_INSTALL)/objs/nginx/html
|
|
@cp -f research/favicon.ico \$(__REAL_INSTALL)/objs/nginx/html
|
|
@cp -Rf research/players \$(__REAL_INSTALL)/objs/nginx/html/
|
|
@cp -Rf research/console \$(__REAL_INSTALL)/objs/nginx/html/
|
|
@cp -Rf 3rdparty/signaling/www/demos \$(__REAL_INSTALL)/objs/nginx/html/
|
|
@echo "Now copy binary files"
|
|
@mkdir -p \$(__REAL_INSTALL)/objs
|
|
@cp -f objs/srs \$(__REAL_INSTALL)/objs
|
|
@echo "Now copy srs conf files"
|
|
@mkdir -p \$(__REAL_INSTALL)/conf
|
|
@cp -f conf/*.conf \$(__REAL_INSTALL)/conf
|
|
@cp -f conf/server.key conf/server.crt \$(__REAL_INSTALL)/conf
|
|
@echo "Now copy init.d script files"
|
|
@mkdir -p \$(__REAL_INSTALL)/etc/init.d
|
|
@cp -f etc/init.d/srs \$(__REAL_INSTALL)/etc/init.d
|
|
@sed -i "s|^ROOT=.*|ROOT=\"\$(SRS_PREFIX)\"|g" \$(__REAL_INSTALL)/etc/init.d/srs
|
|
@sed -i "s|^CONFIG=.*|CONFIG=\"\$(SRS_DEFAULT_CONFIG)\"|g" \$(__REAL_INSTALL)/etc/init.d/srs
|
|
@echo "Now copy systemctl service files"
|
|
@mkdir -p \$(__REAL_INSTALL)/usr/lib/systemd/system
|
|
@cp -f usr/lib/systemd/system/srs.service \$(__REAL_INSTALL)/usr/lib/systemd/system/srs.service
|
|
@echo ""
|
|
@echo "@see: https://ossrs.net/lts/zh-cn/docs/v7/doc/service"
|
|
|
|
END
|
|
|
|
if [[ $SRS_UTEST == YES ]]; then
|
|
cat << END >> ${SRS_MAKEFILE}
|
|
utest: server
|
|
@echo "Building the utest for srs"
|
|
${SrsUtestMakeEntry}
|
|
@echo "The utest is built ok."
|
|
|
|
END
|
|
else
|
|
cat << END >> ${SRS_MAKEFILE}
|
|
utest: server
|
|
@echo "Ignore utest for it's disabled."
|
|
|
|
END
|
|
fi
|
|
|
|
cat << END >> ${SRS_MAKEFILE}
|
|
# the ./configure will generate it.
|
|
_prepare_dir:
|
|
@mkdir -p ${SRS_OBJS}
|
|
END
|
|
|
|
# generate makefile ok, append the tails.
|
|
cat ${SRS_MAKEFILE}.bk >> ${SRS_MAKEFILE} &&
|
|
rm -f ${SRS_MAKEFILE}.bk
|
|
|
|
echo 'Configure ok! '
|
|
|
|
#####################################################################################
|
|
# when configure success, prepare build
|
|
#####################################################################################
|
|
# create objs/logs for ffmpeg to write log.
|
|
mkdir -p ${SRS_OBJS}/logs
|
|
|
|
#####################################################################################
|
|
# configure summary
|
|
#####################################################################################
|
|
# summary
|
|
echo ""
|
|
echo "Configure summary:"
|
|
echo " ${SRS_AUTO_USER_CONFIGURE}"
|
|
echo " ${SRS_AUTO_CONFIGURE}"
|
|
if [[ $SRS_HLS == YES ]]; then
|
|
echo -e "${GREEN}HLS is enabled.${BLACK}"
|
|
else
|
|
echo -e "${YELLOW}Warning: HLS is disabled.${BLACK}"
|
|
fi
|
|
if [[ $SRS_STREAM_CASTER == YES ]]; then
|
|
echo -e "${YELLOW}Experiment: StreamConverter is enabled.${BLACK}"
|
|
else
|
|
echo -e "${GREEN}Note: StreamConverter 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_GB28181 == YES ]]; then
|
|
echo -e "${YELLOW}Experiment: GB28181 is enabled. https://github.com/ossrs/srs/issues/3176${BLACK}"
|
|
else
|
|
echo -e "${GREEN}Warning: GB28181 is disabled.${BLACK}"
|
|
fi
|
|
echo -e "${GREEN}Experiment: HEVC/H.265 is enabled. https://github.com/ossrs/srs/issues/465${BLACK}"
|
|
echo -e "${YELLOW}Experiment: SRT is enabled. https://github.com/ossrs/srs/issues/1147${BLACK}"
|
|
echo -e "${YELLOW}Experiment: RTC is enabled. https://github.com/ossrs/srs/issues/307${BLACK}"
|
|
if [[ $SRS_RTSP == YES ]]; then
|
|
echo -e "${YELLOW}Experiment: RTSP is enabled (requires RTC).${BLACK}"
|
|
else
|
|
echo -e "${GREEN}Warning: RTSP 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_NASM == YES ]]; then
|
|
echo -e "${GREEN}NASM for HTTPS(openssl) and FFmepg is enabled${BLACK}"
|
|
else
|
|
echo -e "${YELLOW}Warning: NASM for HTTPS(openssl) and FFmepg is disabled${BLACK}"
|
|
fi
|
|
if [[ $SRS_SRTP_ASM == YES ]]; then
|
|
echo -e "${GREEN}SRTP-NASM for WebRTC(openssl) is enabled${BLACK}"
|
|
else
|
|
echo -e "${YELLOW}Warning: SRTP-NASM for WebRTC(openssl) is disabled${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_CROSS_BUILD == YES ]]; then
|
|
echo -e "${YELLOW}The cross-build is enabled.${BLACK}"
|
|
else
|
|
echo -e "${GREEN}Note: The cross-build 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
|
|
if [[ $SRS_SANITIZER == YES ]]; then
|
|
echo -e "${GREEN}The sanitizer is enabled.${BLACK}"
|
|
else
|
|
echo -e "${GREEN}Note: The sanitizer is disabled.${BLACK}"
|
|
fi
|
|
|
|
#####################################################################################
|
|
# Do cleanup when configure done.
|
|
#####################################################################################
|
|
if [[ $SRS_CLEAN == YES && -f Makefile ]]; then
|
|
#echo "Do full cleanup, you can disable it by: --clean=off"
|
|
make clean
|
|
fi
|
|
|
|
#####################################################################################
|
|
# next step
|
|
#####################################################################################
|
|
echo ""
|
|
echo "You can build SRS:"
|
|
echo "\" make \" to build the SRS server"
|
|
echo "\" make help \" to get some help"
|
|
|