1 SDP组成
SDP是由多行文本组成的一个纯文本协议,如果将SDP
从语义上分解成不同组件来描述一个多媒体会话信息,那么SDP
由以下部分组成:
- 会话信息
- 网络信息
- 媒体信息
- 安全信息
- 服务质量和分组信息
+---------------------+ | v= | +---------------------+ +---------------------+ +---------------------+ ==== | Session Metadata | ===== | o= | | +---------------------+ +---------------------- | +---------------------+ | | t= | | +---------------------+ | | | +---------------------+ | | c= | | +---------------------+ | +---------------------+ ==== | Network Description | ===== | +---------------------+ | +---------------------+ | | a=candidate | | +---------------------+ | | | +---------------------+ | | m= | | +---------------------+ | +---------------------+ +---------------------+ ==== | Stream Description | ===== | a=rtpmap | | +---------------------+ +---------------------- | +---------------------+ | | a=fmtp | | +---------------------+ | +---------------------+ | | a=sendrecv.. | | +---------------------+ +---------------+ | SEMANTIC | | COMPONENTS OF | | SDP | +---------------+ | +---------------------+ | | a=crypto | | +---------------------+ | +---------------------+ +---------------------+ ==== |Security Descriptions| =====| a=ice-frag | | +---------------------+ +---------------------- | +---------------------+ | | a=ice-pwd | | +---------------------+ | +---------------------+ | | a=fingerprint | | +---------------------+ | | | | +---------------------+ | | a=rtcp-fb | | +---------------------+ | +---------------------+ +---------------------+ ==== | Qos,Grouping | | | | Descriptions | =====| a=group | +---------------------+ +---------------------- +---------------------+ | a=rtcpmux | +---------------------+
2 SDP格式
SDP由多行组成,每行的的格式如下:
<type>=<value>
- <type>: 区分大小写,代表特定的属性,例如v代表SDP版本。
- <value>:UTF8编码的文本,具体格式与类型有关。
- =两边不允许存在空格。
- =*表示该项是可选的。
下面是WebRTC(branch_76)的一个真实SDP样本:
// -------------------------------- 【Session Metadata部分】 -------------------------------- // sdp版本号 v=0 // o=<username> <sess-id> <sess-version> <nettype> <addrtype> <unicast-address> // username如何没有使用-代替,3967017503571418851是整个会话的编号,2代表会话版本,如果在会话过程中有改变编码之类的操作,重新生成sdp时,sess-id不变,sess-version加1 o=- 3967017503571418851 2 IN IP4 127.0.0.1 //会话名 s=- // 会话的起始时间和结束时间,0代表没有限制 t=0 0 // 表示需要共用一个传输通道传输的媒体,通过ssrc进行区分不同的流。如果没有这一行,音视频数据就会分别用单独udp端口来发送. a=group:BUNDLE audio video // WMS是WebRTC Media Stream简称; // 这一行定义了本客户端支持同时传输多个流,一个流可以包括多个track. // 一般定义了这个,后面a=ssrc这一行就会有msid,mslabel等属性. a=msid-semantic: WMS stream_id // -------------------------------- 【Stream Description部分】 -------------------------------- // ------------ audio部分 ------------- // m意味着它是一个媒体行. // m=audio说明本会话包含音频,9代表音频使用端口9来传输,但是在webrtc中现在一般不使用,如果设置为0,代表不传输音频, // UDP/TLS/RTP/SAVPF是表示用户支持来传输音频的协议,udp,tls,rtp代表使用udp来传输rtp包,并使用tls加密 // SAVPF代表使用srtcp的反馈机制来控制通信过程 // 后面的111 103 104 9 102 0 8 106 105 13 110 112 113 126表示本会话音频支持的编码,后面几行会有详细补充说明. m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 102 0 8 106 105 13 110 112 113 126 // 表示你要用来接收或者发送音频使用的IP地址. // webrtc使用ice传输,不使用这个地址 c=IN IP4 0.0.0.0 // 用来传输rtcp的地址和端口,webrtc中不使用 a=rtcp:9 IN IP4 0.0.0.0 // 下面2行是ice协商过程中的安全验证信息 a=ice-ufrag:kSq0 a=ice-pwd:pWLGrCTwFNq6rm249ZEasHPY // 通知对端支持trickle,即sdp里面描述媒体信息和ice候选项的信息可以分开传输 a=ice-options:trickle // dtls协商过程中需要的认证信息 a=fingerprint:sha-256 A0:D6:B2:63:1B:69:0E:91:01:C3:88:A9:92:6F:E7:EF:5B:36:52:66:08:DF:94:A0:FE:0C:C9:06:BF:2C:38:A2 // 代表本客户端在dtls协商过程中,可以做客户端也可以做服务端, 参考rfc4145 rfc4572 a=setup:actpass // 前面BUNDLE行中用到的媒体标识 a=mid:audio // 指出要在rtp头部中加入音量信息,参考 rfc6464 a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level // 指出是双向通信,另外几种类型是recvonly,sendonly,inactive a=sendrecv // 指出rtp,rtcp包使用同一个端口来传输 a=rtcp-mux // 下面十几行都是对m=audio这一行的媒体编码补充说明,指出了编码采用的编号,采样率,声道等 a=rtpmap:111 opus/48000/2 a=rtcp-fb:111 transport-cc // 下面一行对opus编码可选的补充说明,minptime代表最小打包时长是10ms,useinbandfec=1代表使用opus编码内置fec特性 a=fmtp:111 minptime=10;useinbandfec=1 a=rtpmap:103 ISAC/16000 a=rtpmap:104 ISAC/32000 a=rtpmap:9 G722/8000 a=rtpmap:102 ILBC/8000 a=rtpmap:0 PCMU/8000 a=rtpmap:8 PCMA/8000 a=rtpmap:106 CN/32000 a=rtpmap:105 CN/16000 a=rtpmap:13 CN/8000 a=rtpmap:110 telephone-event/48000 a=rtpmap:112 telephone-event/32000 a=rtpmap:113 telephone-event/16000 a=rtpmap:126 telephone-event/8000 // cname用来标识一个数据源,ssrc当发生冲突时可能会发生变化,但是cname不会发生变化,也会出现在rtcp包中SDEC中,用于音视频同步 a=ssrc:2603526440 cname:AyMDWB+q6ApWdpfU a=ssrc:2603526440 msid:stream_id audio_label a=ssrc:2603526440 mslabel:stream_id a=ssrc:2603526440 label:audio_label // ------------ video部分 ------------- m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 127 c=IN IP4 0.0.0.0 a=rtcp:9 IN IP4 0.0.0.0 a=ice-ufrag:kSq0 a=ice-pwd:pWLGrCTwFNq6rm249ZEasHPY a=ice-options:trickle a=fingerprint:sha-256 A0:D6:B2:63:1B:69:0E:91:01:C3:88:A9:92:6F:E7:EF:5B:36:52:66:08:DF:94:A0:FE:0C:C9:06:BF:2C:38:A2 a=setup:actpass a=mid:video a=extmap:2 urn:ietf:params:rtp-hdrext:toffset a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time a=extmap:4 urn:3gpp:video-orientation a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01 a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/video-timing a=sendrecv a=rtcp-mux a=rtcp-rsize a=rtpmap:96 VP8/90000 a=rtcp-fb:96 goog-remb a=rtcp-fb:96 transport-cc a=rtcp-fb:96 ccm fir a=rtcp-fb:96 nack a=rtcp-fb:96 nack pli a=rtpmap:97 rtx/90000 a=fmtp:97 apt=96 a=rtpmap:98 VP9/90000 a=rtcp-fb:98 goog-remb a=rtcp-fb:98 transport-cc a=rtcp-fb:98 ccm fir a=rtcp-fb:98 nack a=rtcp-fb:98 nack pli a=rtpmap:99 rtx/90000 a=fmtp:99 apt=98 a=rtpmap:100 red/90000 a=rtpmap:101 rtx/90000 a=fmtp:101 apt=100 a=rtpmap:127 ulpfec/90000 a=ssrc-group:FID 1563406656 4103641903 a=ssrc:1563406656 cname:AyMDWB+q6ApWdpfU a=ssrc:1563406656 msid:stream_id video_label a=ssrc:1563406656 mslabel:stream_id a=ssrc:1563406656 label:video_label a=ssrc:4103641903 cname:AyMDWB+q6ApWdpfU a=ssrc:4103641903 msid:stream_id video_label a=ssrc:4103641903 mslabel:stream_id a=ssrc:4103641903 label:video_label
2.1 协议版本
第一行v=0
定义了sdp协议的版本号。
2.2 会话发起者
第二行
o=- 3967017503571418851 2 IN IP4 127.0.0.1
定义了会话发起者的信息,格式如下:
o=<username> <sess-id> <sess-version> <nettype> <addrtype> <unicast-address>
- username:发起者的用户名,不允许存在空格,如果应用不支持用户名,则为-。
- sess-id:会话id,由应用自行定义,SDP规范建议使用NTP(Network Time Protocol)时间戳。
- sess-version:会话版本,用途由应用自行定义,只要会话数据发生变化时(比如编码)sess-version随着递增即可。SDP规范建议使用NTP时间戳。
- nettype:网络类型,比如IN表示Internet。
- addrtype:地址类型,比如IP4、IV6
- unicast-address:域名,或者IP地址。
2.3 会话名
s=<session name>
必选,有且仅有一个s=字段,且不能为空。可以赋一个空格(即s=),或者-。
2.4 连接信息
c=<nettype> <addrtype> <connection-address>
每个SDP至少需要包含一个会话级别的c=字段,或者在每个媒体描述后面各包含一个c=字段,(媒体描述后的c=会覆盖会话级别的c=)。例如,在上面的示例SDP样本中,就不存在会话级别的连接信息。
- nettype:网络类型,比如IN,表示 Internet。
- addrtype:地址类型,比如IP4、IP6。
- connection-address:如果是广播,则为广播地址组;如果是单播,则为单播地址;
2.4.1 何为会话级别和媒体级别
会话级部分以v =行开始,到第一个媒体级部分结束。
每个媒体级部分以m =行开始,持续到下一个媒体级(即下一个m=)。
2.5 媒体描述信息
SDP可以同时包含多个媒体描述信息(如音频、视频等),格式如下:
m=<media> <port> <proto> <fmt> ...
示例:
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 102 0 8 106 105 13 110 112 113 126
c=IN IP4 0.0.0.0
- media:媒体类型,包括 video、audio、text、application、message等。
- port:传输媒体流使用的端口,具体含义取决于使用的网络类型(在c=中声明)和使用的传输协议(proto)。
- proto:传输协议,具体含义取决于c=中定义的地址类型,比如c=IP4,那么这里的传输协议运行在IP4之上。
- fmt:媒体格式的描述,可能有多个。根据proto的不同,fmt的含义也不同。比如proto为 RTP/SAVP 时,fmt 表示RTP payload 的类型。如果有多个,表示在这次会话中,多种payload类型可能会用到,且第一个为默认的payload类型。
数字0-95是静态负载类型;96-127是动态负载类型,需要在后面使用附近属性a =
rtpmap:指定具体的格式参数。具体的每个数字代表的负载类型和含义可以参考:Real-Time Transport Protocol (RTP) Parameters
如上例中的audio类型的111表示使用opus编码。
2.6 附加属性
附加属性用于扩展SDP,有2种作用范围:会话级别、媒体级别:
- 媒体级别:媒体描述(m=)后面可以跟任意数量的 a= 字段,对媒体描述进行扩展。
- 会话级别:在第一个媒体字段(media field)前,添加的 a= 字段是会话级别的。
有下面2种格式:
2.7 时间
用于声明会话的开始、结束时间,格式如下:
t=<start-time> <stop-time>
如果<stop-time>是0,表示会话没有结束的边界,但是需要在<start-time>之后会话才是活跃(active)的;如果<start-time>是0,表示会话是永久的。
原文地址:https://blog.csdn.net/china_jeffery/article/details/79991986
背景:预测式外呼需要服务端呼叫座席和客户,之前话务控件一直作为主叫存在
场景 fs(73)---->opensips(72)------->194(webrtc控件)
问题一:控件收不到invite信令
这个问题,之前遇到过,原因是webrtc话务控件,注册时,携带的信息是加密的,opensips的location表中的contact地址为“sip:o309vcvo@qph24fkpjojh.invalid;transport=ws ”。通过抓包和日志发现,73将invite信令发给了72,72收到后,并不能解析下一跳的地址“qph24fkpjojh.invalid”。
解决方案:使用fix_nated_contact()方法可以将contact地址中的ip修复。它将采用register信令的源地址作为contact地址保存在location中。
问题二:话务控件振铃后,报错DOMException: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection': Failed to set remote offer sdp: Called with SDP without DTLS fingerprint.
从话务控件的js代码中定位到报错的行
this._accept(options)
.then(function (_a) {
var message = _a.message, session = _a.session;
session.delegate = {
onAck: function (ackRequest) { return _this.onAck(ackRequest); },
onAckTimeout: function () { return _this.onAckTimeout(); },
onBye: function (byeRequest) { return _this.receiveRequest(byeRequest); },
onInfo: function (infoRequest) { return _this.receiveRequest(infoRequest); },
onInvite: function (inviteRequest) { return _this.receiveRequest(inviteRequest); },
onMessage: function (messageRequest) { return _this.receiveRequest(messageRequest); },
onNotify: function (notifyRequest) { return _this.receiveRequest(notifyRequest); },
onPrack: function (prackRequest) { return _this.receiveRequest(prackRequest); },
onRefer: function (referRequest) { return _this.receiveRequest(referRequest); },
};
_this.session = session;
_this.status = Enums_1.SessionStatus.STATUS_WAITING_FOR_ACK;
_this.accepted(message, Utils_1.Utils.getReasonPhrase(200));
})
.catch(function (error) {
_this.onContextError(error);
// FIXME: Assuming error due to async race on CANCEL and eating error.
if (!_this._canceled) {
throw error;
}
});
结合报错信息,是在sdp协商中,设置offer sdp失败,原因是sdp中没有dtls 指纹。报错信息我都认识,就是看不懂啥意思
之前webrtc话务控件注册在freeswitch上,是可以作为被叫正常接听的,就对比了一下两个invite信令中携带的sdp信息有啥不同:
注册在fs上,opensips发给话务控件的invite.txt
注册在opensips上,opensips发给话务控件的invite.txt
通过对比发现,注册在fs上的话务控件,收到的invite信令中,sdp消息中多了下面这几行
a=msid-semantic: WMS fvORye3n7hVTLlioFlJEM5Yl604Yy0aE
// dtls协商过程中需要的认证信息
a=fingerprint:sha-256 1A:3C:B4:1D:D1:68:CA:30:B6:3C:3B:EE:8C:09:26:97:42:78:CB:9C:2A:B1:5C:4C:91:92:1F:16:5E:41:92:E3
//代表本客户端在dtls协商过程中,既可以做客户端也可以做服务端
a=setup:actpass
a=rtcp-mux
a=rtcp:18064 IN IP4 10.225.155.69
a=ssrc:3394676291 cname:mPmLUgYRnKAYWJds
a=ssrc:3394676291 msid:fvORye3n7hVTLlioFlJEM5Yl604Yy0aE a0
a=ssrc:3394676291 mslabel:fvORye3n7hVTLlioFlJEM5Yl604Yy0aE
a=ssrc:3394676291 label:fvORye3n7hVTLlioFlJEM5Yl604Yy0aEa0
//下面这两行是ice协商过程中的安全认证信息
a=ice-ufrag:UMWxILIEBDt0tuvG
a=ice-pwd:9HhTN6B0H1PuBSMluzJ3biyD
a=candidate:7852700335 1 udp 659136 10.225.155.69 18064 typ host generation 0
a=candidate:7852700335 2 udp 659136 10.225.155.69 18064 typ host generation 0
fs(73)发起的originate命令是一致的,当控件注册在fs(72)上,sdp中就会有a=fingerprint,注册在opensips(72)上,则没有。也可以这么理解,fs(72)知道话务控件是一个webrtc控件,所以他发给话务控件的是携带dtls信息的invite,而opensips(72)并不支持dtls,所以需要73发起invite的时候就携带dtls。
解决方案:发起originate命令时,携带{media_webrtc=true}参数,这样,sdp中就会携带dtls信息
问题三:接通电话后,话务控件点击挂断,话务不能正常挂断
通过抓包发现,194收到invite后,返给opensips(72)200ok,opensips返给fs(73)200ok,73返给72ack,72并没有将200ok返给194,没有收到ack消息的通话是不能通过bye请求来挂断的。这个问题和问题一本质上是一致的,还是contact地址乱码的问题,opensips.cfg脚本中,
if (nat_uac_test("1")){
xlog("onreply_route execute fix_nated_contact\n");
fix_nated_contact();
}
这个if条件没有进去,删除掉if后, ack传输正常,bye也正常了。