SDP(session Description Protocol)它只是一种信令服务器格式的描述标准,本身不属于传输协议,但是可以被其他传输协议用来交换必要的信息。

它最主要用的地方就是在通讯之前首先进行媒体的协商,也就是说对于呼叫者它首先要创建这个offer,然后将自己的媒体信息拿到,然后通过信令转给被呼叫者,那被呼叫者收到这个信令之后要创建answer,也就是说将它自己的媒体信息也获取到,然后在通过这个信令传给这个呼叫者,这样就将这个媒体信息就交换完了,那这些媒体信息就是用SDP这个格式来描述的,那拿到双方的SDP之后,他们就取一个交集,就是大家都可以支撑的编解码器和带宽等信息就交换完成了,后面才能进行真正的数据的传输,那我们今天就上节端对端实战例子的基础上,来获取到双方的SDP,我们先从整体上看SDP是什么样子的,它大概都包括哪些内容,那么在后面的课程中,我们再逐一的去讲解每一行每一段的意思是什么,当我们实践完这个例子之后它展示这样。

端对端1V1传输基本流程 十、第五节 获取 offer/answer 创建的 SDP_数据

左边是Local的部分,在右边是remote,Local部分就是从本地获取的视频,最终要进行createoffer,生成它本地的这个SDP,这个就没有信令了,真实的应该是通过这个信令然后将这个SDP传输到远端,我们实际假设就到远端了,那么在远端它收到这个offer之后呢,自己要创建一个answer,这样就形成 了一个他自己本地的SDP,那我们最终的结果就会看到offer和answer两个SDP是什么样的。他们有一些什么区别,下面我们就来实操作一下:

// 1、使用JS严格语法
'use strict'

// 2、获取HTML中的元素标签
var localVideo = document.querySelector('video#localVideo');
var remoteVideo = document.querySelector('video#remoteVideo');
var btnStart = document.querySelector('button#start');
var btnCall = document.querySelector('button#call');
var btnHangUp= document.querySelector('button#hangup');

// 49、获取新增加的元素
var offerSdpTextarea = document.querySelector('textarea#offer');
var answerSdpTextarea = document.querySelector('textarea#answer');

// 16、定义全局变量localStream
var localStream;
// 22、定义全局变量pc1
var pc1;
// 23、定义全局变量pc2
var pc2;

// 12、在我们采集成功之后我们会调用gotMediaStream,它有个参数就是stream
function gotMediaStream(stream){
// 13、在这个stream里面要做两件事
// 14、第一件事就是将它设置为localVideo,这样采集到数据之后我们本地的localVideo就能将它展示出来
localVideo.srcObject = stream;
/** 15、第二件事就是我们要将它赋值给一个全局的stream,这个localStream就是为了后面我们去添加流用的,就是我们在其他地方还要用到这个流,
所以我们不能让它是一个局部的,得让他是一个全局的 */
localStream = stream;
}

// 17、处理错误
function handleError(err){
// 18、它有个参数err,当我们收到这个错误我们将它打印出来
console.log("Failed to call getUserMedia", err);
}

