WebRTC
什么是WebRTC
WebRTC(Web Real-Time Communication)是一项由 W3C 和 IETF 推动的开源项目,旨在为浏览器和移动应用提供实时通信(RTC)功能。
WebRTC 支持音频、视频和数据在点对点(P2P)连接中直接传输,而无需中间服务器。也支持配合SFU(Selective Forwarding Unit),MCU(Multipoint Control Unit)技术,构建多对多的传输通信。
特点
- 实时通信: 支持音频、视频和数据的实时传输,延迟低,适用于实时互动场景。
- 点对点连接: 通过 P2P 连接,数据直接在客户端之间传输,减少了服务器的负载和延迟。
- 跨平台支持: 支持主流浏览器(如 Chrome、Firefox、Safari、Edge)和移动平台(如 Android、iOS)。
- 安全性: 默认使用 SRTP(Secure Real-time Transport Protocol)加密音视频流,DTLS(Datagram Transport Layer Security)加密数据通道,确保通信安全。
- 开源和标准化: WebRTC 是一个开源项目,并且由 W3C 和 IETF 标准化,确保广泛的兼容性和支持。
谁在用
可以看出 基于 网络语音/视频通话 的场景,尤其是类似 实时网络 语音电话这种。
各大语音app (whats app, Facebook, Google系软件) 都有基于webrtc或者参考webrtc的思路进行实现。refs: https://telnyx.com/resources/5-applications-that-demonstrate-the-power-of-webrtc-and-sip
OPENAPI 也推出了实时流视频接口,Realtime API with WebRTC https://platform.openai.com/docs/guides/realtime-websocket
快速构建
速览图
一图速览,可以看出 建立WebRTC的通信,整体分 建立网络连接 和 推流 两大步。
而网络连接 又分P2P模式 和 中继模式。
构建一个完整的WebRTC,可以满足不同环境,以及网络情况下的端到端通信 ,则我们需要构建
- 信令服务
- STUN服务
- TURN服务
此外演示的代码跑在浏览器上,访问浏览器展示页面,创建WebRTC Client,所以还需要一个Web服务
- Web HTTP服务
废话不多说,逐一部署,一个个击破。接下来,上家伙 (完整代码见附录)
1. 部署 Web HTTP 服务 + Signaling 信令服务
构建一个Web Http 服务器,可以返回 前端页面所需数据,
...
const server = http.createServer((req, res) => {
console.log(`request url: ${req.url}, method: ${req.method}`);
// 提供静态文件服务
// 返回主页
if (req.method === "GET" && req.url === "/") {
console.log("request index.html");
const filePath = path.join(
__dirname,
"static/my-webrtc-client/index.html"
);
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(404, { "Content-Type": "text/plain" });
res.end("404 Not Found");
return;
}
// 根据文件扩展名设置正确的 Content-Type
const ext = path.extname(filePath).toLowerCase();
const contentType = mimeTypes[ext] || "application/octet-stream";
res.writeHead(200, { "Content-Type": contentType });
res.end(data);
});
} else if (req.method === "GET" && isRequestFile(req)) {
// 返回 js / css 等文件
const dirpath = __dirname + "/static/my-webrtc-client";
const filePath = path.join(dirpath, req.url);
console.log(`request ${filePath}`);
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(404, { "Content-Type": "text/plain" });
res.end("404 Not Found");
return;
}
// 根据文件扩展名设置正确的 Content-Type
const ext = path.extname(filePath).toLowerCase();
const contentType = mimeTypes[ext] || "application/octet-stream";
res.writeHead(200, { "Content-Type": contentType });
res.end(data);
});
} else {
res.writeHead(404, { "Content-Type": "text/plain" });
res.end("404 Not Found");
}
});
...
构建一个Signaling server,用于两端数据的交换。
io.on("connection", (sock) => {
console.log("连接成功...");
// 向客户端发送连接成功的消息
sock.emit("connectionSuccess");
// 监听客户端event
sock.on("offer", (event) => {
console.log(`receive offer from device : ${event.fromDeviceId}`);
// 向其余客户端发送offer
sock.broadcast.emit("offer", event);
});
sock.on("candidate", (event) => {
console.log(`receive candidate from device : ${event.fromDeviceId}`);
// 向其余客户端发送offer
sock.broadcast.emit("candidate", event);
});
sock.on("answer", (event) => {
console.log(
`receive answer from deviceId : ${event.fromDeviceId}, to deviceId : ${event.toDeviceId}`
);
// 向其余客户端发送offer
sock.broadcast.emit("answer", event);
});
...
});
2. 部署 STUN服务 + TURN服务
此处选用coturn服务,部署安装
安装
apt install coturn
修改coturn config, 参考 【WebRTC - STUN/TURN服务 - COTURN配置】
vim /etc/turnserver.conf
启动coturn服务
turnserver --log-file stdout
3. 使用 WebRTC Client进行连接
构建webrtc client , webrtc场景下, 端到端通信,A为 Caller,B为 Called。WebRTC Client A → WebRTC Client B 。
- Caller 和 Called 两端分别初始化
caller / called = new RTCPeerConnection({
encodedInsertableStreams: true, // needed by chrome shim
iceServers: [
{
// default "stun:stun.l.google.com:19302",
urls: STUN_URL,
},
{
urls: TURN_URL,
username: TURN_USERNAME,
credential: TURN_CREDENTIAL,
},
],
});
- Caller 请求对端 ,创建offer然后存储在本地,然后发送offer
// 创建offer
const offer = await pc.createOffer(offerOptions);
// 存储在本地
caller.setLocalDescription(offer)
// 发送到对端
sock.emit("offer", {
offer: offer,
...
});
- Called接收到offer,存储在本地,然后创建answer 然后回复answer给Caller
// 收到offer
// 存储offer在本地
called.setRemoteDescription(event.offer)
// 创建answer
answer = await called.createAnswer();
// 存储answer在本地
await called.setLocalDescription(answer);
- Caller在收到answer以后,存储在本地
await pc.setRemoteDescription(event.answer);
- 同时 在无论是Caller 还是 Called 在调用setLocalDescription后,会触发浏览器请求STUN服务器获取candidate信息,Caler / Called 需要获取到candidate 然后存在本地
// 收到candidate
caller/called.addEventListener("icecandidate", (e) => {
// 发给对端
sock.emit("candidate", {
candidate: candidate,
...
});
})
// 对端收到后 ,存储在本地
await caller/called.addIceCandidate(candidate);
当两端互换candidate 协商后,根据candidate的type (host, relay 等) 尝试连通,最终决定是p2p通信,还是 relay通信。至此便完成了两端网络连通。
当网络连通后,Caller获取本地音视频数据后 ,用刚刚打通的网络,便可进行数据传输。Called接收数据。
Caller
// 获取 音视频数据
stream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: true,
});
// 传输
stream.getTracks().forEach((track) => {
caller.addTrack(track, stream);
});
Called
// 收到后, 将数据放到 video element上,进行播放
called.addEventListener("track", (e) => {
const remoteVideo = document.getElementById("remoteVideo");
remoteVideo.srcObject = e.streams[0];
});
WebRTC - 展示
p2p 模式:两个设备都在同一个局域网内 ,
打开两个网页端,分别是Caller 和 Called。
Caller 点击 Start 获取媒体流,然后点击Call 和 对端Called连通网络后,便可以推流到对端。
中继模式: 两个移动端设备在公网环境 ,一个笔记本电脑 ,一个手机
笔记本视角:
手机端视角:
注意:
- 如果两端不在同一局域网内,大概率是需要中继服务的,则此时,信令服务,TURN/STUN服务需要部署在有公网地址的机器上。
- STUN有免费公共的可以使用,但是TURN由于带宽费用高,没有免费公共的,所以必须要自部署。
附录
代码
示例代码,包括 前后端代码,信令服务 。
名词解释
ICE 候选者 (Candidate)
ICE 候选者包含关于如何连接到对等方的网络信息。每个候选者提供一个可能的网络路径。具体信息包括:
- IP 地址: 设备的公共或私有 IP 地址。
- 端口: 用于通信的端口号。
- 优先级: 候选者的优先级,用于选择最佳路径。
- 类型: 候选者的类型(如 host、srflx、relay)。
- 协议: 使用的传输协议(如 UDP、TCP)。
candidate 示例
candidate:842163049 1 udp 1677729535 192.168.1.2 56143 typ srflx raddr 10.0.0.1 rport 8998 generation 0 ufrag EEtu network-id 3 network-cost 10
SDP (Session Description Protocol)
SDP 是一种格式化的文本,用于描述多媒体会话的参数。它包含的信息包括:
- 会话描述: 包括会话的名称、时间、参与者等。
- 媒体描述: 包括媒体类型(音频、视频)、编解码器、比特率等。
- 连接信息: 包括 IP 地址和端口号。
- 媒体格式: 支持的媒体格式和编解码器。
- 带宽信息: 会话的带宽要求。
- 属性: 其他会话属性,如方向(发送、接收)。
sdp 示例
v=0
o=- 46117327 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE audio video
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104
c=IN IP4 192.168.1.2
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:EEtu
a=ice-pwd:asd88fgpdd777uzjYhagZg
a=mid:audio
a=sendrecv
a=rtpmap:111 opus/48000/2
m=video 9 UDP/TLS/RTP/SAVPF 100 101
c=IN IP4 192.168.1.2
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:EEtu
a=ice-pwd:asd88fgpdd777uzjYhagZg
a=mid:video
a=sendrecv
a=rtpmap:100 VP8/90000











网友评论