简介
本文将详细介绍如何利用 WebRTC 技术实现 P2P 音视频通话,并提供了一个跨平台的方案,包括:基于 socket.io 和 Node.js 实现的服务端,以及 JavaScript 和 Android 客户端。让我们一起来探讨如何搭建这个系统,以及如何编写代码吧。
由于 server 、js、android 代码还在整理中,预计还需要 2-3 天时间。地址:github.com/yangkun1992…
下面是 PC 与 IOS 在不同网络环境下的效果图(WiFi <->移动网络):
服务端
1. 使用 nodejs 和 socket.io 实现信令服务器
我们借助上一篇信令服务的流程图,来实现一个 nodejs 信令服务器
我们先设计一个信令
join: 当前用户和远端用户加入到房间中的信令
leave: 当前用户和远端用户离开房间的信令
message: 交换双方的 SDP、ICE 信令
首先,我们需要搭建一个 Node.js 服务端,用于处理信令交换。在这里,我们将使用 socket.io 库作为通信协议,借助 http、https、fs 等组件。实现一个简单的 Node.js 服务端实例:
create server.js 下面就是信令服务的核心代码
var log4js = require('log4js');
var http = require('http');
var https = require('https');
var fs = require('fs');
var socketIo = require('socket.io');
var express = require('express');
var serveIndex = require('serve-index');
var USERCOUNT = 3;
...
//http server
var http_server = http.createServer(app);
http_server.listen(80, '0.0.0.0');
var options = {
key : fs.readFileSync('./cert/xxx.key'),
cert: fs.readFileSync('./cert/xxx.pem')
}
//https server
var https_server = https.createServer(options, app);
var io = socketIo.listen(https_server);
io.sockets.on('connection', (socket)=> {
socket.on('message', (room, data)=>{
socket.to(room).emit('message',room, data);//发送给当前房间的其它客户端
});
socket.on('join', (room)=>{
socket.join(room);
var myRoom = io.sockets.adapter.rooms[room];
var users = (myRoom)? Object.keys(myRoom.sockets).length : 0;
logger.debug('the user number of room is: ' + users);
if(users < USERCOUNT){
socket.emit('joined', room, socket.id); //发送给自己,相当于回调
if(users > 1){
socket.to(room).emit('otherjoin', room, socket.id); //发送给当前房间的其它客户端
}
}else{
socket.leave(room);
socket.emit('full', room, socket.id);
}
});
socket.on('leave', (room)=>{
var myRoom = io.sockets.adapter.rooms[room];
var users = (myRoom)? Object.keys(myRoom.sockets).length : 0;
logger.debug('the user number of room is: ' + (users-1));
socket.to(room).emit('bye', room, socket.id);
socket.emit('leaved', room, socket.id);
});
});
https_server.listen(443, '0.0.0.0');
要运行上面的 server.js 信令服务器,您需要按照以下步骤进行安装和运行:
- 安装 Node.js 和 npm:
- 安装所需的依赖项
npm install express socket.io fs http https
- 启动 server
node server.js
2. 搭建 sturn/turn 服务器
由于网络环境的影响我们需要搭建一个 sturn/turn 服务器,以便提升 P2P 的成功率,下面是一个粗略的搭建方式,但是也够用了。
- 安装 Coturn
在终端中输入以下命令,使用 yum 包管理器安装 Coturn:
sudo yum install coturn
- 配置 Coturn
找到并编辑 Coturn 的配置文件 /etc/coturn/turnserver.conf
,根据您的需求修改以下配置项:
# 配置监听的端口号
listening-port=3478
min-port=49152
max-port=65535
#配置域名
realm=xxx.com
#允许使用 TURN/STUN 服务的用户的凭据
user=123456:123456
cert=/path/to/xxx.pem
pkey=/path/to/xxx.pem
# 配置日志文件路径
log-file=/root/log/turnserver.log
- 启动 Coturn
在终端中输入以下命令,启动 Coturn 服务:
sudo systemctl start coturn
sudo systemctl stop coturn
sudo systemctl restart coturn
sudo systemctl status coturn
-
测试 coturn
我们可以去 trickle-ice 测试网站进行测试
正如 trickle-ice 网站所说: 如果你测试一个 STUN 服务器,你能收集到一个类型为“srflx”的候选者,它就可以工作。如果你测试一个 TURN 服务器,你能收集到一个类型为“relay”的候选人,它就会工作.
由此上图 sturn 和 turn 候选者地址都能成功连接。
客户端
WebRTC 是一种基于 Web 技术的实时通信解决方案,可用于在浏览器中实现P2P音视频通话。当然,现在基本上所有上层平台都支持了。在 WebRTC 中,双方通信通过 ICE 协议进行连接,通过 SDP 协议交换媒体信息,通过 DTLS 协议进行加密,通过 SRTP 协议进行媒体传输。
下面,我们将为你介绍如何使用 WebRTC 在浏览器和 Android 中实现 P2P 音视频通话。
Web
我们按照上面信令的流程来实现:
1. 获取媒体流
WebRTC 支持从设备摄像头和麦克风获取视频和音频流。使用 JavaScript 的getUserMedia
API,您可以请求用户授权,从摄像头和麦克风获取本地媒体流,并将其添加到一个MediaStream
对象中。
function startCall(){
if(!navigator.mediaDevices ||
!navigator.mediaDevices.getUserMedia){
console.error('the getUserMedia is not supported!');
return;
}else {
var constraints = {
video: true, //传输视频
audio: true //传输音频
}
navigator.mediaDevices.getUserMedia(constraints)
.then(getMediaStream)//打开成功的回调
.catch(handleError);//打开失败
}
}
2.连接信令服务器并加入到房间中
function connect(){
//连接信令服务器
socket = io.connect();
//加入成功的通知
socket.on('joined', (roomid, id) => {
...
});
//远端加入
socket.on('otherjoin', (roomid) => {
...
});
//房间满了
socket.on('full', (roomid, id) => {
...
});
//接收自己离开房间的回调
socket.on('leaved', (roomid, id) => {
...
});
//收到对方挂断的消息
socket.on('bye', (room, id) => {
...
});
//收到服务断开的消息
socket.on('disconnect', (socket) => {
...
});
//收消息,用于交换 SDP 和 ICE 消息等
socket.on('message', (roomid, data) => {
...
});
//发送 join 消息到信令服务器并加入到 123456 房间中
socket.emit('join', 123456);
}
3. 创建 PeerConnection 并添加媒体轨道
当收到自己加入房间成功的消息后,连接到远程对等方,我们就需要创建一个RTCPeerConnection
对象,并将本地媒体流添加到其中。然后,您需要创建一个RTCDataChannel
对象,用于在对等方之间传输数据。
var pcConfig = {
'iceServers': [{
'urls': 'turn:xxx:3478',
'credential': "1234",
'username': "1234"
}]
};
pc = new RTCPeerConnection(pcConfig);
//当前 icecandida 数据
pc.onicecandidate = (e)=>{
...
}
//datachannel 传输通道
pc.ondatachannel = e=> {
...
}
// 添加远端的媒体流到 <video> element
pc.ontrack = getRemoteStream;
//最后添加媒体轨道到 peerconnection 对象中
localStream.getTracks().forEach((track)=>{
pc.addTrack(track, localStream);
});
//创建一个非音视频的数据通道
dc = pc.createDataChannel('test');
dc.onmessage = receivemsg;//接收对端消息
dc.onopen = dataChannelStateChange;//当打开
dc.onclose = dataChannelStateChange;//当关闭
function getRemoteStream(e){
remoteStream = e.streams[0];
remoteVideo.srcObject = e.streams[0];
}
4. 发送 createOffer 数据到远端
当对方加入到房间中,我们需要把当前 UserA 的 SDP 信息告诉 UserB 用户,使用如下代码
var offerOptions = {//同时接收远端的音、视频数据
offerToRecieveAudio: 1,
offerToRecieveVideo: 1
}
pc.createOffer(offerOptions)
.then(getOffer)//创建成功的回调
.catch(handleOfferError);
function getOffer(desc){
//设置 UserA SDP 信息
pc.setLocalDescription(desc);
offerdesc = desc;
//将 usera 的 SDP 发送到信令服务器,信令服务器再根据 roomid 进行转发
sendMessage(roomid, offerdesc);
}
5. 发送 answer 消息到对方
当 UserB 收到 UserA 发来的 offer 消息,我们需要设置 UserA 的 SDP 并且设置当前的 SDP 然后再讲自己的 SDP 发送给 UserA,以进行媒体协商, 如下代码:
//1. 当收到 UserA OFFER 消息,设置 SDP
pc.setRemoteDescription(new RTCSessionDescription(data));
//2. 然后创建 answer 消息
pc.createAnswer()
.then(getAnswer)
.catch(handleAnswerError);
//3. 当创建成功后,拿到 UserB 自己的 SDP 消息并设置当前的 SDP 信息,最后再讲 SDP 消息发给信令再转发给 roomid 房间中的客户端
function getAnswer(desc){
pc.setLocalDescription(desc);
optBw.disabled = false;
//send answer sdp
sendMessage(roomid, desc);
}
6. 接收 answer 消息,并设置 UserB 的 SDP 信息
当我们收到 UserB 发来的 answer sdp 消息后告诉底层
pc.setRemoteDescription(new RTCSessionDescription(data));
7. 交换 ICE 候选
SDP 协商完后,UserA / UserB 交换 ice 消息,用于 nat 和转发媒体数据,如果都在局域网其实可以省略这一步
//user A / UserB 收到 onicecandidate 回调然后将 candidate 发送给 UserB
pc.onicecandidate = (e)=>{
if(e.candidate) {
sendMessage(roomid, {
type: 'candidate',
label:event.candidate.sdpMLineIndex,
id:event.candidate.sdpMid,
candidate: event.candidate.candidate
});
}else{
console.log('this is the end candidate');
}
}
//当 UserB / UserA 接收到 UserA / UserB 的candidate 后进行添加
function addIcecandida(data){
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);
});
}
通过如上核心步骤代码,你已经完成了一个基于 WebRTC JS 版的跨平台 P2P 音视频通话系统。当然,这里展示的代码只是简化版示例,完整版的代码可以点击文末简介处有说明。
Android
上面我们实现了 服务端和跨平台的 JS 端,最后我们实现一个 Android 端,毕竟最开始我就是搞 Android 的