媒体协商技术

媒体协商技术

WebRTC通过ICE技术可以实现客户端P2P连接,但是在两端进行P2P连接前还需要先进行媒体协商。WebRTC媒体协商技术主要包括两部分:一部分是SDP(Session Description Protocol)协议,另一部分是JSEP(JavaScript Session Establishment Protocol)协议。 >

SDP协议

1
SDP协议,即会话描述协议,它提供一种描述实时音视频通信所需的各种参数的通用描述,是一个文本信息。简单来说,SDP就是客户端的各端的音视频实时通信的能力,能力指的是各端所支持的音视频编解码能力及相关参数、传输协议和音视频媒体类型等等。

具体信息参见webrtc网络基础

SEP协议

1
2
3
4
5
6
7
8
9
SEP协议,即JavaScript会话建立协议,是一个信令控制协议。在媒体协商的过程中,JSEP协议通过“Offer/Answer”媒体协商机制,承载着遵循SDP协议的信息进行数据交互。JSEP数据一般使用JSON数据结构表示,比如使用“type”字段表示JSEP类型,“sdp”字段承载 SDP 文本数据,结合RTCPeerConnection API 的媒体协商流程如下:

1.客户端A创建RTCPeerConnection对象,然后调用offerForConstraints函数创建 SDP Offer信息,并通过setLocalDescription函数设置 RTCPeerConnection 的本地会话描述;
2.客户端A将SDP Offer信息发送给信令服务器,由信令服务器转发给客户端B;
3.客户端B收到客户端A的SDP Offer 后,通过调用setRemoteDescription函数保存客户端A的SDP;
4.客户端B创建RTCPeerConnection对象,然后调用answerForConstraints函数创建SDP Answer信息,并通过setLocalDescription函数设置 RTCPeerConnection 的本地会话描述;
5.客户端B将SDP Answer信息发送给信令服务器,由信令服务器转发给客户端A;
6.客户端A收到客户端B的SDP Answer后,通过调用setRemoteDescription函数保存客户端A的SDP;
至此,客户端A和客户端B都拿到双方的SDP信息,然后双方就可以通过ICE技术正式进行P2P连接。

一对一通信流程

  1. 首先A是发起端也就是呼叫端,呼叫端要与信令服务器建立连接,被呼叫端B端也要与信令服务器建立连接,这样他们就可以经过信令服务器对信令消息进行中转。
  2. A如果想要发起呼叫,首先它要创建一个PeerConnect(对端的连接对象)之后通过getUserMedia拿到本地的音视频流,将这个流添加到连接里去。在进行媒体协商之前,我们需要先将流(本地采集的数据)添加到peerConnection连接中去。这样在媒体协商之前,我们才知道有哪些媒体数据。
    如果先做媒体协商的话,知道这是连接中没有数据媒体流,就不会设置相关底层的接收器、发送器,即使后面设置了媒体流,传递给了peerConnection,他也不会进行媒体传输,所以我们要先添加流。
  3. 调用PeerConnect的CreateOffer的方法去创建一个Offer的SDP,创建好SDP之后再调用setLocalDescription,把它设置到LocalDescription这个槽里去,那调用完这个方法之后底层会发送一个bind请求给stun和turn服务,此时它就开始收集所有与对方连接的候选者。(还没收集完成,因为stun和turn服务还没有进行响应)
  4. 与此同时调用完setLocalDescription之后,之前CreateOffer方法拿到这个SDP也要发送给信令服务器,通过信令服务器的中转,最终转给B,此时B就拿到了offer,即A这端的媒体相关的描述信息。
  5. B端收到这个SDP之后,首先创建一个PeerConnetion。创建好这个对象之后它会调用setRemoteDescription将这个收到的SDP设置进去,设置完成之后它要给一个应答。它要调用Create Answer,这时候它就产生了本机相关的媒体的信息也就是Answer SDP,创建好之后它也要调用setLocalDescription,将这个本地的Answer SDP设置进去,这样对B来说它的协商就OK了。也就是说它有远端的SDP同时它自己这端的SDP也获取到了,这时候在底层就会进行协商。
  6. 对于B端,在setLocalDescription的时候它也要向stun和turn服务发送一个bind请求,收集能够与A进行通信的所有的候选者,在调用setLocalDescription之后,B端将自己的Answer SDP发送给信令服务器 ,通过信令服务器转给A,A此时就拿到了B 这一端的媒体描述信息,然后它再设置setRemoteDescription,此时A也可以进行媒体协商了,此时A和B进行媒体协商的动作就算完成了。这是媒体协商这一部分。
  7. 那接下来stun和turn服务将这个信息回给A,此时就会触发A端的onIceCandidate事件,因为我们上面是有一个请求(3中出现的),所有此时我们就能收到很多不同的onIceCandidate,A收到候选者之后它将候选者发送给信令服务器,通过信令服务器转给对端,也就是让对端知道我都有哪些通路(让B端知道本机A有哪些通路),对端B收到这个Candidate之后要调用AddIceCandidate这个方法将它添加到对端的连接通路的候选者列表中。
  8. 同样的道理,当B收到这个Candidate之后,它也发给信令,通过信令转发给A,此时A也拿到B的所有的候选者,并将它添加到这个候选者列表中,也就是AddIceCandidate,那此时双方就拿到了所有的对方的可以互通的候选者,此时它底层就会做连接检测。
  9. 当它找到一个最优的线路之后呢,A与B就进行通讯,首先是A将数据流发送给B,B在收到这个数据流之后,因为它们前面已经做了绑定,就知道是谁来的数据,之后就与它的这个Connection进行对连,B虽然收到数据但是还是显示不出来,要将这个数据进行onAddStream,添加进行之后才能把这个视频数据和音频数据向上抛,才能进行视频的渲染和音频的渲染。
  10. 总结:

