WebRTC服务搭建之Kurento

前言

STUN

Session Traversal Utilities for NAT,NAT 会话传输应用程序,一种网络协议,它允许位于 NAT(或多重 NAT)后的客户端找出自己的公网地址,查出自己位于哪种类型的 NAT 之后以及NAT为某一个本地端口所绑定的 Internet 端端口。这些信息被用来在两个同时处于 NAT 路由器之后的主机之间建立 UDP 通信。

WebRTC的常见的几个组成部分

  • 流媒体服务器
  • 信令服务器
  • 客户端

安装coturn

NAT穿透服务器 STUN Server

假如该服务器的公网IP为:39.104.20.110

Github:https://github.com/coturn/coturn

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#安装依赖
yum install -y openssl-devel libevent-devel git

#clone 源码
git clone https://gitee.com/psvmc/coturn.git
cd coturn
./configure
make
sudo make install

# 启动turnserver
nohup turnserver -L 0.0.0.0 -a -u kurento:kurento -v -f -r psvmc.cn &
#然后查看相应的端口号3478是否存在进程
sudo lsof -i:3478

这样就说明已经可以启动了,接下来我们先停掉turnserver,重新配置。
turnserver 默认加载配置文件是etc/turnserver.conf/usr/local/etc/turnserver.conf

1
cp /usr/local/etc/turnserver.conf.default /usr/local/etc/turnserver.conf

生成证书

1
openssl req -x509 -newkey rsa:2048 -keyout /usr/local/etc/turn_server_pkey.pem -out /usr/local/etc/turn_server_cert.pem -days 99999 -nodes

修改的turnserver.conf内容:

1
vi /usr/local/etc/turnserver.conf

修改为如下

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
# 设置转发的ip(局域网ip),如果不设置,他会自己选择默认的
relay-ip=172.24.197.1

# 转发的外网ip(本机外网ip),用于NAT 地址映射
external-ip=39.104.20.110

# 转发的线程数,其实默认不设置最好
relay-threads=5

#UDP 最小端口和最大端口
min-port=40000
max-port=60000

# WebRTC 的消息里会用到
fingerprint

# WebRTC 认证需要
lt-cred-mech

#中继服务器的监听器IP地址
listening-ip=0.0.0.0

#静态账号
user=kurento:kurento

# 统计状态信息的redis db
# redis-statsdb="ip=xx.xx.xxx.xx dbname=3 password=xxxx port=6379 connect_timeout=30"

# 用户登录域
realm=psvmc.cn

# 证书
cert=/usr/local/etc/turn_server_cert.pem
pkey=/usr/local/etc/turn_server_pkey.pem

# 输出log
log-file=stdout

mobility

默认的turnserver日志没有时间,可以借助于工具包:moreutils。 这个工具包的一个 ts 工具就是可以让输出的内容加上时间格式。
注意:服务器内置也有一个 ts 命令,但是这个不是我们要的,而是 openssl 的一个指令。

1
2
3
yum install moreutils
# 安装完之后
man ts

然后接下来试着输出:

1
echo -e "hello world" | ts '[%Y-%m-%d %H:%M:%S]'

这样就会自动带上时间格式了。重新启动turnserver

1
nohup turnserver -c /usr/local/etc/turnserver.conf -v | ts '[%Y-%m-%d %H:%M:%S]'  >> /usr/local/etc/turn.log 2>&1 &

记得开放使用的端口:

webrtc_ip_seting

可以用这个网址去测试stun和turn的有效性:
https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/

先移出默认的

stun:stun.l.google.com:19302

测试stun
测试stun的时候不需要输入账号密码,只需要输入第一行,以stun:开头

stun:39.104.20.110:3478

stun

注意

只要出现Done即为成功,下面提示的fail不用在意。

测试turn
需要输入turn地址,以turn:开头,以及账号密码

turn:39.104.20.110:3478
kurento
kurento

turn

注意

只要出现Done即为成功,下面提示的fail不用在意。

Kurento服务端

假如该服务器的公网IP为:39.104.20.110

官方网址:https://github.com/Kurento/kurento-media-server

官网教程:https://doc-kurento.readthedocs.io/en/latest/user/installation.html

Kurento是一个WebRTC媒体服务器,同时提供了一系列的客户端API,可以简化供浏览器、移动平台使用的视频类应用程序的开发。Kurento支持:

  1. 群组通信(group communications)
  2. 媒体流的转码(transcoding)、录制(recording)、广播(broadcasting)、路由(routing)
  3. 高级媒体处理特性,包括:机器视觉(CV)、视频索引、增强现实(AR)、语音分析

Kurento的模块化架构使其与第三方媒体处理算法 —— 语音识别、人脸识别 —— 很容易集成。

和大部分多媒体通信技术一样,Kurento应用的整体架构包含两个层(layer)或者叫平面(plane):

  1. 信号平面(Signaling Plane):负责通信的管理,例如媒体协商、QoS、呼叫建立、身份验证等
  2. 媒体平面(Media Plane):负责媒体传输、编解码等