// 6、实现start
function start(){
// 11、在调getUserMedia之前我们还有一个限制,是constraints
// 这这里面我们可以写video和audio,我们这里暂不采集音频了,如果你要采集设置audio为true好了
var constraints = {
video: true,
audio: false
}
// 7、判断浏览器是否支持navigator.mediaDevices 和 navigator.mediaDevices.getUserMedia
// 如果有任何一个不支持我们就要打印一个错误信息并退出
if(!navigator.mediaDevices ||
!navigator.mediaDevices.getUserMedia){
console.error('the getUserMedia is not supported!')
return;
}else { // 8、如果支持就要传人constraints使用getUserMedia获取音视频数据了
navigator.mediaDevices.getUserMedia(constraints)
.then(gotMediaStream) // 9、如果成功我们就让他调gotMediaStream,这时候说明我们可以拿到这个stream了
.catch(handleError); // 10、否则就要处理这个错误
}

}
// 45、这样也会传入一个描述信息
function getAnswer(desc){
// 46、当远端它得到这个Answer之后,它也要设置它的setLocalDescription,当它调用了setLocalDescription之后它也开始收集candidate了
pc2.setLocalDescription(desc);
// 54、设置textarea,这个也就是我们获取的answer成功之后answer的内容
answerSdpTextarea.value = desc.sdp

//send sdp to caller
//recieve sdp from callee
/** 47、完了之后它去进行send desc to signal与pc1进行交换,pc1会接收recieve desc from signal
那么收到之后他就会设置这个pc1的setRemoteDescription
那么经过这样一个步骤整个协商就完成了
当所有协商完成之后,这些底层对candidate就收集完成了
收集完了进行交换形成对方的列表然后进行连接检测
连接检测完了之后就开始真正的数据发送过来了
*/
pc1.setRemoteDescription(desc);

}
// 41、首先传入一个desc一个描述信息
function getOffer(desc){
/** 42、当我们拿到这个描述信息之后呢,还是回到我们当时协商的逻辑,对于A来说它首先创建Offer,创建Offer之后它会调用setLocalDescription
* 将它设置到这个PeerConnection当中去,那么这个时候它会触发底层的ICE的收集candidate的这个动作
* 所以这里要调用pc1.setLocalDescription这个时候处理完了它就会收集candidate
* 这个处理完了之后按照正常逻辑它应该send desc to signal到信令服务器
*/
/**
* 51、在这里首先是设置setLocalDescription
*/
pc1.setLocalDescription(desc);
/**
* 52、设置完了setLocalDescription之后呢,我们就可以将它的内容展示在textarea里
* desc本身并不是SDP,它下面有一个SDP的属性,它里面包含了我们第一个连接创建的offer的SDP
* */
offerSdpTextarea.value = desc.sdp
//send sdp to callee
/**
* 43、到了信令服务器之后,信令服务器会发给第二个人(b)
* 所以第二个人就会receive
* 所以第二个人收到desc之后呢首先pc2要调用setRemoteDescription,这时候将desc设置成它的远端
*/
//receive sdp from caller
pc2.setRemoteDescription(desc);
/**
* 44、设成远端之后呢它也要做它自己的事
* pc2就要调用createAnswer,如果调用createAnswer成功了它要调用gotAnswerDescription
*/
/**
* 53、同样的到了我们在找找这个answer,调用createAnswer成功我们再调用getAnswer
*/
pc2.createAnswer().then(getAnswer)
.catch(handleError);
}
/**
* 33、在这个函数里它实际就是有多个流了,
*/
function gotRemoteStream(e){
// 34、所以我们只要取其中的第一个就可以了
if(remoteVideo.srcObject !== e.streams[0]){
// 35、这样我们就将远端的音视频流传给了remoteVideo,当发送ontrack的时候也就是数据通过的时候,
remoteVideo.srcObject = e.streams[0];
}
}
// 19、实现call
function call(){
// 20、这个逻辑略显复杂,首先是创建RTCPeerConnection
/**
* 38、创建offerOptions,那在这里你可以指定我创建我本地的媒体的时候,那都包括哪些信息,
* 可以有视频流和音频流,因为我们这里没有采集音频所以offerToReceiveAudio是0
* 有了这个之后我们就可以创建本地的媒体信息了
*/
var offerOptions = {
offerToReceiveAudio: 0,
offerToReceiveVideo: 1
}
/** 21、所以在这里先设置一个pc1等于new RTCPeerConnection()
在这个Connection里面实际上是有一个可选参数的,这个可选参数就涉及到网络传输的一些配置
我们整个ICE的一个配置,但是由于是我们在本机内进行传输,所以在这里我们就不设置参数了,因为它也是可选的
所以它这里就会使用本机host类型的candidate
这个pc1我们后面也要用到,所以我给他全局化一下
*/
pc1 = new RTCPeerConnection();
pc1.onicecandidate = (e) => {
// 25、这些事件有几个重要的,那第一个因为这个是连接,连接建立好之后当我发送收集Candidate的时候,那我要知道现在已经收到一个了
// 收到之后我们要做一些事情,所以我们要处理addIceCandidate事件,其实做主要的就是这个事件
// send candidate to peer
// receive candidate from peer
/**
* 27、它有个参数就是e,当有个事件的时候这个参数就传进来了,对于我们上面的逻辑,我们回顾一些此前的流程
* 当我们A调用者收到candidate之后,它会将这个candidate发送给这个信令服务器
* 那么信令服务器会中转到这个B端,那么这个B端会调用这个AddIceCandidate这个方法,将它存到对端的candidate List里去
* 所以整个过程就是A拿到它所有的可行的通路然后都交给B,B形成一个列表;
* 那么B所以可行的通路又交给A,A拿到它的可行列表,然后双方进行这个连通性检测
* 那么如果通过之后那就可以传数据了,就是这样一个过程
* */
/** 28、所以我们收到这个candidate之后就要交给对方去处理,所以pc1要调用pc2的这个,因为是本机这里就没有信令了,假设信令被传回来了
* 当我们收集到一个candidate之后交给信令,那么信令传回来,这时候就给了pc2
*/
/**
* 29、pc2收到这个candidate之后就调用addIceCandidate方法,传入的参数就是e.candidate
*/
pc2.addIceCandidate(e.candidate)
.catch(handleError);
console.log('pc1 ICE candidate:', e.candidate);
}

pc1.iceconnectionstatechange = (e) => {
console.log(`pc1 ICE state: ${pc.iceConnectionState}`);
console.log('ICE state change event: ', e);
}

// 24、在创建一个pc2这样我们就创建了两个连接,拿到两个连接之后我们要添加一些事件给它
// 26、对于pc2还要处理一个onTrack,当双方通讯连接之后,当有流从对端过来的时候,会触发这个onTrack事件,所以这个事件是我们要处理的
pc2 = new RTCPeerConnection();
/**
* 30、对于pc2也是同样道理,那它就交给p1
*/
pc2.onicecandidate = (e)=> {

// send candidate to peer
// receive candidate from peer
/**
* 31、所以它就调用pc1.addIceCandidate,这是当他们收集到candidate之后要做的事情
*/
pc1.addIceCandidate(e.candidate)
.catch(handleError);
console.log('pc2 ICE candidate:', e.candidate);
}
pc2.iceconnectionstatechange = (e) => {
console.log(`pc2 ICE state: ${pc.iceConnectionState}`);
console.log('ICE state change event: ', e);
}
/**
* 32、除此之外,pc2是被调用方,被调用方是接收数据的,所以对于pc2它还有个ontrack事件
* 当它收到这个ontrack事件之后它需要调用gotRemoteStream
*/
pc2.ontrack = gotRemoteStream;
/**
* 36、接下来我们就要将本地采集的数据添加到第一添加到第一个pc1 = new RTCPeerConnection()中去
* 这样在创建媒体协商的时候才知道我们有哪些媒体数据,这个顺序不能乱,必须要先添加媒体数据再做后面的逻辑
* 二不能先做媒体协商然后在添加数据,因为你先做媒体协商的时候它知道你这里没有数据那么在媒体协商的时候它就没有媒体流
* 那么就是说在协商的时候它知道你是没有的,那么它在底层就不设置这些接收信息发收器,那么这个时候即使你后面设置了媒体流传给这个PeerConnection
* 那么它也不会进行传输的,所以我们要先添加流
* 添加流也比较简单,通过localStream调用getTracks就能调用到所有的轨道(音频轨/视频轨)
* 那对于每个轨道我们添加进去就完了,也就是forEach遍历进去,每次循环都能拿到一个track
* 当我们拿到这个track之后直接调用pc1.addTrack添加就好了,第一个参数就是track,第二个参数就是这个track所在的流localStream
* 这样就将本地所采集的音视频流添加到了pc1 这个PeerConnection
*/
//add Stream to caller
localStream.getTracks().forEach((track)=>{
pc1.addTrack(track, localStream);
});
/**
* 37、那么这个时候我们就可以去创建这个pc1去媒体协商了,媒体协商第一步就是创建createOffer,创建这个createOffer实际它里面有个
* offerOptions的,那么这个offerOptions我们在上面定义一下
*/
/**
* 50、在哪去找SDP,实际上就是在createOffer的时候,如果创建成功了,我们就能拿到它的SDP,
*/
pc1.createOffer(offerOptions)
.then(getOffer) // 39、它也是一个Promise的形式,当他成功的时候我们去调用getOffer
.catch(handleError); // 40、如果失败了去调用一下handleError

}
// 48、挂断,将pc1和pc2分别关闭,在将pc1和pc2对象设为null
function hangup(){
pc1.close();
pc2.close();
pc1 = null;
pc2 = null;
}

