// // Created by xzl on 2021/3/27. // #ifndef ZLMEDIAKIT_SDP_H #define ZLMEDIAKIT_SDP_H #include #include #include "assert.h" #include "Extension/Frame.h" using namespace std; using namespace mediakit; #define CHECK(exp) Assert_Throw(!(exp), #exp, __FUNCTION__, __FILE__, __LINE__); //https://datatracker.ietf.org/doc/rfc4566/?include_text=1 //https://blog.csdn.net/aggresss/article/details/109850434 //https://aggresss.blog.csdn.net/article/details/106436703 //Session description // v= (protocol version) // o= (originator and session identifier) // s= (session name) // i=* (session information) // u=* (URI of description) // e=* (email address) // p=* (phone number) // c=* (connection information -- not required if included in // all media) // b=* (zero or more bandwidth information lines) // One or more time descriptions ("t=" and "r=" lines; see below) // z=* (time zone adjustments) // k=* (encryption key) // a=* (zero or more session attribute lines) // Zero or more media descriptions // // Time description // t= (time the session is active) // r=* (zero or more repeat times) // // Media description, if present // m= (media name and transport address) // i=* (media title) // c=* (connection information -- optional if included at // session level) // b=* (zero or more bandwidth information lines) // k=* (encryption key) // a=* (zero or more media attribute lines) enum class RtpDirection { invalid = -1, //只发送 sendonly, //只接收 recvonly, //同时发送接收 sendrecv, //禁止发送数据 inactive }; enum class DtlsRole { invalid = -1, //客户端 active, //服务端 passive, //既可作做客户端也可以做服务端 actpass, }; enum class SdpType { invalid = -1, offer, answer }; DtlsRole getDtlsRole(const string &str); const char* getDtlsRoleString(DtlsRole role); RtpDirection getRtpDirection(const string &str); const char* getRtpDirectionString(RtpDirection val); class SdpItem { public: using Ptr = std::shared_ptr; virtual ~SdpItem() = default; virtual void parse(const string &str) { value = str; } virtual string toString() const { return value; } virtual const char* getKey() const = 0; protected: mutable string value; }; template class SdpString : public SdpItem{ public: SdpString() = default; SdpString(string val) {value = std::move(val);} // *=* const char* getKey() const override { static string key(1, KEY); return key.data();} }; class SdpCommon : public SdpItem { public: string key; SdpCommon(string key) { this->key = std::move(key); } SdpCommon(string key, string val) { this->key = std::move(key); this->value = std::move(val); } const char* getKey() const override { return key.data();} }; class SdpTime : public SdpItem{ public: //5.9. Timing ("t=") // t= uint64_t start {0}; uint64_t stop {0}; void parse(const string &str) override; string toString() const override; const char* getKey() const override { return "t";} }; class SdpOrigin : public SdpItem{ public: // 5.2. Origin ("o=") // o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5 // o= string username {"-"}; string session_id; string session_version; string nettype {"IN"}; string addrtype {"IP4"}; string address {"0.0.0.0"}; void parse(const string &str) override; string toString() const override; const char* getKey() const override { return "o";} bool empty() const { return username.empty() || session_id.empty() || session_version.empty() || nettype.empty() || addrtype.empty() || address.empty(); } }; class SdpConnection : public SdpItem { public: // 5.7. Connection Data ("c=") // c=IN IP4 224.2.17.12/127 // c= string nettype {"IN"}; string addrtype {"IP4"}; string address {"0.0.0.0"}; void parse(const string &str) override; string toString() const override; const char* getKey() const override { return "c";} bool empty() const {return address.empty();} }; class SdpBandwidth : public SdpItem { public: //5.8. Bandwidth ("b=") //b=: //AS、CT string bwtype {"AS"}; uint32_t bandwidth {0}; void parse(const string &str) override; string toString() const override; const char* getKey() const override { return "b";} bool empty() const {return bandwidth == 0;} }; class SdpMedia : public SdpItem { public: // 5.14. Media Descriptions ("m=") // m= ... TrackType type; uint16_t port; //RTP/AVP:应用场景为视频/音频的 RTP 协议。参考 RFC 3551 //RTP/SAVP:应用场景为视频/音频的 SRTP 协议。参考 RFC 3711 //RTP/AVPF: 应用场景为视频/音频的 RTP 协议,支持 RTCP-based Feedback。参考 RFC 4585 //RTP/SAVPF: 应用场景为视频/音频的 SRTP 协议,支持 RTCP-based Feedback。参考 RFC 5124 string proto; vector fmts; void parse(const string &str) override; string toString() const override; const char* getKey() const override { return "m";} }; class SdpAttr : public SdpItem{ public: using Ptr = std::shared_ptr; //5.13. Attributes ("a=") //a= //a=: SdpItem::Ptr detail; void parse(const string &str) override; string toString() const override; const char* getKey() const override { return "a";} }; class SdpAttrGroup : public SdpItem{ public: //a=group:BUNDLE line with all the 'mid' identifiers part of the // BUNDLE group is included at the session-level. //a=group:LS session level attribute MUST be included wth the 'mid' // identifiers that are part of the same lip sync group. string type {"BUNDLE"}; vector mids; void parse(const string &str) override ; string toString() const override ; const char* getKey() const override { return "group";} }; class SdpAttrMsidSemantic : public SdpItem { public: //https://tools.ietf.org/html/draft-alvestrand-rtcweb-msid-02#section-3 //3. The Msid-Semantic Attribute // // In order to fully reproduce the semantics of the SDP and SSRC // grouping frameworks, a session-level attribute is defined for // signalling the semantics associated with an msid grouping. // // This OPTIONAL attribute gives the message ID and its group semantic. // a=msid-semantic: examplefoo LS // // // The ABNF of msid-semantic is: // // msid-semantic-attr = "msid-semantic:" " " msid token // token = // // The semantic field may hold values from the IANA registries // "Semantics for the "ssrc-group" SDP Attribute" and "Semantics for the // "group" SDP Attribute". //a=msid-semantic: WMS 616cfbb1-33a3-4d8c-8275-a199d6005549 string msid{"WMS"}; string token; void parse(const string &str) override; string toString() const override; const char* getKey() const override { return "msid-semantic";} bool empty() const { return msid.empty(); } }; class SdpAttrRtcp : public SdpItem { public: // a=rtcp:9 IN IP4 0.0.0.0 uint16_t port{0}; string nettype {"IN"}; string addrtype {"IP4"}; string address {"0.0.0.0"}; void parse(const string &str) override;; string toString() const override; const char* getKey() const override { return "rtcp";} bool empty() const { return address.empty() || !port; } }; class SdpAttrIceUfrag : public SdpItem { public: SdpAttrIceUfrag() = default; SdpAttrIceUfrag(string str) {value = std::move(str);} //a=ice-ufrag:sXJ3 const char* getKey() const override { return "ice-ufrag";} }; class SdpAttrIcePwd : public SdpItem { public: SdpAttrIcePwd() = default; SdpAttrIcePwd(string str) {value = std::move(str);} //a=ice-pwd:yEclOTrLg1gEubBFefOqtmyV const char* getKey() const override { return "ice-pwd";} }; class SdpAttrIceOption : public SdpItem { public: //a=ice-options:trickle bool trickle{false}; bool renomination{false}; void parse(const string &str) override; string toString() const override; const char* getKey() const override { return "ice-options";} }; class SdpAttrFingerprint : public SdpItem { public: //a=fingerprint:sha-256 22:14:B5:AF:66:12:C7:C7:8D:EF:4B:DE:40:25:ED:5D:8F:17:54:DD:88:33:C0:13:2E:FD:1A:FA:7E:7A:1B:79 string algorithm; string hash; void parse(const string &str) override; string toString() const override; const char* getKey() const override { return "fingerprint";} bool empty() const { return algorithm.empty() || hash.empty(); } }; class SdpAttrSetup : public SdpItem { public: //a=setup:actpass SdpAttrSetup() = default; SdpAttrSetup(DtlsRole r) { role = r; } DtlsRole role{DtlsRole::actpass}; void parse(const string &str) override; string toString() const override; const char* getKey() const override { return "setup";} }; class SdpAttrMid : public SdpItem { public: SdpAttrMid() = default; SdpAttrMid(string val) { value = std::move(val); } //a=mid:audio const char* getKey() const override { return "mid";} }; class SdpAttrExtmap : public SdpItem { public: //https://aggresss.blog.csdn.net/article/details/106436703 //a=extmap:1[/sendonly] urn:ietf:params:rtp-hdrext:ssrc-audio-level uint32_t index; RtpDirection direction{RtpDirection::invalid}; string ext; void parse(const string &str) override; string toString() const override; const char* getKey() const override { return "extmap";} }; class SdpAttrRtpMap : public SdpItem { public: //a=rtpmap:111 opus/48000/2 uint8_t pt; string codec; uint32_t sample_rate; uint32_t channel {0}; void parse(const string &str) override; string toString() const override; const char* getKey() const override { return "rtpmap";} }; class SdpAttrRtcpFb : public SdpItem { public: //a=rtcp-fb:98 nack pli //a=rtcp-fb:120 nack 支持 nack 重传,nack (Negative-Acknowledgment) 。 //a=rtcp-fb:120 nack pli 支持 nack 关键帧重传,PLI (Picture Loss Indication) 。 //a=rtcp-fb:120 ccm fir 支持编码层关键帧请求,CCM (Codec Control Message),FIR (Full Intra Request ),通常与 nack pli 有同样的效果,但是 nack pli 是用于重传时的关键帧请求。 //a=rtcp-fb:120 goog-remb 支持 REMB (Receiver Estimated Maximum Bitrate) 。 //a=rtcp-fb:120 transport-cc 支持 TCC (Transport Congest Control) 。 uint8_t pt; string rtcp_type; void parse(const string &str) override; string toString() const override; const char* getKey() const override { return "rtcp-fb";} }; class SdpAttrFmtp : public SdpItem { public: //fmtp:96 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f uint8_t pt; vector > arr; void parse(const string &str) override; string toString() const override; const char* getKey() const override { return "fmtp";} }; class SdpAttrSSRC : public SdpItem { public: //a=ssrc:3245185839 cname:Cx4i/VTR51etgjT7 //a=ssrc:3245185839 msid:cb373bff-0fea-4edb-bc39-e49bb8e8e3b9 0cf7e597-36a2-4480-9796-69bf0955eef5 //a=ssrc:3245185839 mslabel:cb373bff-0fea-4edb-bc39-e49bb8e8e3b9 //a=ssrc:3245185839 label:0cf7e597-36a2-4480-9796-69bf0955eef5 //a=ssrc: //a=ssrc: : //cname 是必须的,msid/mslabel/label 这三个属性都是 WebRTC 自创的,或者说 Google 自创的,可以参考 https://tools.ietf.org/html/draft-ietf-mmusic-msid-17, // 理解它们三者的关系需要先了解三个概念:RTP stream / MediaStreamTrack / MediaStream : //一个 a=ssrc 代表一个 RTP stream ; //一个 MediaStreamTrack 通常包含一个或多个 RTP stream,例如一个视频 MediaStreamTrack 中通常包含两个 RTP stream,一个用于常规传输,一个用于 nack 重传; //一个 MediaStream 通常包含一个或多个 MediaStreamTrack ,例如 simulcast 场景下,一个 MediaStream 通常会包含三个不同编码质量的 MediaStreamTrack ; //这种标记方式并不被 Firefox 认可,在 Firefox 生成的 SDP 中一个 a=ssrc 通常只有一行,例如: //a=ssrc:3245185839 cname:Cx4i/VTR51etgjT7 uint32_t ssrc; string attribute; string attribute_value; void parse(const string &str) override; string toString() const override; const char* getKey() const override { return "ssrc";} }; class SdpAttrSSRCGroup : public SdpItem { public: //a=ssrc-group 定义参考 RFC 5576(https://tools.ietf.org/html/rfc5576) ,用于描述多个 ssrc 之间的关联,常见的有两种: //a=ssrc-group:FID 2430709021 3715850271 // FID (Flow Identification) 最初用在 FEC 的关联中,WebRTC 中通常用于关联一组常规 RTP stream 和 重传 RTP stream 。 //a=ssrc-group:SIM 360918977 360918978 360918980 // 在 Chrome 独有的 SDP munging 风格的 simulcast 中使用,将三组编码质量由低到高的 MediaStreamTrack 关联在一起。 string type{"FID"}; union { struct { uint32_t rtp_ssrc; uint32_t rtx_ssrc; } fid; struct { uint32_t rtp_ssrc_low; uint32_t rtp_ssrc_mid; uint32_t rtp_ssrc_high; } sim; } u; bool isFID() const { return type == "FID"; } bool isSIM() const { return type == "SIM"; } void parse(const string &str) override; string toString() const override; const char* getKey() const override { return "ssrc-group";} }; class SdpAttrSctpMap : public SdpItem { public: //https://tools.ietf.org/html/draft-ietf-mmusic-sctp-sdp-05 //a=sctpmap:5000 webrtc-datachannel 1024 //a=sctpmap: sctpmap-number media-subtypes [streams] uint16_t port; string subtypes; uint32_t streams; void parse(const string &str) override; string toString() const override; const char* getKey() const override { return "sctpmap";} }; class SdpAttrCandidate : public SdpItem { public: using Ptr = std::shared_ptr; //https://tools.ietf.org/html/rfc5245 //15.1. "candidate" Attribute //a=candidate:4 1 udp 2 192.168.1.7 58107 typ host //a=candidate:
typ string foundation; //传输媒体的类型,1代表RTP;2代表 RTCP。 uint32_t component; string transport {"udp"}; uint32_t priority; string address; uint16_t port; string type; vector > arr; void parse(const string &str) override; string toString() const override; const char* getKey() const override { return "candidate";} }; class SdpAttrMsid : public SdpItem{ public: const char* getKey() const override { return "msid";} }; class SdpAttrExtmapAllowMixed : public SdpItem{ public: const char* getKey() const override { return "extmap-allow-mixed";} }; class SdpAttrSimulcast : public SdpItem{ public: //todo const char* getKey() const override { return "simulcast";} }; class SdpAttrRid : public SdpItem{ public: //todo const char* getKey() const override { return "rid";} }; class RtcSdpBase { public: vector items; public: virtual ~RtcSdpBase() = default; virtual string toString() const; int getVersion() const; SdpOrigin getOrigin() const; string getSessionName() const; string getSessionInfo() const; SdpTime getSessionTime() const; SdpConnection getConnection() const; SdpBandwidth getBandwidth() const; string getUri() const; string getEmail() const; string getPhone() const; string getTimeZone() const; string getEncryptKey() const; string getRepeatTimes() const; RtpDirection getDirection() const; template cls getItemClass(char key, const char *attr_key = nullptr) const{ auto item = dynamic_pointer_cast(getItem(key, attr_key)); if (!item) { return cls(); } return *item; } string getStringItem(char key, const char *attr_key = nullptr) const{ auto item = getItem(key, attr_key); if (!item) { return ""; } return item->toString(); } SdpItem::Ptr getItem(char key, const char *attr_key = nullptr) const; template vector getAllItem(char key_c, const char *attr_key = nullptr) const { vector ret; for (auto item : items) { string key(1, key_c); if (strcasecmp(item->getKey(), key.data()) == 0) { if (!attr_key) { auto c = dynamic_pointer_cast(item); if (c) { ret.emplace_back(*c); } } else { auto attr = dynamic_pointer_cast(item); if (attr && !strcasecmp(attr->detail->getKey(), attr_key)) { auto c = dynamic_pointer_cast(attr->detail); if (c) { ret.emplace_back(*c); } } } } } return ret; } }; class RtcSessionSdp : public RtcSdpBase{ public: using Ptr = std::shared_ptr; vector medias; void parse(const string &str); string toString() const override; }; ////////////////////////////////////////////////////////////////// //ssrc相关信息 class RtcSSRC{ public: uint32_t ssrc {0}; string cname; string msid; string mslabel; string label; bool empty() const {return ssrc == 0 && cname.empty();} }; //rtc传输编码方案 class RtcCodecPlan{ public: using Ptr = shared_ptr; uint8_t pt; string codec; uint32_t sample_rate; //音频时有效 uint32_t channel = 0; //rtcp反馈 vector rtcp_fb; vector > fmtp; string getFmtp(const char *key) const; }; //rtc 媒体描述 class RtcMedia{ public: TrackType type{TrackType::TrackInvalid}; string mid; uint16_t port{0}; SdpConnection addr; string proto; RtpDirection direction{RtpDirection::invalid}; vector plan; //////// rtp //////// RtcSSRC rtp_ssrc; RtcSSRC rtx_ssrc; //////// simulcast //////// RtcSSRC rtp_ssrc_low; RtcSSRC rtp_ssrc_mid; RtcSSRC rtp_ssrc_high; //////// rtcp //////// bool rtcp_mux{false}; bool rtcp_rsize{false}; SdpAttrRtcp rtcp_addr; //////// ice //////// bool ice_trickle{false}; bool ice_lite{false}; bool ice_renomination{false}; string ice_ufrag; string ice_pwd; std::vector candidate; //////// dtls //////// DtlsRole role{DtlsRole::invalid}; SdpAttrFingerprint fingerprint; //////// extmap //////// vector extmap; //////// sctp //////////// SdpAttrSctpMap sctpmap; uint32_t sctp_port{0}; void checkValid() const; const RtcCodecPlan *getPlan(uint8_t pt) const; const RtcCodecPlan *getPlan(const char *codec) const; const RtcCodecPlan *getRelatedRtxPlan(uint8_t pt) const; }; class RtcSession{ public: using Ptr = std::shared_ptr; uint32_t version; SdpOrigin origin; string session_name; string session_info; SdpTime time; SdpConnection connection; SdpBandwidth bandwidth; SdpAttrMsidSemantic msid_semantic; vector media; SdpAttrGroup group; void loadFrom(const string &sdp, bool check = true); void checkValid() const; string toString() const; string toRtspSdp() const; const RtcMedia *getMedia(TrackType type) const; private: RtcSessionSdp::Ptr toRtcSessionSdp() const; }; class RtcConfigure { public: using Ptr = std::shared_ptr; class RtcTrackConfigure { public: bool enable; bool rtcp_mux; bool rtcp_rsize; bool group_bundle; bool support_rtx; bool support_red; bool support_ulpfec; bool ice_lite; bool ice_trickle; bool ice_renomination; string ice_ufrag; string ice_pwd; RtpDirection direction{RtpDirection::invalid}; SdpAttrFingerprint fingerprint; vector rtcp_fb; vector preferred_codec; vector extmap; vector candidate; void setDefaultSetting(TrackType type); }; RtcTrackConfigure video; RtcTrackConfigure audio; RtcTrackConfigure application; void setDefaultSetting(string ice_ufrag, string ice_pwd, RtpDirection direction, const SdpAttrFingerprint &fingerprint); void addCandidate(const SdpAttrCandidate &candidate, TrackType type = TrackInvalid); shared_ptr createAnswer(const RtcSession &offer); void setPlayRtspInfo(const string &sdp); private: void matchMedia(shared_ptr &ret, TrackType type, const vector &medias, const RtcTrackConfigure &configure); bool onMatchCodecPlan(const RtcCodecPlan &plan, CodecId codec); private: RtcCodecPlan::Ptr _rtsp_video_plan; RtcCodecPlan::Ptr _rtsp_audio_plan; }; #endif //ZLMEDIAKIT_SDP_H