媒体的协商:看A端有什么媒体能力看B端有什么媒体能力,他们之间所有的媒体取一个交集,取大家都能够识别的支持的能力,包括音频编解码视频编解码,这个采样率是多少,帧率是多少,以及网络的一些信息;
通过ICE对整个可连通的链路地址进行收集,收集完成之后进行排序和连接检测,找出双方可以连接的最优的这条线路;
媒体数据的传输:当从一端传输到另一端之后呢,另一端会收到一个事件,就是onAddStream,当收到这个事件之后就可以将这个媒体流添加到自己的video标签和audio标签中进行音频的播放和视频的渲染,这个就是整个端对端连接的基本流程。

一对一通信示例

代码示例

1. html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<style>
video{
width: 400px;
height: 400px;
}
</style>
<body>
<video id="localvideo" autoplay></video>
<video id="remotevideo" autoplay></video>
<div>
<button id="start">Start</button>
<!-- 采集音视频数据并显示在localvideo -->
<button id="call">Call</button>
<!-- 开始通信 -->
<button id="hangup">Hangup</button>
<!-- 挂断 -->
</div>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<script src="./index.js"></script>
</body>

</html>

2. js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// 'use strict'
let localVideo = document.querySelector('#localvideo');
let remoteVideo = document.querySelector('#remotevideo');

let start = document.querySelector('#start');
let call = document.querySelector('#call');
let hangup = document.querySelector('#hangup');

let localStream,pc1,pc2;

function getStream(stream) {
localVideo.srcObject = stream;
localStream = stream
}
function handleError(err) {
console.error(err.name + ':' + err.message);
}
function getRemoteStream(e) {
remoteVideo.srcObject = e.streams[0]
}
function getOffer(desc) {
// 设置本地的描述信息,添加到peerconnection
console.log('pc2');
console.dir(desc.sdp);
pc1.setLocalDescription(desc)
//发送描述信息SDP到signal信令服务端,与pc2进行交换
//对端调用setRemoteDescription接收SDP信息
pc2.setRemoteDescription(desc)
// 创建Answer信息 pc2进行应答
pc2.createAnswer().then(getAnswer).catch(handleAnswerError)
}
function getAnswer(desc) {
// 收集候选者
// 远端设置本地描述信息
pc2.setLocalDescription(desc)
//发送描述信息SDP到signal信令服务端,与pc1进行交换
//pc1保存SDP
pc1.setRemoteDescription(desc)
}
function handleOfferError(err) {
console.log('创建offer失败'+err);
}
function handleAnswerError(err) {
console.log('创建answer失败'+err);
}
// 获取本地音视频流并显示
start.onclick = ()=>{
if(navigator.mediaDevices && navigator.mediaDevices.getUserMedia){
navigator.mediaDevices.getUserMedia({
video:true
}).then(getStream).catch(handleError)
}else{
console.error('浏览器不支持getUserMedia');
}
}
call.onclick = ()=>{
// 创建peerConnect,pc1与pc2同时连接到signal服务器(这里是一起到本机)
pc1 = new RTCPeerConnection();//调用方
pc2 = new RTCPeerConnection();//被调用方
pc2.addIceCandidate()
// 当一个 RTCICECandidate对象被添加时,这个事件被触发。
// pc1,pc2将各自收集的candidate交给对方
// 当收到candidate后,会触发事件,获取候选者列表,之后调用send candidate发送给signal服务器,从而发送给对端。双方获取之后进行连通性检测
pc1.onicecandidate = (e)=>{
pc2.addIceCandidate(e.candidate)
}
pc2.onicecandidate = (e)=>{
pc1.addIceCandidate(e.candidate)
}
//被调用方,接收数据,有数据经过的时候调用ontrack事件
pc2.ontrack = getRemoteStream
//要先添加媒体流,然后才进行媒体协商
localStream.getTracks().forEach(track => {
//获取所有的轨 将本地产生的音视频流添加到pc1的peerConnection
pc1.addTrack(track,localStream)
});
// 进行媒体协商
pc1.createOffer({
offerToReceiveVideo:1,
offerToReceiveAudio:0
}).then(getOffer).catch(handleOfferError)
//设置本地的描述信息,添加到peerconnection
}
hangup.onclick = ()=>{
pc1.close();
pc2.close();
pc1 = pc2 = null;
}

1v1视频通话

1v1视频通话实例


媒体协商技术
https://jing-jiu.github.io/jing-jiu/2021/12/05/其他/媒体协商技术/
作者
Jing-Jiu
发布于
2021年12月5日
许可协议