// 3、开始
btnStart.onclick = start;
// 4、当调用call的时候就会调用双方的RTCPeerConnection,
// 当这个两个PeerConnection创建完成之后,它们会作协商处理,
// 协商处理晚上之后进行Candidate采集,也就是说有效地址的采集,
// 采集完了之后进行交换,然后形成这个Candidate pair再进行排序,
// 然后再进行连接性检测,最终找到最有效的那个链路,
// 之后就将localVideo展示的这个数据通过PeerConnection传送到另一端,
// 另一端收集到数据之后会触发onAddStream或者onTrack就是说明我收到数据了,那当收到这个事件之后,
// 我们再将它设置到这个remoteVideo里面去,
// 这样远端的这个video就展示出来了,显示出我们本地采集的数据了,
// 大家会看到两个视频是一摸一样的,它的整个底层都是从本机自己IO的那个逻辑网卡转过来的,
// 不过这个整个流程都是一样的,当我们完成这个,在做真实的网络传输的时候就和这个一摸一样,只是将信令部分加了进来
btnCall.onclick = call;
// 5、挂断
btnHangUp.onclick = hangup;

首先我们要点击start,当再掉进call的时候它才真正的调用answer获取到SDP

端对端1V1传输基本流程 十、第五节 获取 offer/answer 创建的 SDP_数据_02

