WebRTC音视频通话简单音视频通话
Posted 音视频开发老舅
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WebRTC音视频通话简单音视频通话相关的知识,希望对你有一定的参考价值。
本篇不详细介绍websocket,只针对websocket整合rtc。
一、简单说下webrtc的流程
webrtc是P2P通信,也就是实际交流的只有两个人,而要建立通信,这两个人需要交换一些信息来保证通信安全。而且,webrtc必须通过ssh加密,也就是使用https协议、wss协议。
借用一幅图
1.1 创建端点的解析
以下解析不包括websockt,只针对stun做解析。与上图略有不同
-
首先,Client A创建端点(Create PeerConnection),并添加音视频流(Add Streams)。接下来通知Client B,让Client B也创建一个端点。
-
Client B收到通知后,Client B创建端点(Create PeerConnection),并添加音视频流(Add Streams),
-
接下来,Client B创建一个用于answer的SDP对象(Create Answer),保存并发送给Client A。
-
Client A收到用于answer的SDP后,保存下来。
-
然后, Client A创建一个用于offer的SDP对象(Create Office),保存并发送给Client B。
-
最后,Client B保存收到的用于offer的SDP对象
以上步骤完成之后:
1、rtc会自动收集Candidate信息,并通过回调函数通知你,用于交换Candidate信息。
2、交换完Candidate信息后,P2P连接就建立好了。并通过回调函数,将远程视频流给你
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
1.2 交换Candidate信息
Candidate信息是交换完SDP对象之后,自动收集的信息。在创建端点(PeerConnection)的时候,添加回调函数(onIceCandidate)
创建回调函数(onIceCandidate)
将Candidate信息发送给另一端(a发给b,b发给a)
保存发过来的 Candidate信息(addIceCandidate)。注意是保存发过来的,不是保存自己的!!!
交换完Candidate信息后,P2P连接就建立好了。
二、新建springboot项目,并开启ssh
因为rtc必须使用ssh,所以springboot需要使用https协议才可以
2.1 生成ssh自签名文件
在终端中执行
keytool -genkey -alias webrtc -dname "CN=Andy,OU=kfit,O=kfit,L=HaiDian,ST=BeiJing,C=CN" -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore webrtc.keystore -validity 36500
执行时,会要求输入密码;
执行后,会在根目录下生成一个webrtc.keystore的文件
2.2 配置ssh信息
将webrtc.keystore文件放在resource目录下
在application.yaml中填写配置信息
server:
ssl:
#证书的路径
key-store: classpath:webrtc.keystore
#证书密码
key-store-password: 123456
#秘钥库类型
key-store-type: JKS
2.3 检测一下能不能跑起来
运行就行,能跑起来就OK。
三、编写websocket服务类
这个简单的demo只考虑一个房间,没有房间控制,所以websocket代码很少,主要代码都在js里面。
3.1 先放一下Message实体类
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Message
private String operation;
private Object msg;
public Message setOperation(String operation)
this.operation = operation;
return this;
public Message setMsg(Object msg)
this.msg = msg;
return this;
public String getMsgStr()
return msg == null ? "" : msg.toString();
3.2 服务类
主要有以下信息:
- 加入房间(into)
- 发送 sdp 对象(send-sdp)
- 交换 candidate 信息(send-candidate)
package com.websocket.controller;
import com.alibaba.fastjson.JSONObject;
import com.websocket.pojo.Message;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Controller;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
@Log4j2
@Controller
@ServerEndpoint("/webrtc")
public class WebrtcController
/**
* 这里只做一个最简单的, 只有一个房间, 后面有需要自己可以改
*/
private static Session offer;
private static Session answer;
@OnMessage
public void onMessage(Session session, String message)
final Message data = JSONObject.parseObject(message, Message.class);
final Message response = Message.builder()
.operation(data.getOperation())
.build();
switch (data.getOperation())
//加入房间
case "into":
if (offer == null)
offer = session;
response.setMsg("offer");
else if (answer == null)
answer = session;
response.setMsg("answer");
else
response.setMsg("none");
sendMessage(session, response);
break;
case "start":
sendMessage(offer, response);
break;
//发送 offer 的 SDP 对象
case "send-offer":
//发送 answer 的 SDP 对象
case "send-answer":
//交换 candidate 信息
case "send-candidate":
sendOther(session, response.setMsg(data.getMsg()));
break;
@OnClose
public void onClose(Session session, CloseReason closeReason)
if (offer != null && session.getId().equals(offer.getId()))
offer = null;
else if (answer != null && session.getId().equals(answer.getId()))
answer = null;
public static void sendOther(Session session, Object msg)
if (offer != null && session.getId().equals(offer.getId()))
sendMessage(answer, msg);
else if (answer != null && session.getId().equals(answer.getId()))
sendMessage(offer, msg);
public static void sendMessage(Session session, Object msg)
sendMessage(session, JSONObject.toJSONString(msg));
@SneakyThrows
private static void sendMessage(Session session, String msg)
final RemoteEndpoint.Basic basic = session.getBasicRemote();
basic.sendText(msg);
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
四、页面
4.1 html
这部分不太重要,就直接放上来了
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
<meta charset="UTF-8">
<title>websocket-demo</title>
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.2.1/css/bootstrap.min.css">
</head>
<body>
<div class="container py-3">
<div class="row">
<div class="col-12">
<div id="addRoom" class="btn btn-primary">加入房间</div>
</div>
<div class="col-12 col-lg-6">
<p>本地视频:</p>
<video id="localVideo" width="500px" height="300px" autoplay style="border: 1px solid black;"></video>
</div>
<div class="col-12 col-lg-6">
<p>远程视频:</p>
<video id="remoteVideo" width="500px" height="300px" autoplay style="border: 1px solid black;"></video>
</div>
</div>
</div>
<script src="https://s3.pstatp.com/cdn/expire-1-M/jquery/3.3.1/jquery.min.js" type="text/javascript"></script>
</body>
</html>
4.2 webrtc工具类 webrtc-util.js
包括以下方法:
打开本地音视频流
创建PeerConnection对象
创建用于office的SDP对象
创建用于answer的SDP对象
保存SDP对象
保存Candidate信息
收集 candidate 的回调
获得远程视频流的回调
需要注意的是:最后的两个回调,需要在创建PeerConnection对象之后,打开本地音视频流之前执行。
4.2.1 本地变量
其中的 ice对象,根据上一篇测试通过的stun服务器信息填写。
//端点对象
let rtcPeerConnection;
//本地视频流
let localMediaStream = null;
//ice服务器信息, 用于创建 SDP 对象
let iceServers =
"iceServers": [
// "url": "stun:stun.l.google.com:19302",
"urls": ["stun:159.75.239.36:3478"],
"urls": ["turn:159.75.239.36:3478"], "username": "chr", "credential": "123456",
]
;
// 本地音视频信息, 用于 打开本地音视频流
const mediaConstraints =
video: width: 500, height: 300,
audio: true //由于没有麦克风,所有如果请求音频,会报错,不过不会影响视频流播放
;
// 创建 offer 的信息
const offerOptions =
iceRestart: true,
offerToReceiveAudio: true, //由于没有麦克风,所有如果请求音频,会报错,不过不会影响视频流播放
;
4.2.2 打开本地音视频流
// 1、打开本地音视频流
const openLocalMedia = (callback) =>
console.log('打开本地视频流');
navigator.mediaDevices.getUserMedia(mediaConstraints)
.then(stream =>
localMediaStream = stream;
//将 音视频流 添加到 端点 中
for (const track of localMediaStream.getTracks())
rtcPeerConnection.addTrack(track, localMediaStream);
callback(localMediaStream);
)
4.2.3 创建 PeerConnection 对象
// 2、创建 PeerConnection 对象
const createPeerConnection = () =>
rtcPeerConnection = new RTCPeerConnection(iceServers);
4.2.4 创建用于 offer 的 SDP 对象
// 3、创建用于 offer 的 SDP 对象
const createOffer = (callback) =>
// 调用PeerConnection的 CreateOffer 方法创建一个用于 offer的SDP对象,SDP对象中保存当前音视频的相关参数。
rtcPeerConnection.createOffer(offerOptions)
.then(sdp =>
// 保存自己的 SDP 对象
rtcPeerConnection.setLocalDescription(sdp)
.then(() => callback(sdp));
)
.catch(() => console.log('createOffer 失败'));
4.2.5 创建用于 answer 的 SDP 对象
// 4、创建用于 answer 的 SDP 对象
const createAnswer = (callback) =>
// 调用PeerConnection的 CreateAnswer 方法创建一个 answer的SDP对象
rtcPeerConnection.createAnswer(offerOptions)
.then(sdp =>
// 保存自己的 SDP 对象
rtcPeerConnection.setLocalDescription(sdp)
.then(() => callback(sdp));
)
.catch(() => console.log('createAnswer 失败'))
4.2.6 保存远程的 SDP 对象
// 5、保存远程的 SDP 对象
const saveSdp = (answerSdp, callback) =>
rtcPeerConnection.setRemoteDescription(new RTCSessionDescription(answerSdp))
.then(callback);
4.2.7 保存 candidate 信息
// 6、保存 candidate 信息
const saveIceCandidate = (candidate) =>
let iceCandidate = new RTCIceCandidate(candidate);
rtcPeerConnection.addIceCandidate(iceCandidate)
.then(() => console.log('addIceCandidate 成功'));
4.2.8 收集 candidate 的回调
// 7、收集 candidate 的回调
const bindOnIceCandidate = (callback) =>
// 绑定 收集 candidate 的回调
rtcPeerConnection.onicecandidate = (event) =>
if (event.candidate)
callback(event.candidate);
;
;
4.2.9 获得 远程视频流 的回调
// 8、获得 远程视频流 的回调
const bindOnTrack = (callback) =>
rtcPeerConnection.ontrack = (event) => callback(event.streams[0]);
;
以上代码都写在 webrtc-util.js 中,是可以复用滴
接下来,就是在html中引入这个js,然后和websocket整合一下,把通知、candidate 信息等等,通过websocket发送给另一端。
End
如果你对音视频开发感兴趣,觉得文章对您有帮助,别忘了点赞、收藏哦!或者对本文的一些阐述有自己的看法,有任何问题,欢迎在下方评论区讨论!
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
浅聊WebRTC视频通话
WebRTC 提供了一套标准 API,使 Web 应用可以直接提供实时音视频通信功能。大部分浏览器及操作系统都支持 WebRTC,直接可以在浏览器端发起实时音视频通话,本文以 WebRTC 初学者的视角去完成一个 1V1 网页版实时音视频通话。
完成音视频通话需要了解四个模块:音视频采集、STUN/TURN 服务器、信令服务器、端与端之间 P2P 连接。使用 WebRTC 的 API 完成音视频采集,配合信令服务器和 WebRTC 的RTCPeerConnection方法能实现 1V1 通话,简易流程如下图:
接下来依次讲解它们的作用和核心 API。
音视频采集
通话的人像和声音采集和播放
WebRTC 使用getUserMedia
获取摄像头与话筒对应的媒体流对象MediaStream
,媒体流可以通过 WebRTC 进行传输,并在多个对等端之间共享。将流对象赋值给视频元素的 srcObject,实现本地播放音视频
API:navigator.mediaDevices.getUserMedia
参数:constraints
返回:promise,方法调用成功得到MediaStream对象。
const localVideo = document.querySelector("video");
function gotLocalMediaStream(mediaStream)
localVideo.srcObject = mediaStream;
navigator.mediaDevices
.getUserMedia(
video:
width: 640,
height: 480,
frameRate:15,
facingMode: 'enviroment', // 设置为后置摄像头
deviceId : deviceId ? exact:deviceId : undefined
,
audio: false
)
.then(gotLocalMediaStream)
.catch((error) => console.log("navigator.getUserMedia error: ", error));
连接管理
知道怎么捕获本地音视频,接下来了解怎么与另一端建立连接传输音视频数据。
RTCPeerConnection是 WebRTC 实现网络连接、媒体管理、数据管理的统一接口。建立 P2P 连接需要用到RTCPeerConnection中的几个重要类:SDP、ICE、STUN/TURN。
会话描述信息 RTCSessionDescription(SDP)
SDP 是各端的能力,包括音频编解码器类型、传输协议等。这些信息是建立连接是必须传递的,双方知道视频是否支持音频、编码方式是什么,都能通过 SDP 获得。
比如进行视频传输,我的编码是 H264 对方只能解 H265,就没法进行通信了。
SDP 描述分为两部分,分别是会话级别的描述(session level)和媒体级别的描述(media level),其具体的组成可参考 RFC4566[1],带星号 (*) 的是可选的。常见的内容如下:
Session description(会话级别描述)
v= (protocol version)
o= (originator and session identifier)
s= (session name)
c=* (connection information -- not required if included in all media) One or more Time descriptions ("t=" and "r=" lines; see below)
a=* (zero or more session attribute lines) Zero or more Media descriptions
Time description
t= (time the session is active)
Media description(媒体级别描述), if present
m= (media name and transport address)
c=* (connection information -- optional if included at session level)
a=* (zero or more media attribute lines)
SDP 解析时,每个 SDP Line 都是以 key=...
形式,解析出 key 是 a 后,可能有两种方式,可参考 RFC4566[2]:
a=<attribute>
a=<attribute>:<value>
有时候并非冒号 (:) 就一定是 <attribute>:<value>
,实际上 value 里面也会有冒号,比如:
a=fingerprint:sha-256 7C:93:85:40:01:07:91:BE
a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=ssrc:2527104241 msid:gLzQPGuagv3xXolwPiiGAULOwOLNItvl8LyS
看一下具体例子:
alert(pc.remoteDescription.sdp);
v=0
o=alice 2890844526 2890844526 IN IP4 host.anywhere.com
s=
c=IN IP4 host.anywhere.com
t=0 0
//下面的媒体描述,在媒体描述部分包括音频和视频两路媒体
m=audio 49170 RTP/AVP 0
a=fmtp:111 minptime=10;useinbandfec=1 //对格式参数的描述
a=rtpmap:0 PCMU/8000 //对RTP数据的描述
...
//上面是音频媒体描述,下面是视频媒体描述
m=video 51372 RTP/AVP 31
a=rtpmap:31 H261/90000
...
m=video 53000 RTP/AVP 32
a=rtpmap:32 MPV/90000
ICE 候选者 RTCIceCandidate
WebRTC 点对点连接最方便的方法是双方 IP 直连,但是在实际的应用中,双方会隔着NAT设备给获取地址造成了麻烦。
WebRTC 通过ICE框架确定两端建立网络连接的最佳路径,为开发者者屏蔽了复杂的技术细节。
(NAT 及ICE框架对于使用 WebRTC 的开发者是一个黑盒,为了优化阅读体验将这部分放在最后作为补充知识)
开发者需要了解
原理
两个节点交换 ICE 候选来协商他们自己具体如何连接,一旦两端同意了一个互相兼容的候选,该候选的 SDP 就被用来创建并打开一个连接,通过该连接媒体流就开始运转。
两个 API
onicecandidate:本地代理创建 SDP Offer 并调用 setLocalDescription(offer) 后触发,在 eventHandler 中通过信令服务器将候选信息传递给远端。
addIceCandidate:接收到信令服务器发送过来的候选信息后调用,为本机添加 ICE 代理。
API:pc.onicecandidate = eventHandler
pc.onicecandidate = function(event)
if (event.candidate)
// Send the candidate to the remote peer
else
// All ICE candidates have been sent
API:pc.addIceCandidate
pc.addIceCandidate(candidate).then(_=>
// Do stuff when the candidate is successfully passed to the ICE agent
).catch(e=>
console.log("Error: Failure during addIceCandidate()");
);
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
信令服务器
WebRTC 的 SDP 和 ICE 信息需要依赖信令服务器进行消息传输与交换、建立 P2P 连接,之后才能进行音视频通话、传输文本信息。如果没有信令服务器,WebRTC 无法进行通信。
通常使用socket.io实时通信的能力来构建信令服务器。socket.io跨平台、跨终端、跨语言,方便我们在各个端上去实现信令的各个端,去与我们的服务端进行连接。
这张图就表达了信令服务器在整个通话过程中它起到的作用。
用代码看一下如何建立 socket.io 信令服务器
var express = require("express");
var app = express();
var http = require("http");
const Server = require("socket.io");
const httpServer = http.createServer(app);
const io = new Server(httpServer);
io.on("connection", (socket) =>
console.log("a user connected");
socket.on("message", (room, data) =>
logger.debug("message, room: " + room + ", data, type:" + data.type);
socket.to(room).emit("message", room, data);
)
socket.on("join", (room) =>
socket.join(room);
)
);
端与端之间 P2P 连接
A 和 B 建立网络连接的过程如图:
-
A 向 B 发起 WebRTC 呼叫
-
创建 peerConnection 对象,在参数中指定 Turn/Stun 的地址
var pcConfig =
iceServers: [
urls: "turn:stun.al.learningrtc.cn:3478",
credential: "mypasswd",
username: "garrylea",
,
urls:[
"stun:stun.example.com",
"stun:stun-1.example.com"
]
],
;
pc = new RTCPeerConnection(pcConfig);
A 调用createOffer方法创建本地会话描述(SDP offer),SDP offer 包含有关已附加到 WebRTC 会话,浏览器支持的编解码器和选项的所有MediaStreamTrack信息,以及ICE代理,目的是通过信令信道发送给潜在远程端点,以请求连接或更新现有连接的配置。
A 调用setLocalDescription方法将提案设置为本地会话描述,并传递给 ICE 层。之后通过信令服务器将会话描述发送给 B
API:pc.createOffer
参数:无
返回:SDP Offer
API:pc. setLocalDescription
参数:offer
返回:Promise<null>
function sendMessage(roomid, data)
if (!socket)
console.log("socket is null");
socket.emit("message", roomid, data);
const offer = await pc.createOffer()
await pc.setLocalDescription(offer).catch(handleOfferError);
message.log(`传输发起方本地SDP`);
sendMessage(roomid, offer);
A 端 pc.setLocalDescription(offer)创建后,一个 icecandidate 事件就被发送到RTCPeerConnection,onicecandidate事件会被触发。B 端接收到一个从远端页面通过信令发来的新的 ICE 候选地址信息,本机可以通过调用RTCPeerConnection.addIceCandidate() 来添加一个 ICE 代理。
//A端
pc.onicecandidate = (event) =>
if (!event.candidate) return;
sendMessage(roomid,
type: "candidate",
label: event.candidate.sdpMLineIndex,
id: event.candidate.sdpMid,
candidate: event.candidate.candidate,
);
;
//B端
socket.onmessage = e =>
if (e.data.hasOwnProperty("type") && e.data.type === "candidate")
var candidate = new RTCIceCandidate(
sdpMLineIndex: data.label,
candidate: data.candidate,
);
pc.addIceCandidate(candidate)
.then(() =>
console.log("Successed to add ice candidate");
)
.catch((err) =>
console.error(err);
);
-
A 作为呼叫方获取本地媒体流,调用
addtrack
方法将音视频流流加入RTCPeerConnection
对象中传输给另一端,加入时另一端触发ontrack
事件。
媒体流加入媒体轨道
API:stream.getTracks
参数:无
返回:媒体轨道对象数组
const pc = new RTCPeerConnection();
stream.getTracks().forEach((track) =>
pc.addTrack(track, stream);
);
const remoteVideo = document.querySelector("#remote-video");
pc.ontrack = (e) =>
if (e && e.streams)
message.log("收到对方音频/视频流数据...");
remoteVideo.srcObject = e.streams[0];
;
B 作为呼叫方,从信令服务器收到 A 发过来的会话信息,调用setRemoteDescription方法将提案传递到 ICE 层,调用 addTrack 方法加入 RTCPeerConnction
B 调用 createAnswer 方法创建应答,调用setLocalDeacription方法应答设置为本地会话并传递给 ICE 层。
socket.onmessage = e =>
message.log("接收到发送方SDP");
await pc.setRemoteDescription(new RTCSessionDescription(e.data));
message.log("创建接收方(应答)SDP");
const answer = await pc.createAnswer();
message.log(`传输接收方(应答)SDP`);
sendMessage(roomid, answer);
await pc.setLocalDescription(answer);
-
AB 都有了自己和对方的 SDP,媒体交换方面达成一致,收集的 ICE 完成连通性检测建立最连接方式,P2P 连接建立,获得对方的音视频媒体流。
-
pc.ontrack = (e) => if (e && e.streams) message.log("收到对方音频/视频流数据..."); remoteVideo.srcObject = e.streams[0]; ;
-
双向数据通道连接
-
RTCDataChannelton 通过 RTCPeerConnection API 可以建立点对点 P2P 互联,不需要中介服务器,延迟更低。
一端建立 datachannel, 另一端通过 ondatachannel 获取 datachannel 对象
-
API:pc.createDataChannel 参数:label 通道名 options? 通道参数 返回:RTCDataChannel function receivemsg(e) var msg = e.data; if (msg) message.log("-> " + msg + "\\r\\n"); else console.error("received msg is null"); const dc = pc.createDataChannel("chat"); dc.onmessage = receivemsg; dc.onopen = function () console.log("datachannel open"); ; dc.onclose = function () console.log("datachannel close"); ; pc.ondatachannel = e => if(!dc) dc = e.channel; dc.onmessage = receivemsg; dc.onopen = dataChannelStateChange; dc.opclose = dataChannelStateChange; ; //当对接创建数据通道时会回调该方法。
NAT 及 ICE 框架(补充)
上文提到ICE集成了多种 NAT 穿越技术,比如 STUN、TURN,可以实现NAT穿透,在主机之间发现 P2P 传输路径机制。接下来简单介绍下 NAT、STUN、TURN 是什么。网络地址转换( NAT)
NAT常部署在一个组织的网络出口位置。网络分为私网和公网两个部分,NAT 网关设置在私网到公网的路由出口位置,私网与公网间的双向数据必须都要经过 NAT 网关。组织内部的大量设备,通过 NAT 就可以共享一个公网 IP 地址,解决了 IPv4 地址不足的问题。如下图所示,有两个组织,每个组织的 NAT 分配一个公网 IP,分别是 1.2.3.4 以及 1.2.3.5。每个组织私网设备通过 NAT 将内网地址转换为公网地址,然后加入互联网
NAT 及 ICE 框架(补充)
上文提到ICE集成了多种 NAT 穿越技术,比如 STUN、TURN,可以实现NAT穿透,在主机之间发现 P2P 传输路径机制。接下来简单介绍下 NAT、STUN、TURN 是什么。网络地址转换( NAT)
NAT常部署在一个组织的网络出口位置。网络分为私网和公网两个部分,NAT 网关设置在私网到公网的路由出口位置,私网与公网间的双向数据必须都要经过 NAT 网关。组织内部的大量设备,通过 NAT 就可以共享一个公网 IP 地址,解决了 IPv4 地址不足的问题。如下图所示,有两个组织,每个组织的 NAT 分配一个公网 IP,分别是 1.2.3.4 以及 1.2.3.5。每个组织私网设备通过 NAT 将内网地址转换为公网地址,然后加入互联网
-
NAT 对待 UDP 的实现方式有 4 种,分别为:完全圆锥型、地址受限锥型、端口受限锥型、对称型。
Session Traversal Utilities for NAT (STUN)
STUN允许位于 NAT(或多重 NAT)后的客户端找出自己的公网地址,查出自己位于哪种类型的 NAT 之后以及 NAT 为某一个本地端口所绑定的公网端端口。 -
STUN是 C/S 模式的协议,由客户端发送 STUN 请求、STUN 服务响应告知由NAT分配给主机的 IP 地址和端口号,也是一种 Request/Response 的协议,默认端口号是 3478。
想让内网主机知道它的外网 IP,需要在公网上架设一台 STUN server,并向这台服务器发送 Request,服务器就会返回它的公网 IP 了。
下面是抓取的一对 STUN 绑定请求和响应。首先客户端向地址为 216.93.246.18 的 STUN 服务器发送 Binding Request(STUN 绑定请求)。
- 服务器回了 Binding Response,返回公网 IP:
TURN
是一种数据传输协议。允许通过 TCP 或 UDP 方式穿透 NAT 或防火墙。TURN 是一个 Client/Server 协议。TURN 的 NAT 穿透方法与 STUN 类似,都是通过取得应用层中的公网地址达到 NAT 穿透
ICE 收集
ICE两端并不知道所处的网络的位置和 NAT 类型,通过ICE能够动态的发现最优的传输路径。ICE 端收集本地地址、通过STUN服务收集 NAT 外网地址、通过TURN收集中继地址,所以会有三种候选地址:
host 类型,即本机内网的 IP 和端口;
srflx 类型, 即本机 NAT 映射后的外网的 IP 和端口;
relay 类型,即中继服务器的 IP 和端口。
IP: xxx.xxx.xxx.xxx,
port: number,
type: host/srflx/relay,
priority: number,
protocol: UDP/TCP,
usernameFragment: string
...
下图中,Alice 与 Bob 通过 STUN 以及 TURN 服务器收集了三种类型的 candidate。
ICE 收集 candidate 后进行连通性检测,确定主机之间 P2P 最佳传输路径。
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
以上是关于WebRTC音视频通话简单音视频通话的主要内容,如果未能解决你的问题,请参考以下文章
如何构建一个可以进行基本视频通话的简单 Native WebRTC Android 应用程序?
Android端WebRTC音视频通话录音-获取音频输出数据