diff --git a/trunk/auto/options.sh b/trunk/auto/options.sh index 59bc9f7d55..7ab9e8feca 100755 --- a/trunk/auto/options.sh +++ b/trunk/auto/options.sh @@ -432,12 +432,6 @@ function apply_auto_options() { SRS_TOOL_LD=$SRS_TOOL_CC fi - # The SRT code in SRS requires c++11, although we build libsrt without c++11. - # TODO: FIXME: Remove c++11 code in SRT of SRS. - if [[ $SRS_SRT == YES ]]; then - SRS_CXX11=YES - fi - # Enable FFmpeg fit for RTC to transcode audio from AAC to OPUS, if user enabled it. if [[ $SRS_RTC == YES && $SRS_FFMPEG_FIT == RESERVED ]]; then SRS_FFMPEG_FIT=YES diff --git a/trunk/conf/srt.conf b/trunk/conf/srt.conf index 396dccd3ae..ed1c57f0b1 100644 --- a/trunk/conf/srt.conf +++ b/trunk/conf/srt.conf @@ -18,6 +18,11 @@ http_server { srt_server { enabled on; + tsbpdmode off; + tlpktdrop off; + latency 0; + sendbuf 2000000; + recvbuf 2000000; listen 10080; maxbw 1000000000; connect_timeout 4000; diff --git a/trunk/configure b/trunk/configure index aa812011a0..c28404154f 100755 --- a/trunk/configure +++ b/trunk/configure @@ -164,7 +164,6 @@ fi # srt code path if [[ $SRS_SRT == YES ]]; then - SrsSRTRoot="${SRS_WORKDIR}/src/srt" LibSRTRoot="${SRS_OBJS_DIR}/srt/include"; LibSRTfile="${SRS_OBJS_DIR}/srt/lib/libsrt.a" if [[ $SRS_SHARED_SRT == YES ]]; then LibSRTfile="-L${SRS_OBJS_DIR}/srt/lib -lsrt"; fi fi @@ -228,6 +227,10 @@ MODULE_FILES=("srs_protocol_amf0" "srs_protocol_io" "srs_rtmp_stack" "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_SRT == YES ]]; then + MODULE_FILES+=("srs_service_st_srt") + ModuleLibIncs+=(${LibSRTRoot}) +fi if [[ $SRS_RTC == YES ]]; then MODULE_FILES+=("srs_rtc_stun_stack") ModuleLibIncs+=(${LibSrtpRoot}) @@ -237,16 +240,6 @@ if [[ $SRS_FFMPEG_FIT == YES ]]; then fi PROTOCOL_INCS="src/protocol"; MODULE_DIR=${PROTOCOL_INCS} . auto/modules.sh PROTOCOL_OBJS="${MODULE_OBJS[@]}" -# -#srt protocol features. -if [[ $SRS_SRT == YES ]]; then - MODULE_ID="SRT" - MODULE_DEPENDS=("CORE" "KERNEL" "PROTOCOL" "APP") - ModuleLibIncs=(${SRS_OBJS_DIR} ${LibSSLRoot} ${LibSRTRoot}) - MODULE_FILES=("srt_server" "srt_handle" "srt_conn" "srt_to_rtmp" "ts_demux" "srt_data" "srt_log") - SRT_INCS=(${LibSRTRoot} ${SrsSRTRoot}); MODULE_DIR=${SrsSRTRoot} . auto/modules.sh - SRT_OBJS="${MODULE_OBJS[@]}" -fi # #App Module, for SRS server only. @@ -262,6 +255,9 @@ fi if [[ $SRS_FFMPEG_FIT == YES ]]; then ModuleLibIncs+=("${LibFfmpegRoot[*]}") fi +if [[ $SRS_SRT == YES ]]; then + ModuleLibIncs+=("${LibSRTRoot[*]}") +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" @@ -273,6 +269,9 @@ MODULE_FILES=("srs_app_server" "srs_app_conn" "srs_app_rtmp_conn" "srs_app_sourc "srs_app_caster_flv" "srs_app_latest_version" "srs_app_uuid" "srs_app_process" "srs_app_ng_exec" "srs_app_hourglass" "srs_app_dash" "srs_app_fragment" "srs_app_dvr" "srs_app_coworkers" "srs_app_hybrid" "srs_app_threads") +if [[ $SRS_SRT == YES ]]; then + MODULE_FILES+=("srs_app_srt_server" "srs_app_srt_listener" "srs_app_srt_conn" "srs_app_srt_utility" "srs_app_srt_source") +fi 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") @@ -294,9 +293,6 @@ APP_OBJS="${MODULE_OBJS[@]}" #Server Module, for SRS only. 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}) @@ -306,7 +302,6 @@ if [[ $SRS_FFMPEG_FIT == YES ]]; then fi if [[ $SRS_SRT == YES ]]; then ModuleLibIncs+=(${LibSRTRoot}) - ModuleLibIncs+=("${SrsSRTRoot[*]}") fi MODULE_FILES=("srs_main_server") SERVER_INCS="src/main"; MODULE_DIR=${SERVER_INCS} . auto/modules.sh @@ -324,7 +319,6 @@ if [[ $SRS_FFMPEG_FIT == YES ]]; then fi if [[ $SRS_SRT == YES ]]; then ModuleLibIncs+=(${LibSRTRoot}) - ModuleLibIncs+=("${SrsSRTRoot[*]}") fi MODULE_FILES=() DEFINES="" @@ -370,7 +364,6 @@ if [[ $SRS_FFMPEG_FIT == YES ]]; then fi if [[ $SRS_SRT == YES ]]; then ModuleLibIncs+=(${LibSRTRoot}) - ModuleLibIncs+=("${SrsSRTRoot[*]}") MODULE_OBJS="${MODULE_OBJS} ${SRT_OBJS[@]}" fi LINK_OPTIONS="${SrsLinkOptions}${SrsGprofLink}${SrsGperfLink}" @@ -413,7 +406,7 @@ if [ $SRS_UTEST = YES ]; then ModuleLibIncs+=("${LibFfmpegRoot[*]}") fi if [[ $SRS_SRT == YES ]]; then - ModuleLibIncs+=("${SrsSRTRoot[*]}") + ModuleLibIncs+=("${LibSRTRoot[*]}") fi ModuleLibFiles=(${LibSTfile} ${LibSSLfile}) if [[ $SRS_RTC == YES ]]; then @@ -426,9 +419,6 @@ if [ $SRS_UTEST = YES ]; then ModuleLibFiles+=("${LibSRTfile[*]}") fi MODULE_DEPENDS=("CORE" "KERNEL" "PROTOCOL" "APP") - if [[ $SRS_SRT == YES ]]; then - MODULE_DEPENDS+=("SRT") - fi 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 diff --git a/trunk/src/app/srs_app_config.cpp b/trunk/src/app/srs_app_config.cpp index 204ca2eeed..f15296933f 100644 --- a/trunk/src/app/srs_app_config.cpp +++ b/trunk/src/app/srs_app_config.cpp @@ -2546,8 +2546,9 @@ srs_error_t SrsConfig::check_normal_config() && n != "mss" && n != "latency" && n != "recvlatency" && n != "peerlatency" && n != "tlpkdrop" && n != "connect_timeout" && n != "sendbuf" && n != "recvbuf" && n != "payloadsize" - && n != "default_app" && n != "mix_correct" && n != "sei_filter") { - return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "illegal srt_stream.%s", n.c_str()); + && n != "default_app" && n != "mix_correct" && n != "sei_filter" + && n != "tlpktdrop" && n != "tsbpdmode") { + return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "illegal srt_server.%s", n.c_str()); } } } @@ -6798,6 +6799,20 @@ int SrsConfig::get_srto_mss() { return atoi(conf->arg0().c_str()); } +bool SrsConfig::get_srto_tsbpdmode() { + static bool DEFAULT = false; + SrsConfDirective* conf = root->get("srt_server"); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("tsbpdmode"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + return SRS_CONF_PERFER_TRUE(conf->arg0()); +} + int SrsConfig::get_srto_latency() { static int DEFAULT = 120; SrsConfDirective* conf = root->get("srt_server"); @@ -6854,14 +6869,18 @@ bool SrsConfig::get_srt_sei_filter() { return SRS_CONF_PERFER_TRUE(conf->arg0()); } -bool SrsConfig::get_srto_tlpkdrop() { +bool SrsConfig::get_srto_tlpktdrop() { static bool DEFAULT = true; - SrsConfDirective* conf = root->get("srt_server"); - if (!conf) { + SrsConfDirective* srt_server_conf = root->get("srt_server"); + if (!srt_server_conf) { return DEFAULT; } - conf = conf->get("tlpkdrop"); + SrsConfDirective* conf = srt_server_conf->get("tlpkdrop"); + if (! conf) { + // make it compatible tlpkdrop and tlpktdrop opt. + conf = srt_server_conf->get("tlpktdrop"); + } if (!conf || conf->arg0().empty()) { return DEFAULT; } @@ -6882,6 +6901,20 @@ int SrsConfig::get_srto_conntimeout() { return atoi(conf->arg0().c_str()); } +int SrsConfig::get_srto_peeridletimeout() { + static int DEFAULT = 10000; + SrsConfDirective* conf = root->get("srt_server"); + if (!conf) { + return DEFAULT; + } + + conf = conf->get("peer_idle_timeout"); + if (!conf || conf->arg0().empty()) { + return DEFAULT; + } + return atoi(conf->arg0().c_str()); +} + int SrsConfig::get_srto_sendbuf() { static int64_t DEFAULT = 8192 * (1500-28); SrsConfDirective* conf = root->get("srt_server"); diff --git a/trunk/src/app/srs_app_config.hpp b/trunk/src/app/srs_app_config.hpp index 5d4fdddbab..7cebd5c802 100644 --- a/trunk/src/app/srs_app_config.hpp +++ b/trunk/src/app/srs_app_config.hpp @@ -636,6 +636,8 @@ class SrsConfig virtual int get_srto_maxbw(); // Get the srt SRTO_MSS, Maximum Segment Size, default is 1500. virtual int get_srto_mss(); + // Get the srt SRTO_TSBPDMODE, timestamp base packet delivery mode, default is false. + virtual bool get_srto_tsbpdmode(); // Get the srt SRTO_LATENCY, latency, default is 0 which means peer/recv latency is 120ms. virtual int get_srto_latency(); // Get the srt SRTO_RCVLATENCY, recv latency, default is 120ms. @@ -644,10 +646,12 @@ class SrsConfig virtual int get_srto_peer_latency(); // Get the srt h264 sei filter, default is on, it will drop h264 sei packet. virtual bool get_srt_sei_filter(); - // Get the srt SRTO_TLPKDROP, Too-late Packet Drop, default is true. - virtual bool get_srto_tlpkdrop(); + // Get the srt SRTO_TLPKTDROP, Too-late Packet Drop, default is true. + virtual bool get_srto_tlpktdrop(); // Get the srt SRTO_CONNTIMEO, connection timeout, default is 3000ms. virtual int get_srto_conntimeout(); + // Get the srt SRTO_PEERIDLETIMEO, peer idle timeout, default is 10000ms. + virtual int get_srto_peeridletimeout(); // Get the srt SRTO_SNDBUF, send buffer, default is 8192 × (1500-28). virtual int get_srto_sendbuf(); // Get the srt SRTO_RCVBUF, recv buffer, default is 8192 × (1500-28). diff --git a/trunk/src/app/srs_app_pithy_print.cpp b/trunk/src/app/srs_app_pithy_print.cpp index ecbb3306ee..d4891f4025 100644 --- a/trunk/src/app/srs_app_pithy_print.cpp +++ b/trunk/src/app/srs_app_pithy_print.cpp @@ -219,6 +219,13 @@ SrsPithyPrint::SrsPithyPrint(int _stage_id) // for the rtc recv #define SRS_CONSTS_STAGE_RTC_RECV 14 +#ifdef SRS_SRT +// the pithy stage for srt play clients. +#define SRS_CONSTS_STAGE_SRT_PLAY 15 +// the pithy stage for srt publish clients. +#define SRS_CONSTS_STAGE_SRT_PUBLISH 16 +#endif + SrsPithyPrint* SrsPithyPrint::create_rtmp_play() { return new SrsPithyPrint(SRS_CONSTS_STAGE_PLAY_USER); @@ -289,6 +296,18 @@ SrsPithyPrint* SrsPithyPrint::create_rtc_recv(int fd) return new SrsPithyPrint(fd<<16 | SRS_CONSTS_STAGE_RTC_RECV); } +#ifdef SRS_SRT +SrsPithyPrint* SrsPithyPrint::create_srt_play() +{ + return new SrsPithyPrint(SRS_CONSTS_STAGE_SRT_PLAY); +} + +SrsPithyPrint* SrsPithyPrint::create_srt_publish() +{ + return new SrsPithyPrint(SRS_CONSTS_STAGE_SRT_PUBLISH); +} +#endif + SrsPithyPrint::~SrsPithyPrint() { leave_stage(); diff --git a/trunk/src/app/srs_app_pithy_print.hpp b/trunk/src/app/srs_app_pithy_print.hpp index b0173f630b..961d13ed8c 100644 --- a/trunk/src/app/srs_app_pithy_print.hpp +++ b/trunk/src/app/srs_app_pithy_print.hpp @@ -130,6 +130,10 @@ class SrsPithyPrint // For RTC sender and receiver, we create printer for each fd. static SrsPithyPrint* create_rtc_send(int fd); static SrsPithyPrint* create_rtc_recv(int fd); +#ifdef SRS_SRT + static SrsPithyPrint* create_srt_play(); + static SrsPithyPrint* create_srt_publish(); +#endif virtual ~SrsPithyPrint(); private: // Enter the specified stage, return the client id. diff --git a/trunk/src/app/srs_app_srt_conn.cpp b/trunk/src/app/srs_app_srt_conn.cpp new file mode 100644 index 0000000000..a0a279c070 --- /dev/null +++ b/trunk/src/app/srs_app_srt_conn.cpp @@ -0,0 +1,678 @@ +// +// Copyright (c) 2013-2021 The SRS Authors +// +// SPDX-License-Identifier: MIT or MulanPSL-2.0 +// + +#include + +using namespace std; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +SrsSrtConnection::SrsSrtConnection(SRTSOCKET srt_fd) +{ + srt_fd_ = srt_fd; + srt_skt_ = new SrsSrtSocket(_srt_eventloop->get_srt_poller(), srt_fd_); +} + +SrsSrtConnection::~SrsSrtConnection() +{ + srs_freep(srt_skt_); +} + +srs_error_t SrsSrtConnection::initialize() +{ + srs_error_t err = srs_success; + return err; +} + +void SrsSrtConnection::set_recv_timeout(srs_utime_t tm) +{ + srt_skt_->set_recv_timeout(tm); +} + +srs_utime_t SrsSrtConnection::get_recv_timeout() +{ + return srt_skt_->get_recv_timeout(); +} + +srs_error_t SrsSrtConnection::read_fully(void* buf, size_t size, ssize_t* nread) +{ + return srs_error_new(ERROR_SRT_CONN, "unsupport method"); +} + +int64_t SrsSrtConnection::get_recv_bytes() +{ + return srt_skt_->get_recv_bytes(); +} + +int64_t SrsSrtConnection::get_send_bytes() +{ + return srt_skt_->get_send_bytes(); +} + +srs_error_t SrsSrtConnection::read(void* buf, size_t size, ssize_t* nread) +{ + return srt_skt_->recvmsg(buf, size, nread); +} + +void SrsSrtConnection::set_send_timeout(srs_utime_t tm) +{ + srt_skt_->set_send_timeout(tm); +} + +srs_utime_t SrsSrtConnection::get_send_timeout() +{ + return srt_skt_->get_send_timeout(); +} + +srs_error_t SrsSrtConnection::write(void* buf, size_t size, ssize_t* nwrite) +{ + return srt_skt_->sendmsg(buf, size, nwrite); +} + +srs_error_t SrsSrtConnection::writev(const iovec *iov, int iov_size, ssize_t* nwrite) +{ + return srs_error_new(ERROR_SRT_CONN, "unsupport method"); +} + +SrsMpegtsSrtConn::SrsMpegtsSrtConn(SrsSrtServer* srt_server, SRTSOCKET srt_fd, std::string ip, int port) +{ + // Create a identify for this client. + _srs_context->set_id(_srs_context->generate_id()); + + srt_server_ = srt_server; + + srt_fd_ = srt_fd; + srt_conn_ = new SrsSrtConnection(srt_fd_); + clock_ = new SrsWallClock(); + kbps_ = new SrsKbps(clock_); + kbps_->set_io(srt_conn_, srt_conn_); + ip_ = ip; + port_ = port; + + trd_ = new SrsSTCoroutine("ts-srt", this, _srs_context->get_id()); + + srt_source_ = NULL; + req_ = new SrsRequest(); + mode_ = SrtModePull; +} + +SrsMpegtsSrtConn::~SrsMpegtsSrtConn() +{ + srs_freep(trd_); + + srs_freep(kbps_); + srs_freep(clock_); + + srs_freep(srt_conn_); + + srs_freep(req_); +} + +std::string SrsMpegtsSrtConn::desc() +{ + return "srt-ts-conn"; +} + +void SrsMpegtsSrtConn::remark(int64_t* in, int64_t* out) +{ + // TODO: FIXME: no impl currently. + kbps_->remark(in, out); +} + +srs_error_t SrsMpegtsSrtConn::start() +{ + srs_error_t err = srs_success; + + if ((err = trd_->start()) != srs_success) { + return srs_error_wrap(err, "coroutine"); + } + + return err; +} + +std::string SrsMpegtsSrtConn::remote_ip() +{ + return ip_; +} + +const SrsContextId& SrsMpegtsSrtConn::get_id() +{ + return trd_->cid(); +} + +srs_error_t SrsMpegtsSrtConn::cycle() +{ + srs_error_t err = srs_success; + + err = do_cycle(); + + // Notify manager to remove it. + // Note that we create this object, so we use manager to remove it. + srt_server_->remove(this); + + // success. + if (err == srs_success) { + srs_trace("srt client finished."); + return err; + } + + srs_error("srt serve error %s", srs_error_desc(err).c_str()); + srs_freep(err); + return srs_success; +} + +srs_error_t SrsMpegtsSrtConn::do_cycle() +{ + srs_error_t err = srs_success; + + if ((err != fetch_or_create_source()) != srs_success) { + return srs_error_wrap(err, "fetch or create srt source"); + } + + if ((err = http_hooks_on_connect()) != srs_success) { + return srs_error_wrap(err, "on connect"); + } + + if (mode_ == SrtModePush) { + err = publishing(); + } else if (mode_ == SrtModePull) { + err = playing(); + } + + http_hooks_on_close(); + + return err; +} + +srs_error_t SrsMpegtsSrtConn::fetch_or_create_source() +{ + srs_error_t err = srs_success; + + string streamid = ""; + if ((err = srs_srt_get_streamid(srt_fd_, streamid)) != srs_success) { + return srs_error_wrap(err, "get srt streamid"); + } + + // Must have streamid, because srt ts packet will convert to rtmp or rtc. + if (streamid.empty()) { + return srs_error_new(ERROR_SRT_CONN, "empty srt streamid"); + } + + // Detect streamid of srt to request. + if (! srs_srt_streamid_to_request(streamid, mode_, req_)) { + return srs_error_new(ERROR_SRT_CONN, "invalid srt streamid=%s", streamid.c_str()); + } + + srs_trace("@srt, streamid=%s, stream_url=%s, vhost=%s, app=%s, stream=%s, param=%s", + streamid.c_str(), req_->get_stream_url().c_str(), req_->vhost.c_str(), req_->app.c_str(), req_->stream.c_str(), req_->param.c_str()); + + if ((err = _srs_srt_sources->fetch_or_create(req_, &srt_source_)) != srs_success) { + return srs_error_wrap(err, "fetch srt source"); + } + + return err; +} + +srs_error_t SrsMpegtsSrtConn::publishing() +{ + srs_error_t err = srs_success; + + if ((err = http_hooks_on_publish()) != srs_success) { + return srs_error_wrap(err, "srt: callback on publish"); + } + + if ((err = acquire_publish()) == srs_success) { + err = do_publishing(); + release_publish(); + } + + http_hooks_on_unpublish(); + + return err; +} + +srs_error_t SrsMpegtsSrtConn::playing() +{ + srs_error_t err = srs_success; + + if ((err = http_hooks_on_play()) != srs_success) { + return srs_error_wrap(err, "rtmp: callback on play"); + } + + err = do_playing(); + http_hooks_on_stop(); + + return err; +} + +srs_error_t SrsMpegtsSrtConn::acquire_publish() +{ + srs_error_t err = srs_success; + + // Check srt stream is busy. + if (! srt_source_->can_publish()) { + return srs_error_new(ERROR_SRT_SOURCE_BUSY, "srt stream %s busy", req_->get_stream_url().c_str()); + } + + // Check rtmp stream is busy. + SrsLiveSource *live_source = _srs_sources->fetch(req_); + if (live_source && !live_source->can_publish(false)) { + return srs_error_new(ERROR_SYSTEM_STREAM_BUSY, "live_source stream %s busy", req_->get_stream_url().c_str()); + } + + if ((err = _srs_sources->fetch_or_create(req_, _srs_hybrid->srs()->instance(), &live_source)) != srs_success) { + return srs_error_wrap(err, "create source"); + } + + SrsRtmpFromTsBridge *bridger = new SrsRtmpFromTsBridge(live_source); + if ((err = bridger->initialize(req_)) != srs_success) { + srs_freep(bridger); + return srs_error_wrap(err, "create bridger"); + } + + srt_source_->set_bridger(bridger); + + if ((err = srt_source_->on_publish()) != srs_success) { + return srs_error_wrap(err, "srt source publish"); + } + + return err; +} + +void SrsMpegtsSrtConn::release_publish() +{ + srt_source_->on_unpublish(); +} + +/* +srs_error_t SrsMpegtsSrtConn::do_cycle() +{ + srs_error_t err = srs_success; + + string streamid = ""; + if ((err = srs_srt_get_streamid(srt_fd_, streamid)) != srs_success) { + return srs_error_wrap(err, "get srt streamid"); + } + + // Must have streamid, because srt ts packet will convert to rtmp or rtc. + if (streamid.empty()) { + return srs_error_new(ERROR_SRT_CONN, "empty srt streamid"); + } + + // Detect streamid of srt to request. + if (! srs_srt_streamid_to_request(streamid, mode_, req_)) { + return srs_error_new(ERROR_SRT_CONN, "invalid srt streamid=%s", streamid.c_str()); + } + + srs_trace("@srt, streamid=%s, stream_url=%s, vhost=%s, app=%s, stream=%s, param=%s", + streamid.c_str(), req_->get_stream_url().c_str(), req_->vhost.c_str(), req_->app.c_str(), req_->stream.c_str(), req_->param.c_str()); + + if ((err = _srs_srt_sources->fetch_or_create(req_, &srt_source_)) != srs_success) { + return srs_error_wrap(err, "fetch srt source"); + } + + if (mode_ == SrtModePush) { + if ((err = http_hooks_on_publish()) != srs_success) { + return srs_error_wrap(err, "srt: callback on publish"); + } + // Do srt publish. + if (! srt_source_->can_publish()) { + return srs_error_new(ERROR_SRT_SOURCE_BUSY, "srt stream %s busy", req_->get_stream_url().c_str()); + } + + SrsLiveSource *live_source = _srs_sources->fetch(req_); + if (live_source && !live_source->can_publish(false)) { + return srs_error_new(ERROR_SYSTEM_STREAM_BUSY, "live_source stream %s busy", req_->get_stream_url().c_str()); + } + + if ((err = _srs_sources->fetch_or_create(req_, _srs_hybrid->srs()->instance(), &live_source)) != srs_success) { + return srs_error_wrap(err, "create source"); + } + + SrsRtmpFromTsBridge *bridger = new SrsRtmpFromTsBridge(live_source); + if ((err = bridger->initialize(req_)) != srs_success) { + srs_freep(bridger); + return srs_error_wrap(err, "create bridger"); + } + + srt_source_->set_bridger(bridger); + + if ((err = srt_source_->on_publish()) != srs_success) { + return srs_error_wrap(err, "srt source publish"); + } + + err = do_publish_cycle(); + + srt_source_->on_unpublish(); + http_hooks_on_unpublish(); + } else if (mode_ == SrtModePull) { + if ((err = http_hooks_on_play()) != srs_success) { + return srs_error_wrap(err, "srt: callback on play"); + } + // Do srt play. + err = do_play_cycle(); + + http_hooks_on_stop(); + } else { + srs_assert(false); + } + + return err; +} +*/ + +srs_error_t SrsMpegtsSrtConn::do_publishing() +{ + srs_error_t err = srs_success; + + SrsPithyPrint* pprint = SrsPithyPrint::create_srt_publish(); + SrsAutoFree(SrsPithyPrint, pprint); + + int nb_packets = 0; + + // Max udp packet size equal to 1500. + char buf[1500]; + while (true) { + if ((err = trd_->pull()) != srs_success) { + return srs_error_wrap(err, "srt: thread quit"); + } + + pprint->elapse(); + + // reportable + if (pprint->can_print()) { + kbps_->sample(); + srs_trace("<- " SRS_CONSTS_LOG_SRT_PUBLISH " time=%d, packets=%d, okbps=%d,%d,%d, ikbps=%d,%d,%d", + (int)pprint->age(), nb_packets, kbps_->get_send_kbps(), kbps_->get_send_kbps_30s(), kbps_->get_send_kbps_5m(), + kbps_->get_recv_kbps(), kbps_->get_recv_kbps_30s(), kbps_->get_recv_kbps_5m()); + nb_packets = 0; + } + + ssize_t nb = 0; + if ((err = srt_conn_->read(buf, sizeof(buf), &nb)) != srs_success) { + return srs_error_wrap(err, "srt: recvmsg"); + } + + ++nb_packets; + + if ((err = on_srt_packet(buf, nb)) != srs_success) { + return srs_error_wrap(err, "srt: process packet"); + } + } + + return err; +} + +srs_error_t SrsMpegtsSrtConn::do_playing() +{ + srs_error_t err = srs_success; + + SrsSrtConsumer* consumer = NULL; + SrsAutoFree(SrsSrtConsumer, consumer); + if ((err = srt_source_->create_consumer(consumer)) != srs_success) { + return srs_error_wrap(err, "create consumer, ts source=%s", req_->get_stream_url().c_str()); + } + + srs_assert(consumer); + + // TODO: FIXME: Dumps the SPS/PPS from gop cache, without other frames. + if ((err = srt_source_->consumer_dumps(consumer)) != srs_success) { + return srs_error_wrap(err, "dumps consumer, url=%s", req_->get_stream_url().c_str()); + } + + SrsPithyPrint* pprint = SrsPithyPrint::create_srt_play(); + SrsAutoFree(SrsPithyPrint, pprint); + + int nb_packets = 0; + + while (true) { + if ((err = trd_->pull()) != srs_success) { + return srs_error_wrap(err, "srt play thread"); + } + + pprint->elapse(); + + // Wait for amount of packets. + SrsSrtPacket* pkt = NULL; + SrsAutoFree(SrsSrtPacket, pkt); + consumer->dump_packet(&pkt); + if (!pkt) { + // TODO: FIXME: We should check the quit event. + consumer->wait(1); + continue; + } + + // reportable + if (pprint->can_print()) { + kbps_->sample(); + srs_trace("-> " SRS_CONSTS_LOG_SRT_PLAY " time=%d, packets=%d, okbps=%d,%d,%d, ikbps=%d,%d,%d", + (int)pprint->age(), nb_packets, kbps_->get_send_kbps(), kbps_->get_send_kbps_30s(), kbps_->get_send_kbps_5m(), + kbps_->get_recv_kbps(), kbps_->get_recv_kbps_30s(), kbps_->get_recv_kbps_5m()); + nb_packets = 0; + } + + ++nb_packets; + + ssize_t nb_write = 0; + if ((err = srt_conn_->write(pkt->data(), pkt->size(), &nb_write)) != srs_success) { + return srs_error_wrap(err, "srt send, size=%d", pkt->size()); + } + } + + return err; +} + +srs_error_t SrsMpegtsSrtConn::on_srt_packet(char* buf, int nb_buf) +{ + srs_error_t err = srs_success; + + // Check srt payload, mpegts must be N times of SRS_TS_PACKET_SIZE, and the first byte must be 0x47 + if ((nb_buf <= 0) || (nb_buf % SRS_TS_PACKET_SIZE != 0) || (buf[0] != 0x47)) { + return srs_error_new(ERROR_SRT_CONN, "invalid ts packet"); + } + + SrsSrtPacket* packet = new SrsSrtPacket(); + SrsAutoFree(SrsSrtPacket, packet); + packet->wrap(buf, nb_buf); + + if ((err = srt_source_->on_packet(packet)) != srs_success) { + return srs_error_wrap(err, "on srt packet"); + } + + return err; +} + +srs_error_t SrsMpegtsSrtConn::http_hooks_on_connect() +{ + srs_error_t err = srs_success; + + if (!_srs_config->get_vhost_http_hooks_enabled(req_->vhost)) { + return err; + } + + // the http hooks will cause context switch, + // so we must copy all hooks for the on_connect may freed. + // @see https://github.com/ossrs/srs/issues/475 + vector hooks; + + if (true) { + SrsConfDirective* conf = _srs_config->get_vhost_on_connect(req_->vhost); + + if (!conf) { + return err; + } + + hooks = conf->args; + } + + for (int i = 0; i < (int)hooks.size(); i++) { + std::string url = hooks.at(i); + if ((err = SrsHttpHooks::on_connect(url, req_)) != srs_success) { + return srs_error_wrap(err, "srt on_connect %s", url.c_str()); + } + } + + return err; +} + +void SrsMpegtsSrtConn::http_hooks_on_close() +{ + if (!_srs_config->get_vhost_http_hooks_enabled(req_->vhost)) { + return; + } + + // the http hooks will cause context switch, + // so we must copy all hooks for the on_connect may freed. + // @see https://github.com/ossrs/srs/issues/475 + vector hooks; + + if (true) { + SrsConfDirective* conf = _srs_config->get_vhost_on_close(req_->vhost); + + if (!conf) { + return; + } + + hooks = conf->args; + } + + for (int i = 0; i < (int)hooks.size(); i++) { + std::string url = hooks.at(i); + SrsHttpHooks::on_close(url, req_, kbps_->get_send_bytes(), kbps_->get_recv_bytes()); + } +} + +srs_error_t SrsMpegtsSrtConn::http_hooks_on_publish() +{ + srs_error_t err = srs_success; + + if (!_srs_config->get_vhost_http_hooks_enabled(req_->vhost)) { + return err; + } + + // the http hooks will cause context switch, + // so we must copy all hooks for the on_connect may freed. + // @see https://github.com/ossrs/srs/issues/475 + vector hooks; + + if (true) { + SrsConfDirective* conf = _srs_config->get_vhost_on_publish(req_->vhost); + + if (!conf) { + return err; + } + + hooks = conf->args; + } + + for (int i = 0; i < (int)hooks.size(); i++) { + std::string url = hooks.at(i); + if ((err = SrsHttpHooks::on_publish(url, req_)) != srs_success) { + return srs_error_wrap(err, "srt on_publish %s", url.c_str()); + } + } + + return err; +} + +void SrsMpegtsSrtConn::http_hooks_on_unpublish() +{ + if (!_srs_config->get_vhost_http_hooks_enabled(req_->vhost)) { + return; + } + + // the http hooks will cause context switch, + // so we must copy all hooks for the on_connect may freed. + // @see https://github.com/ossrs/srs/issues/475 + vector hooks; + + if (true) { + SrsConfDirective* conf = _srs_config->get_vhost_on_unpublish(req_->vhost); + + if (!conf) { + return; + } + + hooks = conf->args; + } + + for (int i = 0; i < (int)hooks.size(); i++) { + std::string url = hooks.at(i); + SrsHttpHooks::on_unpublish(url, req_); + } +} + +srs_error_t SrsMpegtsSrtConn::http_hooks_on_play() +{ + srs_error_t err = srs_success; + + if (!_srs_config->get_vhost_http_hooks_enabled(req_->vhost)) { + return err; + } + + // the http hooks will cause context switch, + // so we must copy all hooks for the on_connect may freed. + // @see https://github.com/ossrs/srs/issues/475 + vector hooks; + + if (true) { + SrsConfDirective* conf = _srs_config->get_vhost_on_play(req_->vhost); + + if (!conf) { + return err; + } + + hooks = conf->args; + } + + for (int i = 0; i < (int)hooks.size(); i++) { + std::string url = hooks.at(i); + if ((err = SrsHttpHooks::on_play(url, req_)) != srs_success) { + return srs_error_wrap(err, "srt on_play %s", url.c_str()); + } + } + + return err; +} + +void SrsMpegtsSrtConn::http_hooks_on_stop() +{ + if (!_srs_config->get_vhost_http_hooks_enabled(req_->vhost)) { + return; + } + + // the http hooks will cause context switch, + // so we must copy all hooks for the on_connect may freed. + // @see https://github.com/ossrs/srs/issues/475 + vector hooks; + + if (true) { + SrsConfDirective* conf = _srs_config->get_vhost_on_stop(req_->vhost); + + if (!conf) { + return; + } + + hooks = conf->args; + } + + for (int i = 0; i < (int)hooks.size(); i++) { + std::string url = hooks.at(i); + SrsHttpHooks::on_stop(url, req_); + } + + return; +} diff --git a/trunk/src/app/srs_app_srt_conn.hpp b/trunk/src/app/srs_app_srt_conn.hpp new file mode 100644 index 0000000000..351894f708 --- /dev/null +++ b/trunk/src/app/srs_app_srt_conn.hpp @@ -0,0 +1,109 @@ +// +// Copyright (c) 2013-2021 The SRS Authors +// +// SPDX-License-Identifier: MIT or MulanPSL-2.0 +// + +#ifndef SRS_APP_SRT_CONN_HPP +#define SRS_APP_SRT_CONN_HPP + +#include + +#include +#include + +#include +#include +#include +#include + +class SrsBuffer; +class SrsLiveSource; +class SrsSrtSource; +class SrsSrtServer; + +// The basic connection of SRS, for SRT based protocols, +// all srt connections accept from srt listener must extends from this base class, +// srt server will add the connection to manager, and delete it when remove. +class SrsSrtConnection : public ISrsProtocolReadWriter +{ +public: + SrsSrtConnection(SRTSOCKET srt_fd); + virtual ~SrsSrtConnection(); +public: + virtual srs_error_t initialize(); +// 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); +private: + // The underlayer srt fd handler. + SRTSOCKET srt_fd_; + // The underlayer srt socket. + SrsSrtSocket* srt_skt_; +}; + +class SrsMpegtsSrtConn : public ISrsStartableConneciton, public ISrsCoroutineHandler +{ +public: + SrsMpegtsSrtConn(SrsSrtServer* srt_server, SRTSOCKET srt_fd, std::string ip, int port); + virtual ~SrsMpegtsSrtConn(); +// Interface ISrsResource. +public: + virtual std::string desc(); +// Interface ISrsKbpsDelta +public: + virtual void remark(int64_t* in, int64_t* out); +public: + virtual srs_error_t start(); +// Interface ISrsConnection. +public: + virtual std::string remote_ip(); + virtual const SrsContextId& get_id(); +// Interface ISrsCoroutineHandler +public: + virtual srs_error_t cycle(); +protected: + virtual srs_error_t do_cycle(); +private: + srs_error_t fetch_or_create_source(); + srs_error_t publishing(); + srs_error_t playing(); + srs_error_t acquire_publish(); + void release_publish(); + srs_error_t do_publishing(); + srs_error_t do_playing(); +private: + srs_error_t on_srt_packet(char* buf, int nb_buf); +private: + srs_error_t http_hooks_on_connect(); + void http_hooks_on_close(); + srs_error_t http_hooks_on_publish(); + void http_hooks_on_unpublish(); + srs_error_t http_hooks_on_play(); + void http_hooks_on_stop(); +private: + SrsSrtServer* srt_server_; + SRTSOCKET srt_fd_; + SrsSrtConnection* srt_conn_; + SrsWallClock* clock_; + SrsKbps* kbps_; + std::string ip_; + int port_; + SrsCoroutine* trd_; + + SrsRequest* req_; + SrtMode mode_; + SrsSrtSource* srt_source_; +}; + +#endif + diff --git a/trunk/src/app/srs_app_srt_listener.cpp b/trunk/src/app/srs_app_srt_listener.cpp new file mode 100644 index 0000000000..f3b9951f4b --- /dev/null +++ b/trunk/src/app/srs_app_srt_listener.cpp @@ -0,0 +1,100 @@ +// +// Copyright (c) 2013-2021 The SRS Authors +// +// SPDX-License-Identifier: MIT or MulanPSL-2.0 +// + +#include + +#include + +using namespace std; + +#include + +ISrsSrtHandler::ISrsSrtHandler() +{ +} + +ISrsSrtHandler::~ISrsSrtHandler() +{ +} + +SrsSrtListener::SrsSrtListener(ISrsSrtHandler* h, std::string i, int p) +{ + handler_ = h; + ip_ = i; + port_ = p; + + lfd_ = SRT_INVALID_SOCK; + srt_skt_ = NULL; + + trd_ = new SrsDummyCoroutine(); +} + +SrsSrtListener::~SrsSrtListener() +{ + srs_freep(trd_); + srs_freep(srt_skt_); + srt_close(lfd_); +} + +int SrsSrtListener::fd() +{ + return lfd_; +} + +srs_error_t SrsSrtListener::create_socket() +{ + srs_error_t err = srs_success; + if ((err = srs_srt_socket(&lfd_)) != srs_success) { + return srs_error_wrap(err, "create_socket"); + } + return err; +} + +srs_error_t SrsSrtListener::listen() +{ + srs_error_t err = srs_success; + + if ((err = srs_srt_listen(lfd_, ip_, port_)) != srs_success) { + return srs_error_wrap(err, "srs_srt_listen"); + } + + srt_skt_ = new SrsSrtSocket(_srt_eventloop->get_srt_poller(), lfd_); + // Accept never timeout. + srt_skt_->set_recv_timeout(ST_UTIME_NO_TIMEOUT); + srt_skt_->set_send_timeout(ST_UTIME_NO_TIMEOUT); + + srs_freep(trd_); + trd_ = new SrsSTCoroutine("srt_listener", this); + if ((err = trd_->start()) != srs_success) { + return srs_error_wrap(err, "start coroutine"); + } + + return err; +} + +srs_error_t SrsSrtListener::cycle() +{ + srs_error_t err = srs_success; + + while (true) { + if ((err = trd_->pull()) != srs_success) { + return srs_error_wrap(err, "srt listener"); + } + + SRTSOCKET client_srt_fd = SRT_INVALID_SOCK; + if ((err = srt_skt_->accept(&client_srt_fd)) != srs_success) { + return srs_error_wrap(err, "srt accept"); + } + + // TODO: FIXME: print some log and client srt options. + + if ((err = handler_->on_srt_client(client_srt_fd)) != srs_success) { + return srs_error_wrap(err, "handle srt fd=%d", client_srt_fd); + } + } + + return err; +} diff --git a/trunk/src/app/srs_app_srt_listener.hpp b/trunk/src/app/srs_app_srt_listener.hpp new file mode 100644 index 0000000000..6bd3e2501a --- /dev/null +++ b/trunk/src/app/srs_app_srt_listener.hpp @@ -0,0 +1,53 @@ +// +// Copyright (c) 2013-2021 The SRS Authors +// +// SPDX-License-Identifier: MIT or MulanPSL-2.0 +// + +#ifndef SRS_APP_SRT_LISTENER_HPP +#define SRS_APP_SRT_LISTENER_HPP + +#include +#include +#include + +#include + +// The srt connection handler. +class ISrsSrtHandler +{ +public: + ISrsSrtHandler(); + virtual ~ISrsSrtHandler(); +public: + // When got srt client. + virtual srs_error_t on_srt_client(SRTSOCKET srt_fd) = 0; +}; + +// Bind and listen SRT(udp) port, use handler to process the client. +class SrsSrtListener : public ISrsCoroutineHandler +{ +private: + SRTSOCKET lfd_; + SrsSrtSocket* srt_skt_; + SrsCoroutine* trd_; +private: + ISrsSrtHandler* handler_; + std::string ip_; + int port_; +public: + SrsSrtListener(ISrsSrtHandler* h, std::string i, int p); + virtual ~SrsSrtListener(); +public: + virtual SRTSOCKET fd(); +public: + // Create srt socket, separate this step because of srt have some option must set before listen. + virtual srs_error_t create_socket(); + virtual srs_error_t listen(); +// Interface ISrsReusableThreadHandler. +public: + virtual srs_error_t cycle(); +}; + +#endif + diff --git a/trunk/src/app/srs_app_srt_server.cpp b/trunk/src/app/srs_app_srt_server.cpp new file mode 100644 index 0000000000..7e709706d1 --- /dev/null +++ b/trunk/src/app/srs_app_srt_server.cpp @@ -0,0 +1,392 @@ +// +// Copyright (c) 2013-2021 The SRS Authors +// +// SPDX-License-Identifier: MIT or MulanPSL-2.0 +// + +#include + +using namespace std; + +#include +#include +#include +#include +#include + +std::string srs_srt_listener_type2string(SrsSrtListenerType type) +{ + switch (type) { + case SrsSrtListenerMpegts: + return "SRT-MPEGTS"; + default: + return "UNKONWN"; + } +} + +SrsSrtAcceptor::SrsSrtAcceptor(SrsSrtServer* srt_server, SrsSrtListenerType t) +{ + port_ = 0; + srt_server_ = srt_server; + type_ = t; +} + +SrsSrtAcceptor::~SrsSrtAcceptor() +{ +} + +SrsSrtListenerType SrsSrtAcceptor::listen_type() +{ + return type_; +} + +SrsSrtMessageAcceptor::SrsSrtMessageAcceptor(SrsSrtServer* srt_server, SrsSrtListenerType listen_type) + : SrsSrtAcceptor(srt_server, listen_type) +{ + listener_ = NULL; +} + +SrsSrtMessageAcceptor::~SrsSrtMessageAcceptor() +{ + srs_freep(listener_); +} + +srs_error_t SrsSrtMessageAcceptor::listen(std::string ip, int port) +{ + srs_error_t err = srs_success; + + ip_ = ip; + port_ = port; + + srs_freep(listener_); + listener_ = new SrsSrtListener(this, ip_, port_); + + // Create srt socket. + if ((err = listener_->create_socket()) != srs_success) { + return srs_error_wrap(err, "message srt acceptor"); + } + + // Set all the srt option from config. + if ((err = set_srt_opt()) != srs_success) { + return srs_error_wrap(err, "set opt"); + } + + // Start listen srt socket, this function will set the socket in async mode. + if ((err = listener_->listen()) != srs_success) { + return srs_error_wrap(err, "message srt acceptor"); + } + + string v = srs_srt_listener_type2string(type_); + srs_trace("%s listen at srt://%s:%d, fd=%d", v.c_str(), ip_.c_str(), port_, listener_->fd()); + + return err; +} + +srs_error_t SrsSrtMessageAcceptor::set_srt_opt() +{ + srs_error_t err = srs_success; + + if ((err = srs_srt_set_maxbw(listener_->fd(), _srs_config->get_srto_maxbw())) != srs_success) { + return srs_error_wrap(err, "set opt"); + } + + if ((err = srs_srt_set_mss(listener_->fd(), _srs_config->get_srto_mss())) != srs_success) { + return srs_error_wrap(err, "set opt"); + } + + if ((err = srs_srt_set_tsbpdmode(listener_->fd(), _srs_config->get_srto_tsbpdmode())) != srs_success) { + return srs_error_wrap(err, "set opt"); + } + + if ((err = srs_srt_set_latency(listener_->fd(), _srs_config->get_srto_latency())) != srs_success) { + return srs_error_wrap(err, "set opt"); + } + + if ((err = srs_srt_set_rcv_latency(listener_->fd(), _srs_config->get_srto_recv_latency())) != srs_success) { + return srs_error_wrap(err, "set opt"); + } + + if ((err = srs_srt_set_peer_latency(listener_->fd(), _srs_config->get_srto_peer_latency())) != srs_success) { + return srs_error_wrap(err, "set opt"); + } + + if ((err = srs_srt_set_tlpktdrop(listener_->fd(), _srs_config->get_srto_tlpktdrop())) != srs_success) { + return srs_error_wrap(err, "set opt"); + } + + if ((err = srs_srt_set_connect_timeout(listener_->fd(), _srs_config->get_srto_conntimeout())) != srs_success) { + return srs_error_wrap(err, "set opt"); + } + + if ((err = srs_srt_set_peer_idle_timeout(listener_->fd(), _srs_config->get_srto_peeridletimeout())) != srs_success) { + return srs_error_wrap(err, "set opt"); + } + + if ((err = srs_srt_set_sndbuf(listener_->fd(), _srs_config->get_srto_sendbuf())) != srs_success) { + return srs_error_wrap(err, "set opt"); + } + + if ((err = srs_srt_set_rcvbuf(listener_->fd(), _srs_config->get_srto_recvbuf())) != srs_success) { + return srs_error_wrap(err, "set opt"); + } + + if ((err = srs_srt_set_payload_size(listener_->fd(), _srs_config->get_srto_payloadsize())) != srs_success) { + return srs_error_wrap(err, "set opt"); + } + + return err; +} + +srs_error_t SrsSrtMessageAcceptor::on_srt_client(SRTSOCKET srt_fd) +{ + // Notify srt server to accept srt client, and create new SrsSrtConn on it. + srs_error_t err = srt_server_->accept_srt_client(type_, srt_fd); + if (err != srs_success) { + srs_warn("accept srt client failed, err is %s", srs_error_desc(err).c_str()); + srs_freep(err); + } + + return srs_success; +} + +SrsSrtServer::SrsSrtServer() +{ + conn_manager_ = new SrsResourceManager("SRT", true); +} + +SrsSrtServer::~SrsSrtServer() +{ + srs_freep(conn_manager_); +} + +srs_error_t SrsSrtServer::initialize() +{ + srs_error_t err = srs_success; + return err; +} + +srs_error_t SrsSrtServer::listen() +{ + srs_error_t err = srs_success; + + // Listen mpegts over srt. + if ((err = listen_srt_mpegts()) != srs_success) { + return srs_error_wrap(err, "srt mpegts listen"); + } + + if ((err = conn_manager_->start()) != srs_success) { + return srs_error_wrap(err, "srt connection manager"); + } + + return err; +} + +srs_error_t SrsSrtServer::listen_srt_mpegts() +{ + srs_error_t err = srs_success; + + if (! _srs_config->get_srt_enabled()) { + return err; + } + + // TODO: FIXME: bad code, refine it. + std::vector ip_ports; + std::stringstream ss; + ss << _srs_config->get_srt_listen_port(); + ip_ports.push_back(ss.str()); + + close_listeners(SrsSrtListenerMpegts); + + for (int i = 0; i < (int)ip_ports.size(); i++) { + SrsSrtAcceptor* acceptor = new SrsSrtMessageAcceptor(this, SrsSrtListenerMpegts); + acceptors_.push_back(acceptor); + + int port; string ip; + srs_parse_endpoint(ip_ports[i], ip, port); + + if ((err = acceptor->listen(ip, port)) != srs_success) { + return srs_error_wrap(err, "srt listen %s:%d", ip.c_str(), port); + } + } + + return err; +} + +void SrsSrtServer::close_listeners(SrsSrtListenerType type) +{ + std::vector::iterator it; + for (it = acceptors_.begin(); it != acceptors_.end();) { + SrsSrtAcceptor* acceptor = *it; + + if (acceptor->listen_type() != type) { + ++it; + continue; + } + + srs_freep(acceptor); + it = acceptors_.erase(it); + } +} + +srs_error_t SrsSrtServer::accept_srt_client(SrsSrtListenerType type, SRTSOCKET srt_fd) +{ + srs_error_t err = srs_success; + + ISrsStartableConneciton* conn = NULL; + + if ((err = fd_to_resource(type, srt_fd, &conn)) != srs_success) { + //close fd on conn error, otherwise will lead to fd leak -gs + srt_close(srt_fd); + return srs_error_wrap(err, "srt fd to resource"); + } + srs_assert(conn); + + // directly enqueue, the cycle thread will remove the client. + conn_manager_->add(conn); + + if ((err = conn->start()) != srs_success) { + return srs_error_wrap(err, "start srt conn coroutine"); + } + + return err; +} + +srs_error_t SrsSrtServer::fd_to_resource(SrsSrtListenerType type, SRTSOCKET srt_fd, ISrsStartableConneciton** pr) +{ + srs_error_t err = srs_success; + + string ip = ""; + int port = 0; + + if ((err = srs_srt_get_remote_ip_port(srt_fd, ip, port)) != srs_success) { + return srs_error_wrap(err, "get srt ip port"); + } + + srs_trace("accept srt client from %s:%d, fd=%d", ip.c_str(), port, srt_fd); + + // TODO: FIXME: need to check max connection? + + // The context id may change during creating the bellow objects. + SrsContextRestore(_srs_context->get_id()); + + if (type == SrsSrtListenerMpegts) { + *pr = new SrsMpegtsSrtConn(this, srt_fd, ip, port); + } else { + srs_warn("close for no service handler. srtfd=%d, ip=%s:%d", srt_fd, ip.c_str(), port); + srt_close(srt_fd); + return err; + } + + return err; +} + +void SrsSrtServer::remove(ISrsResource* c) +{ + // TODO: FIXME: add some statistic of srt. + // ISrsStartableConneciton* conn = dynamic_cast(c); + + // SrsStatistic* stat = SrsStatistic::instance(); + // stat->kbps_add_delta(c->get_id().c_str(), conn); + // stat->on_disconnect(c->get_id().c_str()); + + // use manager to free it async. + conn_manager_->remove(c); +} + +SrsSrtServerAdapter::SrsSrtServerAdapter() +{ + srt_server_ = new SrsSrtServer(); +} + +SrsSrtServerAdapter::~SrsSrtServerAdapter() +{ + srs_freep(srt_server_); +} + +srs_error_t SrsSrtServerAdapter::initialize() +{ + srs_error_t err = srs_success; + return err; +} + +srs_error_t SrsSrtServerAdapter::run(SrsWaitGroup* wg) +{ + srs_error_t err = srs_success; + + // Initialize the whole system, set hooks to handle server level events. + if ((err = srt_server_->initialize()) != srs_success) { + return srs_error_wrap(err, "srt server initialize"); + } + + if ((err = srt_server_->listen()) != srs_success) { + return srs_error_wrap(err, "srt listen"); + } + + return err; +} + +void SrsSrtServerAdapter::stop() +{ +} + +SrsSrtServer* SrsSrtServerAdapter::instance() +{ + return srt_server_; +} + +SrsSrtEventLoop::SrsSrtEventLoop() +{ + srt_poller_ = NULL; + trd_ = NULL; +} + +SrsSrtEventLoop::~SrsSrtEventLoop() +{ + srs_freep(trd_); + srs_freep(srt_poller_); +} + +srs_error_t SrsSrtEventLoop::initialize() +{ + srs_error_t err = srs_success; + + srt_poller_ = new SrsSrtPoller(); + + if ((err = srt_poller_->initialize()) != srs_success) { + return srs_error_wrap(err, "srt poller initialize"); + } + + return err; +} + +srs_error_t SrsSrtEventLoop::start() +{ + srs_error_t err = srs_success; + + trd_ = new SrsSTCoroutine("srt_listener", this); + if ((err = trd_->start()) != srs_success) { + return srs_error_wrap(err, "start coroutine"); + } + + return err; +} + +srs_error_t SrsSrtEventLoop::cycle() +{ + srs_error_t err = srs_success; + + while (true) { + if ((err = trd_->pull()) != srs_success) { + return srs_error_wrap(err, "srt listener"); + } + + if ((err = srt_poller_->wait(0)) != srs_success) { + srs_error("srt poll wait failed, err=%s", srs_error_desc(err).c_str()); + srs_error_reset(err); + } + + srs_usleep(10 * SRS_UTIME_MILLISECONDS); + } + + return err; +} diff --git a/trunk/src/app/srs_app_srt_server.hpp b/trunk/src/app/srs_app_srt_server.hpp new file mode 100644 index 0000000000..e3311a77ac --- /dev/null +++ b/trunk/src/app/srs_app_srt_server.hpp @@ -0,0 +1,130 @@ +// +// Copyright (c) 2013-2021 The SRS Authors +// +// SPDX-License-Identifier: MIT or MulanPSL-2.0 +// + +#ifndef SRS_APP_SRT_SERVER_HPP +#define SRS_APP_SRT_SERVER_HPP + +#include + +#include +#include +#include + +class SrsSrtServer; + +enum SrsSrtListenerType +{ + SrsSrtListenerMpegts = 1, +}; + +// A common srt acceptor, for SRT server. +class SrsSrtAcceptor +{ +protected: + SrsSrtListenerType type_; +protected: + std::string ip_; + int port_; + SrsSrtServer* srt_server_; +public: + SrsSrtAcceptor(SrsSrtServer* srt_server, SrsSrtListenerType listen_type); + virtual ~SrsSrtAcceptor(); +public: + virtual SrsSrtListenerType listen_type(); + virtual srs_error_t listen(std::string ip, int port) = 0; +}; + +// A srt messge acceptor. +class SrsSrtMessageAcceptor : public SrsSrtAcceptor, public ISrsSrtHandler +{ +private: + SrsSrtListener* listener_; +public: + SrsSrtMessageAcceptor(SrsSrtServer* srt_server, SrsSrtListenerType listen_type); + virtual ~SrsSrtMessageAcceptor(); +public: + virtual srs_error_t listen(std::string i, int p); + virtual srs_error_t set_srt_opt(); +// Interface ISrsSrtHandler +public: + virtual srs_error_t on_srt_client(SRTSOCKET srt_fd); +}; + +// SRS SRT server, initialize and listen, start connection service thread, destroy client. +class SrsSrtServer : public ISrsResourceManager +{ +private: + SrsResourceManager* conn_manager_; +private: + std::vector acceptors_; +public: + SrsSrtServer(); + virtual ~SrsSrtServer(); +public: + virtual srs_error_t initialize(); + virtual srs_error_t listen(); +private: + // listen at specified srt protocol. + virtual srs_error_t listen_srt_mpegts(); + // Close the listeners for specified type, + // Remove the listen object from manager. + virtual void close_listeners(SrsSrtListenerType type); +// For internal only +public: + // When listener got a fd, notice server to accept it. + // @param type, the client type, used to create concrete connection, + // for instance SRT connection to serve client. + // @param srt_fd, the client fd in srt boxed, the underlayer fd. + virtual srs_error_t accept_srt_client(SrsSrtListenerType type, SRTSOCKET srt_fd); +private: + virtual srs_error_t fd_to_resource(SrsSrtListenerType type, SRTSOCKET srt_fd, ISrsStartableConneciton** pr); +// Interface ISrsResourceManager +public: + // A callback for connection to remove itself. + // When connection thread cycle terminated, callback this to delete connection. + virtual void remove(ISrsResource* c); +}; + +// The srt server adapter, the master server. +class SrsSrtServerAdapter : public ISrsHybridServer +{ +private: + SrsSrtServer* srt_server_; +public: + SrsSrtServerAdapter(); + virtual ~SrsSrtServerAdapter(); +public: + virtual srs_error_t initialize(); + virtual srs_error_t run(SrsWaitGroup* wg); + virtual void stop(); +public: + virtual SrsSrtServer* instance(); +}; + +// The srt event loop, run srt poller and wait event happeed. +class SrsSrtEventLoop : public ISrsCoroutineHandler +{ +public: + SrsSrtEventLoop(); + virtual ~SrsSrtEventLoop(); +public: + SrsSrtPoller* get_srt_poller() { return srt_poller_; } +public: + srs_error_t initialize(); + srs_error_t start(); +// Interface ISrsCoroutineHandler. +public: + virtual srs_error_t cycle(); +private: + SrsSrtPoller* srt_poller_; + SrsCoroutine* trd_; +}; + +// SrsSrtEventLoop is global singleton instance. +extern SrsSrtEventLoop* _srt_eventloop; + +#endif + diff --git a/trunk/src/app/srs_app_srt_source.cpp b/trunk/src/app/srs_app_srt_source.cpp new file mode 100644 index 0000000000..f3025ca4a4 --- /dev/null +++ b/trunk/src/app/srs_app_srt_source.cpp @@ -0,0 +1,798 @@ +// +// Copyright (c) 2013-2021 The SRS Authors +// +// SPDX-License-Identifier: MIT or MulanPSL-2.0 +// + +#include + +#include +using namespace std; + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +SrsSrtPacket::SrsSrtPacket() +{ + shared_buffer_ = NULL; + actual_buffer_size_ = 0; +} + +SrsSrtPacket::~SrsSrtPacket() +{ + srs_freep(shared_buffer_); +} + +char* SrsSrtPacket::wrap(int size) +{ + // The buffer size is larger or equals to the size of packet. + actual_buffer_size_ = size; + + // If the buffer is large enough, reuse it. + if (shared_buffer_ && shared_buffer_->size >= size) { + return shared_buffer_->payload; + } + + // Create a large enough message, with under-layer buffer. + srs_freep(shared_buffer_); + shared_buffer_ = new SrsSharedPtrMessage(); + + char* buf = new char[size]; + shared_buffer_->wrap(buf, size); + + return shared_buffer_->payload; +} + +char* SrsSrtPacket::wrap(char* data, int size) +{ + char* buf = wrap(size); + memcpy(buf, data, size); + return buf; +} + +char* SrsSrtPacket::wrap(SrsSharedPtrMessage* msg) +{ + // Generally, the wrap(msg) is used for RTMP to SRT, where the msg + // is not generated by SRT. + srs_freep(shared_buffer_); + + // Copy from the new message. + shared_buffer_ = msg->copy(); + // If we wrap a message, the size of packet equals to the message size. + actual_buffer_size_ = shared_buffer_->size; + + return msg->payload; +} + +SrsSrtPacket* SrsSrtPacket::copy() +{ + SrsSrtPacket* cp = new SrsSrtPacket(); + + cp->shared_buffer_ = shared_buffer_? shared_buffer_->copy2() : NULL; + cp->actual_buffer_size_ = actual_buffer_size_; + + return cp; +} + +char* SrsSrtPacket::data() +{ + return shared_buffer_->payload; +} + +int SrsSrtPacket::size() +{ + return shared_buffer_->size; +} + +SrsSrtSourceManager::SrsSrtSourceManager() +{ + lock = srs_mutex_new(); +} + +SrsSrtSourceManager::~SrsSrtSourceManager() +{ + srs_mutex_destroy(lock); +} + +srs_error_t SrsSrtSourceManager::fetch_or_create(SrsRequest* r, SrsSrtSource** pps) +{ + srs_error_t err = srs_success; + + // Use lock to protect coroutine switch. + // @bug https://github.com/ossrs/srs/issues/1230 + SrsLocker(lock); + + SrsSrtSource* source = NULL; + if ((source = fetch(r)) != NULL) { + // 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); + *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 ts source, stream_url=%s", stream_url.c_str()); + + source = new SrsSrtSource(); + 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; +} + +SrsSrtSource* SrsSrtSourceManager::fetch(SrsRequest* r) +{ + SrsSrtSource* source = NULL; + + string stream_url = r->get_stream_url(); + if (pool.find(stream_url) == pool.end()) { + return NULL; + } + + source = pool[stream_url]; + + return source; +} + +SrsSrtSourceManager* _srs_srt_sources = NULL; + +SrsSrtConsumer::SrsSrtConsumer(SrsSrtSource* s) +{ + source = s; + should_update_source_id = false; + + mw_wait = srs_cond_new(); + mw_min_msgs = 0; + mw_waiting = false; +} + +SrsSrtConsumer::~SrsSrtConsumer() +{ + source->on_consumer_destroy(this); + + vector::iterator it; + for (it = queue.begin(); it != queue.end(); ++it) { + SrsSrtPacket* pkt = *it; + srs_freep(pkt); + } + + srs_cond_destroy(mw_wait); +} + +void SrsSrtConsumer::update_source_id() +{ + should_update_source_id = true; +} + +srs_error_t SrsSrtConsumer::enqueue(SrsSrtPacket* packet) +{ + srs_error_t err = srs_success; + + queue.push_back(packet); + + 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 SrsSrtConsumer::dump_packet(SrsSrtPacket** ppkt) +{ + 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; + } + + // TODO: FIXME: Refine performance by ring buffer. + if (!queue.empty()) { + *ppkt = queue.front(); + queue.erase(queue.begin()); + } + + return err; +} + +void SrsSrtConsumer::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); +} + +ISrsTsSourceBridger::ISrsTsSourceBridger() +{ +} + +ISrsTsSourceBridger::~ISrsTsSourceBridger() +{ +} + +SrsRtmpFromTsBridge::SrsRtmpFromTsBridge(SrsLiveSource* source) +{ + ts_ctx_ = new SrsTsContext(); + + sps_pps_change_ = false; + sps_ = ""; + pps_ = ""; + + live_source_ = source; + req_ = NULL; +} + +SrsRtmpFromTsBridge::~SrsRtmpFromTsBridge() +{ + srs_freep(ts_ctx_); + srs_freep(req_); +} + +srs_error_t SrsRtmpFromTsBridge::on_publish() +{ + srs_error_t err = srs_success; + + if ((err = live_source_->on_publish()) != srs_success) { + return srs_error_wrap(err, "on publish"); + } + + return err; +} + +srs_error_t SrsRtmpFromTsBridge::on_packet(SrsSrtPacket *pkt) +{ + srs_error_t err = srs_success; + + char* buf = pkt->data(); + int nb_buf = pkt->size(); + + // use stream to parse ts packet. + int nb_packet = nb_buf / SRS_TS_PACKET_SIZE; + for (int i = 0; i < nb_packet; i++) { + char* p = buf + (i * SRS_TS_PACKET_SIZE); + + SrsBuffer* stream = new SrsBuffer(p, SRS_TS_PACKET_SIZE); + SrsAutoFree(SrsBuffer, stream); + + // process each ts packet + if ((err = ts_ctx_->decode(stream, this)) != srs_success) { + srs_warn("parse ts packet err=%s", srs_error_desc(err).c_str()); + srs_error_reset(err); + continue; + } + } + + return err; +} + +void SrsRtmpFromTsBridge::on_unpublish() +{ + live_source_->on_unpublish(); +} + +srs_error_t SrsRtmpFromTsBridge::initialize(SrsRequest* req) +{ + srs_error_t err = srs_success; + + // TODO: FIXME: check srt2rtmp enable in config. + req_ = req->copy(); + + return err; +} + +srs_error_t SrsRtmpFromTsBridge::on_ts_message(SrsTsMessage* msg) +{ + srs_error_t err = srs_success; + + // When the audio SID is private stream 1, we use common audio. + // @see https://github.com/ossrs/srs/issues/740 + if (msg->channel->apply == SrsTsPidApplyAudio && msg->sid == SrsTsPESStreamIdPrivateStream1) { + msg->sid = SrsTsPESStreamIdAudioCommon; + } + + // when not audio/video, or not adts/annexb format, donot support. + if (msg->stream_number() != 0) { + return srs_error_new(ERROR_STREAM_CASTER_TS_ES, "ts: unsupported stream format, sid=%#x(%s-%d)", + msg->sid, msg->is_audio()? "A":msg->is_video()? "V":"N", msg->stream_number()); + } + + // check supported codec + if (msg->channel->stream != SrsTsStreamVideoH264 && msg->channel->stream != SrsTsStreamAudioAAC) { + return srs_error_new(ERROR_STREAM_CASTER_TS_CODEC, "ts: unsupported stream codec=%d", msg->channel->stream); + } + + // parse the stream. + SrsBuffer avs(msg->payload->bytes(), msg->payload->length()); + + // publish audio or video. + if (msg->channel->stream == SrsTsStreamVideoH264) { + if ((err = on_ts_video(msg, &avs)) != srs_success) { + return srs_error_wrap(err, "ts: consume video"); + } + } + if (msg->channel->stream == SrsTsStreamAudioAAC) { + if ((err = on_ts_audio(msg, &avs)) != srs_success) { + return srs_error_wrap(err, "ts: consume audio"); + } + } + + // TODO: FIXME: implements other codec? + return err; +} + +srs_error_t SrsRtmpFromTsBridge::on_ts_video(SrsTsMessage* msg, SrsBuffer* avs) +{ + srs_error_t err = srs_success; + + vector > ipb_frames; + + SrsRawH264Stream* avc = new SrsRawH264Stream(); + SrsAutoFree(SrsRawH264Stream, avc); + + // 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 sps(7), pps(8), aud(9) + if (nal_unit_type == SrsAvcNaluTypeAccessUnitDelimiter) { + continue; + } + + // for sps + if (avc->is_sps(frame, frame_size)) { + std::string sps; + if ((err = avc->sps_demux(frame, frame_size, sps)) != srs_success) { + return srs_error_wrap(err, "demux sps"); + } + + if (! sps.empty() && sps_ != sps) { + sps_pps_change_ = true; + } + + sps_ = sps; + 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 (! pps.empty() && pps_ != pps) { + sps_pps_change_ = true; + } + + pps_ = pps; + continue; + } + + ipb_frames.push_back(make_pair(frame, frame_size)); + } + + if ((err = check_sps_pps_change(msg)) != srs_success) { + return srs_error_wrap(err, "check sps pps"); + } + + return on_h264_frame(msg, ipb_frames); +} + +srs_error_t SrsRtmpFromTsBridge::check_sps_pps_change(SrsTsMessage* msg) +{ + srs_error_t err = srs_success; + + if (! sps_pps_change_) { + return err; + } + + // sps/pps changed, generate new video sh frame and dispatch it. + sps_pps_change_ = false; + + // ts tbn to flv tbn. + uint32_t dts = (uint32_t)(msg->dts / 90); + + //type_codec1 + avc_type + composition time + fix header + count of sps + len of sps + sps + count of pps + len of pps + pps + int nb_payload = 1 + 1 + 3 + 5 + 1 + 2 + sps_.size() + 1 + 2 + pps_.size(); + SrsCommonMessage rtmp; + rtmp.header.initialize_video(nb_payload, dts, 1); + rtmp.create_payload(nb_payload); + rtmp.size = nb_payload; + SrsBuffer payload(rtmp.payload, rtmp.size); + //TODO: call api + payload.write_1bytes(0x17);// type(4 bits): key frame; code(4bits): avc + payload.write_1bytes(0x0); // avc_type: sequence header + payload.write_1bytes(0x0); // composition time + payload.write_1bytes(0x0); + payload.write_1bytes(0x0); + payload.write_1bytes(0x01); // version + payload.write_1bytes(sps_[1]); + payload.write_1bytes(sps_[2]); + payload.write_1bytes(sps_[3]); + payload.write_1bytes(0xff); + payload.write_1bytes(0xe1); + payload.write_2bytes(sps_.size()); + payload.write_bytes((char*)sps_.data(), sps_.size()); + payload.write_1bytes(0x01); + payload.write_2bytes(pps_.size()); + payload.write_bytes((char*)pps_.data(), pps_.size()); + if ((err = live_source_->on_video(&rtmp)) != srs_success) { + return srs_error_wrap(err, "srt to rtmp sps/pps"); + } + + return err; +} + +srs_error_t SrsRtmpFromTsBridge::on_h264_frame(SrsTsMessage* msg, vector >& ipb_frames) +{ + srs_error_t err = srs_success; + + if (ipb_frames.empty()) { + return srs_error_new(ERROR_SRT_CONN, "empty frame"); + } + + bool is_keyframe = false; + + // ts tbn to flv tbn. + uint32_t dts = (uint32_t)(msg->dts / 90); + uint32_t pts = (uint32_t)(msg->pts / 90); + int32_t cts = pts - dts; + + int frame_size = 5; // 5bytes video tag header + for (size_t i = 0; i != ipb_frames.size(); ++i) { + // 4 bytes for nalu length. + frame_size += 4 + ipb_frames[i].second; + if (((SrsAvcNaluType)(ipb_frames[i].first[0] & 0x1f)) == SrsAvcNaluTypeIDR) { + is_keyframe = true; + } + } + + SrsCommonMessage rtmp; + rtmp.header.initialize_video(frame_size, dts, 1/*streamid*/); + rtmp.create_payload(frame_size); + rtmp.size = frame_size; + SrsBuffer payload(rtmp.payload, rtmp.size); + // Write 5bytes video tag header. + if (is_keyframe) { + payload.write_1bytes(0x17); // type(4 bits): key frame; code(4bits): avc + } else { + payload.write_1bytes(0x27); // type(4 bits): inter frame; code(4bits): avc + } + payload.write_1bytes(0x01); // avc_type: nalu + payload.write_3bytes(cts); // composition time + + // Write video nalus. + for (size_t i = 0; i != ipb_frames.size(); ++i) { + char* nal = ipb_frames[i].first; + int nal_size = ipb_frames[i].second; + + // write 4 bytes of nalu length. + payload.write_4bytes(nal_size); + // write nalu + payload.write_bytes(nal, nal_size); + } + + if ((err = live_source_->on_video(&rtmp)) != srs_success) { + return srs_error_wrap(err ,"srt ts video to rtmp"); + } + + return err; +} + +srs_error_t SrsRtmpFromTsBridge::on_ts_audio(SrsTsMessage* msg, SrsBuffer* avs) +{ + srs_error_t err = srs_success; + + SrsRawAacStream* aac = new SrsRawAacStream(); + SrsAutoFree(SrsRawAacStream, aac); + + // ts tbn to flv tbn. + uint32_t pts = (uint32_t)(msg->pts / 90); + + int frame_idx = 0; + + // send each frame. + while (!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"); + } + + // ignore invalid frame, + // * atleast 1bytes for aac to decode the data. + if (frame_size <= 0) { + continue; + } + + std::string sh; + if ((err = aac->mux_sequence_header(&codec, sh)) != srs_success) { + return srs_error_wrap(err, "mux sequence header"); + } + + if (! sh.empty() && sh != audio_sh_) { + audio_sh_ = sh; + audio_sh_change_ = true; + } + + // May have more than one aac frame in PES packet, and shared same timestamp, + // so we must calculate each aac frame's timestamp. + int sample_rate = 44100; + switch (codec.sound_rate) { + case SrsAudioSampleRate5512: sample_rate = 5512; break; + case SrsAudioSampleRate11025: sample_rate = 11025; break; + case SrsAudioSampleRate22050: sample_rate = 22050; break; + case SrsAudioSampleRate44100: + default: sample_rate = 44100; break; + } + uint32_t frame_pts = (double)pts + (frame_idx * (1024.0 * 1000.0 / sample_rate)); + ++frame_idx; + + if ((err = check_audio_sh_change(msg, frame_pts)) != srs_success) { + return srs_error_wrap(err, "audio sh"); + } + + if ((err = on_aac_frame(msg, frame_pts, frame, frame_size)) != srs_success) { + return srs_error_wrap(err, "audio frame"); + } + } + + return err; +} + +srs_error_t SrsRtmpFromTsBridge::check_audio_sh_change(SrsTsMessage* msg, uint32_t pts) +{ + srs_error_t err = srs_success; + + if (! audio_sh_change_) { + return err; + } + + // audio specific config changed, generate new audio sh and dispatch it. + audio_sh_change_ = false; + + int rtmp_len = audio_sh_.size() + 2; + + SrsCommonMessage rtmp; + rtmp.header.initialize_audio(rtmp_len, pts, 1); + rtmp.create_payload(rtmp_len); + rtmp.size = rtmp_len; + + SrsBuffer stream(rtmp.payload, rtmp_len); + uint8_t aac_flag = (SrsAudioCodecIdAAC << 4) | (SrsAudioSampleRate44100 << 2) | (SrsAudioSampleBits16bit << 1) | SrsAudioChannelsStereo; + stream.write_1bytes(aac_flag); + stream.write_1bytes(0); + stream.write_bytes((char*)audio_sh_.data(), audio_sh_.size()); + + if ((err = live_source_->on_audio(&rtmp)) != srs_success) { + return srs_error_wrap(err, "srt to rtmp audio sh"); + } + + return err; +} + +srs_error_t SrsRtmpFromTsBridge::on_aac_frame(SrsTsMessage* msg, uint32_t pts, char* frame, int frame_size) +{ + srs_error_t err = srs_success; + + int rtmp_len = frame_size + 2/* 2 bytes of flv audio tag header*/; + + SrsCommonMessage rtmp; + rtmp.header.initialize_audio(rtmp_len, pts, 2/*streamid*/); + rtmp.create_payload(rtmp_len); + rtmp.size = rtmp_len; + + SrsBuffer stream(rtmp.payload, rtmp_len); + uint8_t aac_flag = (SrsAudioCodecIdAAC << 4) | (SrsAudioSampleRate44100 << 2) | (SrsAudioSampleBits16bit << 1) | SrsAudioChannelsStereo; + // Write 2bytes audio tag header. + stream.write_1bytes(aac_flag); + stream.write_1bytes(1); + // Write audio frame. + stream.write_bytes(frame, frame_size); + + if ((err = live_source_->on_audio(&rtmp)) != srs_success) { + return srs_error_wrap(err, "srt to rtmp audio sh"); + } + + return err; +} + +SrsSrtSource::SrsSrtSource() +{ + req = NULL; + can_publish_ = true; + bridger_ = NULL; +} + +SrsSrtSource::~SrsSrtSource() +{ + // never free the consumers, + // for all consumers are auto free. + consumers.clear(); + + srs_freep(bridger_); +} + +srs_error_t SrsSrtSource::initialize(SrsRequest* r) +{ + srs_error_t err = srs_success; + + req = r->copy(); + + return err; +} + +srs_error_t SrsSrtSource::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) { + SrsSrtConsumer* consumer = *it; + consumer->update_source_id(); + } + + return err; +} + +SrsContextId SrsSrtSource::source_id() +{ + return _source_id; +} + +SrsContextId SrsSrtSource::pre_source_id() +{ + return _pre_source_id; +} + +void SrsSrtSource::update_auth(SrsRequest* r) +{ + req->update_auth(r); +} + +void SrsSrtSource::set_bridger(ISrsTsSourceBridger *bridger) +{ + srs_freep(bridger_); + bridger_ = bridger; +} + +srs_error_t SrsSrtSource::create_consumer(SrsSrtConsumer*& consumer) +{ + srs_error_t err = srs_success; + + consumer = new SrsSrtConsumer(this); + consumers.push_back(consumer); + + return err; +} + +srs_error_t SrsSrtSource::consumer_dumps(SrsSrtConsumer* consumer) +{ + srs_error_t err = srs_success; + + // print status. + srs_trace("create ts consumer, no gop cache"); + + return err; +} + +void SrsSrtSource::on_consumer_destroy(SrsSrtConsumer* consumer) +{ + std::vector::iterator it; + it = std::find(consumers.begin(), consumers.end(), consumer); + if (it != consumers.end()) { + consumers.erase(it); + } +} + +bool SrsSrtSource::can_publish() +{ + return can_publish_; +} + +srs_error_t SrsSrtSource::on_publish() +{ + srs_error_t err = srs_success; + + can_publish_ = false; + + if ((err = on_source_id_changed(_srs_context->get_id())) != srs_success) { + return srs_error_wrap(err, "source id change"); + } + + if (bridger_) { + if ((err = bridger_->on_publish()) != srs_success) { + return srs_error_wrap(err, "bridger on publish"); + } + } + + SrsStatistic* stat = SrsStatistic::instance(); + stat->on_stream_publish(req, _source_id.c_str()); + + return err; +} + +void SrsSrtSource::on_unpublish() +{ + // ignore when already unpublished. + if (can_publish_) { + return; + } + + can_publish_ = true; + + if (bridger_) { + bridger_->on_unpublish(); + srs_freep(bridger_); + } +} + +srs_error_t SrsSrtSource::on_packet(SrsSrtPacket* packet) +{ + srs_error_t err = srs_success; + + for (int i = 0; i < (int)consumers.size(); i++) { + SrsSrtConsumer* consumer = consumers.at(i); + if ((err = consumer->enqueue(packet->copy())) != srs_success) { + return srs_error_wrap(err, "consume ts packet"); + } + } + + if (bridger_ && (err = bridger_->on_packet(packet)) != srs_success) { + return srs_error_wrap(err, "bridger consume message"); + } + + return err; +} diff --git a/trunk/src/app/srs_app_srt_source.hpp b/trunk/src/app/srs_app_srt_source.hpp new file mode 100644 index 0000000000..d285143c9c --- /dev/null +++ b/trunk/src/app/srs_app_srt_source.hpp @@ -0,0 +1,205 @@ +// +// Copyright (c) 2013-2021 The SRS Authors +// +// SPDX-License-Identifier: MIT or MulanPSL-2.0 +// + +#ifndef SRS_APP_SRT_SOURCE_HPP +#define SRS_APP_SRT_SOURCE_HPP + +#include + +#include +#include + +#include +#include + +class SrsSharedPtrMessage; +class SrsRequest; +class SrsLiveSource; +class SrsSrtSource; + +// The SRT packet with shared message. +class SrsSrtPacket +{ +public: + SrsSrtPacket(); + virtual ~SrsSrtPacket(); +public: + // Wrap buffer to shared_message, which is managed by us. + char* wrap(int size); + char* wrap(char* data, int size); + // Wrap the shared message, we copy it. + char* wrap(SrsSharedPtrMessage* msg); + // Copy the SRT packet. + virtual SrsSrtPacket* copy(); +public: + char* data(); + int size(); +private: + SrsSharedPtrMessage* shared_buffer_; + // The size of SRT packet or SRT payload. + int actual_buffer_size_; +}; + +class SrsSrtSourceManager +{ +private: + srs_mutex_t lock; + std::map pool; +public: + SrsSrtSourceManager(); + virtual ~SrsSrtSourceManager(); +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, SrsSrtSource** pps); +public: + // Get the exists source, NULL when not exists. + virtual SrsSrtSource* fetch(SrsRequest* r); +}; + +// Global singleton instance. +extern SrsSrtSourceManager* _srs_srt_sources; + +class SrsSrtConsumer +{ +public: + SrsSrtConsumer(SrsSrtSource* source); + virtual ~SrsSrtConsumer(); +private: + SrsSrtSource* source; + std::vector queue; + // when source id changed, notice all consumers + bool should_update_source_id; + // The cond wait for mw. + srs_cond_t mw_wait; + bool mw_waiting; + int mw_min_msgs; +public: + // When source id changed, notice client to print. + void update_source_id(); + // Put SRT packet into queue. + srs_error_t enqueue(SrsSrtPacket* packet); + // For SRT, we only got one packet, because there is not many packets in queue. + virtual srs_error_t dump_packet(SrsSrtPacket** ppkt); + // Wait for at-least some messages incoming in queue. + virtual void wait(int nb_msgs); +}; + +class ISrsTsSourceBridger +{ +public: + ISrsTsSourceBridger(); + virtual ~ISrsTsSourceBridger(); +public: + virtual srs_error_t on_publish() = 0; + virtual srs_error_t on_packet(SrsSrtPacket *pkt) = 0; + virtual void on_unpublish() = 0; +}; + +class SrsRtmpFromTsBridge : public ISrsTsSourceBridger, public ISrsTsHandler +{ +public: + SrsRtmpFromTsBridge(SrsLiveSource* source); + virtual ~SrsRtmpFromTsBridge(); +public: + virtual srs_error_t on_publish(); + virtual srs_error_t on_packet(SrsSrtPacket *pkt); + virtual void on_unpublish(); +public: + srs_error_t initialize(SrsRequest* req); +// Interface ISrsTsHandler +public: + virtual srs_error_t on_ts_message(SrsTsMessage* msg); +private: + srs_error_t on_ts_video(SrsTsMessage* msg, SrsBuffer* avs); + srs_error_t on_ts_audio(SrsTsMessage* msg, SrsBuffer* avs); + srs_error_t check_sps_pps_change(SrsTsMessage* msg); + srs_error_t on_h264_frame(SrsTsMessage* msg, std::vector >& ipb_frames); + srs_error_t check_audio_sh_change(SrsTsMessage* msg, uint32_t pts); + srs_error_t on_aac_frame(SrsTsMessage* msg, uint32_t pts, char* frame, int frame_size); +private: + SrsTsContext* ts_ctx_; + + // Record sps/pps had changed, if change, need to generate new video sh frame. + bool sps_pps_change_; + std::string sps_; + std::string pps_; + + // Record audio sepcific config had changed, if change, need to generate new audio sh frame. + bool audio_sh_change_; + std::string audio_sh_; + + SrsRequest* req_; + SrsLiveSource* live_source_; +}; + +class SrsSrtSource +{ +public: + SrsSrtSource(); + virtual ~SrsSrtSource(); +public: + virtual srs_error_t initialize(SrsRequest* r); +public: + // 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(); + // Update the authentication information in request. + virtual void update_auth(SrsRequest* r); +public: + void set_bridger(ISrsTsSourceBridger *bridger); +public: + // Create consumer + // @param consumer, output the create consumer. + virtual srs_error_t create_consumer(SrsSrtConsumer*& consumer); + // Dumps packets in cache to consumer. + virtual srs_error_t consumer_dumps(SrsSrtConsumer* consumer); + virtual void on_consumer_destroy(SrsSrtConsumer* consumer); + // Whether we can publish stream to the source, return false if it exists. + virtual bool can_publish(); + // When start publish stream. + virtual srs_error_t on_publish(); + // When stop publish stream. + virtual void on_unpublish(); +public: + srs_error_t on_packet(SrsSrtPacket* packet); +private: + // Source id. + SrsContextId _source_id; + // previous source id. + SrsContextId _pre_source_id; + SrsRequest* req; + // To delivery packets to clients. + std::vector consumers; + bool can_publish_; + ISrsTsSourceBridger* bridger_; +}; + +/* +class SrsTsFromRtmpBridger : public ISrsLiveSourceBridger +{ +private: + SrsRequest* req; + SrsSrtSource* source_; +public: + SrsTsFromRtmpBridger(SrsSrtSource* source); + virtual ~SrsTsFromRtmpBridger(); +public: + virtual srs_error_t initialize(SrsRequest* r); +// Interface for ISrsLiveSourceBridger +public: + virtual srs_error_t on_publish(); + virtual void on_unpublish(); + virtual srs_error_t on_audio(SrsSharedPtrMessage* msg); + virtual srs_error_t on_video(SrsSharedPtrMessage* msg); +}; +*/ + +#endif + diff --git a/trunk/src/app/srs_app_srt_utility.cpp b/trunk/src/app/srs_app_srt_utility.cpp new file mode 100644 index 0000000000..5bb26ef2e1 --- /dev/null +++ b/trunk/src/app/srs_app_srt_utility.cpp @@ -0,0 +1,143 @@ +// +// Copyright (c) 2013-2021 The SRS Authors +// +// SPDX-License-Identifier: MIT or MulanPSL-2.0 +// + +#include + +using namespace std; + +#include +#include +#include +#include +#include +#include +#include + +// See streamid of https://github.com/ossrs/srs/issues/2893 +// TODO: FIMXE: We should parse SRT streamid to URL object, rather than a HTTP url subpath. +bool srs_srt_streamid_info(const std::string& streamid, SrtMode& mode, std::string& vhost, std::string& url_subpath) +{ + mode = SrtModePull; + + size_t pos = streamid.find("#!::"); + if (pos != 0) { + pos = streamid.find("/"); + if (pos == streamid.npos) { + url_subpath = _srs_config->get_default_app_name() + "/" + streamid; + return true; + } + url_subpath = streamid; + return true; + } + + //SRT url supports multiple QueryStrings, which are passed to RTMP to realize authentication and other capabilities + //@see https://github.com/ossrs/srs/issues/2893 + std::string params; + std::string real_streamid; + real_streamid = streamid.substr(4); + + // Compatible with previous auth querystring, like this one: + // srt://127.0.0.1:10080?streamid=#!::h=live/livestream?secret=xxx,m=publish + real_streamid = srs_string_replace(real_streamid, "?", ","); + + std::map query; + srs_parse_query_string(real_streamid, query); + for (std::map::iterator it = query.begin(); it != query.end(); ++it) { + if (it->first == "h") { + std::string host = it->second; + + size_t r0 = host.find("/"); + size_t r1 = host.rfind("/"); + if (r0 != std::string::npos && r0 != std::string::npos) { + // Compatible with previous style, see https://github.com/ossrs/srs/issues/2893#compatible + // srt://127.0.0.1:10080?streamid=#!::h=live/livestream,m=publish + // srt://127.0.0.1:10080?streamid=#!::h=live/livestream,m=request + // srt://127.0.0.1:10080?streamid=#!::h=srs.srt.com.cn/live/livestream,m=publish + if (r0 != r1) { + // We got vhost in host. + url_subpath = host.substr(r0 + 1); + host = host.substr(0, r0); + + params.append("vhost="); + params.append(host); + params.append("&"); + vhost = host; + } else { + // Only stream in host. + url_subpath = host; + } + } else { + // New URL style, see https://github.com/ossrs/srs/issues/2893#solution + // srt://host.com:10080?streamid=#!::h=host.com,r=app/stream,key1=value1,key2=value2 + // srt://1.2.3.4:10080?streamid=#!::h=host.com,r=app/stream,key1=value1,key2=value2 + // srt://1.2.3.4:10080?streamid=#!::r=app/stream,key1=value1,key2=value2 + params.append("vhost="); + params.append(host); + params.append("&"); + vhost = host; + } + } else if (it->first == "r") { + url_subpath = it->second; + } else if (it->first == "m") { + std::string mode_str = it->second; // support m=publish or m=request + std::transform(it->second.begin(), it->second.end(), mode_str.begin(), ::tolower); + if (mode_str == "publish") { + mode = SrtModePush; + } else if (mode_str == "request") { + mode = SrtModePull; + } else { + srs_warn("unknown mode_str:%s", mode_str.c_str()); + return false; + } + } else { + params.append(it->first); + params.append("="); + params.append(it->second); + params.append("&"); + } + } + + if (url_subpath.empty()) { + return false; + } + + if (!params.empty()) { + url_subpath.append("?"); + url_subpath.append(params); + url_subpath.pop_back(); // remove last '&' + } + + return true; +} + +bool srs_srt_streamid_to_request(const std::string& streamid, SrtMode& mode, SrsRequest* request) +{ + string url_subpath = ""; + bool ret = srs_srt_streamid_info(streamid, mode, request->vhost, url_subpath); + if (! ret) { + return ret; + } + + size_t pos = url_subpath.find("/"); + string stream_with_params = ""; + if (pos == string::npos) { + request->app = _srs_config->get_default_app_name(); + stream_with_params = url_subpath; + } else { + request->app = url_subpath.substr(0, pos); + stream_with_params = url_subpath.substr(pos + 1); + } + + pos = stream_with_params.find("?"); + if (pos == string::npos) { + request->stream = stream_with_params; + } else { + request->stream = stream_with_params.substr(0, pos); + request->param = stream_with_params.substr(pos + 1); + } + + return ret; +} diff --git a/trunk/src/app/srs_app_srt_utility.hpp b/trunk/src/app/srs_app_srt_utility.hpp new file mode 100644 index 0000000000..c542c04a28 --- /dev/null +++ b/trunk/src/app/srs_app_srt_utility.hpp @@ -0,0 +1,32 @@ +// +// Copyright (c) 2013-2021 The SRS Authors +// +// SPDX-License-Identifier: MIT or MulanPSL-2.0 +// + +#ifndef SRS_APP_SRT_UTILITY_HPP +#define SRS_APP_SRT_UTILITY_HPP + +#include + +#include + +#include +#include + +class SrsRequest; + +enum SrtMode +{ + SrtModePull = 1, + SrtModePush = 2, +}; + +// Get SRT streamid info. +extern bool srs_srt_streamid_info(const std::string& streamid, SrtMode& mode, std::string& vhost, std::string& url_subpath); + +// SRT streamid to request. +extern bool srs_srt_streamid_to_request(const std::string& streamid, SrtMode& mode, SrsRequest* request); + +#endif + diff --git a/trunk/src/app/srs_app_threads.cpp b/trunk/src/app/srs_app_threads.cpp index bcfd78c738..594da79184 100644 --- a/trunk/src/app/srs_app_threads.cpp +++ b/trunk/src/app/srs_app_threads.cpp @@ -22,6 +22,10 @@ #include #endif +#ifdef SRS_SRT +#include +#endif + #include using namespace std; @@ -298,6 +302,10 @@ srs_error_t srs_thread_initialize() _srs_stages = new SrsStageManager(); _srs_circuit_breaker = new SrsCircuitBreaker(); +#ifdef SRS_SRT + _srs_srt_sources = new SrsSrtSourceManager(); +#endif + #ifdef SRS_RTC _srs_rtc_sources = new SrsRtcSourceManager(); _srs_blackhole = new SrsRtcBlackhole(); diff --git a/trunk/src/kernel/srs_kernel_consts.hpp b/trunk/src/kernel/srs_kernel_consts.hpp index ed3b2fba5d..48f590c734 100644 --- a/trunk/src/kernel/srs_kernel_consts.hpp +++ b/trunk/src/kernel/srs_kernel_consts.hpp @@ -170,6 +170,10 @@ #define SRS_CONSTS_LOG_EXEC "EXE" // The rtc. #define SRS_CONSTS_LOG_RTC "RTC" +// Srt client play +#define SRS_CONSTS_LOG_SRT_PLAY "SRT_PLA" +// Srt client publish +#define SRS_CONSTS_LOG_SRT_PUBLISH "SRT_CPB" /////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////// diff --git a/trunk/src/kernel/srs_kernel_error.hpp b/trunk/src/kernel/srs_kernel_error.hpp index c07bb84c80..94f8c9c547 100644 --- a/trunk/src/kernel/srs_kernel_error.hpp +++ b/trunk/src/kernel/srs_kernel_error.hpp @@ -347,6 +347,18 @@ #define ERROR_RTC_NO_TRACK 5030 #define ERROR_RTC_RTCP_EMPTY_RR 5031 +/////////////////////////////////////////////////////// +// SRT protocol error. +/////////////////////////////////////////////////////// +#define ERROR_SRT_EPOLL 6000 +#define ERROR_SRT_IO 6001 +#define ERROR_SRT_TIMEOUT 6002 +#define ERROR_SRT_INTERRUPT 6003 +#define ERROR_SRT_LISTEN 6004 +#define ERROR_SRT_SOCKOPT 6005 +#define ERROR_SRT_CONN 6006 +#define ERROR_SRT_SOURCE_BUSY 6007 + /////////////////////////////////////////////////////// // HTTP API error. /////////////////////////////////////////////////////// diff --git a/trunk/src/main/srs_main_server.cpp b/trunk/src/main/srs_main_server.cpp index a9d51dfc5b..025a53cab3 100644 --- a/trunk/src/main/srs_main_server.cpp +++ b/trunk/src/main/srs_main_server.cpp @@ -44,7 +44,8 @@ using namespace std; #endif #ifdef SRS_SRT -#include +#include +#include #endif // pre-declare @@ -65,6 +66,10 @@ extern const char* _srs_version; // @global main SRS server, for debugging SrsServer* _srs_server = NULL; +#ifdef SRS_SRT +SrsSrtEventLoop* _srt_eventloop = NULL; +#endif + /** * main entrance. */ @@ -455,7 +460,14 @@ srs_error_t run_hybrid_server() _srs_hybrid->register_server(new SrsServerAdapter()); #ifdef SRS_SRT - _srs_hybrid->register_server(new SrtServerAdapter()); + _srt_eventloop = new SrsSrtEventLoop(); + if ((err = _srt_eventloop->initialize()) != srs_success) { + return srs_error_wrap(err, "srt poller initialize"); + } + if ((err = _srt_eventloop->start()) != srs_success) { + return srs_error_wrap(err, "srt poller start"); + } + _srs_hybrid->register_server(new SrsSrtServerAdapter()); #endif #ifdef SRS_RTC diff --git a/trunk/src/protocol/srs_protocol_utility.cpp b/trunk/src/protocol/srs_protocol_utility.cpp index 88af17e7c7..3934c11d2c 100644 --- a/trunk/src/protocol/srs_protocol_utility.cpp +++ b/trunk/src/protocol/srs_protocol_utility.cpp @@ -533,3 +533,4 @@ string srs_get_cidr_ipv4(string network_address) { return ipv4_address; } + diff --git a/trunk/src/protocol/srs_service_st_srt.cpp b/trunk/src/protocol/srs_service_st_srt.cpp new file mode 100644 index 0000000000..12daacb46a --- /dev/null +++ b/trunk/src/protocol/srs_service_st_srt.cpp @@ -0,0 +1,789 @@ +// +// Copyright (c) 2013-2021 The SRS Authors +// +// SPDX-License-Identifier: MIT or MulanPSL-2.0 +// + +#include + +#include + +using namespace std; + +#include +#include +#include + +#define SET_SRT_OPT_STR(srtfd, optname, buf, size) \ + if (srt_setsockflag(srtfd, optname, buf, size) == SRT_ERROR) { \ + std::stringstream ss; \ + ss << "srtfd=" << srtfd << ",set " << #optname \ + << " failed,err=" << srt_getlasterror_str(); \ + return srs_error_new(ERROR_SRT_SOCKOPT, "%s", ss.str().c_str()); \ + } + +#define SET_SRT_OPT(srtfd, optname, val) \ + if (srt_setsockflag(srtfd, optname, &val, sizeof(val)) == SRT_ERROR) { \ + std::stringstream ss; \ + ss << "srtfd=" << srtfd << ",set " << #optname << "=" << val \ + << " failed,err=" << srt_getlasterror_str(); \ + return srs_error_new(ERROR_SRT_SOCKOPT, "%s", ss.str().c_str()); \ + } + +#define GET_SRT_OPT(srtfd, optname, val) \ + do { \ + int size = sizeof(val); \ + if (srt_getsockflag(srtfd, optname, &val, &size) == SRT_ERROR) { \ + std::stringstream ss; \ + ss << "srtfd=" << srtfd << ",get " << #optname \ + << " failed,err=" << srt_getlasterror_str(); \ + return srs_error_new(ERROR_SRT_SOCKOPT, "%s", ss.str().c_str()); \ + } \ + } while (0) + +static srs_error_t do_srs_srt_listen(SRTSOCKET srt_fd, addrinfo* r) +{ + srs_error_t err = srs_success; + + if ((err = srs_srt_nonblock(srt_fd)) != srs_success) { + return srs_error_wrap(err, "nonblock"); + } + + if (srt_bind(srt_fd, r->ai_addr, r->ai_addrlen) == -1) { + return srs_error_new(ERROR_SOCKET_BIND, "bind"); + } + + if (srt_listen(srt_fd, 100) == -1) { + return srs_error_new(ERROR_SOCKET_LISTEN, "listen"); + } + + return err; +} + +static srs_error_t do_srs_srt_get_streamid(SRTSOCKET srt_fd, string& streamid) +{ + // SRT max streamid length is 512. + char sid[512]; + GET_SRT_OPT(srt_fd, SRTO_STREAMID, sid); + + streamid.assign(sid); + return srs_success; +} + +srs_error_t srs_srt_socket(SRTSOCKET* pfd) +{ + srs_error_t err = srs_success; + + SRTSOCKET srt_fd = 0; + if ((srt_fd = srt_create_socket()) < 0) { + return srs_error_new(ERROR_SOCKET_CREATE, "create srt socket"); + } + + *pfd = srt_fd; + + return err; +} + +srs_error_t srs_srt_socket_with_default_option(SRTSOCKET* pfd) +{ + srs_error_t err = srs_success; + + SRTSOCKET srt_fd = 0; + if ((srt_fd = srt_create_socket()) < 0) { + return srs_error_new(ERROR_SOCKET_CREATE, "create srt socket"); + } + + if ((err = srs_srt_nonblock(srt_fd)) != srs_success) { + return srs_error_wrap(err, "nonblock"); + } + + if ((err = srs_srt_set_tsbpdmode(srt_fd, false)) != srs_success) { + return srs_error_wrap(err, "set tsbpdmode"); + } + + if ((err = srs_srt_set_tlpktdrop(srt_fd, false)) != srs_success) { + return srs_error_wrap(err, "set tlpktdrop"); + } + + if ((err = srs_srt_set_latency(srt_fd, false)) != srs_success) { + return srs_error_wrap(err, "set latency"); + } + + *pfd = srt_fd; + + return err; +} + +srs_error_t srs_srt_listen(SRTSOCKET srt_fd, std::string ip, int port) +{ + srs_error_t err = srs_success; + + 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; + hints.ai_flags = AI_NUMERICHOST; + + addrinfo* r = NULL; + SrsAutoFreeH(addrinfo, r, freeaddrinfo); + if(getaddrinfo(ip.c_str(), sport, (const addrinfo*)&hints, &r)) { + return srs_error_new(ERROR_SYSTEM_IP_INVALID, "getaddrinfo hints=(%d,%d,%d)", + hints.ai_family, hints.ai_socktype, hints.ai_flags); + } + + if ((err = do_srs_srt_listen(srt_fd, r)) != srs_success) { + srt_close(srt_fd); + return srs_error_wrap(err, "srt_fd=%d", srt_fd); + } + + return err; +} + +srs_error_t srs_srt_nonblock(SRTSOCKET srt_fd) +{ + int sync = 0; + SET_SRT_OPT(srt_fd, SRTO_SNDSYN, sync); + SET_SRT_OPT(srt_fd, SRTO_RCVSYN, sync); + + return srs_success; +} + +srs_error_t srs_srt_set_maxbw(SRTSOCKET srt_fd, int maxbw) +{ + SET_SRT_OPT(srt_fd, SRTO_MAXBW, maxbw); + return srs_success; +} + +srs_error_t srs_srt_set_mss(SRTSOCKET srt_fd, int mss) +{ + SET_SRT_OPT(srt_fd, SRTO_MSS, mss); + return srs_success; +} + +srs_error_t srs_srt_set_payload_size(SRTSOCKET srt_fd, int payload_size) +{ + SET_SRT_OPT(srt_fd, SRTO_PAYLOADSIZE, payload_size); + return srs_success; +} + +srs_error_t srs_srt_set_connect_timeout(SRTSOCKET srt_fd, int timeout) +{ + SET_SRT_OPT(srt_fd, SRTO_CONNTIMEO, timeout); + return srs_success; +} + +srs_error_t srs_srt_set_peer_idle_timeout(SRTSOCKET srt_fd, int timeout) +{ + SET_SRT_OPT(srt_fd, SRTO_PEERIDLETIMEO, timeout); + return srs_success; +} + +srs_error_t srs_srt_set_tsbpdmode(SRTSOCKET srt_fd, bool tsbpdmode) +{ + SET_SRT_OPT(srt_fd, SRTO_TSBPDMODE, tsbpdmode); + return srs_success; +} + +srs_error_t srs_srt_set_sndbuf(SRTSOCKET srt_fd, int sndbuf) +{ + SET_SRT_OPT(srt_fd, SRTO_SNDBUF, sndbuf); + return srs_success; +} + +srs_error_t srs_srt_set_rcvbuf(SRTSOCKET srt_fd, int rcvbuf) +{ + SET_SRT_OPT(srt_fd, SRTO_RCVBUF, rcvbuf); + return srs_success; +} + +srs_error_t srs_srt_set_tlpktdrop(SRTSOCKET srt_fd, bool tlpktdrop) +{ + SET_SRT_OPT(srt_fd, SRTO_TLPKTDROP, tlpktdrop); + return srs_success; +} + +srs_error_t srs_srt_set_latency(SRTSOCKET srt_fd, int latency) +{ + SET_SRT_OPT(srt_fd, SRTO_LATENCY, latency); + return srs_success; +} + +srs_error_t srs_srt_set_rcv_latency(SRTSOCKET srt_fd, int rcv_latency) +{ + SET_SRT_OPT(srt_fd, SRTO_RCVLATENCY, rcv_latency); + return srs_success; +} + +srs_error_t srs_srt_set_peer_latency(SRTSOCKET srt_fd, int peer_latency) +{ + SET_SRT_OPT(srt_fd, SRTO_PEERLATENCY, peer_latency); + return srs_success; +} + +srs_error_t srs_srt_set_streamid(SRTSOCKET srt_fd, const std::string& streamid) +{ + SET_SRT_OPT_STR(srt_fd, SRTO_STREAMID, streamid.data(), streamid.size()); + return srs_success; +} + +srs_error_t srs_srt_get_maxbw(SRTSOCKET srt_fd, int& maxbw) +{ + GET_SRT_OPT(srt_fd, SRTO_MAXBW, maxbw); + return srs_success; +} + +srs_error_t srs_srt_get_mss(SRTSOCKET srt_fd, int& mss) +{ + GET_SRT_OPT(srt_fd, SRTO_MSS, mss); + return srs_success; +} + +srs_error_t srs_srt_get_payload_size(SRTSOCKET srt_fd, int& payload_size) +{ + GET_SRT_OPT(srt_fd, SRTO_PAYLOADSIZE, payload_size); + return srs_success; +} + +srs_error_t srs_srt_get_connect_timeout(SRTSOCKET srt_fd, int& timeout) +{ + GET_SRT_OPT(srt_fd, SRTO_CONNTIMEO, timeout); + return srs_success; +} + +srs_error_t srs_srt_get_peer_idle_timeout(SRTSOCKET srt_fd, int& timeout) +{ + GET_SRT_OPT(srt_fd, SRTO_PEERIDLETIMEO, timeout); + return srs_success; +} + +srs_error_t srs_srt_get_tsbpdmode(SRTSOCKET srt_fd, bool& tsbpdmode) +{ + GET_SRT_OPT(srt_fd, SRTO_TSBPDMODE, tsbpdmode); + return srs_success; +} + +srs_error_t srs_srt_get_sndbuf(SRTSOCKET srt_fd, int& sndbuf) +{ + GET_SRT_OPT(srt_fd, SRTO_SNDBUF, sndbuf); + return srs_success; +} + +srs_error_t srs_srt_get_rcvbuf(SRTSOCKET srt_fd, int& rcvbuf) +{ + GET_SRT_OPT(srt_fd, SRTO_RCVBUF, rcvbuf); + return srs_success; +} + +srs_error_t srs_srt_get_tlpktdrop(SRTSOCKET srt_fd, bool& tlpktdrop) +{ + GET_SRT_OPT(srt_fd, SRTO_TLPKTDROP, tlpktdrop); + return srs_success; +} + +srs_error_t srs_srt_get_latency(SRTSOCKET srt_fd, int& latency) +{ + GET_SRT_OPT(srt_fd, SRTO_LATENCY, latency); + return srs_success; +} + +srs_error_t srs_srt_get_rcv_latency(SRTSOCKET srt_fd, int& rcv_latency) +{ + GET_SRT_OPT(srt_fd, SRTO_RCVLATENCY, rcv_latency); + return srs_success; +} + +srs_error_t srs_srt_get_peer_latency(SRTSOCKET srt_fd, int& peer_latency) +{ + GET_SRT_OPT(srt_fd, SRTO_PEERLATENCY, peer_latency); + return srs_success; +} + +srs_error_t srs_srt_get_streamid(SRTSOCKET srt_fd, std::string& streamid) +{ + srs_error_t err = srs_success; + + if ((err = do_srs_srt_get_streamid(srt_fd, streamid)) != srs_success) { + return srs_error_wrap(err, "srt get streamid"); + } + + return err; +} + +srs_error_t srs_srt_get_local_ip_port(SRTSOCKET srt_fd, std::string& ip, int& port) +{ + srs_error_t err = srs_success; + + // discovery client information + sockaddr_storage addr; + int addrlen = sizeof(addr); + if (srt_getsockname(srt_fd, (sockaddr*)&addr, &addrlen) == -1) { + return srs_error_new(ERROR_SRT_SOCKOPT, "srt_getsockname"); + } + + char saddr[64]; + char* h = (char*)saddr; + socklen_t nbh = (socklen_t)sizeof(saddr); + const int r0 = getnameinfo((const sockaddr*)&addr, addrlen, h, nbh,NULL, 0, NI_NUMERICHOST); + if (r0) { + return srs_error_new(ERROR_SRT_SOCKOPT, "getnameinfo"); + } + + 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; + } + + ip.assign(saddr); + return err; +} + +srs_error_t srs_srt_get_remote_ip_port(SRTSOCKET srt_fd, std::string& ip, int& port) +{ + srs_error_t err = srs_success; + + // discovery client information + sockaddr_storage addr; + int addrlen = sizeof(addr); + if (srt_getpeername(srt_fd, (sockaddr*)&addr, &addrlen) == -1) { + return srs_error_new(ERROR_SRT_SOCKOPT, "srt_getpeername"); + } + + char saddr[64]; + char* h = (char*)saddr; + socklen_t nbh = (socklen_t)sizeof(saddr); + const int r0 = getnameinfo((const sockaddr*)&addr, addrlen, h, nbh,NULL, 0, NI_NUMERICHOST); + if (r0) { + return srs_error_new(ERROR_SRT_SOCKOPT, "getnameinfo"); + } + + 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; + } + + ip.assign(saddr); + return err; +} + +SrsSrtPoller::SrsSrtPoller() +{ + srt_epoller_fd_ = -1; +} + +SrsSrtPoller::~SrsSrtPoller() +{ + if (srt_epoller_fd_ > 0) { + srt_epoll_release(srt_epoller_fd_); + } +} + +srs_error_t SrsSrtPoller::initialize() +{ + srs_error_t err = srs_success; + + srt_epoller_fd_ = srt_epoll_create(); + events_.resize(1024); + + return err; +} + +srs_error_t SrsSrtPoller::add_socket(SrsSrtSocket* srt_skt) +{ + srs_error_t err = srs_success; + + int events = srt_skt->events(); + SRTSOCKET srtfd = srt_skt->fd(); + + int ret = srt_epoll_add_usock(srt_epoller_fd_, srtfd, &events); + + srs_info("srt poller %d add srt socket %d, events=%d", srt_epoller_fd_, srtfd, events); + if (ret == SRT_ERROR) { + return srs_error_new(ERROR_SRT_EPOLL, "srt epoll add socket=%lu failed, err=%s", srtfd, srt_getlasterror_str()); + } + + // record srtfd to SrsSrtSocket* + fd_sockets_[srtfd] = srt_skt; + + return err; +} + +srs_error_t SrsSrtPoller::del_socket(SrsSrtSocket* srt_skt) +{ + srs_error_t err = srs_success; + + SRTSOCKET srtfd = srt_skt->fd(); + + int ret = srt_epoll_remove_usock(srt_epoller_fd_, srtfd); + srs_info("srt poller %d remove srt socket %d", srt_epoller_fd_, srtfd); + if (ret == SRT_ERROR) { + return srs_error_new(ERROR_SRT_EPOLL, "srt epoll remove socket=%lu failed, err=%s", srtfd, srt_getlasterror_str()); + } + + fd_sockets_.erase(srtfd); + + return err; +} + +srs_error_t SrsSrtPoller::wait(int timeout_ms) +{ + srs_error_t err = srs_success; + + // wait srt event fired, will timeout after `timeout_ms` milliseconds. + int ret = srt_epoll_uwait(srt_epoller_fd_, events_.data(), events_.size(), timeout_ms); + if (ret < 0) { + return srs_error_new(ERROR_SRT_EPOLL, "srt_epoll_uwait, ret=%d", ret); + } + + for (int i = 0; i < ret; ++i) { + SRT_EPOLL_EVENT event = events_[i]; + map::iterator iter = fd_sockets_.find(event.fd); + if (iter == fd_sockets_.end()) { + srs_assert(false); + } + + SrsSrtSocket* srt_skt = iter->second; + srs_assert(srt_skt != NULL); + + // notify error, don't notify read/write event. + if (event.events & SRT_EPOLL_ERR) { + srt_skt->notify_error(); + } else { + if (event.events & SRT_EPOLL_IN) { + srt_skt->notify_readable(); + } + if (event.events & SRT_EPOLL_OUT) { + srt_skt->notify_writeable(); + } + } + } + + return err; +} + +srs_error_t SrsSrtPoller::mod_socket(SrsSrtSocket* srt_skt) +{ + srs_error_t err = srs_success; + + int events = srt_skt->events(); + SRTSOCKET srtfd = srt_skt->fd(); + + int ret = srt_epoll_update_usock(srt_epoller_fd_, srtfd, &events); + srs_info("srt poller %d update srt socket %d, events=%d", srt_epoller_fd_, srtfd, events); + + if (ret == SRT_ERROR) { + return srs_error_new(ERROR_SRT_EPOLL, "srt epoll update socket=%lu failed, err=%s", srtfd, srt_getlasterror_str()); + } + + return err; +} + +SrsSrtSocket::SrsSrtSocket(SrsSrtPoller* srt_poller, SRTSOCKET srt_fd) +{ + srt_poller_ = srt_poller; + srt_fd_ = srt_fd; + has_error_ = 0; + read_cond_ = srs_cond_new(); + write_cond_ = srs_cond_new(); + + recv_timeout_ = 5 * SRS_UTIME_SECONDS; + send_timeout_ = 5 * SRS_UTIME_SECONDS; + + recv_bytes_ = 0; + send_bytes_ = 0; + + events_ = 0; +} + +SrsSrtSocket::~SrsSrtSocket() +{ + srs_error_t err = srt_poller_->del_socket(this); + if (err != srs_success) { + srs_error("srt poller remove socket failed, err=%s", srs_error_desc(err).c_str()); + srs_error_reset(err); + } + + srs_cond_destroy(read_cond_); + srs_cond_destroy(write_cond_); + + srs_trace("close srt_fd=%d", srt_fd_); + srt_close(srt_fd_); +} + +srs_error_t SrsSrtSocket::connect(const string& ip, int port) +{ + srs_error_t err = srs_success; + + sockaddr_in inaddr; + inaddr.sin_family = AF_INET; + inaddr.sin_port = htons(port); + // TODO: FIXME: inet_addr is deprecated + inaddr.sin_addr.s_addr = inet_addr(ip.c_str()); + + int ret = srt_connect(srt_fd_, (const sockaddr*)&inaddr, sizeof(inaddr)); + + // TODO: FIXME: check return value. + SRT_SOCKSTATUS srt_status = srt_getsockstate(srt_fd_); + if (srt_status != SRTS_CONNECTED) { + // Connect is in progress, wait until it finish or error. + if ((err = wait_writeable()) != srs_success) { + return srs_error_wrap(err, "wait writeable"); + } + + // Double check if connect is established. + srt_status = srt_getsockstate(srt_fd_); + if (srt_status != SRTS_CONNECTED) { + return srs_error_new(ERROR_SRT_IO, "srt_connect, err=%s", srt_getlasterror_str()); + } + } + + return err; +} + +srs_error_t SrsSrtSocket::accept(SRTSOCKET* client_srt_fd) +{ + srs_error_t err = srs_success; + + while (true) { + sockaddr_in inaddr; + int addrlen = sizeof(inaddr); + SRTSOCKET srt_fd = srt_accept(srt_fd_, (sockaddr*)&inaddr, &addrlen); + if (srt_fd == SRT_INVALID_SOCK) { + if (srt_getlasterror(NULL) == SRT_EASYNCRCV) { + // Accept would block, wait until new client connect or error. + if ((err = wait_readable()) != srs_success) { + return srs_error_wrap(err, "wait readable"); + } + continue; + } else { + return srs_error_new(ERROR_SRT_IO, "srt_accept, err=%s", srt_getlasterror_str()); + } + } else { + *client_srt_fd = srt_fd; + break; + } + } + + return err; +} + +srs_error_t SrsSrtSocket::recvmsg(void* buf, size_t size, ssize_t* nread) +{ + srs_error_t err = srs_success; + + while (true) { + int ret = srt_recvmsg(srt_fd_, (char*)buf, size); + if (ret < 0) { + if (srt_getlasterror(NULL) == SRT_EASYNCRCV) { + if ((err = wait_readable()) != srs_success) { + return srs_error_wrap(err, "wait readable"); + } + continue; + } else { + return srs_error_new(ERROR_SRT_IO, "srt_recvmsg, err=%s", srt_getlasterror_str()); + } + } else { + recv_bytes_ += ret; + *nread = ret; + break; + } + } + + return err; +} + +srs_error_t SrsSrtSocket::sendmsg(void* buf, size_t size, ssize_t* nwrite) +{ + srs_error_t err = srs_success; + + while (true) { + int ret = srt_sendmsg(srt_fd_, (const char*)buf, size, -1, 1); + if (ret < 0) { + if (srt_getlasterror(NULL) == SRT_EASYNCSND) { + if ((err = wait_writeable()) != srs_success) { + return srs_error_wrap(err, "wait writeable"); + } + continue; + } else { + return srs_error_new(ERROR_SRT_IO, "srt_sendmsg, err=%s", srt_getlasterror_str()); + } + } else { + send_bytes_ += ret; + *nwrite = ret; + break; + } + } + + return err; +} + +srs_error_t SrsSrtSocket::wait_readable() +{ + srs_error_t err = srs_success; + + // Check if error occured. + if ((err = check_error()) != srs_success) { + return srs_error_wrap(err, "has error"); + } + + // Subscribe in and error event + if ((err = enable_read()) != srs_success) { + return srs_error_wrap(err, "enable read"); + } + + // Wait event fired or timeout. + int ret = srs_cond_timedwait(read_cond_, recv_timeout_); + // TODO: FIXME: need to disable it? + err = disable_read(); + if (err != srs_success) { + srs_freep(err); + } + + if (ret != 0) { + // Timeout and events no fired. + if (errno == ETIME) { + return srs_error_new(ERROR_SRT_TIMEOUT, "srt socket %d timeout", srt_fd_); + } + // Interrupted, maybe coroutine terminated. + if (errno == EINTR) { + return srs_error_new(ERROR_SRT_INTERRUPT, "srt socket %d interrupted", srt_fd_); + } + return srs_error_new(ERROR_SRT_IO, "srt socket %d wait read", srt_fd_); + } + + // Check if we are notify with error event. + if ((err = check_error()) != srs_success) { + return srs_error_wrap(err, "has error"); + } + + return err; +} + +srs_error_t SrsSrtSocket::wait_writeable() +{ + srs_error_t err = srs_success; + + if ((err = check_error()) != srs_success) { + return srs_error_wrap(err, "has error"); + } + + if ((err = enable_write()) != srs_success) { + return srs_error_wrap(err, "enable write"); + } + + int ret = srs_cond_timedwait(write_cond_, send_timeout_); + err = disable_write(); + if (err != srs_success) { + srs_freep(err); + } + + if (ret != 0) { + if (errno == ETIME) { + return srs_error_new(ERROR_SRT_TIMEOUT, "srt socket %d timeout", srt_fd_); + } + if (errno == EINTR) { + return srs_error_new(ERROR_SRT_INTERRUPT, "srt socket %d interrupted", srt_fd_); + } + return srs_error_new(ERROR_SRT_IO, "srt socket %d wait write", srt_fd_); + } + + if ((err = check_error()) != srs_success) { + return srs_error_wrap(err, "has error"); + } + + return err; +} + +void SrsSrtSocket::notify_readable() +{ + srs_cond_signal(read_cond_); +} + +void SrsSrtSocket::notify_writeable() +{ + srs_cond_signal(write_cond_); +} + +void SrsSrtSocket::notify_error() +{ + // mark error, and check when read/write + has_error_ = true; + srs_cond_signal(read_cond_); + srs_cond_signal(write_cond_); +} + +srs_error_t SrsSrtSocket::enable_read() +{ + return enable_event(SRT_EPOLL_IN | SRT_EPOLL_ERR); +} + +srs_error_t SrsSrtSocket::disable_read() +{ + return disable_event(SRT_EPOLL_IN); +} + +srs_error_t SrsSrtSocket::enable_write() +{ + return enable_event(SRT_EPOLL_OUT | SRT_EPOLL_ERR); +} + +srs_error_t SrsSrtSocket::disable_write() +{ + return disable_event(SRT_EPOLL_OUT); +} + +srs_error_t SrsSrtSocket::enable_event(int event) { + srs_error_t err = srs_success; + // Event has been subscribed. + if ((events_ & event) == event) { + return err; + } + + int old_events = events_; + events_ |= event; + + if (old_events == 0) { + err = srt_poller_->add_socket(this); + } else { + err = srt_poller_->mod_socket(this); + } + return err; +} + +srs_error_t SrsSrtSocket::disable_event(int event) { + srs_error_t err = srs_success; + // Event has been unsubscribed. + if ((events_ & event) == 0) { + return err; + } + + events_ &= (~event); + + if (events_ == 0) { + err = srt_poller_->del_socket(this); + } else { + err = srt_poller_->mod_socket(this); + } + return err; +} + +srs_error_t SrsSrtSocket::check_error() { + srs_error_t err = srs_success; + + if (has_error_) { + return srs_error_new(ERROR_SRT_IO, "has error"); + } + + return err; +} + diff --git a/trunk/src/protocol/srs_service_st_srt.hpp b/trunk/src/protocol/srs_service_st_srt.hpp new file mode 100644 index 0000000000..1e548b490d --- /dev/null +++ b/trunk/src/protocol/srs_service_st_srt.hpp @@ -0,0 +1,154 @@ +// +// Copyright (c) 2013-2021 The SRS Authors +// +// SPDX-License-Identifier: MIT or MulanPSL-2.0 +// + +#ifndef SRS_SERVICE_ST_SRT_HPP +#define SRS_SERVICE_ST_SRT_HPP + +#include +#include + +#include +#include + +#include + +// Create srt socket only, with libsrt's default option. +extern srs_error_t srs_srt_socket(SRTSOCKET* pfd); + +// Create srt socket with srs recommend default option(tsbpdmode=false,tlpktdrop=false,latency=0,sndsyn=0,rcvsyn=0) +extern srs_error_t srs_srt_socket_with_default_option(SRTSOCKET* pfd); + +// For server, listen at SRT endpoint. +extern srs_error_t srs_srt_listen(SRTSOCKET srt_fd, std::string ip, int port); + +// Set read/write no block. +extern srs_error_t srs_srt_nonblock(SRTSOCKET srt_fd); + +// Set SRT options. +extern srs_error_t srs_srt_set_maxbw(SRTSOCKET srt_fd, int maxbw); +extern srs_error_t srs_srt_set_mss(SRTSOCKET srt_fd, int mss); +extern srs_error_t srs_srt_set_payload_size(SRTSOCKET srt_fd, int payload_size); +extern srs_error_t srs_srt_set_connect_timeout(SRTSOCKET srt_fd, int timeout); +extern srs_error_t srs_srt_set_peer_idle_timeout(SRTSOCKET srt_fd, int timeout); +extern srs_error_t srs_srt_set_tsbpdmode(SRTSOCKET srt_fd, bool tsbpdmode); +extern srs_error_t srs_srt_set_sndbuf(SRTSOCKET srt_fd, int sndbuf); +extern srs_error_t srs_srt_set_rcvbuf(SRTSOCKET srt_fd, int rcvbuf); +extern srs_error_t srs_srt_set_tlpktdrop(SRTSOCKET srt_fd, bool tlpktdrop); +extern srs_error_t srs_srt_set_latency(SRTSOCKET srt_fd, int latency); +extern srs_error_t srs_srt_set_rcv_latency(SRTSOCKET srt_fd, int rcv_latency); +extern srs_error_t srs_srt_set_peer_latency(SRTSOCKET srt_fd, int peer_latency); +extern srs_error_t srs_srt_set_streamid(SRTSOCKET srt_fd, const std::string& streamid); + +// Get SRT options. +extern srs_error_t srs_srt_get_maxbw(SRTSOCKET srt_fd, int& maxbw); +extern srs_error_t srs_srt_get_mss(SRTSOCKET srt_fd, int& mss); +extern srs_error_t srs_srt_get_payload_size(SRTSOCKET srt_fd, int& payload_size); +extern srs_error_t srs_srt_get_connect_timeout(SRTSOCKET srt_fd, int& timeout); +extern srs_error_t srs_srt_get_peer_idle_timeout(SRTSOCKET srt_fd, int& timeout); +extern srs_error_t srs_srt_get_tsbpdmode(SRTSOCKET srt_fd, bool& tsbpdmode); +extern srs_error_t srs_srt_get_sndbuf(SRTSOCKET srt_fd, int& sndbuf); +extern srs_error_t srs_srt_get_rcvbuf(SRTSOCKET srt_fd, int& rcvbuf); +extern srs_error_t srs_srt_get_tlpktdrop(SRTSOCKET srt_fd, bool& tlpktdrop); +extern srs_error_t srs_srt_get_latency(SRTSOCKET srt_fd, int& latency); +extern srs_error_t srs_srt_get_rcv_latency(SRTSOCKET srt_fd, int& rcv_latency); +extern srs_error_t srs_srt_get_peer_latency(SRTSOCKET srt_fd, int& peer_latency); +extern srs_error_t srs_srt_get_streamid(SRTSOCKET srt_fd, std::string& streamid); + +// Get SRT socket info. +extern srs_error_t srs_srt_get_local_ip_port(SRTSOCKET srt_fd, std::string& ip, int& port); +extern srs_error_t srs_srt_get_remote_ip_port(SRTSOCKET srt_fd, std::string& ip, int& port); + +class SrsSrtSocket; + +// Srt poller, subscribe/unsubscribed events and wait them fired. +class SrsSrtPoller +{ +public: + SrsSrtPoller(); + virtual ~SrsSrtPoller(); +public: + srs_error_t initialize(); + srs_error_t add_socket(SrsSrtSocket* srt_skt); + srs_error_t mod_socket(SrsSrtSocket* srt_skt); + srs_error_t del_socket(SrsSrtSocket* srt_skt); + srs_error_t wait(int timeout_ms); +private: + // Find SrsSrtSocket* context by SRTSOCKET. + std::map fd_sockets_; + int srt_epoller_fd_; + std::vector events_; +}; + +// Srt ST socket, wrap SRT io and make it adapt to ST-thread. +class SrsSrtSocket +{ +public: + SrsSrtSocket(SrsSrtPoller* srt_poller, SRTSOCKET srt_fd); + virtual ~SrsSrtSocket(); +public: // IO API + srs_error_t connect(const std::string& ip, int port); + srs_error_t accept(SRTSOCKET* client_srt_fd); + srs_error_t recvmsg(void* buf, size_t size, ssize_t* nread); + srs_error_t sendmsg(void* buf, size_t size, ssize_t* nwrite); +public: + SRTSOCKET fd() const { return srt_fd_; } + int events() const { return events_; } +public: + void set_recv_timeout(srs_utime_t tm) { recv_timeout_ = tm; } + void set_send_timeout(srs_utime_t tm) { send_timeout_ = tm; } + srs_utime_t get_send_timeout() { return send_timeout_; } + srs_utime_t get_recv_timeout() { return recv_timeout_; } + int64_t get_send_bytes() { return send_bytes_; } + int64_t get_recv_bytes() { return recv_bytes_; } + // Yiled coroutine and wait this socket readable. + srs_error_t wait_readable(); + // Yiled coroutine and wait this socket writeable. + srs_error_t wait_writeable(); + // Notify this socket readable, and resume coroutine later. + void notify_readable(); + // Notify this socket writeable, and resume coroutine later. + void notify_writeable(); + // Notify this socket error, resume coroutine later and error will return in all the operator of this socket. + void notify_error(); +public: + // Subscribed IN/ERR event to srt poller. + srs_error_t enable_read(); + // Unsubscribed IN event to srt poller. + srs_error_t disable_read(); + // Subscribed OUT/ERR event to srt poller. + srs_error_t enable_write(); + // Unsubscribed OUT event to srt poller. + srs_error_t disable_write(); +private: + srs_error_t enable_event(int event); + srs_error_t disable_event(int event); + srs_error_t check_error(); + +private: + SRTSOCKET srt_fd_; + // Mark if some error occured in srt socket. + bool has_error_; + // When read operator like recvmsg/accept would block, wait this condition timeout or notified, + // and the coroutine itself will be yiled and resume when condition be notified. + srs_cond_t read_cond_; + // When write operator like sendmsg/connectt would block, wait this condition timeout or notified, + // and the coroutine itself will be yiled and resume when condition be notified. + srs_cond_t write_cond_; + + srs_utime_t recv_timeout_; + srs_utime_t send_timeout_; + + int64_t recv_bytes_; + int64_t send_bytes_; + + // Event of this socket subscribed. + int events_; + // Srt poller which this socket attach to. + SrsSrtPoller* srt_poller_; +}; + +#endif + diff --git a/trunk/src/utest/srs_utest.cpp b/trunk/src/utest/srs_utest.cpp index 2b07563f6b..f2478eb9f5 100644 --- a/trunk/src/utest/srs_utest.cpp +++ b/trunk/src/utest/srs_utest.cpp @@ -17,6 +17,10 @@ #include using namespace std; +#ifdef SRS_SRT +#include +#endif + // Temporary disk config. std::string _srs_tmp_file_prefix = "/tmp/srs-utest-"; // Temporary network config. @@ -32,6 +36,10 @@ SrsConfig* _srs_config = NULL; SrsServer* _srs_server = NULL; bool _srs_in_docker = false; +#ifdef SRS_SRT +SrsSrtEventLoop* _srt_eventloop = NULL; +#endif + #include // Initialize global settings. @@ -52,6 +60,16 @@ srs_error_t prepare_main() { srs_freep(_srs_context); _srs_context = new SrsThreadContext(); +#ifdef SRS_SRT + _srt_eventloop = new SrsSrtEventLoop(); + if ((err = _srt_eventloop->initialize()) != srs_success) { + return srs_error_wrap(err, "srt poller initialize"); + } + if ((err = _srt_eventloop->start()) != srs_success) { + return srs_error_wrap(err, "srt poller start"); + } +#endif + return err; } diff --git a/trunk/src/utest/srs_utest_srt.cpp b/trunk/src/utest/srs_utest_srt.cpp index 7d420a22ca..8abba1288d 100644 --- a/trunk/src/utest/srs_utest_srt.cpp +++ b/trunk/src/utest/srs_utest_srt.cpp @@ -6,84 +6,461 @@ #include #include -#include +#include +#include +#include +#include +#include +#include #include using namespace std; -VOID TEST(ProtocolSrtTest, SrtGetStreamInfoNormal) { +extern SrsSrtEventLoop* _srt_eventloop; + +// Test srt st service +VOID TEST(ServiceSrtPoller, SrtPollOperateSocket) +{ + srs_error_t err = srs_success; + + SrsSrtPoller* srt_poller = new SrsSrtPoller(); + HELPER_EXPECT_SUCCESS(srt_poller->initialize()); + + SRTSOCKET srt_fd = SRT_INVALID_SOCK; + HELPER_EXPECT_SUCCESS(srs_srt_socket(&srt_fd)); + EXPECT_TRUE(srt_fd > 0); + + SrsSrtSocket* srt_socket = new SrsSrtSocket(srt_poller, srt_fd); + EXPECT_EQ(srt_socket->events(), 0); + + // Enable read, will subscribe SRT_EPOLL_IN and SRT_EPOLL_ERR event in srt poller. + HELPER_EXPECT_SUCCESS(srt_socket->enable_read()); + EXPECT_TRUE(srt_socket->events() & SRT_EPOLL_IN); + EXPECT_TRUE(srt_socket->events() & SRT_EPOLL_ERR); + + // Enable read, will subscribe SRT_EPOLL_OUT and SRT_EPOLL_ERR event in srt poller. + HELPER_EXPECT_SUCCESS(srt_socket->enable_write()); + EXPECT_TRUE(srt_socket->events() & SRT_EPOLL_OUT); + EXPECT_TRUE(srt_socket->events() & SRT_EPOLL_ERR); + + // Disable read, will unsubscribe SRT_EPOLL_IN event in srt poller. + HELPER_EXPECT_SUCCESS(srt_socket->disable_read()); + EXPECT_FALSE(srt_socket->events() & SRT_EPOLL_IN); + EXPECT_TRUE(srt_socket->events() & SRT_EPOLL_ERR); + + // Disable write, will unsubscribe SRT_EPOLL_OUT event in srt poller. + HELPER_EXPECT_SUCCESS(srt_socket->disable_write()); + EXPECT_FALSE(srt_socket->events() & SRT_EPOLL_OUT); + EXPECT_TRUE(srt_socket->events() & SRT_EPOLL_ERR); + + EXPECT_EQ(srt_poller->fd_sockets_.size(), 1); + // Delete socket, will remove in srt poller. + srs_freep(srt_socket); + EXPECT_EQ(srt_poller->fd_sockets_.size(), 0); + + srs_freep(srt_poller); +} + +VOID TEST(ServiceSrtPoller, SrtSetGetSocketOpt) +{ + srs_error_t err = srs_success; + + SRTSOCKET srt_fd = SRT_INVALID_SOCK; + HELPER_EXPECT_SUCCESS(srs_srt_socket(&srt_fd)); + HELPER_EXPECT_SUCCESS(srs_srt_nonblock(srt_fd)); + + int maxbw = 20000; + int mss = 1400; + int payload_size = 1316; + int connect_timeout = 5000; + int peer_idle_timeout = 10000; + bool tsbpdmode = false; + int sndbuf = 2 * 1024 * 1024; + int rcvbuf = 10 * 1024 * 1024; + bool tlpktdrop = false; + int latency = 0; + int rcv_latency = 120; + int peer_latency = 120; + std::string streamid = "SRS_SRT"; + + HELPER_EXPECT_SUCCESS(srs_srt_set_maxbw(srt_fd, maxbw)); + HELPER_EXPECT_SUCCESS(srs_srt_set_mss(srt_fd, mss)); + HELPER_EXPECT_SUCCESS(srs_srt_set_payload_size(srt_fd, payload_size)); + HELPER_EXPECT_SUCCESS(srs_srt_set_connect_timeout(srt_fd, connect_timeout)); + HELPER_EXPECT_SUCCESS(srs_srt_set_peer_idle_timeout(srt_fd, peer_idle_timeout)); + HELPER_EXPECT_SUCCESS(srs_srt_set_tsbpdmode(srt_fd, tsbpdmode)); + HELPER_EXPECT_SUCCESS(srs_srt_set_sndbuf(srt_fd, sndbuf)); + HELPER_EXPECT_SUCCESS(srs_srt_set_rcvbuf(srt_fd, rcvbuf)); + HELPER_EXPECT_SUCCESS(srs_srt_set_tlpktdrop(srt_fd, tlpktdrop)); + HELPER_EXPECT_SUCCESS(srs_srt_set_latency(srt_fd, latency)); + HELPER_EXPECT_SUCCESS(srs_srt_set_rcv_latency(srt_fd, rcv_latency)); + HELPER_EXPECT_SUCCESS(srs_srt_set_peer_latency(srt_fd, peer_latency)); + HELPER_EXPECT_SUCCESS(srs_srt_set_streamid(srt_fd, streamid)); + + bool b; + int i = 0; + std::string s; + + HELPER_EXPECT_SUCCESS(srs_srt_get_maxbw(srt_fd, i)); + EXPECT_EQ(i, maxbw); + HELPER_EXPECT_SUCCESS(srs_srt_get_mss(srt_fd, i)); + EXPECT_EQ(i, mss); + HELPER_EXPECT_SUCCESS(srs_srt_get_payload_size(srt_fd, i)); + EXPECT_EQ(i, payload_size); + HELPER_EXPECT_SUCCESS(srs_srt_get_connect_timeout(srt_fd, i)); + EXPECT_EQ(i, connect_timeout); + HELPER_EXPECT_SUCCESS(srs_srt_get_peer_idle_timeout(srt_fd, i)); + EXPECT_EQ(i, peer_idle_timeout); + + // Don't check b equal to option blow, because some opt will deterimated after srt handshake done or change when set. + HELPER_EXPECT_SUCCESS(srs_srt_get_tsbpdmode(srt_fd, b)); + HELPER_EXPECT_SUCCESS(srs_srt_get_sndbuf(srt_fd, i)); + HELPER_EXPECT_SUCCESS(srs_srt_get_rcvbuf(srt_fd, i)); + HELPER_EXPECT_SUCCESS(srs_srt_get_tlpktdrop(srt_fd, b)); + HELPER_EXPECT_SUCCESS(srs_srt_get_latency(srt_fd, i)); + HELPER_EXPECT_SUCCESS(srs_srt_get_rcv_latency(srt_fd, i)); + HELPER_EXPECT_SUCCESS(srs_srt_get_peer_latency(srt_fd, i)); + + HELPER_EXPECT_SUCCESS(srs_srt_get_streamid(srt_fd, s)); + EXPECT_EQ(s, streamid); +} + +class MockSrtServer +{ +public: + SrsSrtSocket* srt_socket_; + SRTSOCKET srt_server_fd_; + + MockSrtServer() { + srt_server_fd_ = SRT_INVALID_SOCK; + srt_socket_ = NULL; + } + + srs_error_t create_socket() { + srs_error_t err = srs_success; + if ((err = srs_srt_socket_with_default_option(&srt_server_fd_)) != srs_success) { + return srs_error_wrap(err, "create srt socket"); + } + return err; + } + + srs_error_t listen(std::string ip, int port) { + srs_error_t err = srs_success; + + if ((err = srs_srt_listen(srt_server_fd_, ip, port)) != srs_success) { + return srs_error_wrap(err, "srt listen"); + } + + srt_socket_ = new SrsSrtSocket(_srt_eventloop->get_srt_poller(), srt_server_fd_); + + return err; + } + + virtual ~MockSrtServer() { + srs_freep(srt_socket_); + } + + virtual srs_error_t accept(SRTSOCKET* client_fd) { + srs_error_t err = srs_success; + + if ((err = srt_socket_->accept(client_fd)) != srs_success) { + return srs_error_wrap(err, "srt accept"); + } + + return err; + } +}; + +VOID TEST(ServiceStSRTTest, ListenConnectAccept) +{ + srs_error_t err = srs_success; + + std::string server_ip = "127.0.0.1"; + int server_port = 9000; + + MockSrtServer srt_server; + HELPER_EXPECT_SUCCESS(srt_server.create_socket()); + HELPER_EXPECT_SUCCESS(srt_server.listen(server_ip, server_port)); + + SRTSOCKET srt_client_fd = SRT_INVALID_SOCK; + HELPER_EXPECT_SUCCESS(srs_srt_socket(&srt_client_fd)); + + SrsSrtSocket* srt_client_socket = new SrsSrtSocket(_srt_eventloop->get_srt_poller(), srt_client_fd); + + // No client connected, accept will timeout. + SRTSOCKET srt_fd = SRT_INVALID_SOCK; + // Make utest fast timeout. + srt_server.srt_socket_->set_recv_timeout(50 * SRS_UTIME_MILLISECONDS); + err = srt_server.accept(&srt_fd); + EXPECT_EQ(srs_error_code(err), ERROR_SRT_TIMEOUT); + EXPECT_EQ(srt_fd, SRT_INVALID_SOCK); + + // Client connect to server + HELPER_EXPECT_SUCCESS(srt_client_socket->connect(server_ip, server_port)); + + // Server will accept one client. + HELPER_EXPECT_SUCCESS(srt_server.accept(&srt_fd)); + EXPECT_NE(srt_fd, SRT_INVALID_SOCK); +} + +VOID TEST(ServiceStSRTTest, ConnectTimeout) +{ + srs_error_t err = srs_success; + + SRTSOCKET srt_client_fd = SRT_INVALID_SOCK; + HELPER_EXPECT_SUCCESS(srs_srt_socket_with_default_option(&srt_client_fd)); + SrsSrtSocket* srt_client_socket = new SrsSrtSocket(_srt_eventloop->get_srt_poller(), srt_client_fd); + + srt_client_socket->set_send_timeout(50 * SRS_UTIME_MILLISECONDS); + // Client connect to server which is no listening. + HELPER_EXPECT_FAILED(srt_client_socket->connect("127.0.0.1", 9099)); +} + +VOID TEST(ServiceStSRTTest, ConnectWithStreamid) +{ + srs_error_t err = srs_success; + + std::string server_ip = "127.0.0.1"; + int server_port = 9000; + + MockSrtServer srt_server; + HELPER_EXPECT_SUCCESS(srt_server.create_socket()); + HELPER_EXPECT_SUCCESS(srt_server.listen(server_ip, server_port)); + + std::string streamid = "SRS_SRT_Streamid"; + SRTSOCKET srt_client_fd = SRT_INVALID_SOCK; + HELPER_EXPECT_SUCCESS(srs_srt_socket_with_default_option(&srt_client_fd)); + HELPER_EXPECT_SUCCESS(srs_srt_set_streamid(srt_client_fd, streamid)); + SrsSrtSocket* srt_client_socket = new SrsSrtSocket(_srt_eventloop->get_srt_poller(), srt_client_fd); + + HELPER_EXPECT_SUCCESS(srt_client_socket->connect("127.0.0.1", 9000)); + + SRTSOCKET srt_server_accepted_fd = SRT_INVALID_SOCK; + HELPER_EXPECT_SUCCESS(srt_server.accept(&srt_server_accepted_fd)); + EXPECT_NE(srt_server_accepted_fd, SRT_INVALID_SOCK); + std::string s; + HELPER_EXPECT_SUCCESS(srs_srt_get_streamid(srt_server_accepted_fd, s)); + EXPECT_EQ(s, streamid); +} + +VOID TEST(ServiceStSRTTest, ReadWrite) +{ + srs_error_t err = srs_success; + + std::string server_ip = "127.0.0.1"; + int server_port = 9000; + + MockSrtServer srt_server; + HELPER_EXPECT_SUCCESS(srt_server.create_socket()); + HELPER_EXPECT_SUCCESS(srt_server.listen(server_ip, server_port)); + + SRTSOCKET srt_client_fd = SRT_INVALID_SOCK; + HELPER_EXPECT_SUCCESS(srs_srt_socket_with_default_option(&srt_client_fd)); + SrsSrtSocket* srt_client_socket = new SrsSrtSocket(_srt_eventloop->get_srt_poller(), srt_client_fd); + + // Client connect to server + HELPER_EXPECT_SUCCESS(srt_client_socket->connect(server_ip, server_port)); + + // Server will accept one client. + SRTSOCKET srt_server_accepted_fd = SRT_INVALID_SOCK; + HELPER_EXPECT_SUCCESS(srt_server.accept(&srt_server_accepted_fd)); + EXPECT_NE(srt_server_accepted_fd, SRT_INVALID_SOCK); + SrsSrtSocket* srt_server_accepted_socket = new SrsSrtSocket(_srt_eventloop->get_srt_poller(), srt_server_accepted_fd); + + if (true) { + std::string content = "Hello, SRS SRT!"; + + // Client send msg to server. + ssize_t nb_write = 0; + HELPER_EXPECT_SUCCESS(srt_client_socket->sendmsg((char*)content.data(), content.size(), &nb_write)); + EXPECT_EQ(nb_write, content.size()); + + // Server recv msg from client + char buf[1500]; + ssize_t nb_read = 0; + HELPER_EXPECT_SUCCESS(srt_server_accepted_socket->recvmsg(buf, sizeof(buf), &nb_read)); + EXPECT_EQ(nb_read, content.size()); + EXPECT_EQ(std::string(buf, nb_read), content); + + // Server echo msg back to client. + HELPER_EXPECT_SUCCESS(srt_server_accepted_socket->sendmsg(buf, nb_read, &nb_write)); + EXPECT_EQ(nb_write, content.size()); + + // Client recv echo msg from server. + HELPER_EXPECT_SUCCESS(srt_client_socket->recvmsg(buf, sizeof(buf), &nb_read)); + EXPECT_EQ(nb_read, content.size()); + EXPECT_EQ(std::string(buf, nb_read), content); + } + if (true) { - int mode; string vhost; string subpath; - EXPECT_TRUE(get_streamid_info("#!::r=live/livestream,key1=value1,key2=value2", mode, vhost, subpath)); - EXPECT_EQ(PULL_SRT_MODE, mode); + char buf[1500]; + ssize_t nb_read = 0; + // Make socket fast timeout in ustet. + srt_server_accepted_socket->set_recv_timeout(50 * SRS_UTIME_MILLISECONDS); + // Recv msg from client, but client no send any msg, so will be timeout. + err = srt_server_accepted_socket->recvmsg(buf, sizeof(buf), &nb_read); + EXPECT_EQ(srs_error_code(err), ERROR_SRT_TIMEOUT); + } +} + +// Test srt server +class MockSrtHandler : public ISrsSrtHandler +{ +private: + SRTSOCKET srt_fd; +public: + MockSrtHandler() { + srt_fd = SRT_INVALID_SOCK; + } + virtual ~MockSrtHandler() { + } +public: + virtual srs_error_t on_srt_client(SRTSOCKET fd) { + srt_fd = fd; + return srs_success; + } +}; + +VOID TEST(SrtServerTest, SrtListener) +{ + srs_error_t err = srs_success; + + if (true) { + MockSrtHandler h; + SrsSrtListener srt_listener(&h, "127.0.0.1", 9000); + HELPER_EXPECT_SUCCESS(srt_listener.create_socket()); + HELPER_EXPECT_SUCCESS(srt_listener.listen()); + EXPECT_TRUE(srt_listener.fd() > 0); + } +} + +// Test srt app +VOID TEST(ProtocolSrtTest, SrtGetStreamInfoNormal) +{ + if (true) { + SrtMode mode; string vhost; string subpath; + EXPECT_TRUE(srs_srt_streamid_info("#!::r=live/livestream,key1=value1,key2=value2", mode, vhost, subpath)); + EXPECT_EQ(SrtModePull, mode); EXPECT_STREQ("", vhost.c_str()); EXPECT_STREQ("live/livestream?key1=value1&key2=value2", subpath.c_str()); } if (true) { - int mode; string vhost; string subpath; - EXPECT_TRUE(get_streamid_info("#!::h=host.com,r=live/livestream,key1=value1,key2=value2", mode, vhost, subpath)); - EXPECT_EQ(PULL_SRT_MODE, mode); + SrtMode mode; string vhost; string subpath; + EXPECT_TRUE(srs_srt_streamid_info("#!::h=host.com,r=live/livestream,key1=value1,key2=value2", mode, vhost, subpath)); + EXPECT_EQ(SrtModePull, mode); EXPECT_STREQ("host.com", vhost.c_str()); EXPECT_STREQ("live/livestream?vhost=host.com&key1=value1&key2=value2", subpath.c_str()); } } -VOID TEST(ProtocolSrtTest, SrtGetStreamInfoMethod) { +VOID TEST(ProtocolSrtTest, SrtGetStreamInfoMethod) +{ if (true) { - int mode; string vhost; string subpath; - EXPECT_TRUE(get_streamid_info("#!::r=live/livestream,m=request", mode, vhost, subpath)); - EXPECT_EQ(PULL_SRT_MODE, mode); + SrtMode mode; string vhost; string subpath; + EXPECT_TRUE(srs_srt_streamid_info("#!::r=live/livestream,m=request", mode, vhost, subpath)); + EXPECT_EQ(SrtModePull, mode); EXPECT_STREQ("live/livestream", subpath.c_str()); } if (true) { - int mode; string vhost; string subpath; - EXPECT_TRUE(get_streamid_info("#!::r=live/livestream,m=publish", mode, vhost, subpath)); - EXPECT_EQ(PUSH_SRT_MODE, mode); + SrtMode mode; string vhost; string subpath; + EXPECT_TRUE(srs_srt_streamid_info("#!::r=live/livestream,m=publish", mode, vhost, subpath)); + EXPECT_EQ(SrtModePush, mode); EXPECT_STREQ("live/livestream", subpath.c_str()); } } -VOID TEST(ProtocolSrtTest, SrtGetStreamInfoCompatible) { +VOID TEST(ProtocolSrtTest, SrtGetStreamInfoCompatible) +{ if (true) { - int mode; string vhost; string subpath; - EXPECT_TRUE(get_streamid_info("#!::h=live/livestream,m=request", mode, vhost, subpath)); - EXPECT_EQ(PULL_SRT_MODE, mode); + SrtMode mode; string vhost; string subpath; + EXPECT_TRUE(srs_srt_streamid_info("#!::h=live/livestream,m=request", mode, vhost, subpath)); + EXPECT_EQ(SrtModePull, mode); EXPECT_STREQ("", vhost.c_str()); EXPECT_STREQ("live/livestream", subpath.c_str()); } if (true) { - int mode; string vhost; string subpath; - EXPECT_TRUE(get_streamid_info("#!::h=live/livestream,m=publish", mode, vhost, subpath)); - EXPECT_EQ(PUSH_SRT_MODE, mode); + SrtMode mode; string vhost; string subpath; + EXPECT_TRUE(srs_srt_streamid_info("#!::h=live/livestream,m=publish", mode, vhost, subpath)); + EXPECT_EQ(SrtModePush, mode); EXPECT_STREQ("", vhost.c_str()); EXPECT_STREQ("live/livestream", subpath.c_str()); } if (true) { - int mode; string vhost; string subpath; - EXPECT_TRUE(get_streamid_info("#!::h=srs.srt.com.cn/live/livestream,m=request", mode, vhost, subpath)); - EXPECT_EQ(PULL_SRT_MODE, mode); + SrtMode mode; string vhost; string subpath; + EXPECT_TRUE(srs_srt_streamid_info("#!::h=srs.srt.com.cn/live/livestream,m=request", mode, vhost, subpath)); + EXPECT_EQ(SrtModePull, mode); EXPECT_STREQ("srs.srt.com.cn", vhost.c_str()); EXPECT_STREQ("live/livestream?vhost=srs.srt.com.cn", subpath.c_str()); } if (true) { - int mode; string vhost; string subpath; - EXPECT_TRUE(get_streamid_info("#!::h=srs.srt.com.cn/live/livestream,m=publish", mode, vhost, subpath)); - EXPECT_EQ(PUSH_SRT_MODE, mode); + SrtMode mode; string vhost; string subpath; + EXPECT_TRUE(srs_srt_streamid_info("#!::h=srs.srt.com.cn/live/livestream,m=publish", mode, vhost, subpath)); + EXPECT_EQ(SrtModePush, mode); EXPECT_STREQ("srs.srt.com.cn", vhost.c_str()); EXPECT_STREQ("live/livestream?vhost=srs.srt.com.cn", subpath.c_str()); } if (true) { - int mode; string vhost; string subpath; - EXPECT_TRUE(get_streamid_info("#!::h=live/livestream?secret=d6d2be37,m=publish", mode, vhost, subpath)); - EXPECT_EQ(PUSH_SRT_MODE, mode); + SrtMode mode; string vhost; string subpath; + EXPECT_TRUE(srs_srt_streamid_info("#!::h=live/livestream?secret=d6d2be37,m=publish", mode, vhost, subpath)); + EXPECT_EQ(SrtModePush, mode); EXPECT_STREQ("", vhost.c_str()); EXPECT_STREQ("live/livestream?secret=d6d2be37", subpath.c_str()); } } +VOID TEST(ProtocolSrtTest, SrtStreamIdToRequest) +{ + if (true) { + SrtMode mode; + SrsRequest req; + EXPECT_TRUE(srs_srt_streamid_to_request("#!::r=live/livestream?key1=val1,key2=val2", mode, &req)); + EXPECT_EQ(mode, SrtModePull); + EXPECT_STREQ(req.vhost.c_str(), ""); + EXPECT_STREQ(req.app.c_str(), "live"); + EXPECT_STREQ(req.stream.c_str(), "livestream"); + EXPECT_STREQ(req.param.c_str(), "key1=val1&key2=val2"); + } + + if (true) { + SrtMode mode; + SrsRequest req; + EXPECT_TRUE(srs_srt_streamid_to_request("#!::h=srs.srt.com.cn,r=live/livestream?key1=val1,key2=val2", mode, &req)); + EXPECT_EQ(mode, SrtModePull); + EXPECT_STREQ(req.vhost.c_str(), "srs.srt.com.cn"); + EXPECT_STREQ(req.app.c_str(), "live"); + EXPECT_STREQ(req.stream.c_str(), "livestream"); + EXPECT_STREQ(req.param.c_str(), "vhost=srs.srt.com.cn&key1=val1&key2=val2"); + } + + if (true) { + SrtMode mode; + SrsRequest req; + EXPECT_TRUE(srs_srt_streamid_to_request("#!::h=live/livestream?key1=val1,key2=val2", mode, &req)); + EXPECT_EQ(mode, SrtModePull); + EXPECT_STREQ(req.vhost.c_str(), ""); + EXPECT_STREQ(req.app.c_str(), "live"); + EXPECT_STREQ(req.stream.c_str(), "livestream"); + EXPECT_STREQ(req.param.c_str(), "key1=val1&key2=val2"); + } + + if (true) { + SrtMode mode; + SrsRequest req; + EXPECT_TRUE(srs_srt_streamid_to_request("#!::h=srs.srt.com.cn/live/livestream?key1=val1,key2=val2", mode, &req)); + EXPECT_EQ(mode, SrtModePull); + EXPECT_STREQ(req.vhost.c_str(), "srs.srt.com.cn"); + EXPECT_STREQ(req.app.c_str(), "live"); + EXPECT_STREQ(req.stream.c_str(), "livestream"); + EXPECT_STREQ(req.param.c_str(), "vhost=srs.srt.com.cn&key1=val1&key2=val2"); + } +} + +// TODO: FIXME: add mpegts conn test +// set srt option, recv srt client, get srt client opt and check. +