首先我们看左边,它包括了一个

v=0 // 表示版本
o=- 1211595589857572724 2 IN IP4 127.0.0.1 表示own是谁的
s=- // 表示session
t=0 0 // 这个时间表示无限时的,表示这个通讯可以一直进行
a=group:BUNDLE 0
a=msid-semantic: WMS vluOnWGLm1AXX2xobcwzLxfd4g242N3EkUI1
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 122 127 121 125 107 108 109 124 120 123 119 114 115 116
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:fo7z
a=ice-pwd:asyFTtCrJHkWpppAR69cChRU
a=ice-options:trickle
a=fingerprint:sha-256 B4:94:D6:FC:E3:75:30:15:9D:38:5E:D0:59:8E:5D:26:1A:6E:39:B7:47:7A:E3:57:17:87:18:68:15:01:47:96
a=setup:actpass
a=mid:0
a=extmap:14 urn:ietf:params:rtp-hdrext:toffset
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:13 urn:3gpp:video-orientation
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=extmap:11 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
a=extmap:8 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07
a=extmap:9 http://www.webrtc.org/experiments/rtp-hdrext/color-space
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
a=sendrecv
a=msid:vluOnWGLm1AXX2xobcwzLxfd4g242N3EkUI1 f6639327-58b8-46c9-8396-1eaf76c605b9
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=fmtp:98 profile-id=0
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=98
a=rtpmap:100 VP9/90000
a=rtcp-fb:100 goog-remb
a=rtcp-fb:100 transport-cc
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=fmtp:100 profile-id=2
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:102 H264/90000
a=rtcp-fb:102 goog-remb
a=rtcp-fb:102 transport-cc
a=rtcp-fb:102 ccm fir
a=rtcp-fb:102 nack
a=rtcp-fb:102 nack pli
a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
a=rtpmap:122 rtx/90000
a=fmtp:122 apt=102
a=rtpmap:127 H264/90000
a=rtcp-fb:127 goog-remb
a=rtcp-fb:127 transport-cc
a=rtcp-fb:127 ccm fir
a=rtcp-fb:127 nack
a=rtcp-fb:127 nack pli
a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f
a=rtpmap:121 rtx/90000
a=fmtp:121 apt=127
a=rtpmap:125 H264/90000
a=rtcp-fb:125 goog-remb
a=rtcp-fb:125 transport-cc
a=rtcp-fb:125 ccm fir
a=rtcp-fb:125 nack
a=rtcp-fb:125 nack pli
a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
a=rtpmap:107 rtx/90000
a=fmtp:107 apt=125
a=rtpmap:108 H264/90000
a=rtcp-fb:108 goog-remb
a=rtcp-fb:108 transport-cc
a=rtcp-fb:108 ccm fir
a=rtcp-fb:108 nack
a=rtcp-fb:108 nack pli
a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
a=rtpmap:109 rtx/90000
a=fmtp:109 apt=108
a=rtpmap:124 H264/90000
a=rtcp-fb:124 goog-remb
a=rtcp-fb:124 transport-cc
a=rtcp-fb:124 ccm fir
a=rtcp-fb:124 nack
a=rtcp-fb:124 nack pli
a=fmtp:124 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d0032
a=rtpmap:120 rtx/90000
a=fmtp:120 apt=124
a=rtpmap:123 H264/90000
a=rtcp-fb:123 goog-remb
a=rtcp-fb:123 transport-cc
a=rtcp-fb:123 ccm fir
a=rtcp-fb:123 nack
a=rtcp-fb:123 nack pli
a=fmtp:123 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640032
a=rtpmap:119 rtx/90000
a=fmtp:119 apt=123
a=rtpmap:114 red/90000
a=rtpmap:115 rtx/90000
a=fmtp:115 apt=114
a=rtpmap:116 ulpfec/90000
a=ssrc-group:FID 3106942290 1308638234
a=ssrc:3106942290 cname:cPrWONTvTQvqoNyg
a=ssrc:3106942290 msid:vluOnWGLm1AXX2xobcwzLxfd4g242N3EkUI1 f6639327-58b8-46c9-8396-1eaf76c605b9
a=ssrc:3106942290 mslabel:vluOnWGLm1AXX2xobcwzLxfd4g242N3EkUI1
a=ssrc:3106942290 label:f6639327-58b8-46c9-8396-1eaf76c605b9
a=ssrc:1308638234 cname:cPrWONTvTQvqoNyg
a=ssrc:1308638234 msid:vluOnWGLm1AXX2xobcwzLxfd4g242N3EkUI1 f6639327-58b8-46c9-8396-1eaf76c605b9
a=ssrc:1308638234 mslabel:vluOnWGLm1AXX2xobcwzLxfd4g242N3EkUI1
a=ssrc:1308638234 label:f6639327-58b8-46c9-8396-1eaf76c605b9
大家可以观察到在整个SDP里面,a的数量是最多的,后面都是a,猛然一看会觉得有点晕,这么多a代表的什么意思呢,其实a就代表一个属性,它对某一个字段的解释,它其实就是对m的解释,我们滑到上面,找到m,这个m就表示midia媒体,在我们这里实际上没有采集音频,只是采集的视频,所以它是video,使用协议是 

UDP

TLS 也就是加密后的

RTP 在UDP之上使用的是RTP

SAVPF 这个RTP不是 普通的RTP,就是包含了音频视频的 RTP,而是经过加密的RTP,S代表加密

 

后面的一堆数字是它的这个配了的type,就是说这个媒体都支持哪些数据类型,那后面的a都对这个媒体信息进一步的说明

 

我们再看右边的Answer部分,Answer格式基本上是一样的 ,后面也是一堆a,这个描述是它自己的信息,这些信息我们后面会一一介绍的,大家现在只要有一个感性的认识就好了。基本上就是有一条媒体信息,还有一堆a,这些a后面会解释。