From 49da2099c1481683a8488a7867d21e5abb682862 Mon Sep 17 00:00:00 2001 From: xiaozhihong Date: Mon, 30 Mar 2020 15:16:29 +0800 Subject: [PATCH] sdp exchange. --- trunk/configure | 2 +- trunk/research/players/rtc_player.html | 8 +- trunk/src/app/srs_app_http_api.cpp | 165 ++++- trunk/src/app/srs_app_http_api.hpp | 11 +- trunk/src/app/srs_app_rtc.hpp | 2 +- trunk/src/app/srs_app_rtc_conn.cpp | 322 +++------ trunk/src/app/srs_app_rtc_conn.hpp | 57 +- trunk/src/app/srs_app_sdp.cpp | 917 +++++++++++++++++++++++++ trunk/src/app/srs_app_sdp.hpp | 219 ++++++ trunk/src/kernel/srs_kernel_error.hpp | 1 + trunk/src/kernel/srs_kernel_rtp.cpp | 27 + trunk/src/kernel/srs_kernel_rtp.hpp | 2 + 12 files changed, 1435 insertions(+), 298 deletions(-) create mode 100644 trunk/src/app/srs_app_sdp.cpp create mode 100644 trunk/src/app/srs_app_sdp.hpp diff --git a/trunk/configure b/trunk/configure index 3b07b394c..0d5ebfb78 100755 --- a/trunk/configure +++ b/trunk/configure @@ -284,7 +284,7 @@ if [ $SRS_EXPORT_LIBRTMP_PROJECT = NO ]; then "srs_app_hourglass" "srs_app_dash" "srs_app_fragment" "srs_app_dvr" "srs_app_coworkers" "srs_app_hybrid") if [[ $SRS_RTC == YES ]]; then - MODULE_FILES+=("srs_app_rtc" "srs_app_rtc_conn" "srs_app_dtls" "srs_app_audio_recode") + MODULE_FILES+=("srs_app_rtc" "srs_app_rtc_conn" "srs_app_dtls" "srs_app_audio_recode" "srs_app_sdp") fi DEFINES="" # add each modules for app diff --git a/trunk/research/players/rtc_player.html b/trunk/research/players/rtc_player.html index 082eb4a69..0835c024b 100644 --- a/trunk/research/players/rtc_player.html +++ b/trunk/research/players/rtc_player.html @@ -70,15 +70,13 @@ $('#rtc_media_player').prop('srcObject', event.stream); }; new Promise(function(resolve, reject) { + pc.addTransceiver("audio", {direction: "recvonly"}); + pc.addTransceiver("video", {direction: "recvonly"}); + pc.createOffer(function(offer){ resolve(offer); },function(reason){ reject(reason); - },{ - mandatory: { - OfferToReceiveAudio: true, - OfferToReceiveVideo: true - } }); }).then(function(offer) { return pc.setLocalDescription(offer).then(function(){ return offer; }); diff --git a/trunk/src/app/srs_app_http_api.cpp b/trunk/src/app/srs_app_http_api.cpp index ab755df2f..424e3e4c7 100644 --- a/trunk/src/app/srs_app_http_api.cpp +++ b/trunk/src/app/srs_app_http_api.cpp @@ -784,12 +784,14 @@ srs_error_t SrsGoApiStreams::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessa } #ifdef SRS_AUTO_RTC -SrsGoApiSdp::SrsGoApiSdp(SrsRtcServer* rtc_svr) +uint32_t SrsGoApiRtcPlay::ssrc_num = 0; + +SrsGoApiRtcPlay::SrsGoApiRtcPlay(SrsRtcServer* rtc_svr) { rtc_server = rtc_svr; } -SrsGoApiSdp::~SrsGoApiSdp() +SrsGoApiRtcPlay::~SrsGoApiRtcPlay() { } @@ -803,7 +805,7 @@ SrsGoApiSdp::~SrsGoApiSdp() // Response: // {"sdp":"answer...", "sid":"..."} // @see https://github.com/rtcdn/rtcdn-draft -srs_error_t SrsGoApiSdp::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r) +srs_error_t SrsGoApiRtcPlay::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r) { srs_error_t err = srs_success; @@ -818,7 +820,7 @@ srs_error_t SrsGoApiSdp::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* return srs_api_response(w, r, res->dumps()); } -srs_error_t SrsGoApiSdp::do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, SrsJsonObject* res) +srs_error_t SrsGoApiRtcPlay::do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, SrsJsonObject* res) { srs_error_t err = srs_success; @@ -881,23 +883,36 @@ srs_error_t SrsGoApiSdp::do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessag // TODO: FIXME: It seems remote_sdp doesn't represents the full SDP information. SrsSdp remote_sdp; - if ((err = remote_sdp.decode(remote_sdp_str)) != srs_success) { - return srs_error_wrap(err, "decode sdp"); + if ((err = remote_sdp.parse(remote_sdp_str)) != srs_success) { + return srs_error_wrap(err, "parse sdp failed"); + } + + if ((err = check_remote_sdp(remote_sdp)) != srs_success) { + return srs_error_wrap(err, "remote sdp check failed"); + } + + SrsSdp local_sdp; + if ((err = exchange_sdp(app, stream_name, remote_sdp, local_sdp)) != srs_success) { + return srs_error_wrap(err, "remote sdp have error or unsupport attributes"); } SrsRequest request; request.app = app; request.stream = stream_name; - SrsSdp local_sdp; + // TODO: FIXME: Maybe need a better name? // TODO: FIXME: When server enabled, but vhost disabled, should report error. SrsRtcSession* rtc_session = rtc_server->create_rtc_session(request, remote_sdp, local_sdp); - string local_sdp_str = ""; - if ((err = local_sdp.encode(local_sdp_str)) != srs_success) { + ostringstream os; + if ((err = local_sdp.encode(os)) != srs_success) { return srs_error_wrap(err, "encode sdp"); } + string local_sdp_str = os.str(); + + srs_trace("local_sdp=%s", local_sdp_str.c_str()); + res->set("code", SrsJsonAny::integer(ERROR_SUCCESS)); res->set("server", SrsJsonAny::integer(SrsStatistic::instance()->server_id())); @@ -910,6 +925,138 @@ srs_error_t SrsGoApiSdp::do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessag return err; } + +srs_error_t SrsGoApiRtcPlay::check_remote_sdp(const SrsSdp& remote_sdp) +{ + srs_error_t err = srs_success; + + if (remote_sdp.group_policy_ != "BUNDLE") { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "now only support BUNDLE, group policy=%s", remote_sdp.group_policy_.c_str()); + } + + if (remote_sdp.media_descs_.empty()) { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no media descriptions"); + } + + for (std::vector::const_iterator iter = remote_sdp.media_descs_.begin(); iter != remote_sdp.media_descs_.end(); ++iter) { + if (iter->type_ != "audio" && iter->type_ != "video") { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "unsupport media type=%s", iter->type_.c_str()); + } + + if (! iter->rtcp_mux_) { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "now only suppor rtcp-mux"); + } + + for (std::vector::const_iterator iter_media = iter->payload_types_.begin(); iter_media != iter->payload_types_.end(); ++iter_media) { + if (iter->sendonly_) { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "play API only support sendrecv/recvonly"); + } + } + } + + return err; +} + +srs_error_t SrsGoApiRtcPlay::exchange_sdp(const std::string& app, const std::string& stream, const SrsSdp& remote_sdp, SrsSdp& local_sdp) +{ + srs_error_t err = srs_success; + local_sdp.version_ = "0"; + + local_sdp.username_ = RTMP_SIG_SRS_SERVER; + local_sdp.session_id_ = srs_int2str((int64_t)this); + local_sdp.session_version_ = "2"; + local_sdp.nettype_ = "IN"; + local_sdp.addrtype_ = "IP4"; + local_sdp.unicast_address_ = "0.0.0.0"; + + local_sdp.session_name_ = "live_play_session"; + + local_sdp.msid_semantic_ = "WMS"; + local_sdp.msids_.push_back(app + "/" + stream); + + local_sdp.group_policy_ = "BUNDLE"; + + int mid = 0; + + for (int i = 0; i < remote_sdp.media_descs_.size(); ++i) { + const SrsMediaDesc& remote_media_desc = remote_sdp.media_descs_[i]; + + if (remote_media_desc.is_audio()) { + local_sdp.media_descs_.push_back(SrsMediaDesc("audio")); + } else if (remote_media_desc.is_video()) { + local_sdp.media_descs_.push_back(SrsMediaDesc("video")); + } + + SrsMediaDesc& local_media_desc = local_sdp.media_descs_.back(); + + if (remote_media_desc.is_audio()) { + // TODO: check opus format specific param + std::vector payloads = remote_media_desc.find_media_with_encoding_name("opus"); + for (std::vector::iterator iter = payloads.begin(); iter != payloads.end(); ++iter) { + // Only choose one match opus codec. + local_media_desc.payload_types_.push_back(*iter); + break; + } + + if (local_media_desc.payload_types_.empty()) { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no valid found opus payload type"); + } + + } else if (remote_media_desc.is_video()) { + std::vector payloads = remote_media_desc.find_media_with_encoding_name("H264"); + for (std::vector::iterator iter = payloads.begin(); iter != payloads.end(); ++iter) { + H264SpecificParam h264_param; + if (parse_h264_fmtp(iter->format_specific_param_, h264_param) != 0) { + continue; + } + + if (h264_param.packetization_mode == 1 && h264_param.level_asymmerty_allow == 1) { + // Only choose first match H.264 payload type. + local_media_desc.payload_types_.push_back(*iter); + break; + } + } + + if (local_media_desc.payload_types_.empty()) { + return srs_error_new(ERROR_RTC_SDP_EXCHANGE, "no valid found H.264 payload type"); + } + } + + local_media_desc.mid_ = remote_media_desc.mid_; + local_sdp.groups_.push_back(local_media_desc.mid_); + + local_media_desc.port_ = 9; + local_media_desc.protos_ = "UDP/TLS/RTP/SAVPF"; + + if (remote_media_desc.session_info_.setup_ == "active") { + local_media_desc.session_info_.setup_ = "passive"; + } else if (remote_media_desc.session_info_.setup_ == "passive") { + local_media_desc.session_info_.setup_ = "active"; + } else if (remote_media_desc.session_info_.setup_ == "actpass") { + local_media_desc.session_info_.setup_ = "passive"; + } + + local_sdp.media_descs_.back().session_info_.ice_options_ = "trickle"; + + if (remote_media_desc.sendonly_) { + local_media_desc.recvonly_ = true; + } else if (remote_media_desc.recvonly_) { + local_media_desc.sendonly_ = true; + } else if (remote_media_desc.sendrecv_) { + local_media_desc.sendrecv_ = true; + } + + local_media_desc.rtcp_mux_ = true; + + SrsSSRCInfo ssrc_info; + ssrc_info.ssrc_ = ++ssrc_num; + ssrc_info.cname_ = "test_sdp_cname"; + local_media_desc.ssrc_infos_.push_back(ssrc_info); + } + + return err; +} + #endif SrsGoApiClients::SrsGoApiClients() diff --git a/trunk/src/app/srs_app_http_api.hpp b/trunk/src/app/srs_app_http_api.hpp index 4caee1de8..5976c5de3 100644 --- a/trunk/src/app/srs_app_http_api.hpp +++ b/trunk/src/app/srs_app_http_api.hpp @@ -33,6 +33,7 @@ class SrsHttpHandler; class SrsServer; class SrsRtcServer; class SrsJsonObject; +class SrsSdp; #include #include @@ -167,17 +168,21 @@ public: }; #ifdef SRS_AUTO_RTC -class SrsGoApiSdp : public ISrsHttpHandler +class SrsGoApiRtcPlay : public ISrsHttpHandler { +public: + static uint32_t ssrc_num; private: SrsRtcServer* rtc_server; public: - SrsGoApiSdp(SrsRtcServer* rtc_svr); - virtual ~SrsGoApiSdp(); + SrsGoApiRtcPlay(SrsRtcServer* rtc_svr); + virtual ~SrsGoApiRtcPlay(); public: virtual srs_error_t serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r); private: virtual srs_error_t do_serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r, SrsJsonObject* res); + srs_error_t exchange_sdp(const std::string& app, const std::string& stream, const SrsSdp& remote_sdp, SrsSdp& local_sdp); + srs_error_t check_remote_sdp(const SrsSdp& remote_sdp); }; #endif diff --git a/trunk/src/app/srs_app_rtc.hpp b/trunk/src/app/srs_app_rtc.hpp index e599ac0f9..e763ca550 100644 --- a/trunk/src/app/srs_app_rtc.hpp +++ b/trunk/src/app/srs_app_rtc.hpp @@ -43,7 +43,7 @@ const int max_payload_size = 1200; const int kRtpPacketSize = 1500; const uint8_t kOpusPayloadType = 111; -const uint8_t kH264PayloadType = 102; +const uint8_t kH264PayloadType = 95; const uint8_t kNalTypeMask = 0x1F; diff --git a/trunk/src/app/srs_app_rtc_conn.cpp b/trunk/src/app/srs_app_rtc_conn.cpp index da08a5cf3..4657366cd 100644 --- a/trunk/src/app/srs_app_rtc_conn.cpp +++ b/trunk/src/app/srs_app_rtc_conn.cpp @@ -89,15 +89,7 @@ static string gen_random_str(int len) const int SRTP_MASTER_KEY_KEY_LEN = 16; const int SRTP_MASTER_KEY_SALT_LEN = 14; -SrsCandidate::SrsCandidate() -{ -} - -SrsCandidate::~SrsCandidate() -{ -} - -std::vector SrsCandidate::get_candidate_ips() +static std::vector get_candidate_ips() { std::vector candidate_ips; @@ -116,173 +108,6 @@ std::vector SrsCandidate::get_candidate_ips() return candidate_ips; } -SrsSdpMediaInfo::SrsSdpMediaInfo() -{ -} - -SrsSdpMediaInfo::~SrsSdpMediaInfo() -{ -} - -SrsSdp::SrsSdp() -{ -} - -SrsSdp::~SrsSdp() -{ -} - -srs_error_t SrsSdp::decode(const string& sdp_str) -{ - srs_error_t err = srs_success; - - if (sdp_str.size() < 2 || sdp_str[0] != 'v' || sdp_str[1] != '=') { - return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid sdp_str"); - } - - string line; - istringstream is(sdp_str); - while (getline(is, line)) { - srs_verbose("line=%s", line.c_str()); - - if (line.size() < 2 || line[1] != '=') { - return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid sdp line=%s", line.c_str()); - } - - switch (line[0]) { - case 'v' :{ - break; - } - case 'o' :{ - break; - } - case 's' :{ - break; - } - case 't' :{ - break; - } - case 'c' :{ - break; - } - case 'a' :{ - if ((err = parse_attr(line)) != srs_success) { - return srs_error_new(ERROR_RTC_SDP_DECODE, "decode sdp line=%s failed", line.c_str()); - } - break; - } - case 'm' :{ - break; - } - } - } - - return err; -} - -srs_error_t SrsSdp::encode(string& sdp_str) -{ - srs_error_t err = srs_success; - - string candidate_lines = ""; - - std::vector candidate_ips = SrsCandidate::get_candidate_ips(); - for (int i = 0; i < (int)candidate_ips.size(); ++i) { - ostringstream os; - os << "a=candidate:10 1 udp 2115783679 " << candidate_ips[i] << " " << _srs_config->get_rtc_server_listen() <<" typ host generation 0\\r\\n"; - candidate_lines += os.str(); - } - - // FIXME: - sdp_str = - "v=0\\r\\n" - "o=- 0 0 IN IP4 127.0.0.1\\r\\n" - "s=-\\r\\n" - "t=0 0\\r\\n" - "a=ice-lite\\r\\n" - "a=group:BUNDLE 0 1\\r\\n" - "a=msid-semantic: WMS 6VrfBKXrwK\\r\\n" - "m=audio 9 UDP/TLS/RTP/SAVPF 111\\r\\n" - "c=IN IP4 0.0.0.0\\r\\n" - + candidate_lines + - "a=rtcp:9 IN IP4 0.0.0.0\\r\\n" - "a=ice-ufrag:" + ice_ufrag + "\\r\\n" - "a=ice-pwd:" + ice_pwd + "\\r\\n" - "a=ice-options:trickle\\r\\n" - "a=fingerprint:sha-256 " + SrsDtls::instance()->get_fingerprint() + "\\r\\n" - "a=sendrecv\\r\\n" - "a=mid:0\\r\\n" - "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\\r\\n" - "a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\\r\\n" - "a=rtcp-mux\\r\\n" - "a=rtpmap:111 opus/48000/2\\r\\n" - "a=fmtp:111 minptime=10;useinbandfec=1\\r\\n" - "a=maxptime:60\\r\\n" - "a=ssrc:3233846890 cname:o/i14u9pJrxRKAsu\\r\\n" - "a=ssrc:3233846890 msid:6VrfBKXrwK a0\\r\\n" - "a=ssrc:3233846890 mslabel:6VrfBKXrwK\\r\\n" - "a=ssrc:3233846890 label:6VrfBKXrwKa0\\r\\n" - "m=video 9 UDP/TLS/RTP/SAVPF 102\\r\\n" - "c=IN IP4 0.0.0.0\\r\\n" - + candidate_lines + - "a=rtcp:9 IN IP4 0.0.0.0\\r\\n" - "b=as:2000000\\r\\n" - "a=ice-ufrag:" + ice_ufrag + "\\r\\n" - "a=ice-pwd:" + ice_pwd + "\\r\\n" - "a=ice-options:trickle\\r\\n" - "a=extmap:2 urn:ietf:params:rtp-hdrext:toffset\\r\\n" - "a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\\r\\n" - "a=extmap:4 urn:3gpp:video-orientation\\r\\n" - "a=fingerprint:sha-256 " + SrsDtls::instance()->get_fingerprint() + "\\r\\n" - "a=sendrecv\\r\\n" - "a=mid:1\\r\\n" - "a=rtcp-mux\\r\\n" - "a=rtpmap:102 H264/90000\\r\\n" - "a=rtcp-fb:102 goog-remb\\r\\n" - "a=rtcp-fb:102 transport-cc\\r\\n" - "a=rtcp-fb:102 ccm fir \\r\\n" - "a=rtcp-fb:102 nack\\r\\n" - "a=rtcp-fb:102 nack pli \\r\\n" - "a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f\\r\\n" - "a=ssrc:3233846889 cname:o/i14u9pJrxRKAsu\\r\\n" - "a=ssrc:3233846889 msid:6VrfBKXrwK v0\\r\\n" - "a=ssrc:3233846889 mslabel:6VrfBKXrwK\\r\\n" - "a=ssrc:3233846889 label:6VrfBKXrwKv0\\r\\n"; - - return err; -} - -srs_error_t SrsSdp::parse_attr(const string& line) -{ - srs_error_t err = srs_success; - - string key = ""; - string val = ""; - string* p = &key; - for (int i = 2; i < (int)line.size(); ++i) { - if (line[i] == ':' && p == &key) { - p = &val; - } else { - if (line[i] != '\r' && line[i] != '\n') { - p->append(1, line[i]); - } - } - } - - srs_verbose("sdp attribute key=%s, val=%s", key.c_str(), val.c_str()); - - if (key == "ice-ufrag") { - ice_ufrag = val; - } else if (key == "ice-pwd") { - ice_pwd = val; - } else if (key == "fingerprint") { - - } else { - } - - return err; -} - SrsDtlsSession::SrsDtlsSession(SrsRtcSession* s) { rtc_session = s; @@ -317,6 +142,31 @@ SrsDtlsSession::~SrsDtlsSession() } } +srs_error_t SrsDtlsSession::initialize() +{ + srs_error_t err = srs_success; + + if ((dtls = SSL_new(SrsDtls::instance()->get_dtls_ctx())) == NULL) { + return srs_error_new(ERROR_OpenSslCreateSSL, "SSL_new dtls"); + } + + // Dtls setup passive, as server role. + SSL_set_accept_state(dtls); + + if ((bio_in = BIO_new(BIO_s_mem())) == NULL) { + return srs_error_new(ERROR_OpenSslBIONew, "BIO_new in"); + } + + if ((bio_out = BIO_new(BIO_s_mem())) == NULL) { + BIO_free(bio_in); + return srs_error_new(ERROR_OpenSslBIONew, "BIO_new out"); + } + + SSL_set_bio(dtls, bio_in, bio_out); + + return err; +} + srs_error_t SrsDtlsSession::handshake(SrsUdpMuxSocket* udp_mux_skt) { srs_error_t err = srs_success; @@ -412,36 +262,6 @@ srs_error_t SrsDtlsSession::on_dtls_application_data(const char* buf, const int return err; } -srs_error_t SrsDtlsSession::send_client_hello(SrsUdpMuxSocket* udp_mux_skt) -{ - srs_error_t err = srs_success; - - if (dtls == NULL) { - srs_verbose("send client hello"); - - if ((dtls = SSL_new(SrsDtls::instance()->get_dtls_ctx())) == NULL) { - return srs_error_new(ERROR_OpenSslCreateSSL, "SSL_new dtls"); - } - - SSL_set_connect_state(dtls); - - if ((bio_in = BIO_new(BIO_s_mem())) == NULL) { - return srs_error_new(ERROR_OpenSslBIONew, "BIO_new in"); - } - - if ((bio_out = BIO_new(BIO_s_mem())) == NULL) { - BIO_free(bio_in); - return srs_error_new(ERROR_OpenSslBIONew, "BIO_new out"); - } - - SSL_set_bio(dtls, bio_in, bio_out); - - return handshake(udp_mux_skt); - } - - return err; -} - srs_error_t SrsDtlsSession::srtp_initialize() { srs_error_t err = srs_success; @@ -494,11 +314,11 @@ srs_error_t SrsDtlsSession::srtp_send_init() policy.allow_repeat_tx = 1; policy.next = NULL; - uint8_t *key = new uint8_t[client_key.size()]; - memcpy(key, client_key.data(), client_key.size()); + uint8_t *key = new uint8_t[server_key.size()]; + memcpy(key, server_key.data(), server_key.size()); policy.key = key; - if (srtp_create(&srtp_send, &policy) != 0) { + if (srtp_create(&srtp_send, &policy) != srtp_err_status_ok) { srs_freepa(key); return srs_error_new(ERROR_RTC_SRTP_INIT, "srtp_create failed"); } @@ -526,11 +346,11 @@ srs_error_t SrsDtlsSession::srtp_recv_init() policy.allow_repeat_tx = 1; policy.next = NULL; - uint8_t *key = new uint8_t[server_key.size()]; - memcpy(key, server_key.data(), server_key.size()); + uint8_t *key = new uint8_t[client_key.size()]; + memcpy(key, client_key.data(), client_key.size()); policy.key = key; - if (srtp_create(&srtp_recv, &policy) != 0) { + if (srtp_create(&srtp_recv, &policy) != srtp_err_status_ok) { srs_freepa(key); return srs_error_new(ERROR_RTC_SRTP_INIT, "srtp_create failed"); } @@ -594,7 +414,7 @@ srs_error_t SrsDtlsSession::unprotect_rtcp(char* out_buf, const char* in_buf, in if (srtp_recv) { memcpy(out_buf, in_buf, nb_out_buf); - if (srtp_unprotect_rtcp(srtp_recv, out_buf, &nb_out_buf) != 0) { + if (srtp_unprotect_rtcp(srtp_recv, out_buf, &nb_out_buf) != srtp_err_status_ok) { return srs_error_new(ERROR_RTC_SRTP_UNPROTECT, "rtcp unprotect failed"); } @@ -620,6 +440,19 @@ SrsRtcSenderThread::~SrsRtcSenderThread() srs_freep(sendonly_ukt); } +srs_error_t SrsRtcSenderThread::initialize(const uint32_t& vssrc, const uint32_t& assrc, const uint16_t& v_pt, const uint16_t& a_pt) +{ + srs_error_t err = srs_success; + + video_ssrc = vssrc; + audio_ssrc = assrc; + + video_payload_type = v_pt; + audio_payload_type = a_pt; + + return err; +} + int SrsRtcSenderThread::cid() { return trd->cid(); @@ -723,6 +556,16 @@ void SrsRtcSenderThread::send_and_free_messages(SrsSharedPtrMessage** msgs, int SrsRtpSharedPacket* pkt = msg->rtp_packets[i]; + if (msg->is_video()) { + pkt->set_payload_type(video_payload_type); + pkt->set_ssrc(video_ssrc); + } + + if (msg->is_audio()) { + pkt->set_payload_type(audio_payload_type); + pkt->set_ssrc(audio_ssrc); + } + int length = pkt->size; char buf[kRtpPacketSize]; if ((err = rtc_session->dtls_session->protect_rtp(buf, pkt->payload, length)) != srs_success) { @@ -746,7 +589,8 @@ SrsRtcSession::SrsRtcSession(SrsRtcServer* rtc_svr, const SrsRequest& req, const { rtc_server = rtc_svr; session_state = INIT; - dtls_session = NULL; + dtls_session = new SrsDtlsSession(this); + dtls_session->initialize(); strd = NULL; username = un; @@ -769,6 +613,11 @@ SrsRtcSession::~SrsRtcSession() srs_freep(strd); } +void SrsRtcSession::set_local_sdp(const SrsSdp& sdp) +{ + local_sdp = sdp; +} + void SrsRtcSession::switch_to_context() { _srs_context->set_id(cid); @@ -836,10 +685,6 @@ srs_error_t SrsRtcSession::on_binding_request(SrsUdpMuxSocket* udp_mux_skt, SrsS } if (get_session_state() == WAITING_STUN) { - if ((err = send_client_hello(udp_mux_skt)) != srs_success) { - return srs_error_wrap(err, "send client hello, failed"); - } - set_session_state(DOING_DTLS_HANDSHAKE); peer_id = udp_mux_skt->get_peer_id(); @@ -1057,19 +902,6 @@ block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ return err; } -srs_error_t SrsRtcSession::send_client_hello(SrsUdpMuxSocket* udp_mux_skt) -{ - srs_error_t err = srs_success; - - if (dtls_session == NULL) { - dtls_session = new SrsDtlsSession(this); - } - - dtls_session->send_client_hello(udp_mux_skt); - - return err; -} - srs_error_t SrsRtcSession::on_connection_established(SrsUdpMuxSocket* udp_mux_skt) { srs_trace("rtc session=%s, connection established", id().c_str()); @@ -1082,6 +914,26 @@ srs_error_t SrsRtcSession::start_play(SrsUdpMuxSocket* udp_mux_skt) srs_freep(strd); strd = new SrsRtcSenderThread(this, udp_mux_skt, _srs_context->get_id()); + + uint32_t video_ssrc = 0; + uint32_t audio_ssrc = 0; + uint16_t video_payload_type = 0; + uint16_t audio_payload_type = 0; + for (int i = 0; i < local_sdp.media_descs_.size(); ++i) { + const SrsMediaDesc& media_desc = local_sdp.media_descs_[i]; + if (media_desc.is_audio()) { + audio_ssrc = media_desc.ssrc_infos_[0].ssrc_; + audio_payload_type = media_desc.payload_types_[0].payload_type_; + } else if (media_desc.is_video()) { + video_ssrc = media_desc.ssrc_infos_[0].ssrc_; + video_payload_type = media_desc.payload_types_[0].payload_type_; + } + } + + if ((err =strd->initialize(video_ssrc, audio_ssrc, video_payload_type, audio_payload_type)) != srs_success) { + return srs_error_wrap(err, "SrsRtcSenderThread init"); + } + if ((err = strd->start()) != srs_success) { return srs_error_wrap(err, "start SrsRtcSenderThread"); } @@ -1237,7 +1089,7 @@ srs_error_t SrsRtcServer::listen_api() // TODO: FIXME: Fetch api from hybrid manager. SrsHttpServeMux* http_api_mux = _srs_hybrid->srs()->instance()->api_server(); - if ((err = http_api_mux->handle("/rtc/v1/play/", new SrsGoApiSdp(this))) != srs_success) { + if ((err = http_api_mux->handle("/rtc/v1/play/", new SrsGoApiRtcPlay(this))) != srs_success) { return srs_error_wrap(err, "handle sdp"); } @@ -1263,6 +1115,12 @@ SrsRtcSession* SrsRtcServer::create_rtc_session(const SrsRequest& req, const Srs local_sdp.set_ice_ufrag(local_ufrag); local_sdp.set_ice_pwd(local_pwd); + local_sdp.set_fingerprint_algo("sha-256"); + local_sdp.set_fingerprint(SrsDtls::instance()->get_fingerprint()); + std::vector candidate_ips = get_candidate_ips(); + for (int i = 0; i < (int)candidate_ips.size(); ++i) { + local_sdp.add_candidate(candidate_ips[i], _srs_config->get_rtc_server_listen(), "host"); + } session->set_remote_sdp(remote_sdp); session->set_local_sdp(local_sdp); diff --git a/trunk/src/app/srs_app_rtc_conn.hpp b/trunk/src/app/srs_app_rtc_conn.hpp index aaf0cbe62..532cb89a7 100644 --- a/trunk/src/app/srs_app_rtc_conn.hpp +++ b/trunk/src/app/srs_app_rtc_conn.hpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -65,50 +66,6 @@ const uint8_t kAFB = 15; const srs_utime_t kSrsRtcSessionStunTimeoutUs = 10*1000*1000LL; -class SrsCandidate -{ -private: -public: - SrsCandidate(); - virtual ~SrsCandidate(); - - static std::vector get_candidate_ips(); -}; - -class SrsSdpMediaInfo -{ -private: -public: - SrsSdpMediaInfo(); - virtual ~SrsSdpMediaInfo(); -}; - -class SrsSdp -{ -private: - std::string sdp; - int version; - std::string ice_ufrag; - std::string ice_pwd; - std::string fingerprint; - std::string setup; - std::vector media_infos; -public: - SrsSdp(); - virtual ~SrsSdp(); - - srs_error_t decode(const std::string& sdp_str); - srs_error_t encode(std::string& sdp_str); - - std::string get_ice_ufrag() const { return ice_ufrag; } - std::string get_ice_pwd() const { return ice_pwd; } - - void set_ice_ufrag(const std::string& u) { ice_ufrag = u; } - void set_ice_pwd(const std::string& p) { ice_pwd = p; } -private: - srs_error_t parse_attr(const std::string& line); -}; - enum SrsRtcSessionStateType { // TODO: FIXME: Should prefixed by enum name. @@ -140,11 +97,11 @@ public: SrsDtlsSession(SrsRtcSession* s); virtual ~SrsDtlsSession(); + srs_error_t initialize(); + srs_error_t on_dtls(SrsUdpMuxSocket* udp_mux_skt); srs_error_t on_dtls_handshake_done(SrsUdpMuxSocket* udp_mux_skt); srs_error_t on_dtls_application_data(const char* data, const int len); - - srs_error_t send_client_hello(SrsUdpMuxSocket* udp_mux_skt); public: srs_error_t protect_rtp(char* protected_buf, const char* ori_buf, int& nb_protected_buf); srs_error_t unprotect_rtp(char* unprotected_buf, const char* ori_buf, int& nb_unprotected_buf); @@ -165,11 +122,17 @@ protected: int _parent_cid; private: SrsRtcSession* rtc_session; + uint32_t video_ssrc; + uint32_t audio_ssrc; + uint16_t video_payload_type; + uint16_t audio_payload_type; public: SrsUdpMuxSocket* sendonly_ukt; public: SrsRtcSenderThread(SrsRtcSession* s, SrsUdpMuxSocket* u, int parent_cid); virtual ~SrsRtcSenderThread(); +public: + srs_error_t initialize(const uint32_t& vssrc, const uint32_t& assrc, const uint16_t& v_pt, const uint16_t& a_pt); public: virtual int cid(); public: @@ -208,7 +171,7 @@ public: virtual ~SrsRtcSession(); public: SrsSdp* get_local_sdp() { return &local_sdp; } - void set_local_sdp(const SrsSdp& sdp) { local_sdp = sdp; } + void set_local_sdp(const SrsSdp& sdp); SrsSdp* get_remote_sdp() { return &remote_sdp; } void set_remote_sdp(const SrsSdp& sdp) { remote_sdp = sdp; } diff --git a/trunk/src/app/srs_app_sdp.cpp b/trunk/src/app/srs_app_sdp.cpp new file mode 100644 index 000000000..047d417fa --- /dev/null +++ b/trunk/src/app/srs_app_sdp.cpp @@ -0,0 +1,917 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2013-2020 Winlin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +using namespace std; + +#include + +#include +#include +#include + +#include +#include + +const std::string kCRLF = "\\r\\n"; + +#define FETCH(is,word) \ +if (! (is >> word)) {\ + return srs_error_new(ERROR_RTC_SDP_DECODE, "fetch failed");\ +}\ + +#define FETCH_WITH_DELIM(is,word,delim) \ +if (! getline(is,word,delim)) {\ + return srs_error_new(ERROR_RTC_SDP_DECODE, "fetch with delim failed");\ +}\ + +static std::vector split_str(const std::string& str, const std::string& delim) +{ + std::vector ret; + size_t pre_pos = 0; + std::string tmp; + size_t pos = 0; + do { + pos = str.find(delim, pre_pos); + tmp = str.substr(pre_pos, pos - pre_pos); + ret.push_back(tmp); + pre_pos = pos + delim.size(); + } while (pos != std::string::npos); + + return ret; +} + +static void skip_first_spaces(std::string& str) +{ + while (! str.empty() && str[0] == ' ') { + str.erase(0, 1); + } +} + +srs_error_t parse_h264_fmtp(const std::string& fmtp, H264SpecificParam& h264_param) +{ + srs_error_t err = srs_success; + std::vector vec = split_str(fmtp, ";"); + for (int i = 0; i < vec.size(); ++i) { + std::vector kv = split_str(vec[i], "="); + if (kv.size() == 2) { + if (kv[0] == "profile-level-id") { + h264_param.profile_level_id = atoi(kv[1].c_str()); + } else if (kv[0] == "packetization-mode") { + h264_param.packetization_mode = atoi(kv[1].c_str()); + } else if (kv[0] == "level-asymmetry-allowed") { + h264_param.level_asymmerty_allow = atoi(kv[1].c_str()); + } else { + return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid h264 param=%s", kv[0].c_str()); + } + } else { + return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid h264 param=%s", vec[i].c_str()); + } + } + + return err; +} + +SrsSessionInfo::SrsSessionInfo() +{ +} + +SrsSessionInfo::~SrsSessionInfo() +{ +} + +srs_error_t SrsSessionInfo::parse_attribute(const std::string& attribute, const std::string& value) +{ + srs_error_t err = srs_success; + if (attribute == "ice-ufrag") { + ice_ufrag_ = value; + } else if (attribute == "ice-pwd") { + ice_pwd_ = value; + } else if (attribute == "ice-options") { + ice_options_ = value; + } else if (attribute == "fingerprint") { + std::istringstream is(value); + FETCH(is, fingerprint_algo_); + FETCH(is, fingerprint_); + } else if (attribute == "setup") { + // @see: https://tools.ietf.org/html/rfc4145#section-4 + setup_ = value; + } + + return err; +} + +srs_error_t SrsSessionInfo::encode(std::ostringstream& os) +{ + srs_error_t err = srs_success; + if (! ice_ufrag_.empty()) { + os << "a=ice-ufrag:" << ice_ufrag_ << kCRLF; + } + if (! ice_pwd_.empty()) { + os << "a=ice-pwd:" << ice_pwd_ << kCRLF; + } + if (! ice_options_.empty()) { + os << "a=ice-options:" << ice_options_ << kCRLF; + } + if (! fingerprint_algo_.empty() && ! fingerprint_.empty()) { + os << "a=fingerprint:" << fingerprint_algo_ << " " << fingerprint_ << kCRLF; + } + if (! setup_.empty()) { + os << "a=setup:" << setup_ << kCRLF; + } + + return err; +} + +bool SrsSessionInfo::operator=(const SrsSessionInfo& rhs) +{ + return ice_ufrag_ == rhs.ice_ufrag_ && + ice_pwd_ == rhs.ice_pwd_ && + ice_options_ == rhs.ice_options_ && + fingerprint_algo_ == rhs.fingerprint_algo_ && + fingerprint_ == rhs.fingerprint_ && + setup_ == rhs.setup_; +} + +SrsSSRCInfo::SrsSSRCInfo() +{ + ssrc_ = 0; +} + +SrsSSRCInfo::~SrsSSRCInfo() +{ +} + +srs_error_t SrsSSRCInfo::encode(std::ostringstream& os) +{ + srs_error_t err = srs_success; + if (ssrc_ == 0) { + return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid ssrc"); + } + + os << "a=ssrc:" << ssrc_ << " cname:" << cname_ << kCRLF; + if (! msid_.empty()) { + os << "a=ssrc:" << ssrc_ << " msid:" << msid_; + if (! msid_tracker_.empty()) { + os << " " << msid_tracker_; + } + os << kCRLF; + } + if (! mslabel_.empty()) { + os << "a=ssrc:" << ssrc_ << " mslabel:" << mslabel_ << kCRLF; + } + if (! label_.empty()) { + os << "a=ssrc:" << ssrc_ << " label:" << label_ << kCRLF; + } + + return err; +} + +SrsMediaPayloadType::SrsMediaPayloadType(int payload_type) +{ + payload_type_ = payload_type; +} + +SrsMediaPayloadType::~SrsMediaPayloadType() +{ +} + +srs_error_t SrsMediaPayloadType::encode(std::ostringstream& os) +{ + srs_error_t err = srs_success; + + os << "a=rtpmap:" << payload_type_ << " " << encoding_name_ << "/" << clock_rate_; + if (! encoding_param_.empty()) { + os << "/" << encoding_param_; + } + os << kCRLF; + + for (std::vector::iterator iter = rtcp_fb_.begin(); iter != rtcp_fb_.end(); ++iter) { + os << "a=rtcp-fb:" << payload_type_ << " " << *iter << kCRLF; + } + + if (! format_specific_param_.empty()) { + os << "a=fmtp:" << payload_type_ << " " << format_specific_param_ << kCRLF; + } + + return err; +} + +SrsMediaDesc::SrsMediaDesc(const std::string& type) +{ + type_ = type; + + rtcp_mux_ = false; + + sendrecv_ = false; + recvonly_ = false; + sendonly_ = false; + inactive_ = false; +} + +SrsMediaDesc::~SrsMediaDesc() +{ +} + +SrsMediaPayloadType* SrsMediaDesc::find_media_with_payload_type(int payload_type) +{ + for (size_t i = 0; i < payload_types_.size(); ++i) { + if (payload_types_[i].payload_type_ == payload_type) { + return &payload_types_[i]; + } + } + + return NULL; +} + +vector SrsMediaDesc::find_media_with_encoding_name(const std::string& encoding_name) const +{ + std::vector payloads; + + for (size_t i = 0; i < payload_types_.size(); ++i) { + if (payload_types_[i].encoding_name_ == encoding_name) { + payloads.push_back(payload_types_[i]); + } + } + + return payloads; +} + +srs_error_t SrsMediaDesc::parse_line(const std::string& line) +{ + srs_error_t err = srs_success; + std::string content = line.substr(2); + + switch (line[0]) { + case 'a': { + return parse_attribute(content); + } + case 'c': { + // TODO: process c-line + break; + } + default: { + srs_trace("ignore media line=%s", line.c_str()); + break; + } + } + + return err; +} + +srs_error_t SrsMediaDesc::encode(std::ostringstream& os) +{ + srs_error_t err = srs_success; + + os << "m=" << type_ << " " << port_ << " " << protos_; + for (std::vector::iterator iter = payload_types_.begin(); iter != payload_types_.end(); ++iter) { + os << " " << iter->payload_type_; + } + + os << kCRLF; + + // TODO:nettype and address type + os << "c=IN IP4 0.0.0.0" << kCRLF; + + if ((err = session_info_.encode(os)) != srs_success) { + return srs_error_wrap(err, "encode session info failed"); + } + + os << "a=mid:" << mid_ << kCRLF; + if (! msid_.empty()) { + os << "a=msid:" << msid_; + + if (! msid_tracker_.empty()) { + os << " " << msid_tracker_; + } + + os << kCRLF; + } + + if (sendonly_) { + os << "a=sendonly" << kCRLF; + } + if (recvonly_) { + os << "a=recvonly" << kCRLF; + } + if (sendrecv_) { + os << "a=sendrecv" << kCRLF; + } + if (inactive_) { + os << "a=inactive" << kCRLF; + } + + if (rtcp_mux_) { + os << "a=rtcp-mux" << kCRLF; + } + + for (std::vector::iterator iter = payload_types_.begin(); iter != payload_types_.end(); ++iter) { + if ((err = iter->encode(os)) != srs_success) { + return srs_error_wrap(err, "encode media payload failed"); + } + } + + for (std::vector::iterator iter = ssrc_infos_.begin(); iter != ssrc_infos_.end(); ++iter) { + SrsSSRCInfo& ssrc_info = *iter; + + if ((err = ssrc_info.encode(os)) != srs_success) { + return srs_error_wrap(err, "encode ssrc failed"); + } + } + + // TODO: Candidate priority + for (std::vector::iterator iter = candidates_.begin(); iter != candidates_.end(); ++iter) { + os << "a=candidate:10 1 udp 2115783679 " << iter->ip_ << " " << iter->port_ <<" typ " << iter->type_ << " generation 0" << kCRLF; + } + + return err; +} + +srs_error_t SrsMediaDesc::parse_attribute(const std::string& content) +{ + srs_error_t err = srs_success; + std::string attribute = ""; + std::string value = ""; + size_t pos = content.find_first_of(":"); + + if (pos != std::string::npos) { + attribute = content.substr(0, pos); + value = content.substr(pos + 1); + } else { + attribute = content; + } + + if (attribute == "extmap") { + // TODO:We don't parse "extmap" currently. + return 0; + } else if (attribute == "rtpmap") { + return parse_attr_rtpmap(value); + } else if (attribute == "rtcp-fb") { + return parse_attr_rtcp_fb(value); + } else if (attribute == "fmtp") { + return parse_attr_fmtp(value); + } else if (attribute == "mid") { + return parse_attr_mid(value); + } else if (attribute == "msid") { + return parse_attr_msid(value); + } else if (attribute == "ssrc") { + return parse_attr_ssrc(value); + } else if (attribute == "ssrc-group") { + return parse_attr_ssrc_group(value); + } else if (attribute == "rtcp-mux") { + rtcp_mux_ = true; + } else if (attribute == "recvonly") { + recvonly_ = true; + } else if (attribute == "sendonly") { + sendonly_ = true; + } else if (attribute == "sendrecv") { + sendrecv_ = true; + } else if (attribute == "inactive") { + inactive_ = true; + } else { + return session_info_.parse_attribute(attribute, value); + } + + return err; +} + +srs_error_t SrsMediaDesc::parse_attr_rtpmap(const std::string& value) +{ + srs_error_t err = srs_success; + // @see: https://tools.ietf.org/html/rfc4566#page-25 + // a=rtpmap: / [/] + + std::istringstream is(value); + + int payload_type = 0; + FETCH(is, payload_type); + + SrsMediaPayloadType* payload = find_media_with_payload_type(payload_type); + if (payload == NULL) { + return srs_error_new(ERROR_RTC_SDP_DECODE, "can not find payload %d when pase rtpmap", payload_type); + } + + std::string word; + FETCH(is, word); + + std::vector vec = split_str(word, "/"); + if (vec.size() < 2) { + return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid rtpmap line=%s", value); + } + + payload->encoding_name_ = vec[0]; + payload->clock_rate_ = atoi(vec[1].c_str()); + + if (vec.size() == 3) { + payload->encoding_param_ = vec[2]; + } + + return err; +} + +srs_error_t SrsMediaDesc::parse_attr_rtcp_fb(const std::string& value) +{ + srs_error_t err = srs_success; + // @see: https://tools.ietf.org/html/rfc5104#section-7.1 + + std::istringstream is(value); + + int payload_type = 0; + FETCH(is, payload_type); + + SrsMediaPayloadType* payload = find_media_with_payload_type(payload_type); + if (payload == NULL) { + return srs_error_new(ERROR_RTC_SDP_DECODE, "can not find payload %d when pase rtcp-fb", payload_type); + } + + std::string rtcp_fb = is.str().substr(is.tellg()); + skip_first_spaces(rtcp_fb); + + payload->rtcp_fb_.push_back(rtcp_fb); + + return err; +} + +srs_error_t SrsMediaDesc::parse_attr_fmtp(const std::string& value) +{ + srs_error_t err = srs_success; + // @see: https://tools.ietf.org/html/rfc4566#page-30 + // a=fmtp: + + std::istringstream is(value); + + int payload_type = 0; + FETCH(is, payload_type); + + SrsMediaPayloadType* payload = find_media_with_payload_type(payload_type); + if (payload == NULL) { + return srs_error_new(ERROR_RTC_SDP_DECODE, "can not find payload %d when pase fmtp", payload_type); + } + + std::string word; + FETCH(is, word); + + payload->format_specific_param_ = word; + + return err; +} + +srs_error_t SrsMediaDesc::parse_attr_mid(const std::string& value) +{ + // @see: https://tools.ietf.org/html/rfc3388#section-3 + srs_error_t err = srs_success; + std::istringstream is(value); + // mid_ means m-line id + FETCH(is, mid_); + srs_trace("mid=%s", mid_.c_str()); + return err; +} + +srs_error_t SrsMediaDesc::parse_attr_msid(const std::string& value) +{ + // @see: https://tools.ietf.org/id/draft-ietf-mmusic-msid-08.html#rfc.section.2 + // TODO: msid and msid_tracker + srs_error_t err = srs_success; + std::istringstream is(value); + // msid_ means media stream id + FETCH(is, msid_); + is >> msid_tracker_; + return err; +} + +srs_error_t SrsMediaDesc::parse_attr_ssrc(const std::string& value) +{ + srs_error_t err = srs_success; + // @see: https://tools.ietf.org/html/rfc5576#section-4.1 + + std::istringstream is(value); + + uint32_t ssrc = 0; + FETCH(is, ssrc); + + std::string ssrc_attr = ""; + FETCH_WITH_DELIM(is, ssrc_attr, ':'); + skip_first_spaces(ssrc_attr); + + std::string ssrc_value = is.str().substr(is.tellg()); + skip_first_spaces(ssrc_value); + + SrsSSRCInfo& ssrc_info = fetch_or_create_ssrc_info(ssrc); + + if (ssrc_attr == "cname") { + // @see: https://tools.ietf.org/html/rfc5576#section-6.1 + ssrc_info.cname_ = ssrc_value; + ssrc_info.ssrc_ = ssrc; + } else if (ssrc_attr == "msid") { + std::vector vec = split_str(ssrc_value, " "); + if (vec.empty()) { + return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid ssrc line=%s", value); + } + + ssrc_info.msid_ = vec[0]; + if (vec.size() > 1) { + ssrc_info.msid_tracker_ = vec[1]; + } + } else if (ssrc_attr == "mslabel") { + ssrc_info.mslabel_ = ssrc_value; + } else if (ssrc_attr == "label") { + ssrc_info.label_ = ssrc_value; + } + + return err; +} + +srs_error_t SrsMediaDesc::parse_attr_ssrc_group(const std::string& value) +{ + srs_error_t err = srs_success; + // @see: https://tools.ietf.org/html/rfc5576#section-4.2 + // a=ssrc-group: ... + + std::istringstream is(value); + + std::string semantics; + FETCH(is, semantics); + + // TODO: ssrc group process + if (semantics == "FID") { + } + + return err; +} + +SrsSSRCInfo& SrsMediaDesc::fetch_or_create_ssrc_info(uint32_t ssrc) +{ + for (size_t i = 0; i < ssrc_infos_.size(); ++i) { + if (ssrc_infos_[i].ssrc_ == ssrc) { + return ssrc_infos_[i]; + } + } + + SrsSSRCInfo ssrc_info; + ssrc_info.ssrc_ = ssrc; + ssrc_infos_.push_back(ssrc_info); + + return ssrc_infos_.back(); +} + +SrsSdp::SrsSdp() +{ + in_media_session_ = false; + + start_time_ = 0; + end_time_ = 0; +} + +SrsSdp::~SrsSdp() +{ +} + +srs_error_t SrsSdp::parse(const std::string& sdp_str) +{ + srs_error_t err = srs_success; + + // All webrtc SrsSdp annotated example + // @see: https://tools.ietf.org/html/draft-ietf-rtcweb-SrsSdp-11 + // Sdp example + // session info + // v= + // o= + // s= + // t= + // media description + // m= + // a= + // ... + // media description + // m= + // a= + // ... + std::istringstream is(sdp_str); + std::string line; + while (getline(is, line)) { + srs_trace("%s", line.c_str()); + if (line.size() < 2 || line[1] != '=') { + return srs_error_new(ERROR_RTC_SDP_DECODE, "invalid sdp line=%s", line); + } + if (! line.empty() && line[line.size()-1] == '\r') { + line.erase(line.size()-1, 1); + } + + if ((err = parse_line(line)) != srs_success) { + return srs_error_wrap(err, "parse sdp line failed"); + } + } + + return err; +} + +srs_error_t SrsSdp::encode(std::ostringstream& os) +{ + srs_error_t err = srs_success; + + os << "v=" << version_ << kCRLF; + os << "o=" << username_ << " " << session_id_ << " " << session_version_ << " " << nettype_ << " " << addrtype_ << " " << unicast_address_ << kCRLF; + os << "s=" << session_name_ << kCRLF; + os << "t=" << start_time_ << " " << end_time_ << kCRLF; + // TODO: ice options + os << "a=ice-lite" << kCRLF; + + if (! groups_.empty()) { + os << "a=group:" << group_policy_; + for (std::vector::iterator iter = groups_.begin(); iter != groups_.end(); ++iter) { + os << " " << *iter; + } + os << kCRLF; + } + + os << "a=msid-semantic: " << msid_semantic_; + for (std::vector::iterator iter = msids_.begin(); iter != msids_.end(); ++iter) { + os << " " << *iter; + } + os << kCRLF; + + if ((err = session_info_.encode(os)) != srs_success) { + return srs_error_wrap(err, "encode session info failed"); + } + + for (std::vector::iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) { + if ((err = (*iter).encode(os)) != srs_success) { + return srs_error_wrap(err, "encode media failed"); + } + } + + return err; +} + +const SrsMediaDesc* SrsSdp::find_media_desc(const std::string& type) const +{ + for (std::vector::const_iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) { + if (iter->type_ == type) { + return &(*iter); + } + } + + return NULL; +} + +void SrsSdp::set_ice_ufrag(const std::string& ufrag) +{ + for (std::vector::iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) { + iter->session_info_.ice_ufrag_ = ufrag; + } +} + +void SrsSdp::set_ice_pwd(const std::string& pwd) +{ + for (std::vector::iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) { + iter->session_info_.ice_pwd_ = pwd; + } +} + +void SrsSdp::set_fingerprint_algo(const std::string& algo) +{ + for (std::vector::iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) { + iter->session_info_.fingerprint_algo_ = algo; + } +} + +void SrsSdp::set_fingerprint(const std::string& fingerprint) +{ + for (std::vector::iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) { + iter->session_info_.fingerprint_ = fingerprint; + } +} + +void SrsSdp::add_candidate(const std::string& ip, const int& port, const std::string& type) +{ + // @see: https://tools.ietf.org/id/draft-ietf-mmusic-ice-sip-sdp-14.html#rfc.section.5.1 + SrsCandidate candidate; + candidate.ip_ = ip; + candidate.port_ = port; + candidate.type_ = type; + + for (std::vector::iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) { + iter->candidates_.push_back(candidate); + } +} + +std::string SrsSdp::get_ice_ufrag() const +{ + // Becaues we use BUNDLE, so we can choose the first element. + for (std::vector::const_iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) { + return iter->session_info_.ice_ufrag_; + } + + return ""; +} + +std::string SrsSdp::get_ice_pwd() const +{ + // Becaues we use BUNDLE, so we can choose the first element. + for (std::vector::const_iterator iter = media_descs_.begin(); iter != media_descs_.end(); ++iter) { + return iter->session_info_.ice_pwd_; + } + + return ""; +} + +srs_error_t SrsSdp::parse_line(const std::string& line) +{ + srs_error_t err = srs_success; + + std::string content = line.substr(2); + + switch (line[0]) { + case 'o': { + return parse_origin(content); + } + case 'v': { + return parse_version(content); + } + case 's': { + return parse_session_name(content); + } + case 't': { + return parse_timing(content); + } + case 'a': { + if (in_media_session_) { + return media_descs_.back().parse_line(line); + } + return parse_attribute(content); + } + case 'm': { + return parse_media_description(content); + } + case 'c': { + // TODO: process c-line + break; + } + default: { + srs_trace("ignore sdp line=%s", line.c_str()); + break; + } + } + + return err; +} + +srs_error_t SrsSdp::parse_origin(const std::string& content) +{ + srs_error_t err = srs_success; + + // @see: https://tools.ietf.org/html/rfc4566#section-5.2 + // o= + // eg. o=- 9164462281920464688 2 IN IP4 127.0.0.1 + std::istringstream is(content); + + FETCH(is, username_); + FETCH(is, session_id_); + FETCH(is, session_version_); + FETCH(is, nettype_); + FETCH(is, addrtype_); + FETCH(is, unicast_address_); + + return err; +} + +srs_error_t SrsSdp::parse_version(const std::string& content) +{ + srs_error_t err = srs_success; + // @see: https://tools.ietf.org/html/rfc4566#section-5.1 + + std::istringstream is(content); + + FETCH(is, version_); + + return err; +} + +srs_error_t SrsSdp::parse_session_name(const std::string& content) +{ + srs_error_t err = srs_success; + // @see: https://tools.ietf.org/html/rfc4566#section-5.3 + // s= + + session_name_ = content; + + return err; +} + +srs_error_t SrsSdp::parse_timing(const std::string& content) +{ + srs_error_t err = srs_success; + // @see: https://tools.ietf.org/html/rfc4566#section-5.9 + // t= + + std::istringstream is(content); + + FETCH(is, start_time_); + FETCH(is, end_time_); + + return err; +} + +srs_error_t SrsSdp::parse_attribute(const std::string& content) +{ + srs_error_t err = srs_success; + // @see: https://tools.ietf.org/html/rfc4566#section-5.13 + // a= + // a=: + + std::string attribute = ""; + std::string value = ""; + size_t pos = content.find_first_of(":"); + + if (pos != std::string::npos) { + attribute = content.substr(0, pos); + value = content.substr(pos + 1); + } + + if (attribute == "group") { + return parse_attr_group(value); + } else if (attribute == "msid-semantic") { + std::istringstream is(value); + FETCH(is, msid_semantic_); + + std::string msid; + while (is >> msid) { + msids_.push_back(msid); + } + } else { + return session_info_.parse_attribute(attribute, value); + } + + return err; +} + +srs_error_t SrsSdp::parse_attr_group(const std::string& value) +{ + srs_error_t err = srs_success; + // @see: https://tools.ietf.org/html/rfc5888#section-5 + + std::istringstream is(value); + + FETCH(is, group_policy_); + + std::string word; + while (is >> word) { + groups_.push_back(word); + } + + return err; +} + +srs_error_t SrsSdp::parse_media_description(const std::string& content) +{ + srs_error_t err = srs_success; + + // @see: https://tools.ietf.org/html/rfc4566#section-5.14 + // m= ... + // m= / ... + std::istringstream is(content); + + std::string media; + FETCH(is, media); + + int port; + FETCH(is, port); + + std::string proto; + FETCH(is, proto); + + media_descs_.push_back(SrsMediaDesc(media)); + media_descs_.back().protos_ = proto; + media_descs_.back().port_ = port; + + int fmt; + while (is >> fmt) { + media_descs_.back().payload_types_.push_back(SrsMediaPayloadType(fmt)); + } + + if (! in_media_session_) { + in_media_session_ = true; + } + + return err; +} diff --git a/trunk/src/app/srs_app_sdp.hpp b/trunk/src/app/srs_app_sdp.hpp new file mode 100644 index 000000000..3aeaea32c --- /dev/null +++ b/trunk/src/app/srs_app_sdp.hpp @@ -0,0 +1,219 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2013-2020 Winlin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef SRS_APP_SDP_HPP +#define SRS_APP_SDP_HPP + +#include +#include + +#include + +#include +#include + +class SrsSessionInfo +{ +public: + SrsSessionInfo(); + ~SrsSessionInfo(); + + srs_error_t parse_attribute(const std::string& attribute, const std::string& value); + srs_error_t encode(std::ostringstream& os); + + bool operator=(const SrsSessionInfo& rhs); +public: + std::string ice_ufrag_; + std::string ice_pwd_; + std::string ice_options_; + std::string fingerprint_algo_; + std::string fingerprint_; + std::string setup_; +}; + +class SrsSSRCInfo +{ +public: + SrsSSRCInfo(); + virtual ~SrsSSRCInfo(); +public: + srs_error_t encode(std::ostringstream& os); +public: + uint32_t ssrc_; + std::string cname_; + std::string msid_; + std::string msid_tracker_; + std::string mslabel_; + std::string label_; +}; + +class SrsSSRCGroup +{ +}; + +struct H264SpecificParam +{ + int profile_level_id; + int packetization_mode; + int level_asymmerty_allow; +}; + +extern srs_error_t parse_h264_fmtp(const std::string& fmtp, H264SpecificParam& h264_param); + +class SrsMediaPayloadType +{ +public: + SrsMediaPayloadType(int payload_type); + ~SrsMediaPayloadType(); + + srs_error_t encode(std::ostringstream& os); +public: + int payload_type_; + + std::string encoding_name_; + int clock_rate_; + std::string encoding_param_; + + std::vector rtcp_fb_; + std::string format_specific_param_; +}; + +struct SrsCandidate +{ + std::string ip_; + int port_; + std::string type_; +}; + +class SrsMediaDesc +{ +public: + SrsMediaDesc(const std::string& type); + ~SrsMediaDesc(); +public: + srs_error_t parse_line(const std::string& line); + srs_error_t encode(std::ostringstream& os); + SrsMediaPayloadType* find_media_with_payload_type(int payload_type); + std::vector find_media_with_encoding_name(const std::string& encoding_name) const; + + bool is_audio() const { return type_ == "audio"; } + bool is_video() const { return type_ == "video"; } +private: + srs_error_t parse_attribute(const std::string& content); + srs_error_t parse_attr_rtpmap(const std::string& value); + srs_error_t parse_attr_rtcp_fb(const std::string& value); + srs_error_t parse_attr_fmtp(const std::string& value); + srs_error_t parse_attr_mid(const std::string& value); + srs_error_t parse_attr_msid(const std::string& value); + srs_error_t parse_attr_ssrc(const std::string& value); + srs_error_t parse_attr_ssrc_group(const std::string& value); +private: + SrsSSRCInfo& fetch_or_create_ssrc_info(uint32_t ssrc); + +public: + SrsSessionInfo session_info_; + std::string type_; + int port_; + + bool rtcp_mux_; + + bool sendonly_; + bool recvonly_; + bool sendrecv_; + bool inactive_; + + std::string mid_; + std::string msid_; + std::string msid_tracker_; + std::string protos_; + std::vector payload_types_; + + std::vector candidates_; + std::vector ssrc_groups_; + std::vector ssrc_infos_; +}; + +class SrsSdp +{ +public: + SrsSdp(); + ~SrsSdp(); +public: + srs_error_t parse(const std::string& sdp_str); + srs_error_t encode(std::ostringstream& os); +public: +public: + const SrsMediaDesc* find_media_desc(const std::string& type) const; +public: + void set_ice_ufrag(const std::string& ufrag); + void set_ice_pwd(const std::string& pwd); + void set_fingerprint_algo(const std::string& algo); + void set_fingerprint(const std::string& fingerprint); + void add_candidate(const std::string& ip, const int& port, const std::string& type); + + std::string get_ice_ufrag() const; + std::string get_ice_pwd() const; +private: + srs_error_t parse_line(const std::string& line); +private: + srs_error_t parse_origin(const std::string& content); + srs_error_t parse_version(const std::string& content); + srs_error_t parse_session_name(const std::string& content); + srs_error_t parse_timing(const std::string& content); + srs_error_t parse_attribute(const std::string& content); + srs_error_t parse_media_description(const std::string& content); + srs_error_t parse_attr_group(const std::string& content); +private: + bool in_media_session_; +public: + // version + std::string version_; + + // origin + std::string username_; + std::string session_id_; + std::string session_version_; + std::string nettype_; + std::string addrtype_; + std::string unicast_address_; + + // session_name + std::string session_name_; + + // timing + int64_t start_time_; + int64_t end_time_; + + SrsSessionInfo session_info_; + + std::vector groups_; + std::string group_policy_; + + std::string msid_semantic_; + std::vector msids_; + + // m-line, media sessions + std::vector media_descs_; +}; + +#endif diff --git a/trunk/src/kernel/srs_kernel_error.hpp b/trunk/src/kernel/srs_kernel_error.hpp index 5997c9cdd..103c5e2ff 100644 --- a/trunk/src/kernel/srs_kernel_error.hpp +++ b/trunk/src/kernel/srs_kernel_error.hpp @@ -346,6 +346,7 @@ #define ERROR_RTC_SRTP_UNPROTECT 5015 #define ERROR_RTC_RTCP_CHECK 5016 #define ERROR_RTC_SOURCE_CHECK 5017 +#define ERROR_RTC_SDP_EXCHANGE 5018 /////////////////////////////////////////////////////// // HTTP API error. diff --git a/trunk/src/kernel/srs_kernel_rtp.cpp b/trunk/src/kernel/srs_kernel_rtp.cpp index 414b2aad1..d43e19aea 100644 --- a/trunk/src/kernel/srs_kernel_rtp.cpp +++ b/trunk/src/kernel/srs_kernel_rtp.cpp @@ -126,3 +126,30 @@ srs_error_t SrsRtpSharedPacket::set_marker(bool marker) return err; } + +srs_error_t SrsRtpSharedPacket::set_ssrc(uint32_t ssrc) +{ + srs_error_t err = srs_success; + + if (payload_ptr == NULL || payload_ptr->payload == NULL || payload_ptr->size < 12) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "rtp payload incorrect"); + } + + SrsBuffer stream(payload_ptr->payload + 8, 4); + stream.write_4bytes(ssrc); + + return err; +} + +srs_error_t SrsRtpSharedPacket::set_payload_type(uint8_t pt) +{ + srs_error_t err = srs_success; + + if (payload_ptr == NULL || payload_ptr->payload == NULL || payload_ptr->size < 2) { + return srs_error_new(ERROR_RTC_RTP_MUXER, "rtp payload incorrect"); + } + + payload_ptr->payload[1] = (payload_ptr->payload[1] & 0x80) | pt; + + return err; +} diff --git a/trunk/src/kernel/srs_kernel_rtp.hpp b/trunk/src/kernel/srs_kernel_rtp.hpp index 1de2269f3..f5720af49 100644 --- a/trunk/src/kernel/srs_kernel_rtp.hpp +++ b/trunk/src/kernel/srs_kernel_rtp.hpp @@ -62,6 +62,8 @@ public: // interface to modify rtp header public: srs_error_t set_marker(bool marker); + srs_error_t set_ssrc(uint32_t ssrc); + srs_error_t set_payload_type(uint8_t pt); }; #endif