WebRTC让浏览器能够进行实时的点对点通信(在没有服务器的情况下)。但是要想实现群组通信、媒体流录制、媒体广播、转码等高级特性,没有媒体服务器是很难实现的。

Kurento的核心是一个媒体服务器(Kurento Media Server,KMS),负责媒体的传输、处理、加载、录制,主要基于 GStreamer实现。此媒体服务器的特性包括:

  1. 网络流协议处理,包括HTTP、RTP、WebRTC
  2. 支持媒体混合(mixing)、路由和分发的群组通信(MCU、SFU功能)
  3. 对机器视觉和增强现实过滤器的一般性支持
  4. 媒体存储支持,支持对WebM、MP4进行录像操作,可以播放任何GStreamer支持的视频格式
  5. 对于GStreamer支持的编码格式,可以进行任意的转码,例如VP8, H.264, H.263, AMR, OPUS, Speex, G.711

安装Docker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 卸载旧版本(如果安装过旧版本的话)
yum remove docker docker-common docker-selinux docker-engine
# 安装需要的软件包
yum install -y yum-utils device-mapper-persistent-data lvm2
#设置docker源
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# 可使用阿里云docker源
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
#安装docker
yum install docker-ce docker-ce-cli containerd.io
#启动 docker
systemctl start docker
#开机自启
systemctl enable docker

#查看KMS日志
docker logs kms
#实时查看:
docker logs -f kms

安装kurento

1
docker pull kurento/kurento-media-server:latest

运行

1
2
3
docker run -d --name kms \
-p:8888:8888/tcp \
kurento/kurento-media-server:latest

运行

1
docker ps -a

显示

ede4f883c11d kurento/kurento-media-server:latest “/entrypoint.sh” 5 seconds ago Up 4 seconds (health: starting) 0.0.0.0:8888->8888/tcp

进入Docker

1
sudo docker exec -it 704d3ed68a49 bash

测试服务是否正常

1
2
3
4
5
6
7
curl \
--include \
--header "Connection: Upgrade" \
--header "Upgrade: websocket" \
--header "Host: 127.0.0.1:8888" \
--header "Origin: 127.0.0.1" \
http://127.0.0.1:8888/kurento

正常启动会显示

HTTP/1.1 500 Internal Server Error
Server: WebSocket++/0.7.0

退出容器

1
exit

查看KMS日志

1
docker logs kms

实时查看日志

1
docker logs -f kms

Kurento媒体服务器的端口(KMS)过程中默认监听8888客户端WebSocket连接。

服务的地址为

ws://39.104.20.110:8888/kurento

配置kurento服务器

进入kurento的镜像编辑kurento的配置文件:

1
2
3
4
5
6
7
8
9
#进入镜像
docker exec -it kms /bin/bash
#安装vim
apt-get update
apt-get install vim
#进入配置文件夹
cd /etc/kurento/modules/kurento/
#编辑配置文件
vim WebRtcEndpoint.conf.ini

修改stun和turn信息

1
2
3
stunServerAddress=39.104.20.110
stunServerPort=3478
turnURL=kurento:kurento@39.104.20.110:3478?transport=udp

退出容器

1
exit

重启kurento容器

1
2
3
#查看当前启动的容器
docker ps
docker restart kms

测试

1
2
3
git clone https://gitee.com/psvmc/kurento-tutorial-java.git
cd kurento-tutorial-java/kurento-hello-world
vi src/main/resources/static/js/index.js

在函数function uiStart()里,增加一个叫iceservers的变量,格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
let iceservers={
"iceServers":[
{
urls:"stun:39.104.20.110:3478"
},
{
urls:["turn:39.104.20.110:3478"],
username:"kurento",
credential: "kurento"
}
]
}

再修改底下的options变量:

1
2
3
4
5
6
7
8
9
10
const options = {
localVideo: uiLocalVideo,
remoteVideo: uiRemoteVideo,
mediaConstraints: { audio: true, video: true },
onicecandidate: (candidate) => sendMessage({
id: 'ADD_ICE_CANDIDATE',
candidate: candidate,
}),
configuration: iceservers //修改在这里,增加了一个configuration的key
};

启动项目

1
mvn -U clean spring-boot:run -Dkms.url=ws://39.104.20.110:8888/kurento

启动完之后用谷歌或者火狐浏览器打开demo页面https://localhost:8443/
点击start启动

或者

使用Idea的可以在启动配置中添加

image-20210220182650547

启动的配置项中添加

-Dkms.url=ws://39.104.20.110:8888/kurento

Kurento客户端

Kurento客户端API基于所谓媒体元素(Media Element)的概念。一个媒体元素持有一种特定的媒体特性。例如:

  1. 媒体元素WebRtcEndpoint的特性是,接收WebRTC媒体流
  2. 媒体元素RecorderEndpoint的特性是,将接收到的媒体流录制到文件系统
  3. 媒体元素FaceOverlayFilter则能够检测人脸,在其上方显示一个特定的图像

WebRTC的标准API

