2021-04-09 20:42:36 +08:00
/*
2023-12-09 16:23:51 +08:00
* Copyright ( c ) 2016 - present The ZLMediaKit project authors . All Rights Reserved .
2021-04-09 20:42:36 +08:00
*
2023-12-09 16:23:51 +08:00
* This file is part of ZLMediaKit ( https : //github.com/ZLMediaKit/ZLMediaKit).
2021-04-09 20:42:36 +08:00
*
2023-12-09 16:23:51 +08:00
* Use of this source code is governed by MIT - like license that can be found in the
2021-04-09 20:42:36 +08:00
* LICENSE file in the root of the source tree . All contributing project authors
* may be found in the AUTHORS file in the root of the source tree .
*/
2021-03-27 18:33:20 +08:00
# ifndef ZLMEDIAKIT_SDP_H
# define ZLMEDIAKIT_SDP_H
2022-11-29 11:07:13 +08:00
# include <set>
# include <map>
2021-03-27 18:33:20 +08:00
# include <string>
2021-03-27 22:23:38 +08:00
# include <vector>
2021-05-07 14:02:03 +08:00
# include "RtpExt.h"
2021-03-31 18:32:43 +08:00
# include "assert.h"
2021-03-27 22:23:38 +08:00
# include "Extension/Frame.h"
2021-04-15 19:35:25 +08:00
# include "Common/Parser.h"
2021-03-27 18:33:20 +08:00
2022-09-18 21:03:05 +08:00
namespace mediakit {
2024-03-23 23:08:10 +08:00
// 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
2021-03-27 18:33:20 +08:00
//
2024-03-23 23:08:10 +08:00
// Time description
// t= (time the session is active)
// r=* (zero or more repeat times)
2021-03-27 18:33:20 +08:00
//
2024-03-23 23:08:10 +08:00
// 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)
2021-03-27 18:33:20 +08:00
2021-03-27 22:23:38 +08:00
enum class RtpDirection {
invalid = - 1 ,
2024-09-19 14:53:50 +08:00
// 只发送 [AUTO-TRANSLATED:d7e7fdb7]
// Send only
2021-03-27 22:23:38 +08:00
sendonly ,
2024-09-19 14:53:50 +08:00
// 只接收 [AUTO-TRANSLATED:f75ca789]
// Receive only
2021-03-28 17:32:53 +08:00
recvonly ,
2024-09-19 14:53:50 +08:00
// 同时发送接收 [AUTO-TRANSLATED:7f900ba1]
// Send and receive simultaneously
2021-03-27 22:23:38 +08:00
sendrecv ,
2024-09-19 14:53:50 +08:00
// 禁止发送数据 [AUTO-TRANSLATED:6045b47e]
// Prohibit sending data
2021-03-27 22:23:38 +08:00
inactive
} ;
enum class DtlsRole {
invalid = - 1 ,
2024-09-19 14:53:50 +08:00
// 客户端 [AUTO-TRANSLATED:915417a2]
// Client
2021-03-27 22:23:38 +08:00
active ,
2024-09-19 14:53:50 +08:00
// 服务端 [AUTO-TRANSLATED:03a80b18]
// Server
2021-03-27 22:23:38 +08:00
passive ,
2024-09-19 14:53:50 +08:00
// 既可作做客户端也可以做服务端 [AUTO-TRANSLATED:5ab1162e]
// Can be used as both client and server
2021-03-27 22:23:38 +08:00
actpass ,
} ;
2024-03-23 23:08:10 +08:00
enum class SdpType { invalid = - 1 , offer , answer } ;
2021-03-27 22:23:38 +08:00
2022-02-02 20:34:50 +08:00
DtlsRole getDtlsRole ( const std : : string & str ) ;
2024-03-23 23:08:10 +08:00
const char * getDtlsRoleString ( DtlsRole role ) ;
2022-02-02 20:34:50 +08:00
RtpDirection getRtpDirection ( const std : : string & str ) ;
2024-03-23 23:08:10 +08:00
const char * getRtpDirectionString ( RtpDirection val ) ;
2021-03-28 17:32:53 +08:00
2021-03-27 18:33:20 +08:00
class SdpItem {
public :
2021-03-28 09:49:34 +08:00
using Ptr = std : : shared_ptr < SdpItem > ;
2021-03-27 18:33:20 +08:00
virtual ~ SdpItem ( ) = default ;
2024-03-23 23:08:10 +08:00
virtual void parse ( const std : : string & str ) { value = str ; }
virtual std : : string toString ( ) const { return value ; }
virtual const char * getKey ( ) const = 0 ;
2021-03-28 17:32:53 +08:00
2024-03-23 23:08:10 +08:00
void reset ( ) { value . clear ( ) ; }
2021-04-27 01:16:01 +08:00
2021-03-28 17:32:53 +08:00
protected :
2022-02-02 20:34:50 +08:00
mutable std : : string value ;
2021-03-27 18:33:20 +08:00
} ;
2021-03-28 09:49:34 +08:00
template < char KEY >
2024-03-23 23:08:10 +08:00
class SdpString : public SdpItem {
2021-03-28 09:49:34 +08:00
public :
2021-03-30 13:09:02 +08:00
SdpString ( ) = default ;
2024-03-23 23:08:10 +08:00
SdpString ( std : : string val ) { value = std : : move ( val ) ; }
2021-03-28 09:49:34 +08:00
// *=*
2022-02-02 20:34:50 +08:00
const char * getKey ( ) const override { static std : : string key ( 1 , KEY ) ; return key . data ( ) ; }
2021-03-28 09:49:34 +08:00
} ;
class SdpCommon : public SdpItem {
public :
2022-02-02 20:34:50 +08:00
std : : string key ;
SdpCommon ( std : : string key ) { this - > key = std : : move ( key ) ; }
SdpCommon ( std : : string key , std : : string val ) {
2021-03-30 13:09:02 +08:00
this - > key = std : : move ( key ) ;
this - > value = std : : move ( val ) ;
}
2024-03-23 23:08:10 +08:00
const char * getKey ( ) const override { return key . data ( ) ; }
2021-03-28 09:49:34 +08:00
} ;
2024-03-23 23:08:10 +08:00
class SdpTime : public SdpItem {
2021-03-27 18:33:20 +08:00
public :
2024-03-23 23:08:10 +08:00
// 5.9. Timing ("t=")
// t=<start-time> <stop-time>
uint64_t start { 0 } ;
uint64_t stop { 0 } ;
2022-02-02 20:34:50 +08:00
void parse ( const std : : string & str ) override ;
std : : string toString ( ) const override ;
2024-03-23 23:08:10 +08:00
const char * getKey ( ) const override { return " t " ; }
2021-03-27 18:33:20 +08:00
} ;
2024-03-23 23:08:10 +08:00
class SdpOrigin : public SdpItem {
2021-03-27 18:33:20 +08:00
public :
2021-03-27 22:23:38 +08:00
// 5.2. Origin ("o=")
2021-03-27 18:33:20 +08:00
// o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5
// o=<username> <sess-id> <sess-version> <nettype> <addrtype> <unicast-address>
2024-03-23 23:08:10 +08:00
std : : string username { " - " } ;
2022-02-02 20:34:50 +08:00
std : : string session_id ;
std : : string session_version ;
2024-03-23 23:08:10 +08:00
std : : string nettype { " IN " } ;
std : : string addrtype { " IP4 " } ;
std : : string address { " 0.0.0.0 " } ;
2022-02-02 20:34:50 +08:00
void parse ( const std : : string & str ) override ;
std : : string toString ( ) const override ;
2024-03-23 23:08:10 +08:00
const char * getKey ( ) const override { return " o " ; }
2021-03-30 09:53:58 +08:00
bool empty ( ) const {
return username . empty ( ) | | session_id . empty ( ) | | session_version . empty ( )
| | nettype . empty ( ) | | addrtype . empty ( ) | | address . empty ( ) ;
}
2021-03-27 18:33:20 +08:00
} ;
class SdpConnection : public SdpItem {
public :
2021-03-27 22:23:38 +08:00
// 5.7. Connection Data ("c=")
2021-03-27 18:33:20 +08:00
// c=IN IP4 224.2.17.12/127
// c=<nettype> <addrtype> <connection-address>
2024-03-23 23:08:10 +08:00
std : : string nettype { " IN " } ;
std : : string addrtype { " IP4 " } ;
std : : string address { " 0.0.0.0 " } ;
2022-02-02 20:34:50 +08:00
void parse ( const std : : string & str ) override ;
std : : string toString ( ) const override ;
2024-03-23 23:08:10 +08:00
const char * getKey ( ) const override { return " c " ; }
bool empty ( ) const { return address . empty ( ) ; }
2021-03-27 22:23:38 +08:00
} ;
class SdpBandwidth : public SdpItem {
public :
2024-03-23 23:08:10 +08:00
// 5.8. Bandwidth ("b=")
// b=<bwtype>:<bandwidth>
2021-03-27 22:23:38 +08:00
2024-09-19 14:53:50 +08:00
// AS、CT [AUTO-TRANSLATED:65298206]
// AS, CT
2024-03-23 23:08:10 +08:00
std : : string bwtype { " AS " } ;
uint32_t bandwidth { 0 } ;
2021-03-27 22:23:38 +08:00
2022-02-02 20:34:50 +08:00
void parse ( const std : : string & str ) override ;
std : : string toString ( ) const override ;
2024-03-23 23:08:10 +08:00
const char * getKey ( ) const override { return " b " ; }
bool empty ( ) const { return bandwidth = = 0 ; }
2021-03-27 22:23:38 +08:00
} ;
class SdpMedia : public SdpItem {
public :
// 5.14. Media Descriptions ("m=")
// m=<media> <port> <proto> <fmt> ...
2022-09-18 21:03:05 +08:00
TrackType type ;
2021-03-27 22:23:38 +08:00
uint16_t port ;
2024-09-19 14:53:50 +08:00
// RTP/AVP: 应用场景为视频/音频的 RTP 协议。参考 RFC 3551 [AUTO-TRANSLATED:7a9d7e86]
// RTP/AVP: The application scenario is the RTP protocol for video/audio. Refer to RFC 3551
// RTP/SAVP: 应用场景为视频/音频的 SRTP 协议。参考 RFC 3711 [AUTO-TRANSLATED:7989a619]
// RTP/SAVP: The application scenario is the SRTP protocol for video/audio. Refer to RFC 3711
// RTP/AVPF: 应用场景为视频/音频的 RTP 协议,支持 RTCP-based Feedback。参考 RFC 4585 [AUTO-TRANSLATED:71241e80]
// RTP/AVPF: The application scenario is the RTP protocol for video/audio, supporting RTCP-based Feedback. Refer to RFC 4585
// RTP/SAVPF: 应用场景为视频/音频的 SRTP 协议,支持 RTCP-based Feedback。参考 RFC 5124 [AUTO-TRANSLATED:69015267]
// RTP/SAVPF: The application scenario is the SRTP protocol for video/audio, supporting RTCP-based Feedback. Refer to RFC 5124
2022-02-02 20:34:50 +08:00
std : : string proto ;
2022-04-03 17:12:23 +08:00
std : : vector < std : : string > fmts ;
2021-03-27 22:23:38 +08:00
2022-02-02 20:34:50 +08:00
void parse ( const std : : string & str ) override ;
std : : string toString ( ) const override ;
2024-03-23 23:08:10 +08:00
const char * getKey ( ) const override { return " m " ; }
2021-03-27 22:23:38 +08:00
} ;
2024-03-23 23:08:10 +08:00
class SdpAttr : public SdpItem {
2021-03-27 22:23:38 +08:00
public :
2021-03-28 09:49:34 +08:00
using Ptr = std : : shared_ptr < SdpAttr > ;
2024-03-23 23:08:10 +08:00
// 5.13. Attributes ("a=")
// a=<attribute>
// a=<attribute>:<value>
2021-03-28 09:49:34 +08:00
SdpItem : : Ptr detail ;
2022-02-02 20:34:50 +08:00
void parse ( const std : : string & str ) override ;
std : : string toString ( ) const override ;
2024-03-23 23:08:10 +08:00
const char * getKey ( ) const override { return " a " ; }
2021-03-27 22:23:38 +08:00
} ;
2024-03-23 23:08:10 +08:00
class SdpAttrGroup : public SdpItem {
2021-03-27 22:23:38 +08:00
public :
2024-03-23 23:08:10 +08:00
// 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.
std : : string type { " BUNDLE " } ;
2022-02-02 20:34:50 +08:00
std : : vector < std : : string > mids ;
2024-03-23 23:08:10 +08:00
void parse ( const std : : string & str ) override ;
std : : string toString ( ) const override ;
const char * getKey ( ) const override { return " group " ; }
2021-03-27 18:33:20 +08:00
} ;
2021-03-28 09:49:34 +08:00
class SdpAttrMsidSemantic : public SdpItem {
2021-03-27 18:33:20 +08:00
public :
2024-03-23 23:08:10 +08:00
// https://tools.ietf.org/html/draft-alvestrand-rtcweb-msid-02#section-3
// 3. The Msid-Semantic Attribute
2021-03-28 17:32:53 +08:00
//
2024-03-23 23:08:10 +08:00
// 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.
2021-03-28 17:32:53 +08:00
//
2024-03-23 23:08:10 +08:00
// This OPTIONAL attribute gives the message ID and its group semantic.
// a=msid-semantic: examplefoo LS
2021-03-28 17:32:53 +08:00
//
//
2024-03-23 23:08:10 +08:00
// The ABNF of msid-semantic is:
2021-03-28 17:32:53 +08:00
//
2024-03-23 23:08:10 +08:00
// msid-semantic-attr = "msid-semantic:" " " msid token
// token = <as defined in RFC 4566>
2021-03-28 17:32:53 +08:00
//
2024-03-23 23:08:10 +08:00
// 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
std : : string msid { " WMS " } ;
2022-02-02 20:34:50 +08:00
std : : string token ;
void parse ( const std : : string & str ) override ;
std : : string toString ( ) const override ;
2024-03-23 23:08:10 +08:00
const char * getKey ( ) const override { return " msid-semantic " ; }
bool empty ( ) const { return msid . empty ( ) ; }
2021-03-28 09:49:34 +08:00
} ;
2021-03-27 18:33:20 +08:00
2021-03-28 09:49:34 +08:00
class SdpAttrRtcp : public SdpItem {
public :
2021-03-28 17:32:53 +08:00
// a=rtcp:9 IN IP4 0.0.0.0
2024-03-23 23:08:10 +08:00
uint16_t port { 0 } ;
std : : string nettype { " IN " } ;
std : : string addrtype { " IP4 " } ;
std : : string address { " 0.0.0.0 " } ;
void parse ( const std : : string & str ) override ;
;
2022-02-02 20:34:50 +08:00
std : : string toString ( ) const override ;
2024-03-23 23:08:10 +08:00
const char * getKey ( ) const override { return " rtcp " ; }
bool empty ( ) const { return address . empty ( ) | | ! port ; }
2021-03-28 09:49:34 +08:00
} ;
class SdpAttrIceUfrag : public SdpItem {
public :
2021-03-30 13:09:02 +08:00
SdpAttrIceUfrag ( ) = default ;
2024-03-23 23:08:10 +08:00
SdpAttrIceUfrag ( std : : string str ) { value = std : : move ( str ) ; }
// a=ice-ufrag:sXJ3
const char * getKey ( ) const override { return " ice-ufrag " ; }
2021-03-28 09:49:34 +08:00
} ;
class SdpAttrIcePwd : public SdpItem {
public :
2021-03-30 13:09:02 +08:00
SdpAttrIcePwd ( ) = default ;
2024-03-23 23:08:10 +08:00
SdpAttrIcePwd ( std : : string str ) { value = std : : move ( str ) ; }
// a=ice-pwd:yEclOTrLg1gEubBFefOqtmyV
const char * getKey ( ) const override { return " ice-pwd " ; }
2021-03-28 23:31:21 +08:00
} ;
class SdpAttrIceOption : public SdpItem {
public :
2024-03-23 23:08:10 +08:00
// a=ice-options:trickle
bool trickle { false } ;
bool renomination { false } ;
2022-02-02 20:34:50 +08:00
void parse ( const std : : string & str ) override ;
std : : string toString ( ) const override ;
2024-03-23 23:08:10 +08:00
const char * getKey ( ) const override { return " ice-options " ; }
2021-03-28 09:49:34 +08:00
} ;
class SdpAttrFingerprint : public SdpItem {
public :
2024-03-23 23:08:10 +08:00
// 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
2022-02-02 20:34:50 +08:00
std : : string algorithm ;
std : : string hash ;
void parse ( const std : : string & str ) override ;
std : : string toString ( ) const override ;
2024-03-23 23:08:10 +08:00
const char * getKey ( ) const override { return " fingerprint " ; }
2021-04-06 22:51:16 +08:00
bool empty ( ) const { return algorithm . empty ( ) | | hash . empty ( ) ; }
2021-03-28 09:49:34 +08:00
} ;
class SdpAttrSetup : public SdpItem {
public :
2024-03-23 23:08:10 +08:00
// a=setup:actpass
2021-03-30 13:09:02 +08:00
SdpAttrSetup ( ) = default ;
SdpAttrSetup ( DtlsRole r ) { role = r ; }
2024-03-23 23:08:10 +08:00
DtlsRole role { DtlsRole : : actpass } ;
2022-02-02 20:34:50 +08:00
void parse ( const std : : string & str ) override ;
std : : string toString ( ) const override ;
2024-03-23 23:08:10 +08:00
const char * getKey ( ) const override { return " setup " ; }
2021-03-28 09:49:34 +08:00
} ;
class SdpAttrMid : public SdpItem {
public :
2021-03-30 13:09:02 +08:00
SdpAttrMid ( ) = default ;
2022-02-02 20:34:50 +08:00
SdpAttrMid ( std : : string val ) { value = std : : move ( val ) ; }
2024-03-23 23:08:10 +08:00
// a=mid:audio
const char * getKey ( ) const override { return " mid " ; }
2021-03-28 09:49:34 +08:00
} ;
class SdpAttrExtmap : public SdpItem {
public :
2024-03-23 23:08:10 +08:00
// https://aggresss.blog.csdn.net/article/details/106436703
// a=extmap:1[/sendonly] urn:ietf:params:rtp-hdrext:ssrc-audio-level
2021-05-07 14:08:43 +08:00
uint8_t id ;
2024-03-23 23:08:10 +08:00
RtpDirection direction { RtpDirection : : invalid } ;
2022-02-02 20:34:50 +08:00
std : : string ext ;
void parse ( const std : : string & str ) override ;
std : : string toString ( ) const override ;
2024-03-23 23:08:10 +08:00
const char * getKey ( ) const override { return " extmap " ; }
2021-03-28 09:49:34 +08:00
} ;
class SdpAttrRtpMap : public SdpItem {
public :
2024-03-23 23:08:10 +08:00
// a=rtpmap:111 opus/48000/2
2021-03-28 09:49:34 +08:00
uint8_t pt ;
2022-02-02 20:34:50 +08:00
std : : string codec ;
2021-03-29 23:03:55 +08:00
uint32_t sample_rate ;
2024-03-23 23:08:10 +08:00
uint32_t channel { 0 } ;
2022-02-02 20:34:50 +08:00
void parse ( const std : : string & str ) override ;
std : : string toString ( ) const override ;
2024-03-23 23:08:10 +08:00
const char * getKey ( ) const override { return " rtpmap " ; }
2021-03-28 09:49:34 +08:00
} ;
class SdpAttrRtcpFb : public SdpItem {
public :
2024-03-23 23:08:10 +08:00
// a=rtcp-fb:98 nack pli
2024-09-19 14:53:50 +08:00
// a=rtcp-fb:120 nack 支持 nack 重传, nack (Negative-Acknowledgment) 。 [AUTO-TRANSLATED:08d5c4e2]
// a=rtcp-fb:120 nack supports nack retransmission, nack (Negative-Acknowledgment).
// a=rtcp-fb:120 nack pli 支持 nack 关键帧重传, PLI (Picture Loss Indication) 。 [AUTO-TRANSLATED:c331c1dd]
// a=rtcp-fb:120 nack pli supports nack keyframe retransmission, PLI (Picture Loss Indication).
// a=rtcp-fb:120 ccm fir 支持编码层关键帧请求, CCM (Codec Control Message), FIR (Full Intra Request ),通常与 nack pli 有同样的效果,但是 nack pli [AUTO-TRANSLATED:7090fdc9]
// a=rtcp-fb:120 ccm fir supports keyframe requests for the coding layer, CCM (Codec Control Message), FIR (Full Intra Request), which usually has the same effect as nack pli, but nack pli
// 是用于重传时的关键帧请求。 a=rtcp-fb:120 goog-remb 支持 REMB (Receiver Estimated Maximum Bitrate) 。 a=rtcp-fb:120 transport-cc 支持 TCC (Transport [AUTO-TRANSLATED:ffac8e91]
// is used for keyframe requests during retransmission. a=rtcp-fb:120 goog-remb supports REMB (Receiver Estimated Maximum Bitrate). a=rtcp-fb:120 transport-cc supports TCC (Transport
// Congest Control) 。 [AUTO-TRANSLATED:dcf53e31]
// Congest Control).
2021-03-28 09:49:34 +08:00
uint8_t pt ;
2022-02-02 20:34:50 +08:00
std : : string rtcp_type ;
void parse ( const std : : string & str ) override ;
std : : string toString ( ) const override ;
2024-03-23 23:08:10 +08:00
const char * getKey ( ) const override { return " rtcp-fb " ; }
2021-03-28 09:49:34 +08:00
} ;
class SdpAttrFmtp : public SdpItem {
public :
2024-03-23 23:08:10 +08:00
// fmtp:96 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
2021-03-28 09:49:34 +08:00
uint8_t pt ;
2024-03-23 23:08:10 +08:00
std : : map < std : : string /*key*/ , std : : string /*value*/ , StrCaseCompare > fmtp ;
2022-02-02 20:34:50 +08:00
void parse ( const std : : string & str ) override ;
std : : string toString ( ) const override ;
2024-03-23 23:08:10 +08:00
const char * getKey ( ) const override { return " fmtp " ; }
2021-03-28 09:49:34 +08:00
} ;
class SdpAttrSSRC : public SdpItem {
public :
2024-03-23 23:08:10 +08:00
// 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:<ssrc-id> <attribute>
// a=ssrc:<ssrc-id> <attribute>:<value>
2024-09-19 14:53:50 +08:00
// cname 是必须的, msid/mslabel/label 这三个属性都是 WebRTC 自创的,或者说 Google 自创的,可以参考 https://tools.ietf.org/html/draft-ietf-mmusic-msid-17, [AUTO-TRANSLATED:d8cb1baf]
// cname is required, msid/mslabel/label these three attributes are all created by WebRTC, or Google created, you can refer to https://tools.ietf.org/html/draft-ietf-mmusic-msid-17,
// 理解它们三者的关系需要先了解三个概念: RTP stream / MediaStreamTrack / MediaStream : [AUTO-TRANSLATED:7d385cf5]
// understanding the relationship between the three requires understanding three concepts: RTP stream / MediaStreamTrack / MediaStream:
// 一个 a=ssrc 代表一个 RTP stream ; [AUTO-TRANSLATED:ee1ecc6f]
// One a=ssrc represents one RTP stream;
// 一个 MediaStreamTrack 通常包含一个或多个 RTP stream, 例如一个视频 MediaStreamTrack 中通常包含两个 RTP stream, 一个用于常规传输, 一个用于 nack 重传; [AUTO-TRANSLATED:e8ddf0fd]
// A MediaStreamTrack usually contains one or more RTP streams, for example, a video MediaStreamTrack usually contains two RTP streams, one for regular transmission and one for nack retransmission;
// 一个 MediaStream 通常包含一个或多个 MediaStreamTrack ,例如 simulcast 场景下,一个 MediaStream 通常会包含三个不同编码质量的 MediaStreamTrack ; [AUTO-TRANSLATED:31318d43]
// A MediaStream usually contains one or more MediaStreamTrack, for example, in a simulcast scenario, a MediaStream usually contains three MediaStreamTrack of different encoding quality;
// 这种标记方式并不被 Firefox 认可,在 Firefox 生成的 SDP 中一个 a=ssrc 通常只有一行,例如: [AUTO-TRANSLATED:8c2c424c]
// This marking method is not recognized by Firefox, in the SDP generated by Firefox, one a=ssrc usually has only one line, for example:
2024-03-23 23:08:10 +08:00
// a=ssrc:3245185839 cname:Cx4i/VTR51etgjT7
2021-03-28 23:52:26 +08:00
2021-03-28 09:49:34 +08:00
uint32_t ssrc ;
2022-02-02 20:34:50 +08:00
std : : string attribute ;
std : : string attribute_value ;
void parse ( const std : : string & str ) override ;
std : : string toString ( ) const override ;
2024-03-23 23:08:10 +08:00
const char * getKey ( ) const override { return " ssrc " ; }
2021-03-28 23:31:21 +08:00
} ;
class SdpAttrSSRCGroup : public SdpItem {
public :
2024-09-19 14:53:50 +08:00
// a=ssrc-group 定义参考 RFC 5576(https://tools.ietf.org/html/rfc5576) ,用于描述多个 ssrc 之间的关联,常见的有两种: [AUTO-TRANSLATED:a87cbcc6]
// a=ssrc-group definition refers to RFC 5576(https://tools.ietf.org/html/rfc5576), used to describe the association between multiple ssrcs, there are two common types:
2024-03-23 23:08:10 +08:00
// a=ssrc-group:FID 2430709021 3715850271
2024-09-19 14:53:50 +08:00
// FID (Flow Identification) 最初用在 FEC 的关联中, WebRTC 中通常用于关联一组常规 RTP stream 和 重传 RTP stream 。 [AUTO-TRANSLATED:f2c0fcbb]
// FID (Flow Identification) was originally used in FEC association, and in WebRTC it is usually used to associate a group of regular RTP streams and retransmission RTP streams.
2024-03-23 23:08:10 +08:00
// a=ssrc-group:SIM 360918977 360918978 360918980
2024-09-19 14:53:50 +08:00
// 在 Chrome 独有的 SDP munging 风格的 simulcast 中使用,将三组编码质量由低到高的 MediaStreamTrack 关联在一起。 [AUTO-TRANSLATED:61bf7596]
// Used in Chrome's unique SDP munging style simulcast, associating three groups of MediaStreamTrack from low to high encoding quality.
2024-03-23 23:08:10 +08:00
std : : string type { " FID " } ;
2022-02-02 20:34:50 +08:00
std : : vector < uint32_t > ssrcs ;
2021-03-28 23:31:21 +08:00
2021-03-29 23:03:55 +08:00
bool isFID ( ) const { return type = = " FID " ; }
bool isSIM ( ) const { return type = = " SIM " ; }
2022-02-02 20:34:50 +08:00
void parse ( const std : : string & str ) override ;
std : : string toString ( ) const override ;
2024-03-23 23:08:10 +08:00
const char * getKey ( ) const override { return " ssrc-group " ; }
2021-03-28 09:49:34 +08:00
} ;
class SdpAttrSctpMap : public SdpItem {
public :
2024-03-23 23:08:10 +08:00
// https://tools.ietf.org/html/draft-ietf-mmusic-sctp-sdp-05
// a=sctpmap:5000 webrtc-datachannel 1024
// a=sctpmap: sctpmap-number media-subtypes [streams]
2022-04-03 17:12:23 +08:00
uint16_t port = 0 ;
2022-02-02 20:34:50 +08:00
std : : string subtypes ;
2022-04-03 17:12:23 +08:00
uint32_t streams = 0 ;
2022-02-02 20:34:50 +08:00
void parse ( const std : : string & str ) override ;
std : : string toString ( ) const override ;
2024-03-23 23:08:10 +08:00
const char * getKey ( ) const override { return " sctpmap " ; }
2022-04-03 17:12:23 +08:00
bool empty ( ) const { return port = = 0 & & subtypes . empty ( ) & & streams = = 0 ; }
2021-03-28 09:49:34 +08:00
} ;
class SdpAttrCandidate : public SdpItem {
public :
2021-04-02 17:08:11 +08:00
using Ptr = std : : shared_ptr < SdpAttrCandidate > ;
2024-03-23 23:08:10 +08:00
// 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:<foundation> <component-id> <transport> <priority> <address> <port> typ <cand-type>
2022-02-02 20:34:50 +08:00
std : : string foundation ;
2024-09-19 14:53:50 +08:00
// 传输媒体的类型,1代表RTP;2代表 RTCP。 [AUTO-TRANSLATED:9ec924a6]
// The type of media to be transmitted, 1 represents RTP; 2 represents RTCP.
2021-03-28 17:32:53 +08:00
uint32_t component ;
2024-03-23 23:08:10 +08:00
std : : string transport { " udp " } ;
2021-03-28 17:32:53 +08:00
uint32_t priority ;
2022-02-02 20:34:50 +08:00
std : : string address ;
2021-03-28 17:32:53 +08:00
uint16_t port ;
2022-02-02 20:34:50 +08:00
std : : string type ;
2024-03-23 23:08:10 +08:00
std : : vector < std : : pair < std : : string , std : : string > > arr ;
2021-03-28 17:32:53 +08:00
2022-02-02 20:34:50 +08:00
void parse ( const std : : string & str ) override ;
std : : string toString ( ) const override ;
2024-03-23 23:08:10 +08:00
const char * getKey ( ) const override { return " candidate " ; }
2021-03-28 09:49:34 +08:00
} ;
2024-03-23 23:08:10 +08:00
class SdpAttrMsid : public SdpItem {
2021-03-31 17:15:26 +08:00
public :
2024-03-23 23:08:10 +08:00
const char * getKey ( ) const override { return " msid " ; }
2021-03-31 17:15:26 +08:00
} ;
2024-03-23 23:08:10 +08:00
class SdpAttrExtmapAllowMixed : public SdpItem {
2021-03-31 17:15:26 +08:00
public :
2024-03-23 23:08:10 +08:00
const char * getKey ( ) const override { return " extmap-allow-mixed " ; }
2021-03-31 17:15:26 +08:00
} ;
2024-03-23 23:08:10 +08:00
class SdpAttrSimulcast : public SdpItem {
2021-03-31 17:15:26 +08:00
public :
2024-03-23 23:08:10 +08:00
// https://www.meetecho.com/blog/simulcast-janus-ssrc/
// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-14
const char * getKey ( ) const override { return " simulcast " ; }
2022-02-02 20:34:50 +08:00
void parse ( const std : : string & str ) override ;
std : : string toString ( ) const override ;
2021-05-08 20:27:46 +08:00
bool empty ( ) const { return rids . empty ( ) ; }
2022-02-02 20:34:50 +08:00
std : : string direction ;
std : : vector < std : : string > rids ;
2021-03-31 17:15:26 +08:00
} ;
2024-03-23 23:08:10 +08:00
class SdpAttrRid : public SdpItem {
2021-03-31 17:15:26 +08:00
public :
2022-02-02 20:34:50 +08:00
void parse ( const std : : string & str ) override ;
std : : string toString ( ) const override ;
2024-03-23 23:08:10 +08:00
const char * getKey ( ) const override { return " rid " ; }
2022-02-02 20:34:50 +08:00
std : : string direction ;
std : : string rid ;
2021-03-31 17:15:26 +08:00
} ;
2021-03-29 11:39:58 +08:00
class RtcSdpBase {
2021-03-28 09:49:34 +08:00
public :
2022-05-20 22:14:21 +08:00
void addItem ( SdpItem : : Ptr item ) { items . push_back ( std : : move ( item ) ) ; }
2022-05-18 09:56:53 +08:00
void addAttr ( SdpItem : : Ptr attr ) {
auto item = std : : make_shared < SdpAttr > ( ) ;
item - > detail = std : : move ( attr ) ;
2022-05-20 22:14:21 +08:00
items . push_back ( std : : move ( item ) ) ;
2022-05-18 09:56:53 +08:00
}
2022-05-20 22:14:21 +08:00
2021-03-29 23:03:55 +08:00
virtual ~ RtcSdpBase ( ) = default ;
2022-02-02 20:34:50 +08:00
virtual std : : string toString ( ) const ;
2022-05-20 22:14:21 +08:00
void toRtsp ( ) ;
2021-03-29 11:39:58 +08:00
2021-03-28 17:32:53 +08:00
RtpDirection getDirection ( ) const ;
2021-03-29 11:39:58 +08:00
2024-03-23 23:08:10 +08:00
template < typename cls >
cls getItemClass ( char key , const char * attr_key = nullptr ) const {
2022-02-02 20:34:50 +08:00
auto item = std : : dynamic_pointer_cast < cls > ( getItem ( key , attr_key ) ) ;
2021-03-29 11:39:58 +08:00
if ( ! item ) {
return cls ( ) ;
}
return * item ;
}
2024-03-23 23:08:10 +08:00
std : : string getStringItem ( char key , const char * attr_key = nullptr ) const {
2021-03-29 11:39:58 +08:00
auto item = getItem ( key , attr_key ) ;
if ( ! item ) {
return " " ;
}
return item - > toString ( ) ;
}
2021-03-29 12:00:42 +08:00
SdpItem : : Ptr getItem ( char key , const char * attr_key = nullptr ) const ;
2021-03-29 23:03:55 +08:00
2024-03-23 23:08:10 +08:00
template < typename cls >
2022-02-02 20:34:50 +08:00
std : : vector < cls > getAllItem ( char key_c , const char * attr_key = nullptr ) const {
std : : vector < cls > ret ;
2022-05-18 09:56:53 +08:00
std : : string key ( 1 , key_c ) ;
2021-03-29 23:03:55 +08:00
for ( auto item : items ) {
2021-03-30 09:53:58 +08:00
if ( strcasecmp ( item - > getKey ( ) , key . data ( ) ) = = 0 ) {
2021-03-29 23:03:55 +08:00
if ( ! attr_key ) {
2022-02-02 20:34:50 +08:00
auto c = std : : dynamic_pointer_cast < cls > ( item ) ;
2021-03-29 23:03:55 +08:00
if ( c ) {
ret . emplace_back ( * c ) ;
}
} else {
2022-02-02 20:34:50 +08:00
auto attr = std : : dynamic_pointer_cast < SdpAttr > ( item ) ;
2021-03-30 09:53:58 +08:00
if ( attr & & ! strcasecmp ( attr - > detail - > getKey ( ) , attr_key ) ) {
2022-02-02 20:34:50 +08:00
auto c = std : : dynamic_pointer_cast < cls > ( attr - > detail ) ;
2021-03-29 23:03:55 +08:00
if ( c ) {
ret . emplace_back ( * c ) ;
}
}
}
}
}
return ret ;
}
2022-05-20 22:14:21 +08:00
private :
std : : vector < SdpItem : : Ptr > items ;
2021-03-27 18:33:20 +08:00
} ;
2021-03-27 22:23:38 +08:00
2024-03-23 23:08:10 +08:00
class RtcSessionSdp : public RtcSdpBase {
2021-03-27 18:33:20 +08:00
public :
2021-04-02 23:01:58 +08:00
using Ptr = std : : shared_ptr < RtcSessionSdp > ;
2022-05-18 09:56:53 +08:00
int getVersion ( ) const ;
SdpOrigin getOrigin ( ) const ;
std : : string getSessionName ( ) const ;
std : : string getSessionInfo ( ) const ;
SdpTime getSessionTime ( ) const ;
SdpConnection getConnection ( ) const ;
SdpBandwidth getBandwidth ( ) const ;
2021-04-02 23:01:58 +08:00
2022-05-18 09:56:53 +08:00
std : : string getUri ( ) const ;
std : : string getEmail ( ) const ;
std : : string getPhone ( ) const ;
std : : string getTimeZone ( ) const ;
std : : string getEncryptKey ( ) const ;
std : : string getRepeatTimes ( ) const ;
2024-03-23 23:08:10 +08:00
2022-02-02 20:34:50 +08:00
std : : vector < RtcSdpBase > medias ;
void parse ( const std : : string & str ) ;
std : : string toString ( ) const override ;
2021-03-27 18:33:20 +08:00
} ;
2021-03-29 10:54:18 +08:00
//////////////////////////////////////////////////////////////////
2024-09-19 14:53:50 +08:00
// ssrc相关信息 [AUTO-TRANSLATED:954c641d]
// ssrc related information
2024-03-23 23:08:10 +08:00
class RtcSSRC {
2021-03-29 10:54:18 +08:00
public :
2024-03-23 23:08:10 +08:00
uint32_t ssrc { 0 } ;
uint32_t rtx_ssrc { 0 } ;
2022-02-02 20:34:50 +08:00
std : : string cname ;
std : : string msid ;
std : : string mslabel ;
std : : string label ;
2021-03-29 23:55:29 +08:00
2024-03-23 23:08:10 +08:00
bool empty ( ) const { return ssrc = = 0 & & cname . empty ( ) ; }
2021-03-29 10:54:18 +08:00
} ;
2024-09-19 14:53:50 +08:00
// rtc传输编码方案 [AUTO-TRANSLATED:8b911508]
// rtc transmission encoding scheme
2024-03-23 23:08:10 +08:00
class RtcCodecPlan {
2021-03-29 10:54:18 +08:00
public :
2022-02-02 20:34:50 +08:00
using Ptr = std : : shared_ptr < RtcCodecPlan > ;
2021-03-29 10:54:18 +08:00
uint8_t pt ;
2022-02-02 20:34:50 +08:00
std : : string codec ;
2021-03-29 10:54:18 +08:00
uint32_t sample_rate ;
2024-09-19 14:53:50 +08:00
// 音频时有效 [AUTO-TRANSLATED:5b230fc8]
// Valid for audio
2021-03-29 10:54:18 +08:00
uint32_t channel = 0 ;
2024-09-19 14:53:50 +08:00
// rtcp反馈 [AUTO-TRANSLATED:580378bd]
// RTCP feedback
2022-02-02 20:34:50 +08:00
std : : set < std : : string > rtcp_fb ;
2024-03-23 23:08:10 +08:00
std : : map < std : : string /*key*/ , std : : string /*value*/ , StrCaseCompare > fmtp ;
2021-03-29 23:55:29 +08:00
2022-02-02 20:34:50 +08:00
std : : string getFmtp ( const char * key ) const ;
2021-03-29 10:54:18 +08:00
} ;
2024-09-19 14:53:50 +08:00
// rtc 媒体描述 [AUTO-TRANSLATED:b1711a11]
// RTC media description
2024-03-23 23:08:10 +08:00
class RtcMedia {
2021-03-29 10:54:18 +08:00
public :
2024-03-23 23:08:10 +08:00
TrackType type { TrackType : : TrackInvalid } ;
2022-02-02 20:34:50 +08:00
std : : string mid ;
2024-03-23 23:08:10 +08:00
uint16_t port { 0 } ;
2021-03-29 23:55:29 +08:00
SdpConnection addr ;
2022-07-07 16:44:42 +08:00
SdpBandwidth bandwidth ;
2022-02-02 20:34:50 +08:00
std : : string proto ;
2024-03-23 23:08:10 +08:00
RtpDirection direction { RtpDirection : : invalid } ;
2022-02-02 20:34:50 +08:00
std : : vector < RtcCodecPlan > plan ;
2021-03-29 10:54:18 +08:00
//////// rtp ////////
2022-02-02 20:34:50 +08:00
std : : vector < RtcSSRC > rtp_rtx_ssrc ;
2021-03-29 23:55:29 +08:00
//////// simulcast ////////
2022-02-02 20:34:50 +08:00
std : : vector < RtcSSRC > rtp_ssrc_sim ;
std : : vector < std : : string > rtp_rids ;
2021-03-29 23:03:55 +08:00
2021-03-29 23:55:29 +08:00
//////// rtcp ////////
2024-03-23 23:08:10 +08:00
bool rtcp_mux { false } ;
bool rtcp_rsize { false } ;
2021-03-29 10:54:18 +08:00
SdpAttrRtcp rtcp_addr ;
//////// ice ////////
2024-03-23 23:08:10 +08:00
bool ice_trickle { false } ;
bool ice_lite { false } ;
bool ice_renomination { false } ;
2022-02-02 20:34:50 +08:00
std : : string ice_ufrag ;
std : : string ice_pwd ;
2021-03-29 10:54:18 +08:00
std : : vector < SdpAttrCandidate > candidate ;
//////// dtls ////////
2024-03-23 23:08:10 +08:00
DtlsRole role { DtlsRole : : invalid } ;
2021-03-29 10:54:18 +08:00
SdpAttrFingerprint fingerprint ;
//////// extmap ////////
2022-02-02 20:34:50 +08:00
std : : vector < SdpAttrExtmap > extmap ;
2021-03-29 23:03:55 +08:00
//////// sctp ////////////
SdpAttrSctpMap sctpmap ;
2024-03-23 23:08:10 +08:00
uint32_t sctp_port { 0 } ;
2021-03-29 23:55:29 +08:00
void checkValid ( ) const ;
const RtcCodecPlan * getPlan ( uint8_t pt ) const ;
2021-03-30 09:53:58 +08:00
const RtcCodecPlan * getPlan ( const char * codec ) const ;
2021-03-30 10:59:15 +08:00
const RtcCodecPlan * getRelatedRtxPlan ( uint8_t pt ) const ;
2021-05-16 12:28:50 +08:00
uint32_t getRtpSSRC ( ) const ;
uint32_t getRtxSSRC ( ) const ;
2021-10-12 21:26:01 +08:00
bool supportSimulcast ( ) const ;
2021-03-29 10:54:18 +08:00
} ;
2021-03-27 18:33:20 +08:00
2021-10-16 16:46:05 +08:00
class RtcSession {
2021-03-29 11:39:58 +08:00
public :
2021-03-31 17:15:26 +08:00
using Ptr = std : : shared_ptr < RtcSession > ;
2021-03-29 23:03:55 +08:00
uint32_t version ;
2021-03-29 11:39:58 +08:00
SdpOrigin origin ;
2022-02-02 20:34:50 +08:00
std : : string session_name ;
std : : string session_info ;
2021-03-30 09:53:58 +08:00
SdpTime time ;
2021-03-29 11:39:58 +08:00
SdpConnection connection ;
2021-03-29 23:03:55 +08:00
SdpAttrMsidSemantic msid_semantic ;
2022-02-02 20:34:50 +08:00
std : : vector < RtcMedia > media ;
2021-03-29 23:03:55 +08:00
SdpAttrGroup group ;
2021-03-29 12:00:42 +08:00
2022-02-02 20:34:50 +08:00
void loadFrom ( const std : : string & sdp ) ;
2021-03-29 23:55:29 +08:00
void checkValid ( ) const ;
2022-02-02 20:34:50 +08:00
std : : string toString ( ) const ;
std : : string toRtspSdp ( ) const ;
2024-03-23 23:08:10 +08:00
const RtcMedia * getMedia ( TrackType type ) const ;
2022-09-18 21:03:05 +08:00
bool supportRtcpFb ( const std : : string & name , TrackType type = TrackType : : TrackVideo ) const ;
2021-07-07 14:47:41 +08:00
bool supportSimulcast ( ) const ;
2022-08-20 10:28:19 +08:00
bool isOnlyDatachannel ( ) const ;
2021-04-02 23:01:58 +08:00
private :
RtcSessionSdp : : Ptr toRtcSessionSdp ( ) const ;
2021-03-29 11:39:58 +08:00
} ;
2021-03-27 18:33:20 +08:00
2021-03-30 11:51:39 +08:00
class RtcConfigure {
public :
2021-04-02 17:08:11 +08:00
using Ptr = std : : shared_ptr < RtcConfigure > ;
2021-03-30 11:51:39 +08:00
class RtcTrackConfigure {
public :
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 ;
2022-02-02 20:34:50 +08:00
std : : string ice_ufrag ;
std : : string ice_pwd ;
2021-03-30 11:51:39 +08:00
2024-03-23 23:08:10 +08:00
RtpDirection direction { RtpDirection : : invalid } ;
2021-03-30 11:51:39 +08:00
SdpAttrFingerprint fingerprint ;
2022-02-02 20:34:50 +08:00
std : : set < std : : string > rtcp_fb ;
std : : map < RtpExtType , RtpDirection > extmap ;
2022-09-18 21:03:05 +08:00
std : : vector < CodecId > preferred_codec ;
2022-02-02 20:34:50 +08:00
std : : vector < SdpAttrCandidate > candidate ;
2021-03-30 11:51:39 +08:00
2022-09-18 21:03:05 +08:00
void setDefaultSetting ( TrackType type ) ;
2021-04-28 15:07:15 +08:00
void enableTWCC ( bool enable = true ) ;
2021-04-30 15:08:43 +08:00
void enableREMB ( bool enable = true ) ;
2021-03-30 11:51:39 +08:00
} ;
RtcTrackConfigure video ;
RtcTrackConfigure audio ;
RtcTrackConfigure application ;
2022-02-02 20:34:50 +08:00
void setDefaultSetting ( std : : string ice_ufrag , std : : string ice_pwd , RtpDirection direction , const SdpAttrFingerprint & fingerprint ) ;
2022-09-18 21:03:05 +08:00
void addCandidate ( const SdpAttrCandidate & candidate , TrackType type = TrackInvalid ) ;
2021-03-30 18:34:17 +08:00
2022-06-11 12:26:31 +08:00
std : : shared_ptr < RtcSession > createAnswer ( const RtcSession & offer ) const ;
2021-03-31 18:32:43 +08:00
2022-02-02 20:34:50 +08:00
void setPlayRtspInfo ( const std : : string & sdp ) ;
2021-04-04 23:58:59 +08:00
2022-09-18 21:03:05 +08:00
void enableTWCC ( bool enable = true , TrackType type = TrackInvalid ) ;
void enableREMB ( bool enable = true , TrackType type = TrackInvalid ) ;
2021-04-28 15:07:15 +08:00
2021-03-31 18:32:43 +08:00
private :
2022-06-11 12:26:31 +08:00
void matchMedia ( const std : : shared_ptr < RtcSession > & ret , const RtcMedia & media ) const ;
2022-09-18 21:03:05 +08:00
bool onCheckCodecProfile ( const RtcCodecPlan & plan , CodecId codec ) const ;
void onSelectPlan ( RtcCodecPlan & plan , CodecId codec ) const ;
2021-04-04 23:58:59 +08:00
private :
RtcCodecPlan : : Ptr _rtsp_video_plan ;
RtcCodecPlan : : Ptr _rtsp_audio_plan ;
2021-03-30 11:51:39 +08:00
} ;
2021-04-30 15:15:35 +08:00
class SdpConst {
public :
2022-02-02 20:34:50 +08:00
static std : : string const kTWCCRtcpFb ;
static std : : string const kRembRtcpFb ;
2021-04-30 15:15:35 +08:00
private :
SdpConst ( ) = delete ;
~ SdpConst ( ) = delete ;
} ;
2024-03-23 23:08:10 +08:00
} // namespace mediakit
2021-03-27 18:33:20 +08:00
2024-03-23 23:08:10 +08:00
# endif // ZLMEDIAKIT_SDP_H