https://developer.mozilla.org/zh-CN/docs/Web/API/WebRTC_API

三方库

https://github.com/feross/simple-peer

兼容不同浏览器

https://github.com/webrtcHacks/adapter

Java

1
2
3
4
5
<dependency>
<groupId>org.kurento</groupId>
<artifactId>kurento-client</artifactId>
<version>6.15.0-SNAPSHOT</version>
</dependency>

Node.js

1
2
3
"dependencies": {
"kurento-client": "Kurento/kurento-client-js#master",
}

Web客户端

为了简化浏览器客户端的WebRTC流处理,Kurento提供了工具WebRtcPeer,你仍然可以使用WebRTC的标准API,以及连接到WebRtcEndpoint。

1
2
3
4
"dependencies": {
"kurento-client": "master",
"kurento-utils": "master",
}

安装

1
2
npm install kurento-client --save
npm install kurento-utils --save

引用

1
var utils = require('kurento-utils');

或者

1
2
3
<script type="text/javascript"
src="http://builds.openvidu.io/dev/master/latest/js/kurento-client.min.js">
</script>

创建连接

WebRtcPeer对RTCPeerConnection进行了包装。连接可以是单向的(进行发送或者接收),也可以是双向的(同时发送接收)。

下面的例子示意了如何基于Utils JS创建一个RTCPeerConnection,并与其它Peer进行会话协商:

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
// 信号处理通道,由你自己决定如何实现,它能够让客户端知道可以和谁通信、如何通信
// 典型的做法是,所有客户端公开一个自己的名字,同时以一条WebSocket连接到服务器
// 客户端通过名字发起通信请求,服务器负责中介会话协商
var signalingChannel = createSignalingChannel(peerName);

// 用于显示远程视频的元素
var videoInput = document.getElementById( 'videoInput' );
// 用于显示本地视频的元素
var videoOutput = document.getElementById( 'videoOutput' );
// getUserMedia约束条件
var constraints = {
audio: true,
video: {
width: 640,
framerate: 15
}
};

var options = {
localVideo: videoInput,
remoteVideo: videoOutput,
onicecandidate: function( candidate ){
// 把本地candidate发送给Peer,基于Trickle ICE,也就是说,一旦发现一个候选,就立即发送
// 不等待所有候选收集成功,这样效率更高。此回调可能被调用多次
signalingChannel.sendCandidate(candidate);
},
mediaConstraints: constraints
};

// 创建一个连接。注意,在双方都需要创建连接,创建的时机,就是服务器确认了两者要进行通信之后
var webRtcPeer = kurentoUtils.WebRtcPeer.WebRtcPeerSendrecv( options, function ( error ) {
// 处理失败
if ( error ) return onError( error );
// 生成本地的SDP Offer
this.generateOffer( onOffer );
} );

// 当收到Peer的candidate后,添加。下面的代码应该在信号处理的回调中调用
webRtcPeer.addIceCandidate(candidate);

// 当本地SDP Offer生成后,调用此回调
function onOffer( error, sdpOffer ) {
if ( error ) return onError( error );
// 发送SDP给Peer,Peer应该给出SDP应答,然后本地调用sdpAnswer回调
signalingChannel.sendOffer( sdpOffer, sdpAnswer );
function onAnswer( sdpAnswer ) {
webRtcPeer.processAnswer( sdpAnswer );
};
}

简述一下上例对应的业务流程:

  1. 通信发起方A,根据接受方B的标识符,向服务器发送WS请求 —— 我要和B通信
  2. 服务器通过WS推送信息给B,A想和你通信,你愿意吗?
  3. 如果B愿意,服务器通过WS推送消息给A、B,你们可以通信了
  4. A、B分别创建连接对象(WebRtcPeer)
  5. WebRtcPeer会自动收集Candidate,你应该通过WS把Candidate发回服务器,服务器再中转给Peer
  6. 一单A、B都收集到Candidate,它们就有可能进行点对点通信了(如果是局域网内)
  7. A发起(Offer)一个会话描述(SDP),B接收到后,给出Answer
  8. 根据双方的SDP,建立媒体流交换

使用数据通道

数据通道允许你通过活动WebRTC连接传递二进制、文本数据。WebRtcPeer对数据通道的使用也提供了封装,将dataChannels选项设置为true即可使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var options = {
localVideo: videoInput,
remoteVideo: videoOutput,
// 启用数据通道
dataChannels: true,
// 下面这个配置是可选的,允许你执行一些声明周期回调
dataChannelConfig: {
id: getChannelName(),
onmessage: onMessage,
onopen: onOpen,
onclose: onClosed,
onbufferedamountlow: onbufferedamountlow,
onerror: onerror
},
onicecandidate: onIceCandidate
}

webRtcPeer = new kurentoUtils.WebRtcPeer.WebRtcPeerSendrecv( options, onWebRtcPeerCreated );

一旦webRtcPeer对象被创建,你就可以调用下面的方法,通过数据通道发送信息:

1
2
// 发送的数据类型取决于应用
webRtcPeer.send('Hello there');